EKS 클러스터 셋업
AWS EKS 위에 진짜 운영 클러스터를 처음부터 띄우는 흐름을 다룹니다. Terraform으로 VPC · EKS 컨트롤 플레인 · 노드 그룹 · IRSA · 필수 애드온 (VPC CNI · CoreDNS · kube-proxy · EBS CSI)을 한 코드베이스로 선언하고, eksctl의 빠른 셋업 옵션, Karpenter의 노드 오토스케일링, 그리고 첫 점검 · 비용 모델까지 한 사이클로 정리합니다.
4부 (EKS 실전)의 첫 챕터입니다. 1~3부의 20장이 매니페스트 한 장의 모델부터 정책 엔진과 옵저버빌리티까지 K8s를 객체 차원에서 따라간 길이었다면, 4부의 6장은 그 위에 진짜 서비스를 하나 올리고 운영하는 흐름입니다. 가상의 백엔드 서비스 myshop-api를 EKS에 올리고, RDS와 연결하고, CI/CD로 배포하고, 모니터링 · 운영 단계까지 한 묶음으로 따라갑니다. 이번 챕터는 그 흐름의 출발점 — EKS 클러스터를 처음부터 띄우는 단계입니다. Terraform으로 VPC와 클러스터를 선언하고, 노드 그룹과 IRSA를 셋업하고, 필수 애드온까지 얹는 흐름을 정리합니다.
이번 챕터의 끝에서는 빈 EKS 클러스터 한 대 + IRSA / OIDC provider / 필수 애드온 / kubeconfig가 준비된 상태가 손에 들어옵니다. 4부의 나머지 5장이 이 빈 클러스터 위에 차근차근 한 서비스를 올려 가는 흐름입니다.
myshop-api — 4부를 관통하는 가상 서비스 #
4부를 묶는 시나리오를 한 줄로 잡아 둡니다. 가상의 회사 myshop이 자체 백엔드 API 서비스 myshop-api를 EKS에 올린다고 가정합니다. 사양은 단순합니다.
- Python (FastAPI) 또는 Go로 짠 작은 REST API
- RDS PostgreSQL을 데이터 저장소로 사용
- HTTPS로 외부 노출
- prod / dev 두 환경
- 트래픽 변동에 따라 Pod 개수 자동 조정
이 시나리오를 21장의 클러스터부터 26장의 운영 사이클까지 일관되게 따라갑니다. 각 챕터가 다음 챕터의 입력이 되는 구조이고, 마지막 26장까지 따라온 시점이라면 실제 운영 클러스터의 한 사이클이 손에 들어옵니다.
셋업 도구의 선택 — Terraform vs eksctl #
EKS 클러스터를 만드는 선택지는 여럿입니다.
| 도구 | 모델 | 적합한 곳 |
|---|---|---|
| AWS 콘솔 | 클릭 | 학습 / 한 번 보기 |
| eksctl | YAML 한 장 + CLI | PoC / 빠른 셋업 / 학습 |
| Terraform | HCL로 선언 | 운영 클러스터 / 멀티 환경 / IaC 표준 |
| AWS CDK / Pulumi | 코드 (TypeScript, Python 등)로 선언 | 코드 친화 팀 / 복잡한 분기 |
운영 클러스터의 사실상 표준은 Terraform입니다. VPC · IAM · EKS 컨트롤 플레인 · 노드 그룹 · 애드온 · RDS · Route53 까지를 한 코드베이스로 선언하고 git에 보관할 수 있어서, 클러스터 자체가 코드로 재현 가능해집니다. 같은 매니페스트가 dev / prod 두 환경에 동일하게 적용되고, 변경은 PR 리뷰를 거쳐 들어갑니다. 20장 GitOps의 패턴이 K8s 매니페스트뿐 아니라 클러스터 자체의 인프라에도 그대로 확장됩니다.
eksctl이 그 위치를 대체하기에는 추상화의 결이 EKS에 너무 한정적이지만, 빠른 셋업과 학습에는 가장 직관적입니다. 본 챕터에서는 Terraform을 메인으로 다루고, eksctl을 비교 옵션으로 짧게 짚습니다.
Terraform 프로젝트 구조 #
myshop-api 인프라의 Terraform 코드 구조부터 잡습니다.
terraform/
├── modules/
│ ├── network/ # VPC, 서브넷, NAT, 라우팅
│ ├── eks/ # EKS 클러스터 + 노드 그룹
│ └── addons/ # VPC CNI, EBS CSI, IRSA roles
├── envs/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ └── prod/
│ ├── main.tf
│ └── ...
└── versions.tfmodules/에 재사용 가능한 단위를 두고, envs/에서 dev / prod를 다르게 인스턴스화하는 구조입니다. dev / prod의 차이는 인스턴스 타입, 노드 개수, 멀티 AZ 정도이고 모듈 자체는 공유합니다. 7장 Namespace와 라벨의 환경별 매니페스트 분기가 K8s 매니페스트의 결이었다면, 본 챕터의 envs/ 구조는 인프라의 결입니다.
Provider와 backend #
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "myshop-tfstate"
key = "eks/prod/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "myshop-tfstate-lock"
encrypt = true
}
}
provider "aws" {
region = "ap-northeast-2"
default_tags {
tags = {
Project = "myshop"
Environment = "prod"
ManagedBy = "terraform"
}
}
}state 파일을 S3에, 잠금을 DynamoDB에 두는 표준 패턴입니다. default_tags가 이 provider로 만든 모든 자원에 자동으로 태그를 붙여 주므로 28장 비용 최적화의 Cost Allocation Tags 기반 비용 추적이 자연스럽게 굴러갑니다.
VPC — EKS의 토대 #
EKS는 자기 VPC를 만들지 않습니다. 사용자가 만든 VPC 위에 컨트롤 플레인의 ENI를 꽂는 구조입니다. 따라서 클러스터를 만들기 전에 VPC와 서브넷부터 정해야 합니다.
VPC 모듈 — terraform-aws-modules/vpc/aws #
VPC를 처음부터 직접 정의하지 않고 커뮤니티 모듈을 쓰는 게 표준입니다.
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "${var.project}-${var.env}"
cidr = "10.10.0.0/16"
azs = ["ap-northeast-2a", "ap-northeast-2c"]
private_subnets = ["10.10.1.0/24", "10.10.2.0/24"]
public_subnets = ["10.10.101.0/24", "10.10.102.0/24"]
database_subnets = ["10.10.201.0/24", "10.10.202.0/24"]
enable_nat_gateway = true
single_nat_gateway = var.env == "dev"
one_nat_gateway_per_az = var.env == "prod"
enable_dns_hostnames = true
enable_dns_support = true
public_subnet_tags = {
"kubernetes.io/role/elb" = "1"
}
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = "1"
}
}EKS와 묶일 때 핵심은 마지막의 두 태그입니다.
kubernetes.io/role/elb = 1— public 서브넷에 붙여, AWS Load Balancer Controller가 외부용 LB를 이 서브넷에 만듭니다.kubernetes.io/role/internal-elb = 1— private 서브넷에 붙여, 내부용 LB (클러스터 내부 + VPC 안)를 이쪽에 만듭니다.
이 태그가 없으면 LB가 어느 서브넷에 들어갈지 알 수 없어 22장 앱 배포 골격에서 만들 Ingress가 동작하지 않습니다.
single NAT vs NAT per AZ #
single_nat_gateway는 dev에서만 켭니다. NAT Gateway 한 개의 비용이 적지 않아서 (시간당 + 데이터 전송량당) dev 환경에서는 한 개로 묶고, prod는 AZ 별로 한 개씩 둬서 한 AZ가 죽어도 다른 AZ의 워크로드가 NAT를 잃지 않게 합니다.
EKS 모듈 — 컨트롤 플레인 + 노드 그룹 #
VPC가 준비되면 EKS 클러스터 자체를 정의합니다.
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
cluster_name = "${var.project}-${var.env}"
cluster_version = "1.32"
vpc_id = var.vpc_id
subnet_ids = var.private_subnet_ids
cluster_endpoint_public_access = true
cluster_endpoint_private_access = true
enable_irsa = true
cluster_addons = {
coredns = {
most_recent = true
}
kube-proxy = {
most_recent = true
}
vpc-cni = {
most_recent = true
}
aws-ebs-csi-driver = {
most_recent = true
service_account_role_arn = module.ebs_csi_irsa.iam_role_arn
}
}
eks_managed_node_groups = {
general = {
desired_size = var.env == "prod" ? 3 : 2
min_size = 2
max_size = 10
instance_types = ["t3.medium"]
capacity_type = var.env == "prod" ? "ON_DEMAND" : "SPOT"
labels = {
role = "general"
}
}
}
}이 한 모듈이 다음을 모두 만들어 냅니다.
- EKS 컨트롤 플레인 (관리형 K8s 1.32)
- 클러스터의 IAM Role
- OIDC provider (IRSA의 기반 —
enable_irsa = true) - Managed Node Group (EC2 인스턴스로 워커 노드 생성)
- 표준 애드온 4종 (VPC CNI, CoreDNS, kube-proxy, EBS CSI Driver)
16장 RBAC / ServiceAccount 깊이의 IRSA 절에서 다룬 OIDC provider가 여기서 자동으로 활성화되고, 이후 23장 DB 연동에서 ServiceAccount를 IAM Role과 묶을 때 그 OIDC가 신뢰의 기반이 됩니다.
Managed Node Group이라는 표준 #
EKS의 워커 노드는 세 갈래로 구성할 수 있습니다.
| 종류 | 모델 |
|---|---|
| Managed Node Group | EKS가 EC2 노드의 라이프사이클을 관리. 가장 표준적인 길 |
| Self-managed Node Group | 사용자가 직접 EC2 그룹 운영. 고급 커스터마이징 시 |
| Fargate | 서버리스. 노드 자체를 신경 쓰지 않음. 비용 · 제약 있음 |
Managed Node Group이 첫 도입의 표준입니다. 인스턴스 타입 · 크기 · capacity_type (ON_DEMAND / SPOT)을 매니페스트로 정의하면 EKS가 노드의 가입 · 제거 · 업그레이드를 자동으로 관리합니다. 노드 자체의 OS는 Amazon Linux 2 또는 Bottlerocket이고, kubelet · containerd · CNI 에이전트가 미리 설치된 AMI를 받습니다.
클러스터 endpoint의 두 모드 #
cluster_endpoint_public_access와 cluster_endpoint_private_access 두 플래그의 조합이 클러스터 API 서버 접근을 제어합니다.
| public | private | 의미 |
|---|---|---|
| true | false | 인터넷에서 누구든 접근 (RBAC 만이 보안 경계) |
| true | true | 인터넷 + VPC 내부 모두 (가장 흔한 설정) |
| false | true | VPC 내부에서만. 가장 엄격. bastion이나 VPN 필요 |
prod 환경의 보안 가이드는 보통 마지막 (private only) 이지만, GitHub Actions에서 직접 kubectl을 부르려면 public이 켜져 있어야 합니다. 일반적으로 public + IP 제한 (cluster_endpoint_public_access_cidrs)을 같이 거는 절충이 자주 쓰입니다. 24장 CI / CD 파이프라인에서는 이 endpoint 모드를 GitHub Actions와 ArgoCD 중 어느 쪽이 부를지에 따라 갈라 잡는 흐름을 다룹니다.
IRSA — 애드온의 IAM Role 부여 #
EBS CSI Driver, AWS Load Balancer Controller, External Secrets 같은 애드온은 클러스터 안에서 AWS API를 호출합니다. 이 호출에 IRSA로 권한을 부여하는 패턴을 짚습니다. 16장에서 본 모델이 본격적인 Terraform 셋업으로 이어지는 단계입니다.
module "ebs_csi_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "~> 5.0"
role_name = "${var.project}-${var.env}-ebs-csi"
attach_ebs_csi_policy = true
oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:ebs-csi-controller-sa"]
}
}
}이 모듈이 다음을 자동으로 만듭니다.
- IAM Role (이름이
myshop-prod-ebs-csi) - 정책 (EBS 디스크 생성 · 삭제 · attach 권한)
- Trust policy (16장의 그 trust policy —
kube-system네임스페이스의ebs-csi-controller-saServiceAccount만 이 Role을 가져갈 수 있도록 제한)
이 IAM Role의 ARN이 위 cluster_addons.aws-ebs-csi-driver.service_account_role_arn에 전달되어, EKS가 EBS CSI 애드온의 ServiceAccount에 자동으로 annotation을 붙여 줍니다. 결과적으로 9장 PV / PVC / StorageClass에서 다룬 PVC를 만들면 EBS 볼륨이 자동으로 프로비저닝되는 흐름이 동작합니다.
같은 패턴이 23장의 External Secrets에도, 25장 모니터링·알람의 CloudWatch 쪽에도 적용됩니다. 워크로드별로 ServiceAccount + IAM Role 1:1 매핑이 K8s 실전의 표준 보안 구조입니다.
kubeconfig — 클러스터에 접근 #
Terraform으로 클러스터를 만들고 나면 로컬에서 kubectl이 그 클러스터를 부를 수 있도록 kubeconfig를 받아와야 합니다. 2장 로컬 환경에서 본 kubeconfig 모델이 그대로 EKS에 적용됩니다.
aws eks update-kubeconfig \
--region ap-northeast-2 \
--name myshop-prodkubectl get nodes
kubectl get pods -ANAME STATUS ROLES AGE VERSION
ip-10-10-1-145.ap-northeast-2.compute.internal Ready <none> 3m v1.32.0
ip-10-10-2-201.ap-northeast-2.compute.internal Ready <none> 3m v1.32.0
ip-10-10-2-83.ap-northeast-2.compute.internal Ready <none> 3m v1.32.0세 노드가 떠 있고 kube-system 네임스페이스의 시스템 Pod (coredns, kube-proxy, aws-node, ebs-csi-controller)가 모두 Running 상태이면 클러스터가 정상입니다.
접근 권한 — aws-auth ConfigMap의 역할 #
EKS 클러스터를 만든 IAM 사용자 / Role이 자동으로 system:masters 권한을 갖습니다. 다른 IAM 사용자에게 접근 권한을 주려면 aws-auth ConfigMap을 수정하거나, 1.23+ 의 새 모델인 EKS Access Entries를 사용합니다. Access Entries가 더 표준이고 Terraform 모듈에서도 권장됩니다. 14장 RBAC / NetworkPolicy / ResourceQuota의 RBAC 표준 ClusterRole (view / edit / admin)이 EKS Access Entries와 자연스럽게 묶입니다.
access_entries = {
developers = {
principal_arn = "arn:aws:iam::123456789012:role/Developer"
policy_associations = {
view = {
policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy"
access_scope = {
type = "cluster"
}
}
}
}
}IAM Role 한 개를 클러스터 전체의 view 권한과 묶는 매니페스트입니다. K8s RBAC의 view ClusterRole이 자동으로 매핑되어, 이 Role을 가진 사용자는 클러스터의 모든 객체를 읽을 수 있게 됩니다.
eksctl — 빠른 셋업의 길 #
학습이나 PoC에서 빠른 클러스터가 필요할 때 eksctl이 한 줄에 끝납니다.
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: myshop-dev
region: ap-northeast-2
version: "1.32"
vpc:
nat:
gateway: Single
managedNodeGroups:
- name: general
instanceType: t3.medium
desiredCapacity: 2
minSize: 2
maxSize: 5
spot: true
addons:
- name: vpc-cni
- name: coredns
- name: kube-proxy
- name: aws-ebs-csi-driver
iam:
withOIDC: trueeksctl create cluster -f cluster.yaml이 한 명령으로 VPC · EKS · 노드 그룹 · OIDC provider · 기본 애드온까지 자동으로 만들어집니다. 15~20분 정도 걸리고, 끝나면 kubeconfig도 자동으로 갱신됩니다.
eksctl의 결을 정리하면 다음과 같습니다.
- 장점 — 학습 곡선이 가장 낮음, 한 매니페스트로 클러스터 한 대 통째로 만듭니다.
- 단점 — 멀티 환경 / RDS / Route53 같은 주변 자원과 한 묶음으로 관리하기 어렵습니다. CloudFormation을 내부적으로 쓰므로 Terraform과 state가 분리됩니다.
운영 클러스터의 목표는 거의 항상 Terraform 이지만, 첫 학습이나 일회성 PoC에는 eksctl이 가장 빠릅니다.
Karpenter — 노드 오토스케일링의 새 길 #
Managed Node Group은 자체 오토스케일링 (Cluster Autoscaler)이 가능하지만, 인스턴스 타입이 미리 정해진 몇 가지로 제한됩니다. 트래픽 변동이 크고 워크로드의 자원 요구가 다양한 환경에서는 Karpenter가 새 표준으로 자리 잡고 있습니다.
13장 오토스케일링 §“Karpenter — EKS의 더 빠른 대안"에서 짚었던 모델이 여기서 본격적인 매니페스트로 이어집니다. Pending 상태인 Pod를 보고, 그 Pod의 자원 요구에 맞는 인스턴스 타입을 실시간으로 골라 새 노드를 띄웁니다. 정해진 인스턴스 풀이 아니라 AWS의 모든 EC2 타입에서 가장 적합한 것을 선택합니다.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["t3.medium", "t3.large", "m5.large", "m5.xlarge"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
limits:
cpu: 100
disruption:
consolidationPolicy: WhenEmptyOrUnderutilizedKarpenter 도입은 21장의 출발점에서는 부담스러우므로, Managed Node Group으로 시작해서 트래픽 패턴이 정착되면 Karpenter로 전환하는 흐름이 자연스럽습니다. 26장 운영 체크리스트와 28장 비용 최적화에서 다시 짚습니다.
클러스터 셋업 후 첫 점검 #
클러스터가 떠오른 직후 한 번씩 돌려 두면 좋은 점검 명령들입니다.
kubectl version --short
kubectl get nodes -o widekubectl get pods -n kube-systemaws eks describe-cluster \
--name myshop-prod \
--region ap-northeast-2 \
--query "cluster.identity.oidc.issuer" \
--output textaws eks list-addons --cluster-name myshop-prod --region ap-northeast-2
aws eks describe-addon --cluster-name myshop-prod \
--addon-name vpc-cni --region ap-northeast-2이 네 명령으로 클러스터 · 노드 · 시스템 Pod · OIDC · 애드온이 모두 정상인지 확인됩니다. 이상이 보이면 다음 챕터로 넘어가기 전에 잡아 두는 게 안전합니다.
비용의 첫 인상 #
EKS 클러스터의 비용은 크게 셋입니다.
| 항목 | 비용 (ap-northeast-2 기준) |
|---|---|
| EKS 컨트롤 플레인 | 시간당 $0.10 (≈ 월 $73) |
| EC2 노드 (t3.medium 3대) | 월 약 $80 (ON_DEMAND) / $25 (SPOT) |
| NAT Gateway | 월 약 $35 + 데이터 전송 |
| EBS / Load Balancer / 데이터 전송 | 사용량에 따라 |
가장 작은 prod 클러스터의 시작 비용이 월 $200~$300 정도입니다. dev 환경에서 SPOT 인스턴스 + Single NAT를 쓰면 그 절반 이하로 떨어집니다. 비용은 28장 비용 최적화에서 본격적으로 다루지만, 클러스터를 띄우는 시점부터 인지하고 시작하는 게 좋습니다.
연습문제 #
- 본인이 학습용으로 사용 가능한 AWS 계정에 본 챕터의 Terraform 코드를 적용해 dev EKS 클러스터 한 대를 띄워 봅니다.
aws eks update-kubeconfig로 kubeconfig를 갱신한 뒤kubectl get nodes -o wide와kubectl get pods -n kube-system출력을 기록합니다. §“클러스터 셋업 후 첫 점검"의 네 명령을 차례로 돌리고, OIDC provider URL · 애드온 상태 · 시스템 Pod 9개 이상이 모두 정상인지 점검합니다. cluster_endpoint_public_access/cluster_endpoint_private_access의 세 조합 (public-only / public + private / private-only)의 트레이드오프를 본인의 운영 시나리오에 비춰 한 단락으로 비교합니다. 24장 CI / CD 파이프라인에서 GitHub Actions가 직접kubectl을 부를 것인지, 아니면 ArgoCD가 git만 watch 하는 pull 모델로 갈 것인지가 이 endpoint 결정과 어떻게 묶이는지 메모합니다.- 본 챕터의 EBS CSI IRSA 모듈을 참고해서 AWS Load Balancer Controller와 External Secrets Operator의 IRSA Terraform 매니페스트 골격을 적어 봅니다. 각 ServiceAccount의 네임스페이스 · 이름 · 필요한 IAM 정책이 무엇인지 16장의 trust policy 모델로 정리하고, “namespace + SA name 둘 다 명시” 원칙이 왜 격리에 중요한지 한 단락으로 설명합니다.
한 줄 요약: EKS 운영 클러스터의 셋업은 Terraform으로 VPC · EKS 컨트롤 플레인 · 노드 그룹 · IRSA · 표준 애드온 (VPC CNI · CoreDNS · kube-proxy · EBS CSI)을 한 코드베이스로 선언하는 흐름이다. eksctl은 빠른 학습 · PoC의 길이고, Karpenter는 트래픽 패턴이 정착된 후 Managed Node Group을 대체하는 노드 오토스케일링의 새 표준이다. 클러스터 자체의 비용은 컨트롤 플레인 + 노드 + NAT의 셋이 주축이고, prod의 시작 비용은 월 $200~$300 수준이다.
다음 챕터 #
이 시점에서 클러스터는 비어 있는 상태입니다 — 노드는 떠 있고 시스템 Pod는 살아 있지만, 우리가 올리려는 myshop-api는 아직 한 줄도 매니페스트로 들어가지 않았습니다. 다음 챕터에서는 그 빈 곳을 채웁니다.
22장 앱 배포 골격에서는 4장 Deployment / 5장 Service / 10장 Ingress / 6장 ConfigMap · Secret의 다섯 객체를 한 묶음으로 정리하고, Helm 차트로 묶어 환경별 배포까지 연결하는 흐름을 다룹니다. AWS Load Balancer Controller와 cert-manager의 본격적인 셋업도 이 챕터에서 이어집니다.