diff --git a/.tool-versions b/.tool-versions index 580373a..955f343 100644 --- a/.tool-versions +++ b/.tool-versions @@ -2,3 +2,5 @@ terraform 1.5.5 terraform-docs 0.16.0 tflint 0.47.0 pre-commit 3.3.3 +kubectl 1.28.2 +yq 4.34.1 diff --git a/README.md b/README.md index f6cb368..ceb21b0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Meet **OPSd**. The unique and effortless way of managing cloud infrastructure. -# terraform-module-template +# terraform-module-aws-kubernetes ## Introduction @@ -11,12 +11,24 @@ What does the module provide? ## Usage ```hcl -module "module_name" { - source = "github.com/opsd-io/module_name?ref=v0.0.1" - - # Variables - variable_1 = "foo" - variable_2 = "bar" +module "kubernetes" { + source = "github.com/opsd-io/terraform-module-aws-kubernetes" + name = "basic-k8s-example" + + subnet_ids = [ + for subnet in module.network.public_subnet_groups["public1"] : subnet.id + ] + node_group_subnet_ids = [ + for subnet in module.network.private_subnet_groups["nodes1"] : subnet.id + ] + + node_groups = { + main = { + max_size = 9 + desired_size = 1 + disk_size = 8 + } + } } ``` @@ -27,11 +39,18 @@ module "module_name" { | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.3.1 | +| [terraform](#requirement\_terraform) | >= 1.5.5 | +| [aws](#requirement\_aws) | ~> 5.0 | +| [kubernetes](#requirement\_kubernetes) | ~> 2.0 | +| [tls](#requirement\_tls) | ~> 4.0 | ## Providers -No providers. +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | ~> 5.0 | +| [kubernetes](#provider\_kubernetes) | ~> 2.0 | +| [tls](#provider\_tls) | ~> 4.0 | ## Modules @@ -39,15 +58,77 @@ No modules. ## Resources -No resources. +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.cluster](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_eks_cluster.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_cluster) | resource | +| [aws_eks_fargate_profile.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_fargate_profile) | resource | +| [aws_eks_node_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_node_group) | resource | +| [aws_iam_openid_connect_provider.eks_cluster](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_openid_connect_provider) | resource | +| [aws_iam_role.cluster](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.fargate](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.node](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.ec2_container_registry_readonly](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.eks_cluster_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.eks_cni_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.eks_fargate_pod_execution_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.eks_worker_node_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [kubernetes_config_map.aws_auth](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/config_map) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.assume_role_cluster](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.assume_role_fargate](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.assume_role_node](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | +| [tls_certificate.oidc_issuer](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/data-sources/certificate) | data source | ## Inputs -No inputs. +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [auth\_map\_accounts](#input\_auth\_map\_accounts) | Maps IAM ARN from these accounts to username. | `list(string)` |
[| no | +| [auth\_map\_roles](#input\_auth\_map\_roles) | Maps an IAM role to a username and set of groups. |
"current"
]
list(object({| `[]` | no | +| [auth\_map\_users](#input\_auth\_map\_users) | Maps an IAM user to a static username and set of groups. |
arn = string
username = optional(string)
groups = optional(list(string))
}))
list(object({| `[]` | no | +| [cluster\_log\_retention\_in\_days](#input\_cluster\_log\_retention\_in\_days) | Specifies the number of days you want to retain log events. | `number` | `7` | no | +| [common\_tags](#input\_common\_tags) | A map of tags to assign to every resource in this module. | `map(string)` | `{}` | no | +| [ec2\_ssh\_key](#input\_ec2\_ssh\_key) | The EC2 Key Pair name that provides access to the worker nodes. | `string` | `null` | no | +| [enabled\_cluster\_log\_types](#input\_enabled\_cluster\_log\_types) | List of the desired control plane logging to enable. | `list(string)` |
arn = string
username = optional(string)
groups = optional(list(string))
}))
[| no | +| [encryption\_key\_arn](#input\_encryption\_key\_arn) | ARN of the KMS customer master key for secrets encryption. | `string` | `null` | no | +| [endpoint\_private\_access](#input\_endpoint\_private\_access) | Whether the Amazon EKS private API server endpoint is enabled. | `bool` | `false` | no | +| [endpoint\_public\_access](#input\_endpoint\_public\_access) | Whether the Amazon EKS public API server endpoint is enabled. | `bool` | `true` | no | +| [fargate\_profiles](#input\_fargate\_profiles) | Map of EKS Fargate Profile definitions. |
"api",
"authenticator",
"controllerManager",
"scheduler"
]
map(object({| `{}` | no | +| [fargate\_role\_arns](#input\_fargate\_role\_arns) | Additional IAM role ARNs of Fargate Profiles managed externally. | `list(string)` | `[]` | no | +| [fargate\_subnet\_ids](#input\_fargate\_subnet\_ids) | Identifiers of private EC2 Subnets to associate with the EKS Fargate Profiles. | `set(string)` | `[]` | no | +| [k8s\_version](#input\_k8s\_version) | Desired Kubernetes master version. | `string` | `null` | no | +| [masters\_role\_arns](#input\_masters\_role\_arns) | List of IAM role to set as system:masters. Shortcut for auth\_map\_roles. | `list(string)` | `[]` | no | +| [name](#input\_name) | The name of the cluster. | `string` | n/a | yes | +| [node\_group\_subnet\_ids](#input\_node\_group\_subnet\_ids) | Identifiers of EC2 Subnets to associate with the EKS Node Groups. | `set(string)` | `[]` | no | +| [node\_groups](#input\_node\_groups) | Map of EKS Node Group definitions. |
subnet_ids = optional(set(string))
namespace = string
labels = optional(map(string))
}))
map(object({| `{}` | no | +| [nodes\_role\_arns](#input\_nodes\_role\_arns) | Additional IAM role ARNs of Node Groups managed externally. | `list(string)` | `[]` | no | +| [public\_access\_cidrs](#input\_public\_access\_cidrs) | List of CIDR blocks that can access the Amazon EKS public API server endpoint when enabled. | `list(string)` |
subnet_ids = optional(set(string))
ami_type = optional(string, "AL2_x86_64")
capacity_type = optional(string, "ON_DEMAND")
instance_type = optional(string, "t3.medium")
disk_size = optional(number, 20)
min_size = optional(number, 0)
max_size = optional(number, 1)
desired_size = optional(number, 0)
labels = optional(map(string))
taints = optional(list(object({
key = string
value = string
effect = string # Valid values: NO_SCHEDULE, NO_EXECUTE, PREFER_NO_SCHEDULE.
})), [])
}))
[| no | +| [security\_group\_ids](#input\_security\_group\_ids) | List of security group IDs to allow communication between your worker nodes and the Kubernetes control plane. | `set(string)` | `[]` | no | +| [source\_security\_group\_ids](#input\_source\_security\_group\_ids) | Set of EC2 Security Group IDs to allow SSH access (port 22) from on the worker nodes. | `set(string)` | `null` | no | +| [subnet\_ids](#input\_subnet\_ids) | List of subnet IDs. Must be in at least two different availability zones. | `set(string)` | n/a | yes | ## Outputs -No outputs. +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN of the cluster. | +| [cluster\_ca](#output\_cluster\_ca) | Decoded CA certificate of the cluster. | +| [cluster\_role\_arn](#output\_cluster\_role\_arn) | The ARN of the IAM role that provides permissions for the Kubernetes control plane. | +| [cluster\_role\_name](#output\_cluster\_role\_name) | The name of the IAM role that provides permissions for the Kubernetes control plane. | +| [endpoint](#output\_endpoint) | Endpoint for your Kubernetes API server. | +| [fargate\_role\_arn](#output\_fargate\_role\_arn) | The ARN of the IAM Role that provides permissions for the EKS Fargate Profile. | +| [fargate\_role\_name](#output\_fargate\_role\_name) | The name of the IAM Role that provides permissions for the EKS Fargate Profile. | +| [id](#output\_id) | The ID of the cluster. | +| [name](#output\_name) | The name of the cluster. | +| [node\_role\_arn](#output\_node\_role\_arn) | The ARN of the IAM Role that provides permissions for the EKS Node Group. | +| [node\_role\_name](#output\_node\_role\_name) | The name of the IAM Role that provides permissions for the EKS Node Group. | +| [oidc\_issuer](#output\_oidc\_issuer) | Issuer URL for the OpenID Connect identity provider. | +| [openid\_arn](#output\_openid\_arn) | The ARN assigned by AWS for IAM OpenID Connect of the cluster. | +| [openid\_sub](#output\_openid\_sub) | The URL of the identity provider. Corresponds to the iss claim. | +| [region](#output\_region) | The region of of the cluster. | +| [version](#output\_version) | The Kubernetes master version. | ## Examples of usage diff --git a/aws-auth.tf b/aws-auth.tf new file mode 100644 index 0000000..dfdbecd --- /dev/null +++ b/aws-auth.tf @@ -0,0 +1,69 @@ +locals { + account_map = { + current = data.aws_caller_identity.current.account_id + } + + nodes_role_arns = concat(var.nodes_role_arns, [aws_iam_role.node.arn]) + nodes_map = [for arn in local.nodes_role_arns : { + rolearn = arn + username = "system:node:{{EC2PrivateDNSName}}" + groups = [ + "system:bootstrappers", + "system:nodes", + ] + }] + + fargate_role_arns = concat(var.fargate_role_arns, [aws_iam_role.fargate.arn]) + fargate_map = [for arn in local.fargate_role_arns : { + rolearn = arn + username = "system:node:{{SessionName}}" + groups = [ + "system:bootstrappers", + "system:nodes", + "system:node-proxier", + ] + }] + + masters_map = [for arn in var.masters_role_arns : { + rolearn = arn + username = "master:{{AccountID}}:{{SessionName}}" + groups = [ + "system:masters", + ] + }] +} + +resource "kubernetes_config_map" "aws_auth" { + metadata { + name = "aws-auth" + namespace = "kube-system" + } + data = { + mapAccounts = yamlencode([ + for account_id in var.auth_map_accounts : lookup(local.account_map, account_id, account_id) + ]) + mapRoles = yamlencode(flatten([ + local.nodes_map, + local.fargate_map, + local.masters_map, [ + for role in var.auth_map_roles : { + rolearn = role.arn + username = role.username + groups = role.groups + } + ] + ])) + mapUsers = yamlencode([ + for user in var.auth_map_users : { + userarn = user.arn + username = user.username + groups = user.groups + } + ]) + } + + depends_on = [ + aws_eks_cluster.main, + ] + +} diff --git a/compute.tf b/compute.tf new file mode 100644 index 0000000..2dc7516 --- /dev/null +++ b/compute.tf @@ -0,0 +1,98 @@ +locals { + fixed_names = true +} + +resource "aws_eks_node_group" "main" { + for_each = var.node_groups + + node_group_name = local.fixed_names ? each.key : null + node_group_name_prefix = local.fixed_names ? null : each.key + cluster_name = aws_eks_cluster.main.name + node_role_arn = aws_iam_role.node.arn + subnet_ids = coalesce(each.value.subnet_ids, var.node_group_subnet_ids) + tags = merge(var.common_tags, { + Name = each.key + }) + + scaling_config { + min_size = each.value.min_size + max_size = each.value.max_size + desired_size = each.value.desired_size + } + + update_config { + max_unavailable = null + max_unavailable_percentage = 10 + } + + ami_type = each.value.ami_type + capacity_type = each.value.capacity_type + disk_size = each.value.disk_size + instance_types = [each.value.instance_type] + labels = each.value.labels + + # launch_template { + # id = each.value.launch_template_id + # name = each.value.launch_template_name + # version = each.value.launch_template_version + # } + + dynamic "remote_access" { + for_each = var.ec2_ssh_key != null ? [1] : [] + content { + ec2_ssh_key = var.ec2_ssh_key + source_security_group_ids = var.source_security_group_ids + } + } + + dynamic "taint" { + for_each = each.value.taints + content { + key = taint.value.key + value = taint.value.value + effect = taint.value.effect + } + } + + lifecycle { + # only if node_group_name_prefix is in use, but (local.fixed_names == false) does not work.. + create_before_destroy = true + ignore_changes = [ + scaling_config[0].desired_size, # needed for autoscaling to work + ] + } + + depends_on = [ + aws_eks_cluster.main, + aws_iam_role.node, + aws_iam_role_policy_attachment.eks_worker_node_policy, + kubernetes_config_map.aws_auth, + ] + +} + + +resource "aws_eks_fargate_profile" "main" { + for_each = var.fargate_profiles + + fargate_profile_name = each.key + cluster_name = aws_eks_cluster.main.name + pod_execution_role_arn = aws_iam_role.fargate.arn + subnet_ids = coalesce(each.value.subnet_ids, var.fargate_subnet_ids) + tags = merge(var.common_tags, { + Name = each.key + }) + + selector { + namespace = each.value.namespace + labels = each.value.labels + } + + depends_on = [ + aws_eks_cluster.main, + aws_iam_role.fargate, + aws_iam_role_policy_attachment.eks_fargate_pod_execution_role_policy, + kubernetes_config_map.aws_auth, + ] + +} diff --git a/examples/basic/main.tf b/examples/basic/main.tf new file mode 100644 index 0000000..4d161a8 --- /dev/null +++ b/examples/basic/main.tf @@ -0,0 +1,75 @@ +module "network" { + source = "github.com/opsd-io/terraform-module-aws-network" + + vpc_name = "k8s-test-vpc" + cidr_block = "10.100.0.0/16" + + public_subnet_groups = { + "public1" = { + availability_zones = { + "a" = { cidr_block = "10.100.1.0/24", nat_gateway = true } + "b" = { cidr_block = "10.100.2.0/24" } + "c" = { cidr_block = "10.100.3.0/24" } + } + } + } + private_subnet_groups = { + "nodes1" = { + nat_group_name = "public1" + availability_zones = { + "a" = { cidr_block = "10.100.101.0/24" } + "b" = { cidr_block = "10.100.102.0/24" } + "c" = { cidr_block = "10.100.103.0/24" } + } + } + "fargate1" = { + nat_group_name = "public1" + availability_zones = { + "a" = { cidr_block = "10.100.201.0/24" } + "b" = { cidr_block = "10.100.202.0/24" } + "c" = { cidr_block = "10.100.203.0/24" } + } + } + } +} + +module "kubernetes" { + source = "github.com/opsd-io/terraform-module-aws-kubernetes" + name = "basic-k8s-example" + + subnet_ids = [ + for subnet in module.network.public_subnet_groups["public1"] : subnet.id + ] + + node_group_subnet_ids = [ + for subnet in module.network.private_subnet_groups["nodes1"] : subnet.id + ] + node_groups = { + main = { + max_size = 9 + desired_size = 1 + disk_size = 8 + } + } + + fargate_subnet_ids = [ + for subnet in module.network.private_subnet_groups["fargate1"] : subnet.id + ] + fargate_profiles = { + "default-namespace" = { # default is bad, better put it "far" + namespace = "default" + } + "amazonaws-components" = { # this should put CoreDNS on fargate + namespace = "*" + labels = { "eks.amazonaws.com/component" = "*" } + } + } +} + +module "autoscaler" { + source = "github.com/opsd-io/terraform-module-eks-autoscaler" + cluster_region = module.kubernetes.region + cluster_name = module.kubernetes.name + openid_arn = module.kubernetes.openid_arn + openid_sub = module.kubernetes.openid_sub +} diff --git a/examples/basic/outputs.tf b/examples/basic/outputs.tf new file mode 100644 index 0000000..9702544 --- /dev/null +++ b/examples/basic/outputs.tf @@ -0,0 +1,9 @@ +# output "network" { +# description = "The network module outputs." +# value = module.network +# } + +output "kubernetes" { + description = "The kubernetes module outputs." + value = module.kubernetes +} diff --git a/examples/basic/override.tf b/examples/basic/override.tf new file mode 100644 index 0000000..eb9cd7e --- /dev/null +++ b/examples/basic/override.tf @@ -0,0 +1,5 @@ +# Make sure we're using working version (from local directory, not git). + +module "kubernetes" { + source = "./../.." +} diff --git a/examples/basic/terraform.tf b/examples/basic/terraform.tf new file mode 100644 index 0000000..90b87e3 --- /dev/null +++ b/examples/basic/terraform.tf @@ -0,0 +1,32 @@ +terraform { + required_version = ">= 1.5.5" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = "eu-central-1" +} + +data "aws_eks_cluster_auth" "this" { + name = module.kubernetes.name +} + +provider "kubernetes" { + host = module.kubernetes.endpoint + cluster_ca_certificate = module.kubernetes.cluster_ca + token = data.aws_eks_cluster_auth.this.token +} + +provider "helm" { + kubernetes { + host = module.kubernetes.endpoint + cluster_ca_certificate = module.kubernetes.cluster_ca + token = data.aws_eks_cluster_auth.this.token + } +} diff --git a/examples/basic/update-kubeconfig.sh b/examples/basic/update-kubeconfig.sh new file mode 100755 index 0000000..aa40fca --- /dev/null +++ b/examples/basic/update-kubeconfig.sh @@ -0,0 +1,12 @@ +#!/bin/sh -eux + +aws sts get-caller-identity + +aws eks update-kubeconfig \ + --region "eu-central-1" \ + --name "basic-k8s-example" \ + --alias "k8s-example" + +kubectl version + +kubectl auth whoami diff --git a/examples/example_of_use/.tool-versions b/examples/example_of_use/.tool-versions deleted file mode 100644 index 0743dde..0000000 --- a/examples/example_of_use/.tool-versions +++ /dev/null @@ -1 +0,0 @@ -terraform 1.5.5 diff --git a/examples/example_of_use/README.mkdn b/examples/example_of_use/README.mkdn deleted file mode 100644 index fc28c30..0000000 --- a/examples/example_of_use/README.mkdn +++ /dev/null @@ -1,63 +0,0 @@ -# Example title - -Brief description of an example. - -## Required tools - -### AZ CLI - -Before you start, you need to install Azure CLI according to the official [documentation](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli). - -Use the `az login` command to login to your Azure subscription. - -### asdf and direnv - -Additinaly `asdf` and `direnv` must be installed and configured according to their official documentation. - -- [asdf](https://asdf-vm.com/) - manages multiple runtime versions; -- [direnv](https://direnv.net/) - augments existing shells with a new feature that can load and unload environment variables. - -### Terraform - -The suggested Terraform version is defined in the [.tool-versions](.tool-versions) file. -To install it, simply execute - -```bash -asdf install terraform -``` - -## Terraforming infrastructure - -To start working with the module, you need to initialize terraform. - -```shell -terraform init -``` - -Execute plan command. - -```shell -terraform plan -``` - -and verify what will be created. - -The last step is to create the repo - -```shell -terrafrorm apply -``` - -**IMPORTANT**: Please double-check the command output. The vital section can be seen in the example `Plan: 6 to add, 0 to change, 0 to destroy`. Ensure that you understand the changes you are making. - -Next, you will be asked - -```shell -Do you want to perform these actions? - Terraform will perform the actions described above. - Only 'yes' will be accepted to approve. - - Enter a value: -``` - -Type `yes` to approve and let the magic happen. diff --git a/examples/example_of_use/main.tf b/examples/example_of_use/main.tf deleted file mode 100644 index 9eda987..0000000 --- a/examples/example_of_use/main.tf +++ /dev/null @@ -1,4 +0,0 @@ -module "module_name" { - source = "../../" - -} diff --git a/examples/example_of_use/versions.tf b/examples/example_of_use/versions.tf deleted file mode 100644 index b7d3369..0000000 --- a/examples/example_of_use/versions.tf +++ /dev/null @@ -1,13 +0,0 @@ -terraform { - required_version = ">= 1.3.1" - required_providers { - # github = { - # source = "integrations/github" - # version = ">= 5.3.0" - # } - # azurerm = { - # source = "hashicorp/azurerm" - # version = ">= 3.22.0" - # } - } -} diff --git a/main.tf b/main.tf index 1c6a2e5..dc365b3 100644 --- a/main.tf +++ b/main.tf @@ -1 +1,85 @@ -# Terraform code goes here +terraform { + required_version = ">= 1.5.5" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.0" + } + tls = { + source = "hashicorp/tls" + version = "~> 4.0" + } + } +} + +data "aws_caller_identity" "current" { + # no arguments +} + +data "aws_region" "current" { + # no arguments +} + +resource "aws_eks_cluster" "main" { + name = var.name + version = var.k8s_version + role_arn = aws_iam_role.cluster.arn + tags = merge(var.common_tags, { + Name = var.name + }) + + enabled_cluster_log_types = var.enabled_cluster_log_types + + vpc_config { + subnet_ids = var.subnet_ids + endpoint_private_access = var.endpoint_private_access + endpoint_public_access = var.endpoint_public_access + public_access_cidrs = var.public_access_cidrs + security_group_ids = var.security_group_ids + } + + kubernetes_network_config { + ip_family = "ipv4" + service_ipv4_cidr = "172.20.0.0/16" + } + + dynamic "encryption_config" { + for_each = var.encryption_key_arn != null ? [1] : [] + content { + provider { + key_arn = var.encryption_key_arn + } + resources = ["secrets"] + + } + } + + depends_on = [ + aws_iam_role_policy_attachment.eks_cluster_policy, + aws_iam_role_policy_attachment.eks_worker_node_policy, + aws_iam_role_policy_attachment.eks_fargate_pod_execution_role_policy, + aws_cloudwatch_log_group.cluster, + ] +} + +resource "aws_cloudwatch_log_group" "cluster" { + name = "/aws/eks/${var.name}/cluster" + retention_in_days = var.cluster_log_retention_in_days +} + +data "tls_certificate" "oidc_issuer" { + url = aws_eks_cluster.main.identity[0].oidc[0].issuer +} + +resource "aws_iam_openid_connect_provider" "eks_cluster" { + url = data.tls_certificate.oidc_issuer.url + thumbprint_list = [ + data.tls_certificate.oidc_issuer.certificates[0].sha1_fingerprint + ] + client_id_list = ["sts.amazonaws.com"] +} diff --git a/outputs.tf b/outputs.tf index 47cec5f..cd214e0 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,4 +1,79 @@ -# output "variable" { -# description = "output variable description" -# value = variable.main.name -# } +output "id" { + description = "The ID of the cluster." + value = aws_eks_cluster.main.id +} + +output "arn" { + description = "The ARN of the cluster." + value = aws_eks_cluster.main.arn +} + +output "name" { + description = "The name of the cluster." + value = aws_eks_cluster.main.name +} + +output "endpoint" { + description = "Endpoint for your Kubernetes API server." + value = aws_eks_cluster.main.endpoint +} + +output "version" { + description = "The Kubernetes master version." + value = aws_eks_cluster.main.version +} + +output "region" { + description = "The region of of the cluster." + value = data.aws_region.current.name +} + +output "cluster_ca" { + description = "Decoded CA certificate of the cluster." + value = base64decode(aws_eks_cluster.main.certificate_authority[0].data) +} + +output "oidc_issuer" { + description = "Issuer URL for the OpenID Connect identity provider." + value = aws_eks_cluster.main.identity[0].oidc[0].issuer +} + +output "openid_arn" { + description = "The ARN assigned by AWS for IAM OpenID Connect of the cluster." + value = aws_iam_openid_connect_provider.eks_cluster.arn +} + +output "openid_sub" { + description = "The URL of the identity provider. Corresponds to the iss claim." + value = format("%s:sub", trimprefix(aws_iam_openid_connect_provider.eks_cluster.url, "https://")) +} + +output "cluster_role_arn" { + description = "The ARN of the IAM role that provides permissions for the Kubernetes control plane." + value = aws_iam_role.cluster.arn +} + +output "cluster_role_name" { + description = "The name of the IAM role that provides permissions for the Kubernetes control plane." + value = aws_iam_role.cluster.name +} + +output "node_role_arn" { + description = "The ARN of the IAM Role that provides permissions for the EKS Node Group." + value = aws_iam_role.node.arn +} + +output "node_role_name" { + description = "The name of the IAM Role that provides permissions for the EKS Node Group." + value = aws_iam_role.node.name +} + +output "fargate_role_arn" { + description = "The ARN of the IAM Role that provides permissions for the EKS Fargate Profile." + value = aws_iam_role.fargate.arn +} + +output "fargate_role_name" { + description = "The name of the IAM Role that provides permissions for the EKS Fargate Profile." + value = aws_iam_role.fargate.name +} diff --git a/role-cluster.tf b/role-cluster.tf new file mode 100644 index 0000000..bd24fd0 --- /dev/null +++ b/role-cluster.tf @@ -0,0 +1,22 @@ +# Reference: https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html + +data "aws_iam_policy_document" "assume_role_cluster" { + statement { + principals { + type = "Service" + identifiers = ["eks.amazonaws.com"] + } + effect = "Allow" + actions = ["sts:AssumeRole"] + } +} + +resource "aws_iam_role" "cluster" { + name = format("eks-%s-cluster-role", trimprefix(var.name, "eks-")) + assume_role_policy = data.aws_iam_policy_document.assume_role_cluster.json +} + +resource "aws_iam_role_policy_attachment" "eks_cluster_policy" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy" + role = aws_iam_role.cluster.name +} diff --git a/role-fargate.tf b/role-fargate.tf new file mode 100644 index 0000000..64b25f0 --- /dev/null +++ b/role-fargate.tf @@ -0,0 +1,22 @@ +# Reference: https://docs.aws.amazon.com/eks/latest/userguide/pod-execution-role.html + +data "aws_iam_policy_document" "assume_role_fargate" { + statement { + principals { + type = "Service" + identifiers = ["eks-fargate-pods.amazonaws.com"] + } + effect = "Allow" + actions = ["sts:AssumeRole"] + } +} + +resource "aws_iam_role" "fargate" { + name = format("eks-%s-fargate-pods-role", trimprefix(var.name, "eks-")) + assume_role_policy = data.aws_iam_policy_document.assume_role_fargate.json +} + +resource "aws_iam_role_policy_attachment" "eks_fargate_pod_execution_role_policy" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSFargatePodExecutionRolePolicy" + role = aws_iam_role.fargate.name +} diff --git a/role-node.tf b/role-node.tf new file mode 100644 index 0000000..4591efc --- /dev/null +++ b/role-node.tf @@ -0,0 +1,34 @@ +# Reference: https://docs.aws.amazon.com/eks/latest/userguide/create-node-role.html + +data "aws_iam_policy_document" "assume_role_node" { + statement { + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + effect = "Allow" + actions = ["sts:AssumeRole"] + } +} + +resource "aws_iam_role" "node" { + name = format("eks-%s-node-role", trimprefix(var.name, "eks-")) + assume_role_policy = data.aws_iam_policy_document.assume_role_node.json +} + +resource "aws_iam_role_policy_attachment" "eks_worker_node_policy" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy" + role = aws_iam_role.node.name +} + +resource "aws_iam_role_policy_attachment" "ec2_container_registry_readonly" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + role = aws_iam_role.node.name +} + +# The AmazonEKS_CNI_Policy policy must be attached to either this role +# or to a different role that is mapped to the aws-node Kubernetes service account. +resource "aws_iam_role_policy_attachment" "eks_cni_policy" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" + role = aws_iam_role.node.name +} diff --git a/variables.tf b/variables.tf index a27d76b..9cd1523 100644 --- a/variables.tf +++ b/variables.tf @@ -1,12 +1,183 @@ -# variable "variable_name" { -# description = "variable description" -# type = number -# default = 1 -# } - -# variable "variable_password" { -# description = "variable description" -# type = string -# sensitive = true -# default = "abc" -# } +variable "common_tags" { + description = "A map of tags to assign to every resource in this module." + type = map(string) + default = {} +} + +variable "name" { + description = "The name of the cluster." + type = string +} + +variable "k8s_version" { + description = "Desired Kubernetes master version." + type = string + default = null +} + +# EKS vpc_config + +variable "subnet_ids" { + description = "List of subnet IDs. Must be in at least two different availability zones." + type = set(string) +} + +variable "security_group_ids" { + description = "List of security group IDs to allow communication between your worker nodes and the Kubernetes control plane." + type = set(string) + default = [] +} + +variable "endpoint_private_access" { + description = "Whether the Amazon EKS private API server endpoint is enabled." + type = bool + default = false +} + +variable "endpoint_public_access" { + description = "Whether the Amazon EKS public API server endpoint is enabled." + type = bool + default = true +} + +variable "public_access_cidrs" { + description = "List of CIDR blocks that can access the Amazon EKS public API server endpoint when enabled." + type = list(string) + default = [ + "0.0.0.0/0", + ] +} + +variable "encryption_key_arn" { + description = "ARN of the KMS customer master key for secrets encryption." + type = string + default = null +} + +# EKS control plane logs + +variable "enabled_cluster_log_types" { + description = "List of the desired control plane logging to enable." + type = list(string) + default = [ + "api", + # "audit", + "authenticator", + "controllerManager", + "scheduler", + ] +} + +variable "cluster_log_retention_in_days" { + description = "Specifies the number of days you want to retain log events." + type = number + default = 7 +} + +# Worker nodes + +variable "ec2_ssh_key" { + description = "The EC2 Key Pair name that provides access to the worker nodes." + type = string + default = null +} + +variable "source_security_group_ids" { + description = "Set of EC2 Security Group IDs to allow SSH access (port 22) from on the worker nodes." + type = set(string) + default = null +} + + +variable "node_group_subnet_ids" { + description = "Identifiers of EC2 Subnets to associate with the EKS Node Groups." + type = set(string) + default = [] +} + +variable "node_groups" { + description = "Map of EKS Node Group definitions." + type = map(object({ + subnet_ids = optional(set(string)) + ami_type = optional(string, "AL2_x86_64") + capacity_type = optional(string, "ON_DEMAND") + instance_type = optional(string, "t3.medium") + disk_size = optional(number, 20) + min_size = optional(number, 0) + max_size = optional(number, 1) + desired_size = optional(number, 0) + labels = optional(map(string)) + taints = optional(list(object({ + key = string + value = string + effect = string # Valid values: NO_SCHEDULE, NO_EXECUTE, PREFER_NO_SCHEDULE. + })), []) + })) + default = {} +} + +# Fargate pods + +variable "fargate_subnet_ids" { + description = "Identifiers of private EC2 Subnets to associate with the EKS Fargate Profiles." + type = set(string) + default = [] +} + +variable "fargate_profiles" { + description = "Map of EKS Fargate Profile definitions." + type = map(object({ + subnet_ids = optional(set(string)) + namespace = string + labels = optional(map(string)) + })) + default = {} +} + +# kube-system/aws-auth configmap + +variable "auth_map_accounts" { + description = "Maps IAM ARN from these accounts to username." + type = list(string) + default = ["current"] +} + +variable "auth_map_roles" { + description = "Maps an IAM role to a username and set of groups." + type = list(object({ + arn = string + username = optional(string) + groups = optional(list(string)) + })) + default = [] +} + +variable "auth_map_users" { + description = "Maps an IAM user to a static username and set of groups." + type = list(object({ + arn = string + username = optional(string) + groups = optional(list(string)) + })) + default = [] +} + +variable "nodes_role_arns" { + description = "Additional IAM role ARNs of Node Groups managed externally." + type = list(string) + default = [] +} + +variable "fargate_role_arns" { + description = "Additional IAM role ARNs of Fargate Profiles managed externally." + type = list(string) + default = [] +} + +variable "masters_role_arns" { + description = "List of IAM role to set as system:masters. Shortcut for auth_map_roles." + type = list(string) + default = [ + # "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/devops", + ] +} diff --git a/versions.tf b/versions.tf deleted file mode 100644 index b7d3369..0000000 --- a/versions.tf +++ /dev/null @@ -1,13 +0,0 @@ -terraform { - required_version = ">= 1.3.1" - required_providers { - # github = { - # source = "integrations/github" - # version = ">= 5.3.0" - # } - # azurerm = { - # source = "hashicorp/azurerm" - # version = ">= 3.22.0" - # } - } -}
"0.0.0.0/0"
]