├── .gitignore ├── README.md ├── eks_iam_roles.tf ├── img └── architecture.png ├── locals.tf ├── main.tf ├── variables.tf └── versions.tf /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | *.terraform.* 8 | 9 | # Crash log files 10 | crash.log 11 | 12 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 13 | # .tfvars files are managed as part of configuration and so should be included in 14 | # version control. 15 | # 16 | # example.tfvars 17 | 18 | # Ignore override files as they are usually used to override resources locally and so 19 | # are not checked in 20 | override.tf 21 | override.tf.json 22 | *_override.tf 23 | *_override.tf.json 24 | 25 | # Include override files you do wish to add to version control using negated pattern 26 | # 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform - Creating an EKS Cluster with Node Group 2 | 3 | 4 | ## Architecture 5 | ![EKSclsuter](img/architecture.png) 6 | -------------------------------------------------------------------------------- /eks_iam_roles.tf: -------------------------------------------------------------------------------- 1 | # EKS CLUSTER ROLE 2 | resource "aws_iam_role" "EKSClusterRole" { 3 | name = "EKSClusterRole" 4 | assume_role_policy = jsonencode({ 5 | Version = "2012-10-17" 6 | Statement = [ 7 | { 8 | Action = "sts:AssumeRole" 9 | Effect = "Allow" 10 | Principal = { 11 | Service = "eks.amazonaws.com" 12 | } 13 | }, 14 | ] 15 | }) 16 | } 17 | 18 | //This policy provides Kubernetes the permissions it requires to manage resources on your behalf. Kubernetes requires Ec2:CreateTags permissions to place identifying information on EC2 resources including but not limited to Instances, Security Groups, and Elastic Network Interfaces 19 | resource "aws_iam_role_policy_attachment" "AmazonEKSClusterPolicy" { 20 | policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy" 21 | role = aws_iam_role.EKSClusterRole.name 22 | } 23 | 24 | # NODE GROUP ROLE 25 | resource "aws_iam_role" "NodeGroupRole" { 26 | name = "EKSNodeGroupRole" 27 | assume_role_policy = jsonencode({ 28 | Version = "2012-10-17" 29 | Statement = [ 30 | { 31 | Action = "sts:AssumeRole" 32 | Effect = "Allow" 33 | Principal = { 34 | Service = "ec2.amazonaws.com" 35 | } 36 | }, 37 | ] 38 | }) 39 | } 40 | 41 | //This policy allows Amazon EKS worker nodes to connect to Amazon EKS Clusters. 42 | resource "aws_iam_role_policy_attachment" "AmazonEKSWorkerNodePolicy" { 43 | policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy" 44 | role = aws_iam_role.NodeGroupRole.name 45 | } 46 | 47 | // Provides read-only access to Amazon EC2 Container Registry repositories. 48 | resource "aws_iam_role_policy_attachment" "AmazonEC2ContainerRegistryReadOnly" { 49 | policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" 50 | role = aws_iam_role.NodeGroupRole.name 51 | } 52 | 53 | // This policy provides the Amazon VPC CNI Plugin (amazon-vpc-cni-k8s) the permissions it requires to modify the IP address configuration on your EKS worker nodes. This permission set allows the CNI to list, describe, and modify Elastic Network Interfaces on your behalf 54 | resource "aws_iam_role_policy_attachment" "AmazonEKS_CNI_Policy" { 55 | policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" 56 | role = aws_iam_role.NodeGroupRole.name 57 | } 58 | -------------------------------------------------------------------------------- /img/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erozedguy/Terraform-EKS-Cluster-with-Node-Group/2f229bfe1e90e5f5da667b47529f673bb5ad405e/img/architecture.png -------------------------------------------------------------------------------- /locals.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | 3 | data "aws_region" "current" {} 4 | 5 | data "aws_eks_cluster" "eks-cluster" { 6 | name = aws_eks_cluster.eks-cluster.name 7 | } 8 | 9 | locals { 10 | oidc = trimprefix(data.aws_eks_cluster.eks-cluster.identity[0].oidc[0].issuer, "https://") 11 | } -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | module "aws_vpc" { 2 | source = "github.com/erozedguy/AWS-VPC-terraform-module.git?ref=v0.1.0" 3 | networking = var.networking 4 | security_groups = var.security_groups 5 | } 6 | 7 | # EKS Cluster 8 | resource "aws_eks_cluster" "eks-cluster" { 9 | name = var.cluster_config.name 10 | role_arn = aws_iam_role.EKSClusterRole.arn 11 | version = var.cluster_config.version 12 | 13 | vpc_config { 14 | subnet_ids = flatten([module.aws_vpc.public_subnets_id, module.aws_vpc.private_subnets_id]) 15 | security_group_ids = flatten(module.aws_vpc.security_groups_id) 16 | } 17 | 18 | depends_on = [ 19 | aws_iam_role_policy_attachment.AmazonEKSClusterPolicy 20 | ] 21 | 22 | } 23 | 24 | # NODE GROUP 25 | resource "aws_eks_node_group" "node-ec2" { 26 | for_each = { for node_group in var.node_groups : node_group.name => node_group } 27 | cluster_name = aws_eks_cluster.eks-cluster.name 28 | node_group_name = each.value.name 29 | node_role_arn = aws_iam_role.NodeGroupRole.arn 30 | subnet_ids = flatten(module.aws_vpc.private_subnets_id) 31 | 32 | scaling_config { 33 | desired_size = try(each.value.scaling_config.desired_size, 2) 34 | max_size = try(each.value.scaling_config.max_size, 3) 35 | min_size = try(each.value.scaling_config.min_size, 1) 36 | } 37 | 38 | update_config { 39 | max_unavailable = try(each.value.update_config.max_unavailable, 1) 40 | } 41 | 42 | ami_type = each.value.ami_type 43 | instance_types = each.value.instance_types 44 | capacity_type = each.value.capacity_type 45 | disk_size = each.value.disk_size 46 | 47 | depends_on = [ 48 | aws_iam_role_policy_attachment.AmazonEKSWorkerNodePolicy, 49 | aws_iam_role_policy_attachment.AmazonEC2ContainerRegistryReadOnly, 50 | aws_iam_role_policy_attachment.AmazonEKS_CNI_Policy 51 | ] 52 | } 53 | 54 | resource "aws_eks_addon" "addons" { 55 | for_each = { for addon in var.addons : addon.name => addon } 56 | cluster_name = aws_eks_cluster.eks-cluster.id 57 | addon_name = each.value.name 58 | addon_version = each.value.version 59 | resolve_conflicts = "OVERWRITE" 60 | } 61 | 62 | resource "aws_iam_openid_connect_provider" "default" { 63 | url = "https://${local.oidc}" 64 | client_id_list = ["sts.amazonaws.com"] 65 | thumbprint_list = ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"] 66 | } -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "networking" { 2 | type = object({ 3 | cidr_block = string 4 | region = string 5 | vpc_name = string 6 | azs = list(string) 7 | public_subnets = list(string) 8 | private_subnets = list(string) 9 | nat_gateways = bool 10 | }) 11 | default = { 12 | cidr_block = "141.0.0.0/16" 13 | region = "eu-central-1" 14 | vpc_name = "terraform-vpc" 15 | azs = ["eu-central-1a", "eu-central-1b"] 16 | public_subnets = ["141.0.1.0/24", "141.0.2.0/24"] 17 | private_subnets = ["141.0.3.0/24", "141.0.4.0/24"] 18 | nat_gateways = true 19 | } 20 | } 21 | 22 | variable "security_groups" { 23 | type = list(object({ 24 | name = string 25 | description = string 26 | ingress = list(object({ 27 | description = string 28 | protocol = string 29 | from_port = number 30 | to_port = number 31 | cidr_blocks = list(string) 32 | ipv6_cidr_blocks = list(string) 33 | })) 34 | egress = list(object({ 35 | description = string 36 | protocol = string 37 | from_port = number 38 | to_port = number 39 | cidr_blocks = list(string) 40 | ipv6_cidr_blocks = list(string) 41 | })) 42 | })) 43 | default = [{ 44 | name = "custom-security-group" 45 | description = "Inbound & Outbound traffic for custom-security-group" 46 | ingress = [ 47 | { 48 | description = "Allow HTTPS" 49 | protocol = "tcp" 50 | from_port = 443 51 | to_port = 443 52 | cidr_blocks = ["0.0.0.0/0"] 53 | ipv6_cidr_blocks = null 54 | }, 55 | { 56 | description = "Allow HTTP" 57 | protocol = "tcp" 58 | from_port = 80 59 | to_port = 80 60 | cidr_blocks = ["0.0.0.0/0"] 61 | ipv6_cidr_blocks = null 62 | }, 63 | ] 64 | egress = [ 65 | { 66 | description = "Allow all outbound traffic" 67 | protocol = "-1" 68 | from_port = 0 69 | to_port = 0 70 | cidr_blocks = ["0.0.0.0/0"] 71 | ipv6_cidr_blocks = ["::/0"] 72 | } 73 | ] 74 | }] 75 | } 76 | 77 | variable "cluster_config" { 78 | type = object({ 79 | name = string 80 | version = string 81 | }) 82 | default = { 83 | name = "eks-cluster" 84 | version = "1.22" 85 | } 86 | } 87 | 88 | variable "node_groups" { 89 | type = list(object({ 90 | name = string 91 | instance_types = list(string) 92 | ami_type = string 93 | capacity_type = string 94 | disk_size = number 95 | scaling_config = object({ 96 | desired_size = number 97 | min_size = number 98 | max_size = number 99 | }) 100 | update_config = object({ 101 | max_unavailable = number 102 | }) 103 | })) 104 | default = [ 105 | { 106 | name = "t3-micro-standard" 107 | instance_types = ["t3.micro"] 108 | ami_type = "AL2_x86_64" 109 | capacity_type = "ON_DEMAND" 110 | disk_size = 20 111 | scaling_config = { 112 | desired_size = 2 113 | max_size = 3 114 | min_size = 1 115 | } 116 | update_config = { 117 | max_unavailable = 1 118 | } 119 | }, 120 | { 121 | name = "t3-micro-spot" 122 | instance_types = ["t3.micro"] 123 | ami_type = "AL2_x86_64" 124 | capacity_type = "SPOT" 125 | disk_size = 20 126 | scaling_config = { 127 | desired_size = 2 128 | max_size = 3 129 | min_size = 1 130 | } 131 | update_config = { 132 | max_unavailable = 1 133 | } 134 | }, 135 | ] 136 | 137 | } 138 | 139 | variable "addons" { 140 | type = list(object({ 141 | name = string 142 | version = string 143 | })) 144 | default = [ 145 | { 146 | name = "kube-proxy" 147 | version = "v1.22.6-eksbuild.1" 148 | }, 149 | { 150 | name = "vpc-cni" 151 | version = "v1.11.0-eksbuild.1" 152 | }, 153 | { 154 | name = "coredns" 155 | version = "v1.8.7-eksbuild.1" 156 | }, 157 | { 158 | name = "aws-ebs-csi-driver" 159 | version = "v1.6.2-eksbuild.0" 160 | } 161 | ] 162 | } -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = ">= 4.18.0" 6 | } 7 | } 8 | } 9 | provider "aws" { 10 | region = var.networking.region 11 | } 12 | --------------------------------------------------------------------------------