├── .gitignore ├── 01_terraform_eks ├── README.md └── main.tf ├── 02_terraform_node_pools ├── README.md └── main.tf ├── 03_terraform_alb_ingress ├── README.md ├── iam-policy.json └── main.tf ├── 04_terraform_helm_provider ├── README.md ├── iam-policy.json └── main.tf ├── 05_terraform_env_module ├── README.md ├── cluster │ └── main.tf ├── iam-policy.json └── main.tf ├── 06_terraform_envs_customised ├── README.md ├── cluster │ └── main.tf ├── iam-policy.json └── main.tf ├── README.md ├── eksctl ├── README.md └── cluster.yaml └── kubernetes ├── README.md ├── deployment.yaml ├── ingress.yaml ├── service-loadbalancer.yaml └── service-nodeport.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | terraform.tfstate 2 | terraform.tfstate.backup 3 | terraform.tfstate.*.backup 4 | .terraform.tfstate.lock.info 5 | kubeconfig* 6 | .terraform 7 | .history 8 | .terraform.lock.* -------------------------------------------------------------------------------- /01_terraform_eks/README.md: -------------------------------------------------------------------------------- 1 | # Basic provisioning of EKS with Terraform 2 | 3 | You can provision a basic EKS cluster with Terraform with the following commands: 4 | 5 | ```bash 6 | terraform init 7 | terraform plan 8 | terraform apply 9 | ``` 10 | 11 | It might take a while for the cluster to be created (up to 15-20 minutes). 12 | 13 | As soon as the cluster is ready, you should find a `kubeconfig_learnk8s` kubeconfig file in the current directory. 14 | -------------------------------------------------------------------------------- /01_terraform_eks/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "ap-south-1" 3 | } 4 | 5 | data "aws_availability_zones" "available" {} 6 | 7 | data "aws_eks_cluster" "cluster" { 8 | name = module.eks.cluster_id 9 | } 10 | 11 | data "aws_eks_cluster_auth" "cluster" { 12 | name = module.eks.cluster_id 13 | } 14 | 15 | locals { 16 | cluster_name = "learnk8s" 17 | } 18 | 19 | provider "kubernetes" { 20 | host = data.aws_eks_cluster.cluster.endpoint 21 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data) 22 | token = data.aws_eks_cluster_auth.cluster.token 23 | } 24 | 25 | module "eks-kubeconfig" { 26 | source = "hyperbadger/eks-kubeconfig/aws" 27 | version = "1.0.0" 28 | 29 | depends_on = [module.eks] 30 | cluster_id = module.eks.cluster_id 31 | } 32 | 33 | resource "local_file" "kubeconfig" { 34 | content = module.eks-kubeconfig.kubeconfig 35 | filename = "kubeconfig_${local.cluster_name}" 36 | } 37 | 38 | module "vpc" { 39 | source = "terraform-aws-modules/vpc/aws" 40 | version = "3.18.1" 41 | 42 | name = "k8s-vpc" 43 | cidr = "172.16.0.0/16" 44 | azs = data.aws_availability_zones.available.names 45 | private_subnets = ["172.16.1.0/24", "172.16.2.0/24", "172.16.3.0/24"] 46 | public_subnets = ["172.16.4.0/24", "172.16.5.0/24", "172.16.6.0/24"] 47 | enable_nat_gateway = true 48 | single_nat_gateway = true 49 | enable_dns_hostnames = true 50 | 51 | public_subnet_tags = { 52 | "kubernetes.io/cluster/${local.cluster_name}" = "shared" 53 | "kubernetes.io/role/elb" = "1" 54 | } 55 | 56 | private_subnet_tags = { 57 | "kubernetes.io/cluster/${local.cluster_name}" = "shared" 58 | "kubernetes.io/role/internal-elb" = "1" 59 | } 60 | } 61 | 62 | module "eks" { 63 | source = "terraform-aws-modules/eks/aws" 64 | version = "18.30.3" 65 | 66 | cluster_name = local.cluster_name 67 | cluster_version = "1.24" 68 | subnet_ids = module.vpc.private_subnets 69 | 70 | vpc_id = module.vpc.vpc_id 71 | 72 | eks_managed_node_groups = { 73 | first = { 74 | desired_capacity = 1 75 | max_capacity = 10 76 | min_capacity = 1 77 | 78 | instance_type = "m5.large" 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /02_terraform_node_pools/README.md: -------------------------------------------------------------------------------- 1 | # Multiple managed node pools with EKS and Terraform 2 | 3 | You can provision an EKS cluster with multiple node pools with: 4 | 5 | ```bash 6 | terraform init 7 | terraform plan 8 | terraform apply 9 | ``` 10 | 11 | It might take a while for the cluster to be created (up to 15-20 minutes). 12 | 13 | As soon as the cluster is ready, you should find a `kubeconfig_learnk8s` kubeconfig file in the current directory. 14 | -------------------------------------------------------------------------------- /02_terraform_node_pools/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "ap-south-1" 3 | } 4 | 5 | data "aws_availability_zones" "available" { 6 | } 7 | 8 | data "aws_eks_cluster" "cluster" { 9 | name = module.eks.cluster_id 10 | } 11 | 12 | data "aws_eks_cluster_auth" "cluster" { 13 | name = module.eks.cluster_id 14 | } 15 | 16 | locals { 17 | cluster_name = "learnk8s" 18 | } 19 | 20 | provider "kubernetes" { 21 | host = data.aws_eks_cluster.cluster.endpoint 22 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data) 23 | token = data.aws_eks_cluster_auth.cluster.token 24 | } 25 | 26 | module "eks-kubeconfig" { 27 | source = "hyperbadger/eks-kubeconfig/aws" 28 | version = "1.0.0" 29 | 30 | depends_on = [module.eks] 31 | cluster_id = module.eks.cluster_id 32 | } 33 | 34 | resource "local_file" "kubeconfig" { 35 | content = module.eks-kubeconfig.kubeconfig 36 | filename = "kubeconfig_${local.cluster_name}" 37 | } 38 | 39 | module "vpc" { 40 | source = "terraform-aws-modules/vpc/aws" 41 | version = "3.18.1" 42 | 43 | name = "k8s-vpc" 44 | cidr = "172.16.0.0/16" 45 | azs = data.aws_availability_zones.available.names 46 | private_subnets = ["172.16.1.0/24", "172.16.2.0/24", "172.16.3.0/24"] 47 | public_subnets = ["172.16.4.0/24", "172.16.5.0/24", "172.16.6.0/24"] 48 | enable_nat_gateway = true 49 | single_nat_gateway = true 50 | enable_dns_hostnames = true 51 | 52 | public_subnet_tags = { 53 | "kubernetes.io/cluster/${local.cluster_name}" = "shared" 54 | "kubernetes.io/role/elb" = "1" 55 | } 56 | 57 | private_subnet_tags = { 58 | "kubernetes.io/cluster/${local.cluster_name}" = "shared" 59 | "kubernetes.io/role/internal-elb" = "1" 60 | } 61 | } 62 | 63 | module "eks" { 64 | source = "terraform-aws-modules/eks/aws" 65 | version = "18.30.3" 66 | 67 | cluster_name = local.cluster_name 68 | cluster_version = "1.24" 69 | subnet_ids = module.vpc.private_subnets 70 | vpc_id = module.vpc.vpc_id 71 | 72 | eks_managed_node_groups = { 73 | first = { 74 | desired_capacity = 1 75 | max_capacity = 10 76 | min_capacity = 1 77 | 78 | instance_type = "m5.large" 79 | } 80 | gpu = { 81 | desired_capacity = 1 82 | max_capacity = 10 83 | min_capacity = 1 84 | 85 | instance_type = "p3.2xlarge" 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /03_terraform_alb_ingress/README.md: -------------------------------------------------------------------------------- 1 | # Setting up IAM policies for the ALB Ingress Controller in EKS with Terraform 2 | 3 | You can provision an EKS cluster with the right policies for the ALB Ingress Controller with: 4 | 5 | ```bash 6 | terraform init 7 | terraform plan 8 | terraform apply 9 | ``` 10 | 11 | It might take a while for the cluster to be created (up to 15-20 minutes). 12 | 13 | As soon as the cluster is ready, you should find a `kubeconfig_learnk8s` kubeconfig file in the current directory. 14 | 15 | You can use the kubeconfig file to deploy the ALB Ingress controller with: 16 | 17 | ```bash 18 | helm install ingress incubator/aws-load-balancer-controller \ 19 | --set autoDiscoverAwsRegion=true \ 20 | --set autoDiscoverAwsVpcID=true \ 21 | --set clusterName=learnk8s 22 | ``` 23 | -------------------------------------------------------------------------------- /03_terraform_alb_ingress/iam-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "iam:CreateServiceLinkedRole" 8 | ], 9 | "Resource": "*", 10 | "Condition": { 11 | "StringEquals": { 12 | "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com" 13 | } 14 | } 15 | }, 16 | { 17 | "Effect": "Allow", 18 | "Action": [ 19 | "ec2:DescribeAccountAttributes", 20 | "ec2:DescribeAddresses", 21 | "ec2:DescribeAvailabilityZones", 22 | "ec2:DescribeInternetGateways", 23 | "ec2:DescribeVpcs", 24 | "ec2:DescribeVpcPeeringConnections", 25 | "ec2:DescribeSubnets", 26 | "ec2:DescribeSecurityGroups", 27 | "ec2:DescribeInstances", 28 | "ec2:DescribeNetworkInterfaces", 29 | "ec2:DescribeTags", 30 | "ec2:GetCoipPoolUsage", 31 | "ec2:DescribeCoipPools", 32 | "elasticloadbalancing:DescribeLoadBalancers", 33 | "elasticloadbalancing:DescribeLoadBalancerAttributes", 34 | "elasticloadbalancing:DescribeListeners", 35 | "elasticloadbalancing:DescribeListenerCertificates", 36 | "elasticloadbalancing:DescribeSSLPolicies", 37 | "elasticloadbalancing:DescribeRules", 38 | "elasticloadbalancing:DescribeTargetGroups", 39 | "elasticloadbalancing:DescribeTargetGroupAttributes", 40 | "elasticloadbalancing:DescribeTargetHealth", 41 | "elasticloadbalancing:DescribeTags" 42 | ], 43 | "Resource": "*" 44 | }, 45 | { 46 | "Effect": "Allow", 47 | "Action": [ 48 | "cognito-idp:DescribeUserPoolClient", 49 | "acm:ListCertificates", 50 | "acm:DescribeCertificate", 51 | "iam:ListServerCertificates", 52 | "iam:GetServerCertificate", 53 | "waf-regional:GetWebACL", 54 | "waf-regional:GetWebACLForResource", 55 | "waf-regional:AssociateWebACL", 56 | "waf-regional:DisassociateWebACL", 57 | "wafv2:GetWebACL", 58 | "wafv2:GetWebACLForResource", 59 | "wafv2:AssociateWebACL", 60 | "wafv2:DisassociateWebACL", 61 | "shield:GetSubscriptionState", 62 | "shield:DescribeProtection", 63 | "shield:CreateProtection", 64 | "shield:DeleteProtection" 65 | ], 66 | "Resource": "*" 67 | }, 68 | { 69 | "Effect": "Allow", 70 | "Action": [ 71 | "ec2:AuthorizeSecurityGroupIngress", 72 | "ec2:RevokeSecurityGroupIngress" 73 | ], 74 | "Resource": "*" 75 | }, 76 | { 77 | "Effect": "Allow", 78 | "Action": [ 79 | "ec2:CreateSecurityGroup" 80 | ], 81 | "Resource": "*" 82 | }, 83 | { 84 | "Effect": "Allow", 85 | "Action": [ 86 | "ec2:CreateTags" 87 | ], 88 | "Resource": "arn:aws:ec2:*:*:security-group/*", 89 | "Condition": { 90 | "StringEquals": { 91 | "ec2:CreateAction": "CreateSecurityGroup" 92 | }, 93 | "Null": { 94 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 95 | } 96 | } 97 | }, 98 | { 99 | "Effect": "Allow", 100 | "Action": [ 101 | "ec2:CreateTags", 102 | "ec2:DeleteTags" 103 | ], 104 | "Resource": "arn:aws:ec2:*:*:security-group/*", 105 | "Condition": { 106 | "Null": { 107 | "aws:RequestTag/elbv2.k8s.aws/cluster": "true", 108 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 109 | } 110 | } 111 | }, 112 | { 113 | "Effect": "Allow", 114 | "Action": [ 115 | "ec2:AuthorizeSecurityGroupIngress", 116 | "ec2:RevokeSecurityGroupIngress", 117 | "ec2:DeleteSecurityGroup" 118 | ], 119 | "Resource": "*", 120 | "Condition": { 121 | "Null": { 122 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 123 | } 124 | } 125 | }, 126 | { 127 | "Effect": "Allow", 128 | "Action": [ 129 | "elasticloadbalancing:CreateLoadBalancer", 130 | "elasticloadbalancing:CreateTargetGroup" 131 | ], 132 | "Resource": "*", 133 | "Condition": { 134 | "Null": { 135 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 136 | } 137 | } 138 | }, 139 | { 140 | "Effect": "Allow", 141 | "Action": [ 142 | "elasticloadbalancing:CreateListener", 143 | "elasticloadbalancing:DeleteListener", 144 | "elasticloadbalancing:CreateRule", 145 | "elasticloadbalancing:DeleteRule" 146 | ], 147 | "Resource": "*" 148 | }, 149 | { 150 | "Effect": "Allow", 151 | "Action": [ 152 | "elasticloadbalancing:AddTags", 153 | "elasticloadbalancing:RemoveTags" 154 | ], 155 | "Resource": [ 156 | "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*", 157 | "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*", 158 | "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*" 159 | ], 160 | "Condition": { 161 | "Null": { 162 | "aws:RequestTag/elbv2.k8s.aws/cluster": "true", 163 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 164 | } 165 | } 166 | }, 167 | { 168 | "Effect": "Allow", 169 | "Action": [ 170 | "elasticloadbalancing:AddTags", 171 | "elasticloadbalancing:RemoveTags" 172 | ], 173 | "Resource": [ 174 | "arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*", 175 | "arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*", 176 | "arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*", 177 | "arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*" 178 | ] 179 | }, 180 | { 181 | "Effect": "Allow", 182 | "Action": [ 183 | "elasticloadbalancing:ModifyLoadBalancerAttributes", 184 | "elasticloadbalancing:SetIpAddressType", 185 | "elasticloadbalancing:SetSecurityGroups", 186 | "elasticloadbalancing:SetSubnets", 187 | "elasticloadbalancing:DeleteLoadBalancer", 188 | "elasticloadbalancing:ModifyTargetGroup", 189 | "elasticloadbalancing:ModifyTargetGroupAttributes", 190 | "elasticloadbalancing:DeleteTargetGroup" 191 | ], 192 | "Resource": "*", 193 | "Condition": { 194 | "Null": { 195 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 196 | } 197 | } 198 | }, 199 | { 200 | "Effect": "Allow", 201 | "Action": [ 202 | "elasticloadbalancing:RegisterTargets", 203 | "elasticloadbalancing:DeregisterTargets" 204 | ], 205 | "Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*" 206 | }, 207 | { 208 | "Effect": "Allow", 209 | "Action": [ 210 | "elasticloadbalancing:SetWebAcl", 211 | "elasticloadbalancing:ModifyListener", 212 | "elasticloadbalancing:AddListenerCertificates", 213 | "elasticloadbalancing:RemoveListenerCertificates", 214 | "elasticloadbalancing:ModifyRule" 215 | ], 216 | "Resource": "*" 217 | } 218 | ] 219 | } 220 | -------------------------------------------------------------------------------- /03_terraform_alb_ingress/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "ap-south-1" 3 | } 4 | 5 | data "aws_eks_cluster" "cluster" { 6 | name = module.eks.cluster_id 7 | } 8 | 9 | data "aws_eks_cluster_auth" "cluster" { 10 | name = module.eks.cluster_id 11 | } 12 | 13 | data "aws_availability_zones" "available" {} 14 | 15 | locals { 16 | cluster_name = "learnk8s" 17 | } 18 | 19 | provider "kubernetes" { 20 | host = data.aws_eks_cluster.cluster.endpoint 21 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data) 22 | token = data.aws_eks_cluster_auth.cluster.token 23 | } 24 | 25 | module "eks-kubeconfig" { 26 | source = "hyperbadger/eks-kubeconfig/aws" 27 | version = "1.0.0" 28 | 29 | depends_on = [module.eks] 30 | cluster_id = module.eks.cluster_id 31 | } 32 | 33 | resource "local_file" "kubeconfig" { 34 | content = module.eks-kubeconfig.kubeconfig 35 | filename = "kubeconfig_${local.cluster_name}" 36 | } 37 | 38 | module "vpc" { 39 | source = "terraform-aws-modules/vpc/aws" 40 | version = "3.18.1" 41 | 42 | name = "k8s-vpc" 43 | cidr = "172.16.0.0/16" 44 | azs = data.aws_availability_zones.available.names 45 | private_subnets = ["172.16.1.0/24", "172.16.2.0/24", "172.16.3.0/24"] 46 | public_subnets = ["172.16.4.0/24", "172.16.5.0/24", "172.16.6.0/24"] 47 | enable_nat_gateway = true 48 | single_nat_gateway = true 49 | enable_dns_hostnames = true 50 | 51 | public_subnet_tags = { 52 | "kubernetes.io/cluster/${local.cluster_name}" = "shared" 53 | "kubernetes.io/role/elb" = "1" 54 | } 55 | 56 | private_subnet_tags = { 57 | "kubernetes.io/cluster/${local.cluster_name}" = "shared" 58 | "kubernetes.io/role/internal-elb" = "1" 59 | } 60 | } 61 | 62 | module "eks" { 63 | source = "terraform-aws-modules/eks/aws" 64 | version = "18.30.3" 65 | 66 | cluster_name = local.cluster_name 67 | cluster_version = "1.24" 68 | subnet_ids = module.vpc.private_subnets 69 | vpc_id = module.vpc.vpc_id 70 | 71 | eks_managed_node_groups = { 72 | first = { 73 | desired_capacity = 1 74 | max_capacity = 10 75 | min_capacity = 1 76 | 77 | instance_type = "m5.large" 78 | } 79 | } 80 | node_security_group_additional_rules = { 81 | ingress_allow_access_from_control_plane = { 82 | type = "ingress" 83 | protocol = "tcp" 84 | from_port = 9443 85 | to_port = 9443 86 | source_cluster_security_group = true 87 | description = "Allow access from control plane to webhook port of AWS load balancer controller" 88 | } 89 | } 90 | } 91 | 92 | resource "aws_iam_policy" "worker_policy" { 93 | name = "worker-policy" 94 | description = "Worker policy for the ALB Ingress" 95 | 96 | policy = file("iam-policy.json") 97 | } 98 | 99 | resource "aws_iam_role_policy_attachment" "additional" { 100 | for_each = module.eks.eks_managed_node_groups 101 | 102 | policy_arn = aws_iam_policy.worker_policy.arn 103 | role = each.value.iam_role_name 104 | 105 | } 106 | -------------------------------------------------------------------------------- /04_terraform_helm_provider/README.md: -------------------------------------------------------------------------------- 1 | # Integrating the Helm provider with Terraform and EKS 2 | 3 | You can provision an EKS cluster and install Helm packages at the same time with: 4 | 5 | ```bash 6 | terraform init 7 | terraform plan 8 | terraform apply 9 | ``` 10 | 11 | It might take a while for the cluster to be created (up to 15-20 minutes). 12 | 13 | As soon as the cluster is ready, you should find a `kubeconfig_learnk8s` kubeconfig file in the current directory. 14 | 15 | The current code automatically installs the ALB Ingress Controller with Helm. 16 | -------------------------------------------------------------------------------- /04_terraform_helm_provider/iam-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "iam:CreateServiceLinkedRole" 8 | ], 9 | "Resource": "*", 10 | "Condition": { 11 | "StringEquals": { 12 | "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com" 13 | } 14 | } 15 | }, 16 | { 17 | "Effect": "Allow", 18 | "Action": [ 19 | "ec2:DescribeAccountAttributes", 20 | "ec2:DescribeAddresses", 21 | "ec2:DescribeAvailabilityZones", 22 | "ec2:DescribeInternetGateways", 23 | "ec2:DescribeVpcs", 24 | "ec2:DescribeVpcPeeringConnections", 25 | "ec2:DescribeSubnets", 26 | "ec2:DescribeSecurityGroups", 27 | "ec2:DescribeInstances", 28 | "ec2:DescribeNetworkInterfaces", 29 | "ec2:DescribeTags", 30 | "ec2:GetCoipPoolUsage", 31 | "ec2:DescribeCoipPools", 32 | "elasticloadbalancing:DescribeLoadBalancers", 33 | "elasticloadbalancing:DescribeLoadBalancerAttributes", 34 | "elasticloadbalancing:DescribeListeners", 35 | "elasticloadbalancing:DescribeListenerCertificates", 36 | "elasticloadbalancing:DescribeSSLPolicies", 37 | "elasticloadbalancing:DescribeRules", 38 | "elasticloadbalancing:DescribeTargetGroups", 39 | "elasticloadbalancing:DescribeTargetGroupAttributes", 40 | "elasticloadbalancing:DescribeTargetHealth", 41 | "elasticloadbalancing:DescribeTags" 42 | ], 43 | "Resource": "*" 44 | }, 45 | { 46 | "Effect": "Allow", 47 | "Action": [ 48 | "cognito-idp:DescribeUserPoolClient", 49 | "acm:ListCertificates", 50 | "acm:DescribeCertificate", 51 | "iam:ListServerCertificates", 52 | "iam:GetServerCertificate", 53 | "waf-regional:GetWebACL", 54 | "waf-regional:GetWebACLForResource", 55 | "waf-regional:AssociateWebACL", 56 | "waf-regional:DisassociateWebACL", 57 | "wafv2:GetWebACL", 58 | "wafv2:GetWebACLForResource", 59 | "wafv2:AssociateWebACL", 60 | "wafv2:DisassociateWebACL", 61 | "shield:GetSubscriptionState", 62 | "shield:DescribeProtection", 63 | "shield:CreateProtection", 64 | "shield:DeleteProtection" 65 | ], 66 | "Resource": "*" 67 | }, 68 | { 69 | "Effect": "Allow", 70 | "Action": [ 71 | "ec2:AuthorizeSecurityGroupIngress", 72 | "ec2:RevokeSecurityGroupIngress" 73 | ], 74 | "Resource": "*" 75 | }, 76 | { 77 | "Effect": "Allow", 78 | "Action": [ 79 | "ec2:CreateSecurityGroup" 80 | ], 81 | "Resource": "*" 82 | }, 83 | { 84 | "Effect": "Allow", 85 | "Action": [ 86 | "ec2:CreateTags" 87 | ], 88 | "Resource": "arn:aws:ec2:*:*:security-group/*", 89 | "Condition": { 90 | "StringEquals": { 91 | "ec2:CreateAction": "CreateSecurityGroup" 92 | }, 93 | "Null": { 94 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 95 | } 96 | } 97 | }, 98 | { 99 | "Effect": "Allow", 100 | "Action": [ 101 | "ec2:CreateTags", 102 | "ec2:DeleteTags" 103 | ], 104 | "Resource": "arn:aws:ec2:*:*:security-group/*", 105 | "Condition": { 106 | "Null": { 107 | "aws:RequestTag/elbv2.k8s.aws/cluster": "true", 108 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 109 | } 110 | } 111 | }, 112 | { 113 | "Effect": "Allow", 114 | "Action": [ 115 | "ec2:AuthorizeSecurityGroupIngress", 116 | "ec2:RevokeSecurityGroupIngress", 117 | "ec2:DeleteSecurityGroup" 118 | ], 119 | "Resource": "*", 120 | "Condition": { 121 | "Null": { 122 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 123 | } 124 | } 125 | }, 126 | { 127 | "Effect": "Allow", 128 | "Action": [ 129 | "elasticloadbalancing:CreateLoadBalancer", 130 | "elasticloadbalancing:CreateTargetGroup" 131 | ], 132 | "Resource": "*", 133 | "Condition": { 134 | "Null": { 135 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 136 | } 137 | } 138 | }, 139 | { 140 | "Effect": "Allow", 141 | "Action": [ 142 | "elasticloadbalancing:CreateListener", 143 | "elasticloadbalancing:DeleteListener", 144 | "elasticloadbalancing:CreateRule", 145 | "elasticloadbalancing:DeleteRule" 146 | ], 147 | "Resource": "*" 148 | }, 149 | { 150 | "Effect": "Allow", 151 | "Action": [ 152 | "elasticloadbalancing:AddTags", 153 | "elasticloadbalancing:RemoveTags" 154 | ], 155 | "Resource": [ 156 | "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*", 157 | "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*", 158 | "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*" 159 | ], 160 | "Condition": { 161 | "Null": { 162 | "aws:RequestTag/elbv2.k8s.aws/cluster": "true", 163 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 164 | } 165 | } 166 | }, 167 | { 168 | "Effect": "Allow", 169 | "Action": [ 170 | "elasticloadbalancing:AddTags", 171 | "elasticloadbalancing:RemoveTags" 172 | ], 173 | "Resource": [ 174 | "arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*", 175 | "arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*", 176 | "arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*", 177 | "arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*" 178 | ] 179 | }, 180 | { 181 | "Effect": "Allow", 182 | "Action": [ 183 | "elasticloadbalancing:ModifyLoadBalancerAttributes", 184 | "elasticloadbalancing:SetIpAddressType", 185 | "elasticloadbalancing:SetSecurityGroups", 186 | "elasticloadbalancing:SetSubnets", 187 | "elasticloadbalancing:DeleteLoadBalancer", 188 | "elasticloadbalancing:ModifyTargetGroup", 189 | "elasticloadbalancing:ModifyTargetGroupAttributes", 190 | "elasticloadbalancing:DeleteTargetGroup" 191 | ], 192 | "Resource": "*", 193 | "Condition": { 194 | "Null": { 195 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 196 | } 197 | } 198 | }, 199 | { 200 | "Effect": "Allow", 201 | "Action": [ 202 | "elasticloadbalancing:RegisterTargets", 203 | "elasticloadbalancing:DeregisterTargets" 204 | ], 205 | "Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*" 206 | }, 207 | { 208 | "Effect": "Allow", 209 | "Action": [ 210 | "elasticloadbalancing:SetWebAcl", 211 | "elasticloadbalancing:ModifyListener", 212 | "elasticloadbalancing:AddListenerCertificates", 213 | "elasticloadbalancing:RemoveListenerCertificates", 214 | "elasticloadbalancing:ModifyRule" 215 | ], 216 | "Resource": "*" 217 | } 218 | ] 219 | } 220 | -------------------------------------------------------------------------------- /04_terraform_helm_provider/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "ap-south-1" 3 | } 4 | 5 | data "aws_eks_cluster" "cluster" { 6 | name = module.eks.cluster_id 7 | } 8 | 9 | data "aws_eks_cluster_auth" "cluster" { 10 | name = module.eks.cluster_id 11 | } 12 | 13 | data "aws_availability_zones" "available" {} 14 | 15 | locals { 16 | cluster_name = "learnk8s" 17 | } 18 | 19 | provider "kubernetes" { 20 | host = data.aws_eks_cluster.cluster.endpoint 21 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data) 22 | token = data.aws_eks_cluster_auth.cluster.token 23 | 24 | } 25 | 26 | module "eks-kubeconfig" { 27 | source = "hyperbadger/eks-kubeconfig/aws" 28 | version = "1.0.0" 29 | 30 | depends_on = [module.eks] 31 | cluster_id = module.eks.cluster_id 32 | } 33 | 34 | resource "local_file" "kubeconfig" { 35 | content = module.eks-kubeconfig.kubeconfig 36 | filename = "kubeconfig_${local.cluster_name}" 37 | } 38 | 39 | module "vpc" { 40 | source = "terraform-aws-modules/vpc/aws" 41 | version = "3.18.1" 42 | 43 | name = "k8s-vpc" 44 | cidr = "172.16.0.0/16" 45 | azs = data.aws_availability_zones.available.names 46 | private_subnets = ["172.16.1.0/24", "172.16.2.0/24", "172.16.3.0/24"] 47 | public_subnets = ["172.16.4.0/24", "172.16.5.0/24", "172.16.6.0/24"] 48 | enable_nat_gateway = true 49 | single_nat_gateway = true 50 | enable_dns_hostnames = true 51 | 52 | public_subnet_tags = { 53 | "kubernetes.io/cluster/${local.cluster_name}" = "shared" 54 | "kubernetes.io/role/elb" = "1" 55 | } 56 | 57 | private_subnet_tags = { 58 | "kubernetes.io/cluster/${local.cluster_name}" = "shared" 59 | "kubernetes.io/role/internal-elb" = "1" 60 | } 61 | } 62 | 63 | module "eks" { 64 | source = "terraform-aws-modules/eks/aws" 65 | version = "18.30.3" 66 | 67 | cluster_name = local.cluster_name 68 | cluster_version = "1.24" 69 | subnet_ids = module.vpc.private_subnets 70 | vpc_id = module.vpc.vpc_id 71 | 72 | eks_managed_node_groups = { 73 | first = { 74 | desired_capacity = 1 75 | max_capacity = 10 76 | min_capacity = 1 77 | 78 | instance_type = "m5.large" 79 | } 80 | } 81 | node_security_group_additional_rules = { 82 | ingress_allow_access_from_control_plane = { 83 | type = "ingress" 84 | protocol = "tcp" 85 | from_port = 9443 86 | to_port = 9443 87 | source_cluster_security_group = true 88 | description = "Allow access from control plane to webhook port of AWS load balancer controller" 89 | } 90 | } 91 | } 92 | 93 | resource "aws_iam_policy" "worker_policy" { 94 | name = "worker-policy" 95 | description = "Worker policy for the ALB Ingress" 96 | 97 | policy = file("iam-policy.json") 98 | } 99 | 100 | resource "aws_iam_role_policy_attachment" "additional" { 101 | for_each = module.eks.eks_managed_node_groups 102 | 103 | policy_arn = aws_iam_policy.worker_policy.arn 104 | role = each.value.iam_role_name 105 | } 106 | 107 | provider "helm" { 108 | kubernetes { 109 | host = data.aws_eks_cluster.cluster.endpoint 110 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data) 111 | token = data.aws_eks_cluster_auth.cluster.token 112 | } 113 | } 114 | 115 | resource "helm_release" "ingress" { 116 | name = "ingress" 117 | chart = "aws-load-balancer-controller" 118 | repository = "https://aws.github.io/eks-charts" 119 | version = "1.4.6" 120 | 121 | set { 122 | name = "autoDiscoverAwsRegion" 123 | value = "true" 124 | } 125 | set { 126 | name = "autoDiscoverAwsVpcID" 127 | value = "true" 128 | } 129 | set { 130 | name = "clusterName" 131 | value = local.cluster_name 132 | } 133 | } -------------------------------------------------------------------------------- /05_terraform_env_module/README.md: -------------------------------------------------------------------------------- 1 | # Encapsulating clusters as Terraform modules 2 | 3 | You can provision multiple EKS clusters with a single `terraform apply`: 4 | 5 | ```bash 6 | terraform init 7 | terraform plan 8 | terraform apply 9 | ``` 10 | 11 | It might take a while for the cluster to be created (up to 15-20 minutes). 12 | 13 | At the end you will have: 14 | 15 | 1. A cluster for development. 16 | 1. A cluster for staging. 17 | 1. A cluster for production. 18 | 19 | In the same folder, you will find a kubeconfig file for each cluster. 20 | -------------------------------------------------------------------------------- /05_terraform_env_module/cluster/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "ap-south-1" 3 | } 4 | 5 | data "aws_eks_cluster" "cluster" { 6 | name = module.eks.cluster_id 7 | } 8 | 9 | data "aws_eks_cluster_auth" "cluster" { 10 | name = module.eks.cluster_id 11 | } 12 | 13 | data "aws_availability_zones" "available" {} 14 | 15 | variable "cluster_name" { 16 | default = "learnk8s" 17 | } 18 | 19 | provider "kubernetes" { 20 | host = data.aws_eks_cluster.cluster.endpoint 21 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data) 22 | token = data.aws_eks_cluster_auth.cluster.token 23 | 24 | } 25 | 26 | module "eks-kubeconfig" { 27 | source = "hyperbadger/eks-kubeconfig/aws" 28 | version = "1.0.0" 29 | 30 | depends_on = [module.eks] 31 | cluster_id = module.eks.cluster_id 32 | } 33 | 34 | resource "local_file" "kubeconfig" { 35 | content = module.eks-kubeconfig.kubeconfig 36 | filename = "kubeconfig_${var.cluster_name}" 37 | } 38 | 39 | module "vpc" { 40 | source = "terraform-aws-modules/vpc/aws" 41 | version = "3.18.1" 42 | 43 | name = "k8s-${var.cluster_name}-vpc" 44 | cidr = "172.16.0.0/16" 45 | azs = data.aws_availability_zones.available.names 46 | private_subnets = ["172.16.1.0/24", "172.16.2.0/24", "172.16.3.0/24"] 47 | public_subnets = ["172.16.4.0/24", "172.16.5.0/24", "172.16.6.0/24"] 48 | enable_nat_gateway = true 49 | single_nat_gateway = true 50 | enable_dns_hostnames = true 51 | 52 | public_subnet_tags = { 53 | "kubernetes.io/cluster/${var.cluster_name}" = "shared" 54 | "kubernetes.io/role/elb" = "1" 55 | } 56 | 57 | private_subnet_tags = { 58 | "kubernetes.io/cluster/${var.cluster_name}" = "shared" 59 | "kubernetes.io/role/internal-elb" = "1" 60 | } 61 | } 62 | 63 | module "eks" { 64 | source = "terraform-aws-modules/eks/aws" 65 | version = "18.30.3" 66 | 67 | cluster_name = "eks-${var.cluster_name}" 68 | cluster_version = "1.24" 69 | subnet_ids = module.vpc.private_subnets 70 | vpc_id = module.vpc.vpc_id 71 | 72 | eks_managed_node_groups = { 73 | first = { 74 | desired_capacity = 1 75 | max_capacity = 10 76 | min_capacity = 1 77 | 78 | instance_type = "m5.large" 79 | } 80 | } 81 | 82 | node_security_group_additional_rules = { 83 | ingress_allow_access_from_control_plane = { 84 | type = "ingress" 85 | protocol = "tcp" 86 | from_port = 9443 87 | to_port = 9443 88 | source_cluster_security_group = true 89 | description = "Allow access from control plane to webhook port of AWS load balancer controller" 90 | } 91 | } 92 | } 93 | 94 | resource "aws_iam_policy" "worker_policy" { 95 | name = "worker-policy-${var.cluster_name}" 96 | description = "Worker policy for the ALB Ingress" 97 | 98 | policy = file("iam-policy.json") 99 | } 100 | 101 | resource "aws_iam_role_policy_attachment" "additional" { 102 | for_each = module.eks.eks_managed_node_groups 103 | 104 | policy_arn = aws_iam_policy.worker_policy.arn 105 | role = each.value.iam_role_name 106 | } 107 | 108 | provider "helm" { 109 | kubernetes { 110 | host = data.aws_eks_cluster.cluster.endpoint 111 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data) 112 | token = data.aws_eks_cluster_auth.cluster.token 113 | } 114 | } 115 | 116 | resource "helm_release" "ingress" { 117 | name = "ingress" 118 | chart = "aws-load-balancer-controller" 119 | repository = "https://aws.github.io/eks-charts" 120 | version = "1.4.6" 121 | 122 | set { 123 | name = "autoDiscoverAwsRegion" 124 | value = "true" 125 | } 126 | set { 127 | name = "autoDiscoverAwsVpcID" 128 | value = "true" 129 | } 130 | set { 131 | name = "clusterName" 132 | value = var.cluster_name 133 | } 134 | } -------------------------------------------------------------------------------- /05_terraform_env_module/iam-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "iam:CreateServiceLinkedRole" 8 | ], 9 | "Resource": "*", 10 | "Condition": { 11 | "StringEquals": { 12 | "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com" 13 | } 14 | } 15 | }, 16 | { 17 | "Effect": "Allow", 18 | "Action": [ 19 | "ec2:DescribeAccountAttributes", 20 | "ec2:DescribeAddresses", 21 | "ec2:DescribeAvailabilityZones", 22 | "ec2:DescribeInternetGateways", 23 | "ec2:DescribeVpcs", 24 | "ec2:DescribeVpcPeeringConnections", 25 | "ec2:DescribeSubnets", 26 | "ec2:DescribeSecurityGroups", 27 | "ec2:DescribeInstances", 28 | "ec2:DescribeNetworkInterfaces", 29 | "ec2:DescribeTags", 30 | "ec2:GetCoipPoolUsage", 31 | "ec2:DescribeCoipPools", 32 | "elasticloadbalancing:DescribeLoadBalancers", 33 | "elasticloadbalancing:DescribeLoadBalancerAttributes", 34 | "elasticloadbalancing:DescribeListeners", 35 | "elasticloadbalancing:DescribeListenerCertificates", 36 | "elasticloadbalancing:DescribeSSLPolicies", 37 | "elasticloadbalancing:DescribeRules", 38 | "elasticloadbalancing:DescribeTargetGroups", 39 | "elasticloadbalancing:DescribeTargetGroupAttributes", 40 | "elasticloadbalancing:DescribeTargetHealth", 41 | "elasticloadbalancing:DescribeTags" 42 | ], 43 | "Resource": "*" 44 | }, 45 | { 46 | "Effect": "Allow", 47 | "Action": [ 48 | "cognito-idp:DescribeUserPoolClient", 49 | "acm:ListCertificates", 50 | "acm:DescribeCertificate", 51 | "iam:ListServerCertificates", 52 | "iam:GetServerCertificate", 53 | "waf-regional:GetWebACL", 54 | "waf-regional:GetWebACLForResource", 55 | "waf-regional:AssociateWebACL", 56 | "waf-regional:DisassociateWebACL", 57 | "wafv2:GetWebACL", 58 | "wafv2:GetWebACLForResource", 59 | "wafv2:AssociateWebACL", 60 | "wafv2:DisassociateWebACL", 61 | "shield:GetSubscriptionState", 62 | "shield:DescribeProtection", 63 | "shield:CreateProtection", 64 | "shield:DeleteProtection" 65 | ], 66 | "Resource": "*" 67 | }, 68 | { 69 | "Effect": "Allow", 70 | "Action": [ 71 | "ec2:AuthorizeSecurityGroupIngress", 72 | "ec2:RevokeSecurityGroupIngress" 73 | ], 74 | "Resource": "*" 75 | }, 76 | { 77 | "Effect": "Allow", 78 | "Action": [ 79 | "ec2:CreateSecurityGroup" 80 | ], 81 | "Resource": "*" 82 | }, 83 | { 84 | "Effect": "Allow", 85 | "Action": [ 86 | "ec2:CreateTags" 87 | ], 88 | "Resource": "arn:aws:ec2:*:*:security-group/*", 89 | "Condition": { 90 | "StringEquals": { 91 | "ec2:CreateAction": "CreateSecurityGroup" 92 | }, 93 | "Null": { 94 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 95 | } 96 | } 97 | }, 98 | { 99 | "Effect": "Allow", 100 | "Action": [ 101 | "ec2:CreateTags", 102 | "ec2:DeleteTags" 103 | ], 104 | "Resource": "arn:aws:ec2:*:*:security-group/*", 105 | "Condition": { 106 | "Null": { 107 | "aws:RequestTag/elbv2.k8s.aws/cluster": "true", 108 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 109 | } 110 | } 111 | }, 112 | { 113 | "Effect": "Allow", 114 | "Action": [ 115 | "ec2:AuthorizeSecurityGroupIngress", 116 | "ec2:RevokeSecurityGroupIngress", 117 | "ec2:DeleteSecurityGroup" 118 | ], 119 | "Resource": "*", 120 | "Condition": { 121 | "Null": { 122 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 123 | } 124 | } 125 | }, 126 | { 127 | "Effect": "Allow", 128 | "Action": [ 129 | "elasticloadbalancing:CreateLoadBalancer", 130 | "elasticloadbalancing:CreateTargetGroup" 131 | ], 132 | "Resource": "*", 133 | "Condition": { 134 | "Null": { 135 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 136 | } 137 | } 138 | }, 139 | { 140 | "Effect": "Allow", 141 | "Action": [ 142 | "elasticloadbalancing:CreateListener", 143 | "elasticloadbalancing:DeleteListener", 144 | "elasticloadbalancing:CreateRule", 145 | "elasticloadbalancing:DeleteRule" 146 | ], 147 | "Resource": "*" 148 | }, 149 | { 150 | "Effect": "Allow", 151 | "Action": [ 152 | "elasticloadbalancing:AddTags", 153 | "elasticloadbalancing:RemoveTags" 154 | ], 155 | "Resource": [ 156 | "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*", 157 | "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*", 158 | "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*" 159 | ], 160 | "Condition": { 161 | "Null": { 162 | "aws:RequestTag/elbv2.k8s.aws/cluster": "true", 163 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 164 | } 165 | } 166 | }, 167 | { 168 | "Effect": "Allow", 169 | "Action": [ 170 | "elasticloadbalancing:AddTags", 171 | "elasticloadbalancing:RemoveTags" 172 | ], 173 | "Resource": [ 174 | "arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*", 175 | "arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*", 176 | "arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*", 177 | "arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*" 178 | ] 179 | }, 180 | { 181 | "Effect": "Allow", 182 | "Action": [ 183 | "elasticloadbalancing:ModifyLoadBalancerAttributes", 184 | "elasticloadbalancing:SetIpAddressType", 185 | "elasticloadbalancing:SetSecurityGroups", 186 | "elasticloadbalancing:SetSubnets", 187 | "elasticloadbalancing:DeleteLoadBalancer", 188 | "elasticloadbalancing:ModifyTargetGroup", 189 | "elasticloadbalancing:ModifyTargetGroupAttributes", 190 | "elasticloadbalancing:DeleteTargetGroup" 191 | ], 192 | "Resource": "*", 193 | "Condition": { 194 | "Null": { 195 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 196 | } 197 | } 198 | }, 199 | { 200 | "Effect": "Allow", 201 | "Action": [ 202 | "elasticloadbalancing:RegisterTargets", 203 | "elasticloadbalancing:DeregisterTargets" 204 | ], 205 | "Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*" 206 | }, 207 | { 208 | "Effect": "Allow", 209 | "Action": [ 210 | "elasticloadbalancing:SetWebAcl", 211 | "elasticloadbalancing:ModifyListener", 212 | "elasticloadbalancing:AddListenerCertificates", 213 | "elasticloadbalancing:RemoveListenerCertificates", 214 | "elasticloadbalancing:ModifyRule" 215 | ], 216 | "Resource": "*" 217 | } 218 | ] 219 | } 220 | -------------------------------------------------------------------------------- /05_terraform_env_module/main.tf: -------------------------------------------------------------------------------- 1 | module "dev_cluster" { 2 | source = "./cluster" 3 | 4 | cluster_name = "dev" 5 | } 6 | 7 | module "staging_cluster" { 8 | source = "./cluster" 9 | 10 | cluster_name = "staging" 11 | } 12 | 13 | module "production_cluster" { 14 | source = "./cluster" 15 | 16 | cluster_name = "production" 17 | } 18 | -------------------------------------------------------------------------------- /06_terraform_envs_customised/README.md: -------------------------------------------------------------------------------- 1 | # Parametrizing clusters as Terraform modules 2 | 3 | You can provision multiple EKS clusters with a single `terraform apply`: 4 | 5 | ```bash 6 | terraform init 7 | terraform plan 8 | terraform apply 9 | ``` 10 | 11 | It might take a while for the cluster to be created (up to 15-20 minutes). 12 | 13 | At the end you will have: 14 | 15 | 1. A cluster for development. 16 | 1. A cluster for staging. 17 | 1. A cluster for production. 18 | 19 | In the same folder you will find a kubeconfig file for each cluster. 20 | -------------------------------------------------------------------------------- /06_terraform_envs_customised/cluster/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "ap-south-1" 3 | } 4 | 5 | data "aws_eks_cluster" "cluster" { 6 | name = module.eks.cluster_id 7 | } 8 | 9 | data "aws_eks_cluster_auth" "cluster" { 10 | name = module.eks.cluster_id 11 | } 12 | 13 | data "aws_availability_zones" "available" {} 14 | 15 | variable "cluster_name" { 16 | default = "learnk8s" 17 | } 18 | 19 | variable "instance_type" { 20 | default = "m5.large" 21 | } 22 | 23 | provider "kubernetes" { 24 | host = data.aws_eks_cluster.cluster.endpoint 25 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data) 26 | token = data.aws_eks_cluster_auth.cluster.token 27 | 28 | } 29 | 30 | module "eks-kubeconfig" { 31 | source = "hyperbadger/eks-kubeconfig/aws" 32 | version = "1.0.0" 33 | 34 | depends_on = [module.eks] 35 | cluster_id = module.eks.cluster_id 36 | } 37 | 38 | resource "local_file" "kubeconfig" { 39 | content = module.eks-kubeconfig.kubeconfig 40 | filename = "kubeconfig_${var.cluster_name}" 41 | } 42 | 43 | module "vpc" { 44 | source = "terraform-aws-modules/vpc/aws" 45 | version = "3.18.1" 46 | 47 | name = "k8s-${var.cluster_name}-vpc" 48 | cidr = "172.16.0.0/16" 49 | azs = data.aws_availability_zones.available.names 50 | private_subnets = ["172.16.1.0/24", "172.16.2.0/24", "172.16.3.0/24"] 51 | public_subnets = ["172.16.4.0/24", "172.16.5.0/24", "172.16.6.0/24"] 52 | enable_nat_gateway = true 53 | single_nat_gateway = true 54 | enable_dns_hostnames = true 55 | 56 | public_subnet_tags = { 57 | "kubernetes.io/cluster/${var.cluster_name}" = "shared" 58 | "kubernetes.io/role/elb" = "1" 59 | } 60 | 61 | private_subnet_tags = { 62 | "kubernetes.io/cluster/${var.cluster_name}" = "shared" 63 | "kubernetes.io/role/internal-elb" = "1" 64 | } 65 | } 66 | 67 | module "eks" { 68 | source = "terraform-aws-modules/eks/aws" 69 | version = "18.30.3" 70 | 71 | cluster_name = "eks-${var.cluster_name}" 72 | cluster_version = "1.24" 73 | subnet_ids = module.vpc.private_subnets 74 | vpc_id = module.vpc.vpc_id 75 | 76 | eks_managed_node_groups = { 77 | first = { 78 | desired_capacity = 1 79 | max_capacity = 10 80 | min_capacity = 1 81 | 82 | instance_type = var.instance_type 83 | } 84 | } 85 | node_security_group_additional_rules = { 86 | ingress_allow_access_from_control_plane = { 87 | type = "ingress" 88 | protocol = "tcp" 89 | from_port = 9443 90 | to_port = 9443 91 | source_cluster_security_group = true 92 | description = "Allow access from control plane to webhook port of AWS load balancer controller" 93 | } 94 | } 95 | } 96 | 97 | resource "aws_iam_policy" "worker_policy" { 98 | name = "worker-policy-${var.cluster_name}" 99 | description = "Worker policy for the ALB Ingress" 100 | 101 | policy = file("iam-policy.json") 102 | } 103 | 104 | resource "aws_iam_role_policy_attachment" "additional" { 105 | for_each = module.eks.eks_managed_node_groups 106 | 107 | policy_arn = aws_iam_policy.worker_policy.arn 108 | role = each.value.iam_role_name 109 | } 110 | 111 | provider "helm" { 112 | kubernetes { 113 | host = data.aws_eks_cluster.cluster.endpoint 114 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data) 115 | token = data.aws_eks_cluster_auth.cluster.token 116 | } 117 | } 118 | 119 | resource "helm_release" "ingress" { 120 | name = "ingress" 121 | chart = "aws-load-balancer-controller" 122 | repository = "https://aws.github.io/eks-charts" 123 | version = "1.4.6" 124 | 125 | set { 126 | name = "autoDiscoverAwsRegion" 127 | value = "true" 128 | } 129 | set { 130 | name = "autoDiscoverAwsVpcID" 131 | value = "true" 132 | } 133 | set { 134 | name = "clusterName" 135 | value = var.cluster_name 136 | } 137 | } -------------------------------------------------------------------------------- /06_terraform_envs_customised/iam-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "iam:CreateServiceLinkedRole" 8 | ], 9 | "Resource": "*", 10 | "Condition": { 11 | "StringEquals": { 12 | "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com" 13 | } 14 | } 15 | }, 16 | { 17 | "Effect": "Allow", 18 | "Action": [ 19 | "ec2:DescribeAccountAttributes", 20 | "ec2:DescribeAddresses", 21 | "ec2:DescribeAvailabilityZones", 22 | "ec2:DescribeInternetGateways", 23 | "ec2:DescribeVpcs", 24 | "ec2:DescribeVpcPeeringConnections", 25 | "ec2:DescribeSubnets", 26 | "ec2:DescribeSecurityGroups", 27 | "ec2:DescribeInstances", 28 | "ec2:DescribeNetworkInterfaces", 29 | "ec2:DescribeTags", 30 | "ec2:GetCoipPoolUsage", 31 | "ec2:DescribeCoipPools", 32 | "elasticloadbalancing:DescribeLoadBalancers", 33 | "elasticloadbalancing:DescribeLoadBalancerAttributes", 34 | "elasticloadbalancing:DescribeListeners", 35 | "elasticloadbalancing:DescribeListenerCertificates", 36 | "elasticloadbalancing:DescribeSSLPolicies", 37 | "elasticloadbalancing:DescribeRules", 38 | "elasticloadbalancing:DescribeTargetGroups", 39 | "elasticloadbalancing:DescribeTargetGroupAttributes", 40 | "elasticloadbalancing:DescribeTargetHealth", 41 | "elasticloadbalancing:DescribeTags" 42 | ], 43 | "Resource": "*" 44 | }, 45 | { 46 | "Effect": "Allow", 47 | "Action": [ 48 | "cognito-idp:DescribeUserPoolClient", 49 | "acm:ListCertificates", 50 | "acm:DescribeCertificate", 51 | "iam:ListServerCertificates", 52 | "iam:GetServerCertificate", 53 | "waf-regional:GetWebACL", 54 | "waf-regional:GetWebACLForResource", 55 | "waf-regional:AssociateWebACL", 56 | "waf-regional:DisassociateWebACL", 57 | "wafv2:GetWebACL", 58 | "wafv2:GetWebACLForResource", 59 | "wafv2:AssociateWebACL", 60 | "wafv2:DisassociateWebACL", 61 | "shield:GetSubscriptionState", 62 | "shield:DescribeProtection", 63 | "shield:CreateProtection", 64 | "shield:DeleteProtection" 65 | ], 66 | "Resource": "*" 67 | }, 68 | { 69 | "Effect": "Allow", 70 | "Action": [ 71 | "ec2:AuthorizeSecurityGroupIngress", 72 | "ec2:RevokeSecurityGroupIngress" 73 | ], 74 | "Resource": "*" 75 | }, 76 | { 77 | "Effect": "Allow", 78 | "Action": [ 79 | "ec2:CreateSecurityGroup" 80 | ], 81 | "Resource": "*" 82 | }, 83 | { 84 | "Effect": "Allow", 85 | "Action": [ 86 | "ec2:CreateTags" 87 | ], 88 | "Resource": "arn:aws:ec2:*:*:security-group/*", 89 | "Condition": { 90 | "StringEquals": { 91 | "ec2:CreateAction": "CreateSecurityGroup" 92 | }, 93 | "Null": { 94 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 95 | } 96 | } 97 | }, 98 | { 99 | "Effect": "Allow", 100 | "Action": [ 101 | "ec2:CreateTags", 102 | "ec2:DeleteTags" 103 | ], 104 | "Resource": "arn:aws:ec2:*:*:security-group/*", 105 | "Condition": { 106 | "Null": { 107 | "aws:RequestTag/elbv2.k8s.aws/cluster": "true", 108 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 109 | } 110 | } 111 | }, 112 | { 113 | "Effect": "Allow", 114 | "Action": [ 115 | "ec2:AuthorizeSecurityGroupIngress", 116 | "ec2:RevokeSecurityGroupIngress", 117 | "ec2:DeleteSecurityGroup" 118 | ], 119 | "Resource": "*", 120 | "Condition": { 121 | "Null": { 122 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 123 | } 124 | } 125 | }, 126 | { 127 | "Effect": "Allow", 128 | "Action": [ 129 | "elasticloadbalancing:CreateLoadBalancer", 130 | "elasticloadbalancing:CreateTargetGroup" 131 | ], 132 | "Resource": "*", 133 | "Condition": { 134 | "Null": { 135 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 136 | } 137 | } 138 | }, 139 | { 140 | "Effect": "Allow", 141 | "Action": [ 142 | "elasticloadbalancing:CreateListener", 143 | "elasticloadbalancing:DeleteListener", 144 | "elasticloadbalancing:CreateRule", 145 | "elasticloadbalancing:DeleteRule" 146 | ], 147 | "Resource": "*" 148 | }, 149 | { 150 | "Effect": "Allow", 151 | "Action": [ 152 | "elasticloadbalancing:AddTags", 153 | "elasticloadbalancing:RemoveTags" 154 | ], 155 | "Resource": [ 156 | "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*", 157 | "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*", 158 | "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*" 159 | ], 160 | "Condition": { 161 | "Null": { 162 | "aws:RequestTag/elbv2.k8s.aws/cluster": "true", 163 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 164 | } 165 | } 166 | }, 167 | { 168 | "Effect": "Allow", 169 | "Action": [ 170 | "elasticloadbalancing:AddTags", 171 | "elasticloadbalancing:RemoveTags" 172 | ], 173 | "Resource": [ 174 | "arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*", 175 | "arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*", 176 | "arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*", 177 | "arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*" 178 | ] 179 | }, 180 | { 181 | "Effect": "Allow", 182 | "Action": [ 183 | "elasticloadbalancing:ModifyLoadBalancerAttributes", 184 | "elasticloadbalancing:SetIpAddressType", 185 | "elasticloadbalancing:SetSecurityGroups", 186 | "elasticloadbalancing:SetSubnets", 187 | "elasticloadbalancing:DeleteLoadBalancer", 188 | "elasticloadbalancing:ModifyTargetGroup", 189 | "elasticloadbalancing:ModifyTargetGroupAttributes", 190 | "elasticloadbalancing:DeleteTargetGroup" 191 | ], 192 | "Resource": "*", 193 | "Condition": { 194 | "Null": { 195 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 196 | } 197 | } 198 | }, 199 | { 200 | "Effect": "Allow", 201 | "Action": [ 202 | "elasticloadbalancing:RegisterTargets", 203 | "elasticloadbalancing:DeregisterTargets" 204 | ], 205 | "Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*" 206 | }, 207 | { 208 | "Effect": "Allow", 209 | "Action": [ 210 | "elasticloadbalancing:SetWebAcl", 211 | "elasticloadbalancing:ModifyListener", 212 | "elasticloadbalancing:AddListenerCertificates", 213 | "elasticloadbalancing:RemoveListenerCertificates", 214 | "elasticloadbalancing:ModifyRule" 215 | ], 216 | "Resource": "*" 217 | } 218 | ] 219 | } 220 | -------------------------------------------------------------------------------- /06_terraform_envs_customised/main.tf: -------------------------------------------------------------------------------- 1 | module "dev_cluster" { 2 | source = "./cluster" 3 | cluster_name = "dev" 4 | instance_type = "t2.micro" 5 | } 6 | 7 | module "staging_cluster" { 8 | source = "./cluster" 9 | cluster_name = "staging" 10 | instance_type = "t2.micro" 11 | } 12 | 13 | module "production_cluster" { 14 | source = "./cluster" 15 | cluster_name = "production" 16 | instance_type = "m5.large" 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Provisioning Kubernetes clusters on AWS with Terraform and EKS 2 | 3 | This repository contains the sample code necessary to provision EKS clusters with the ALB Ingress Controller. 4 | 5 | Code samples: 6 | 7 | 1. [Provisioning EKS with eksctl](eksctl/README.md) 8 | 1. [Basic provisioning of EKS with Terraform](01_terraform_eks/README.md) 9 | 1. [Multiple managed node pools with EKS and Terraform](02_terraform_node_pools/README.md) 10 | 1. [Setting up IAM policies for the ALB Ingress Controller in EKS with Terraform](03_terraform_alb_ingress/README.md) 11 | 1. [Integrating the Helm provider with Terraform and EKS](04_terraform_helm_provider/README.md) 12 | 1. [Encapsulating clusters as Terraform modules](05_terraform_env_module/README.md) 13 | 1. [Parametrizing clusters as Terraform modules](06_terraform_envs_customised/README.md) 14 | 1. [Kubernetes files to deploy a "Hello World" application](kubernetes/README.md) 15 | -------------------------------------------------------------------------------- /eksctl/README.md: -------------------------------------------------------------------------------- 1 | # Eksctl cluster 2 | 3 | In this folder you will find a sample YAML file to deploy an EKS cluster with eksctl. 4 | 5 | You can create the cluster with: 6 | 7 | ```bash 8 | eksctl create cluster -f cluster.yaml 9 | ``` 10 | And delete it with: 11 | ```bash 12 | eksctl delete cluster --name=learnk8s 13 | ``` -------------------------------------------------------------------------------- /eksctl/cluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: eksctl.io/v1alpha5 2 | kind: ClusterConfig 3 | metadata: 4 | name: learnk8s 5 | region: eu-central-1 6 | nodeGroups: 7 | - name: worker-group 8 | instanceType: t2.micro 9 | desiredCapacity: 3 10 | minSize: 3 11 | maxSize: 5 -------------------------------------------------------------------------------- /kubernetes/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes resources 2 | 3 | In this folder you will find the basic resources to deploy [an application that displays a message, the name of the pod and details of the node it's deployed to.](https://github.com/paulbouwer/hello-kubernetes) 4 | 5 | You can deploy the application in your cluster with: 6 | 7 | ```bash 8 | kubectl apply -f deployment.yaml 9 | kubectl apply -f service-nodeport.yaml 10 | kubectl apply -f ingress.yaml 11 | ``` 12 | -------------------------------------------------------------------------------- /kubernetes/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: hello-kubernetes 5 | spec: 6 | selector: 7 | matchLabels: 8 | name: hello-kubernetes 9 | template: 10 | metadata: 11 | labels: 12 | name: hello-kubernetes 13 | spec: 14 | containers: 15 | - name: app 16 | image: paulbouwer/hello-kubernetes:1.10.1 17 | ports: 18 | - containerPort: 8080 -------------------------------------------------------------------------------- /kubernetes/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: hello-kubernetes 5 | annotations: 6 | alb.ingress.kubernetes.io/scheme: internet-facing 7 | kubernetes.io/ingress.class: alb 8 | spec: 9 | rules: 10 | - http: 11 | paths: 12 | - path: / 13 | pathType: Prefix 14 | backend: 15 | service: 16 | name: hello-kubernetes 17 | port: 18 | number: 80 -------------------------------------------------------------------------------- /kubernetes/service-loadbalancer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: hello-kubernetes 5 | spec: 6 | type: LoadBalancer 7 | ports: 8 | - port: 80 9 | targetPort: 8080 10 | selector: 11 | name: hello-kubernetes -------------------------------------------------------------------------------- /kubernetes/service-nodeport.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: hello-kubernetes 5 | spec: 6 | type: NodePort 7 | ports: 8 | - port: 80 9 | targetPort: 8080 10 | selector: 11 | name: hello-kubernetes --------------------------------------------------------------------------------