1
2
3

T101 3기(=Terraform 101 Study)는 Terraform 실무 실습 스터디입니다.
CloudNet@ 유형욱, 윤서율님이 진행하시며, 책 "테라폼으로 시작하는 IaC"을 기반으로 진행하고 있습니다.

지난 포스트 글에서는 테라폼 클라우드를 통해 EKS를 프로비저닝하였습니다. 프로비저닝은 EKS 모듈을 통해 진행하였는데요. 이번 글에서는 EKS 모듈에 대해 자세히 다뤄볼 예정입니다. 테라폼 레파지토리를 확인하면 EKS 모듈 사용 예제를 비롯하여 ALB 컨트롤러, External DNS와 같은 addon를 확인할 수 있습니다. 이번 글에서는 모듈 예제를 학습하고 EKS와 기본 addon을 프로비저닝하겠습니다. 구성 예제는 필자의 깃허브에서 확인이 가능합니다.

1. 테라폼 EKS 모듈

EKS 모듈 예제를 확인하면 EKS에서 프로비저닝할 수 있는 모든 워크 노드 타입이 제공되는 것을 확인할 수 있습니다. 각 워크 노드 타입의 특징은 다음과 같이 요약할 수 있습니다.

  • eks_managed_nodegroup (EKS Managed Node Groups) : EKS 관리형 노드 그룹입니다. AWS가 노드 그룹의 라이프사이클을 자동으로 관리해주는 타입입니다. 작동 상태를 모니터링하고 필요에 따라 패치 적용 및 업데이트를 자동으로 수행합니다.
  • Fargate : 노드 타입을 서버리스로 설정해주는 옵션입니다. 노드(ec2)가 아니라 별도로 관리할 필요가 없으며 컨테이너가 fargate로 할당됩니다. 쿠버네티스의 복잡한 관리 작업 없이 서비스를 실행할 수 있습니다.
  • Karpenter : karpenter는 오픈소스 자동 스케일러로 karpenter 수행되는 서버 타입(EC2 Fleet)로 할당하여 노드 그룹을 구성합니다. karpenter 를 적용하면 빠르게 서버를 스케일러할 수 있게 됩니다.
  • self_managed_nodegroup (Self-Managed Node Groups) : 비관리형 노드 그룹입니다. 사용자가 노드의 모든 측면을 관리해야 합니다. Custom AMI를 통해 노드를 구성할 수 있으며 보안 목적이나 노드의 추가 설정 필요시 사용합니다.
  • Outposts : 로컬 EKS 노드 그룹 옵션입니다. 온프레미스 또는 다른 원격 위치에서 노드 그룹을 프로비저닝하여 EKS 클러스터를 운영할 수 있습니다.

addon.png

위 예제 옵션 중 다루지 않은 complete는 모든 노드 그룹의 유형을 사용하는 예제이며, user_data는 노드의 부트스트랩 스크립트 및 구성하는 예제입니다. 예제 중 가장 기본이 되는 self-managed-node-group 예제를 통해 모듈 사용 예를 확인해보겠습니다. 모듈 구성은 다음과 같습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
.
├── modules
│   ├── _user_data
│   │   ├── README.md
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   ├── variables.tf
│   │   └── versions.tf
│   ├── eks
│   │   ├── README.md
│   │   ├── main.tf
│   │   ├── node_groups.tf
│   │   ├── outputs.tf
│   │   ├── templates
│   │   │   ├── aws_auth_cm.tpl
│   │   │   ├── bottlerocket_user_data.tpl
│   │   │   ├── linux_user_data.tpl
│   │   │   └── windows_user_data.tpl
│   │   ├── variables.tf
│   │   └── versions.tf
│   ├── eks-managed-node-group
│   │   ├── README.md
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   ├── variables.tf
│   │   └── versions.tf
│   ├── fargate-profile
│   │   ├── README.md
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   ├── variables.tf
│   │   └── versions.tf
│   ├── karpenter
│   │   ├── README.md
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   ├── variables.tf
│   │   └── versions.tf
│   └── self-managed-node-group
│       ├── README.md
│       ├── main.tf
│       ├── outputs.tf
│       ├── variables.tf
│       └── versions.tf
└── self_managed_node_group # 루트 모듈 
    ├── README.md
    ├── main.tf
    ├── outputs.tf
    ├── variables.tf
    └── versions.tf

디렉토리 modules 에 사용하는 커스컴 모듈을 모두 정의합니다. self_managed_node_group 폴더에 모듈에 따른 값을 정의합니다. self_managed_node_group main.tf 을 확인하면 eks 클러스터 구성, 노드 그룹 구성, vpc 구성을 확인할 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
module "eks" {
  source = "../modules/eks"
  # 클러스터 정보 입력 
  cluster_name                   = local.name
  cluster_version                = local.cluster_version
  cluster_endpoint_public_access = true

  # addon 구성 
  cluster_addons = {
    coredns = {
      most_recent = true
    }
    kube-proxy = {
      most_recent = true
    }
    vpc-cni = {
      most_recent = true
    }
  }

  # vpc 설정 
  vpc_id                   = module.vpc.vpc_id
  subnet_ids               = module.vpc.private_subnets
  control_plane_subnet_ids = module.vpc.intra_subnets

  # Self-managed node groups에 대한 AWS auth ConfigMap 자동 생성 설정
  create_aws_auth_configmap = true
  manage_aws_auth_configmap = true

  # Self-managed node groups에 대한 기본 설정
  self_managed_node_group_defaults = {
    # Cluster-autoscaler를 위한 자동 스케일링 그룹 태그 활성화
    autoscaling_group_tags = {
      "k8s.io/cluster-autoscaler/enabled" : true,
      "k8s.io/cluster-autoscaler/${local.name}" : "owned",
    }
  }

  # 노드 그룹 설정
  self_managed_node_groups = {
    .. 
  }
  # aws-auth 설정 
  aws_auth_users = [
   ..
   ]
  # 태그 설정
  tags = local.tags
}

1.1 노드 그룹

eks 모듈 내부의 self_managed_node_groups dict 을 통해 노드 그룹을 구성할 수 있습니다.. 구성 예제에서는 5가지의 노드 그룹으로 확인이 가능합니다. 노드 그룹별 키워드만 정리하면 다음과 같이 구성할 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
self_managed_node_groups = {
# 1. default_node_group 초기 설정 노드 그룹 구성
default_node_group = {}

# 2. 컨테이너 운영의 특화된 bottlerocket AMI 사용하여 노드 그룹 구성 
bottlerocket = {
  platform      = "bottlerocket"
  ami_id        = data.aws_ami.eks_default_bottlerocket.id
..
  }

# 3. 혼합 인스턴스 노드 그룹 구성 
mixed = {
  # 혼합 인스턴스 사용
  use_mixed_instances_policy = true
  # 혼합 인스턴스 정책 설정 
  mixed_instances_policy = {
    instances_distribution = {
      on_demand_base_capacity                  = 0  # 온디맨드 인스턴스 초기 0개로 설정
      on_demand_percentage_above_base_capacity = 20 # 온디맨드 인스턴스 비중을 20프로로 설정
      spot_allocation_strategy                 = "capacity-optimized" # spot 인스턴스 할당받을 때 용량 최적화 전략으로 설정
    }
    # 가중치 설정
    override = [
      {
        instance_type     = "m5.large"
        weighted_capacity = "1"
      },
      {
        instance_type     = "m6i.large"
        weighted_capacity = "1"
      },
    ]
    }
  } 

# 4. 고성능 컴퓨팅(HPC) 및 기계 학습 애플리케이션의 속도를 높일 수 있는 네트워크 디바이스 사용 인스턴스 
efa = {
  # 주의 인스턴스 타입이 고비용에서만 제공됩니다. 
  # aws ec2 describe-instance-types --region eu-west-1 --filters Name=network-info.efa-supported,Values=true --query "InstanceTypes[*].[InstanceType]" --output text | sort
  instance_type = "c5n.9xlarge"
  
  network_interfaces = [
      {
        description                 = "EFA interface example"
        delete_on_termination       = true
        device_index                = 0
        associate_public_ip_address = false
        interface_type              = "efa"
      }
    ]
  .. 
  } 
 # 5. 완전 사용자 정의 노드 그룹 설정이나 프로비저닝시, 버그로 넘어갑니다. 
 complete = {
   .. 
 } 
  
}
  • 구성시 efa 노드 그룹 구성을 주의해주세요. efa 기능 지원 인스턴스가 고비용의 인스턴스(9x.large) 이상으로만 설정이 가능합니다. 필자는 제외했습니다.
  • complete 노드 그룹은 사용자 정의 노드 그룹으로 디바이스, 모니터링, 메타데이터 옵션을 통해서 노드 그룹을 구성하지만, eks 에 붙지 않습니다. arg를 통해 노드 그룹으로 연결해야 하는지는 확인이 필요합니다.

EKS 구성시 노드 그룹 정보는 EKS 탭에서 확인이 가능합니다. 자세한 노드 그룹은 정보는 EC2 텝의 오토스케일링 그룹 정보를 확인해주세요.

nodegroup4.png

1.2 보안 구성

EKS 모듈을 통해 보안 구성을 크게 3가지로 분류하여 설정할 수 있습니다.

1.2.1 EKS Auth

AWS IAM 과 kubernetes RBAC 를 통해 EKS 클러스터에 대한 신원 정보를 확인하는 방법입니다. eks 모듈에서 aws_auth_users 에서 iam 정보를 입력합시다. 입력된 정보는 aws-auth configmap에 등록됩니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12

module "eks" {
  source = "../modules/eks"
  .. 
	aws_auth_users = [
	    {
	      userarn  = var.aws_auth_users_arn
	      username = var.aws_auth_users_username
	      groups   = ["system:masters"]
	    }
	   ]
}

auth4.png

  • AWS auth에서 IAM 사용자 정보를 입력하지 않을 경우 EKS 콘솔에서 다음의 메세지가 확인되며 노드 그룹의 리소스를 확인할 수 없습니다.

    This may be due to the current user or role not having Kubernetes RBAC permissions to describe cluster resources or not having an entry in the cluster’s auth config map’

1.2.2 Security Group

보안 그룹(Security Groups, SG)은 인스턴스에 대한 인바운드 및 아웃바운드 트래픽을 제어하는 가상 방화벽 역할을 합니다. main.tf에서는 별도로 설정하지 않았습니다. eks 모듈과 node_group 모듈에서 자동으로 설정됩니다.

sg3.png

초기 sg의 경우 클러스터 sg은 인그래스 포트로 443만 허용되며, 노드 그룹의 경우 다음과 같이 설정됩니다.

sg5.png

1.2.3 AWS IAM

EKS에서는 IAM 역할을 사용하여 노드 그룹이 AWS 리소스와 통신할 수 있는 권한을 부여합니다. main에서는 할당하지 않지만, 모듈에서 기본 IAM 정책을 할당합니다. 클러스터 할당 정책은 모듈에서 확인이 가능합니다. 관리형 정책 AmazonEKSClusterPolicy, AmazonEKSVPCResourceController 할당받으며, kms 사용 정책을 고객 관리형 정책으로 사용합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Policies attached ref https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html
resource "aws_iam_role_policy_attachment" "this" {
  for_each = { for k, v in {
    AmazonEKSClusterPolicy         = local.create_outposts_local_cluster ? "${local.iam_role_policy_prefix}/AmazonEKSLocalOutpostClusterPolicy" : "${local.iam_role_policy_prefix}/AmazonEKSClusterPolicy",
    AmazonEKSVPCResourceController = "${local.iam_role_policy_prefix}/AmazonEKSVPCResourceController",
  } : k => v if local.create_iam_role }

  policy_arn = each.value
  role       = aws_iam_role.this[0].name
}

resource "aws_iam_role_policy_attachment" "additional" {
  for_each = { for k, v in var.iam_role_additional_policies : k => v if local.create_iam_role }

  policy_arn = each.value
  role       = aws_iam_role.this[0].name
}

1.3 배포 확인

테라폼 명령어를 통해 EKS를 프로비저닝합시다. 시간은 약 15분 정도 소요됩니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# /terraform-t101-eks/self_managed_node_group
➜ terraform init 
..
➜ terraform plan
..
➜ terraform apply -auto-approve

# 20분 소요... 

➜ eksctl get cluster                                                                                   
NAME            REGION          EKSCTL CREATED
t1013-eks       ap-northeast-2  False

➜ eksctl utils write-kubeconfig --cluster t1013-eks --region ap-northeast-2
2023-09-15 18:16:45 []  saved kubeconfig as "/Users/mzc02-hseungho/.kube/config-f08"

➜ kubectl get pods -A 
NAMESPACE     NAME                       READY   STATUS    RESTARTS   AGE
kube-system   aws-node-d7sd4             2/2     Running   0          24m
kube-system   aws-node-gx7lk             2/2     Running   0          24m
kube-system   aws-node-xlnsx             2/2     Running   0          24m
kube-system   coredns-754fbc56c6-fmcqr   1/1     Running   0          24m
kube-system   coredns-754fbc56c6-qwkdk   1/1     Running   0          24m
kube-system   kube-proxy-6mg4q           1/1     Running   0          24m
kube-system   kube-proxy-7hlv8           1/1     Running   0          24m
kube-system   kube-proxy-fqbls           1/1     Running   0          24m

2. 테라폼 EKS addon 모듈

EKS (Elastic Kubernetes Service)의 애드온(add-ons)은 EKS 클러스터의 기능성과 운영을 강화하는 소프트웨어 컴포넌트입니다. 이러한 애드온은 주로 네트워킹, 로깅, 모니터링, 보안 등 다양한 목적으로 사용됩니다. EKS는 몇 애드온에 대해 AWS 서비스를 연동하여 제공하기도 합니다.

addons 이름 연동 AWS 서비스 설명
VPC CNI VPC AWS VPC (Virtual Private Cloud) 내 통신을 위한 네트워크 인터페이스(CNI)
CoreDNS VPC 서비스 디스커버리와 DNS를 위한 애드온
kube-proxy VPC Kubernetes 네트워크 프록시 관리
external-dns Route53 쿠버네티스 서비스와 인그레스에 대한 DNS 레코드 관리
ALB Controller ALB ALB를 쿠버네티스 인그레스 리소스로 관리
EBS CSI Controller EBS EBS 볼륨을 쿠버네티스 퍼시스턴트 볼륨으로 관리

EKS addon 도 테라폼 모듈을 통해 설치가 가능합니다. 테라폼 레파지토리 fully-loaded-eks-cluster 에 모듈화된 addon 서비스를 확인할 수 있습니다.

addon2.png

addon5.png

위에 소개한 모듈을 통해 addon 구성은 모듈 변수의 flag를 통해 손 쉽게 구성이 가능합니다. 다만, 개인적으로 내부 동작 이해가 어려워 확장성을 원하는 분들이나 추후 모듈을 구성하려는 분들에게 해당 모듈 사용을 추천드리지 않습니다.

공식적으로 제공하는 모듈외에도 사용자 모듈이 많습니다. 이번 포스트 글에서는 자주 다뤘던 ALB controller,external dns addon에 대해 직관적인 테라폼 모듈을 찾아 구성해보겠습니다.

2.1 ALB Controller

개인적으로 ALB controller 직관적인 모듈로 깃허브 campaand 님의 모듈을 추천드립니다. 모듈 내 main.tf을 확인하면 내부 동작을 확인할 수 있습니다. 먼저, alb 을 관리하기 위한 IAM 정책 선언 후 role에 연결합니다. 그 다음 helm 프로바이더를 통해 aws-load-balancer-controller 차트를 구성합니다.

alb1.png

모듈 문서를 참고하면, 헬름 차트의 추가 매개변수가 필요할 때는 다음 예제와 같이 구성할 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
module "alb_controller" {
  source  = "campaand/alb-controller/aws"
  version = "~> 2.0"

  cluster_name = var.cluster_name
  # 차트 버전 
  helm_chart_version = "1.6.0"
  # 매개변수 선언 
  settings = {
      key1 = value1,
      key2 = value2,
      key3 = value3,
      key4 = value4
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
module "alb_controller" {
  source  = "campaand/alb-controller/aws"
  version = "~> 2.0"

  cluster_name = var.cluster_name
  
  helm_chart_version = "1.6.0"

  settings = {
      key1 = value1,
      key2 = value2,
      key3 = value3,
      key4 = value4
  }
}

필자의 경우 main.tf 아래의 addon 구성을 위한 코드를 다음과 같이 구성하였습니다.

1
2
3
4
5
6
module "alb_controller" {
  source  = "campaand/alb-ingress-controller/aws"
  version = "2.0.0"

  cluster_name = module.eks.cluster_name
}

terraform apply로 배포 시 정상적으로 alb-controller가 구성됩니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# campaand/alb-ingress-controller/aws 모듈 추가 
➜ terraform init 
..
➜ terraform plan
..
➜ terraform apply -auto-approve
..
# module.alb_controller.helm_release.alb_ingress_controller will be created
  + resource "helm_release" "alb_ingress_controller" {
      + atomic                     = false
      + chart                      = "aws-load-balancer-controller"
      + cleanup_on_fail            = true
      + create_namespace           = false
      + dependency_update          = false
      + disable_crd_hooks          = false
      + disable_openapi_validation = false
      + disable_webhooks           = false
      + force_update               = false
      + id                         = (known after apply)
..
..
Apply complete! Resources: 2 added, 1 changed, 0 destroyed.

➜ kubectl get pods -A 
NAMESPACE     NAME                                            READY   STATUS    RESTARTS   AGE
kube-system   aws-load-balancer-controller-7f46f44c48-2st75   1/1     Running   0          17s
kube-system   aws-load-balancer-controller-7f46f44c48-z98zf   1/1     Running   0          17s
kube-system   aws-node-5q745                                  2/2     Running   0          3h35m
kube-system   aws-node-p2r9t                                  2/2     Running   0          3h35m
kube-system   coredns-754fbc56c6-btppz                        1/1     Running   0          3h33m
kube-system   coredns-754fbc56c6-dtfgd                        1/1     Running   0          3h33m
kube-system   kube-proxy-4vgxv                                1/1     Running   0          3h35m
kube-system   kube-proxy-nts26                                1/1     Running   0          3h35m

2.2 External DNS

External DNS 모듈을 찾아본 결과 불안정한 모듈들이 많습니다. 그나마 bohdantverdyi 님의 모듈이 깔끔하고 구성에 성공했네요. 모듈 구성은 다음과 같이 구성했습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# main.tf 

module "external_dns" {
  source  = "terraform-iaac/external-dns/kubernetes"
  version = "1.1.10"
  namespace = "kube-system"
  create_namespace = false

  dns           = ["hanhorang.link"] # route53 domain
  dns_provider  = "aws"
  aws_zone_type = "public"
  txt_owner_id = "" # route53 hosted zone id 
}

테라폼을 통해 모듈 구성을 진행합시다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# terraform-iaac/external-dns/kubernetes 모듈 추가 
➜ terraform init 
..
➜ terraform plan
..
➜ terraform apply -auto-approve
..
+ resource "kubernetes_deployment" "deploy_app" {
      + id               = (known after apply)
      + wait_for_rollout = true

      + metadata {
          + generation       = (known after apply)
          + labels           = {
              + "app" = "external-dns"
            }
          + name             = "external-dns"
          + namespace        = "kube-system"
          + resource_version = (known after apply)
          + uid              = (known after apply)
        }

      + spec {
          + min_ready_seconds         = 0
          + paused                    = false
          + progress_deadline_seconds = 600
          + replicas                  = "1"
          + revision_history_limit    = 10

          + selector {
              + match_labels = {
                  + "app" = "external-dns"
                }
            }

          + strategy {
..
Apply complete! Resources: 2 added, 1 changed, 0 destroyed.

➜ kubectl get pods -A 
NAMESPACE     NAME                                            READY   STATUS    RESTARTS      AGE
kube-system   aws-load-balancer-controller-7f46f44c48-fmptj   1/1     Running   0             6m9s
kube-system   aws-load-balancer-controller-7f46f44c48-tw2gx   1/1     Running   0             6m9s
kube-system   aws-node-5q745                                  2/2     Running   0             5h52m
kube-system   aws-node-p2r9t                                  2/2     Running   0             5h52m
kube-system   coredns-754fbc56c6-btppz                        1/1     Running   0             5h50m
kube-system   coredns-754fbc56c6-dtfgd                        1/1     Running   0             5h50m
kube-system   external-dns-5887c5877c-4l49b                   1/1     Running   4 (85s ago)   6m16s
kube-system   kube-proxy-4vgxv                                1/1     Running   0             5h52m
kube-system   kube-proxy-nts26                                1/1     Running   0             5h52m

3. 마치며

테라폼을 통해 addon을 구성하는 방법은 많습니다. 이번 포스트 글에서는 모듈을 통해 구성하였지만, 모듈 버전호환과 필요 매개 변수 등으로 구성 시간을 많이 소비하였습니다. 개인적으로는 모듈로 addon을 구성하는 것은 참고 예제가 많아지면 시도하는 것을 추천드립니다. 그 전까지는 스크립트나 CD를 통해 구성하는 것이 추천드립니다.