이 문서에서는 Terraform을 사용하여 EKS Auto Mode 클러스터 인프라를 3개의 독립적인 레이어로 구성하는 방법을 설명합니다. 각 레이어는 변경 빈도, 팀 오너십, 그리고 장애 영향 범위(Blast Radius)에 따라 분리되어 있어 운영 안정성과 팀 협업 효율성을 높입니다.
단일 Terraform 상태 파일로 모든 인프라를 관리하면 다음과 같은 문제가 발생합니다:
Blast Radius 확대: 하나의 실수가 전체 인프라에 영향
긴 Plan/Apply 시간: 변경 사항이 없는 리소스도 매번 검사
팀 협업 충돌: 여러 팀이 동시에 작업할 때 Lock 경합
권한 관리 어려움: 네트워크 팀과 애플리케이션 팀의 권한 분리 불가
레이어별 특성 비교
Layer
이름
변경 빈도
주요 오너
Blast Radius
롤백 난이도
00
shared
거의 없음
DevOps
전체
매우 높음
01
network
월 1-2회
네트워크 팀
VPC 전체
높음
02
cluster
월 1-2회
플랫폼 팀
EKS 클러스터
중간
03
platform
주 1-2회
플랫폼 팀
Add-ons만
낮음
디렉토리 구조
핵심 원칙
Terraform은 AWS 인프라만 관리합니다.
Kubernetes 리소스(NodePool, Deployment, Service 등)는 ArgoCD를 통한 GitOps 방식으로 관리합니다. 자세한 내용은 GitOps 멀티 클러스터 배포를 참조하세요.
00-shared: 공통 설정
S3 Backend 구성 (네이티브 S3 잠금)
참고: Terraform 1.10부터 S3 backend에서 use_lockfile = true 옵션을 통해 DynamoDB 없이 네이티브 S3 잠금을 사용할 수 있습니다. S3의 conditional writes를 활용하여 상태 파일 잠금을 처리하므로, DynamoDB 테이블 생성 및 관리가 불필요합니다.
모든 레이어가 공유하는 Terraform 상태 저장소를 먼저 구성합니다.
공통 변수 정의
환경별 변수 파일
01-network: VPC 구성
Backend 설정
변수 정의
VPC 메인 구성
출력 정의
02-cluster: EKS Auto Mode
Backend 설정
Remote State 데이터 소스
변수 정의
EKS Auto Mode 클러스터 구성
출력 정의
03-platform: Add-ons & Pod Identity
Backend 설정
Remote State 데이터 소스
변수 정의
Pod Identity 및 Add-ons 구성
출력 정의
레이어 간 연계
terraform_remote_state 데이터 소스 패턴
각 레이어는 이전 레이어의 출력값을 terraform_remote_state 데이터 소스를 통해 참조합니다.
데이터 흐름 다이어그램
Terraform 3-Layer 데이터 흐름
상태 관리 모범 사례
절대 수동으로 상태 파일을 편집하지 마세요
terraform state mv, terraform import 명령 사용
상태 파일 버전 관리
S3 버전 관리 활성화로 롤백 가능
주기적인 상태 백업 권장
Lock 충돌 해결
출력값 변경 시 주의
하위 레이어에서 참조하는 출력값 변경 시 영향 범위 확인
출력값 삭제 전 의존성 제거 필요
S3 네이티브 잠금 사용 (Terraform 1.10+)
use_lockfile = true 설정으로 S3 conditional writes 기반 잠금 활성화
DynamoDB 테이블 생성/관리 불필요
기존 DynamoDB 잠금에서 마이그레이션 시 terraform init -migrate-state 실행
# 03-platform/outputs.tf
# ArgoCD Pod Identity Role ARNs
output "argocd_role_arn_blue" {
description = "ArgoCD IAM role ARN for Blue cluster"
value = var.enable_blue_cluster ? module.argocd_pod_identity_blue[0].iam_role_arn : null
}
output "argocd_role_arn_green" {
description = "ArgoCD IAM role ARN for Green cluster"
value = var.enable_green_cluster ? module.argocd_pod_identity_green[0].iam_role_arn : null
}
# External Secrets Role ARNs
output "external_secrets_role_arn_blue" {
description = "External Secrets IAM role ARN for Blue cluster"
value = var.enable_blue_cluster ? module.external_secrets_pod_identity_blue[0].iam_role_arn : null
}
output "external_secrets_role_arn_green" {
description = "External Secrets IAM role ARN for Green cluster"
value = var.enable_green_cluster ? module.external_secrets_pod_identity_green[0].iam_role_arn : null
}
# EBS CSI Driver Add-on Status
output "ebs_csi_addon_status_blue" {
description = "EBS CSI addon status for Blue cluster"
value = var.enable_blue_cluster ? aws_eks_addon.ebs_csi_blue[0].status : null
}
output "ebs_csi_addon_status_green" {
description = "EBS CSI addon status for Green cluster"
value = var.enable_green_cluster ? aws_eks_addon.ebs_csi_green[0].status : null
}
# 기본 패턴
data "terraform_remote_state" "previous_layer" {
backend = "s3"
config = {
bucket = "${var.project_name}-${var.environment}-terraform-state"
key = "layer-name/terraform.tfstate"
region = var.region
}
}
# 출력값 접근
local {
value_from_previous = data.terraform_remote_state.previous_layer.outputs.output_name
}
# Lock 강제 해제 (주의: 다른 작업이 없는지 확인 후)
terraform force-unlock LOCK_ID
# 1. 환경 변수 설정
export ENV="prod"
export AWS_REGION="ap-northeast-2"
export PROJECT="eks-platform"
# 2. 00-shared: Backend 인프라 생성 (최초 1회)
cd eks-terraform/00-shared
terraform init
terraform apply -var-file="../environments/${ENV}.tfvars"
# 3. 01-network: VPC 생성
cd ../01-network
terraform init
terraform plan -var-file="../environments/${ENV}.tfvars"
terraform apply -var-file="../environments/${ENV}.tfvars"
# 4. 02-cluster: EKS 클러스터 생성
cd ../02-cluster
terraform init
terraform plan -var-file="../environments/${ENV}.tfvars"
terraform apply -var-file="../environments/${ENV}.tfvars"
# 5. 03-platform: Add-ons 및 Pod Identity 구성
cd ../03-platform
terraform init
terraform plan -var-file="../environments/${ENV}.tfvars"
terraform apply -var-file="../environments/${ENV}.tfvars"
# kubeconfig 업데이트 (Blue 클러스터)
aws eks update-kubeconfig \
--region ap-northeast-2 \
--name eks-platform-prod-blue \
--alias blue-prod
# kubeconfig 업데이트 (Green 클러스터)
aws eks update-kubeconfig \
--region ap-northeast-2 \
--name eks-platform-prod-green \
--alias green-prod
# Blue 클러스터 확인
kubectl --context blue-prod get nodes
kubectl --context blue-prod get nodepools
kubectl --context blue-prod get pods -A
# Green 클러스터 확인
kubectl --context green-prod get nodes
kubectl --context green-prod get nodepools
kubectl --context green-prod get pods -A
# Auto Mode NodePool 확인
kubectl --context blue-prod describe nodepool general-purpose
kubectl --context blue-prod describe nodepool system
#!/bin/bash
# smoke-test.sh
set -e
BLUE_CONTEXT="blue-prod"
GREEN_CONTEXT="green-prod"
echo "=== EKS Cluster Smoke Test ==="
# 함수: 클러스터 상태 확인
check_cluster() {
local context=$1
local cluster_name=$2
echo ""
echo "--- Checking $cluster_name cluster ---"
# 노드 상태
echo "Nodes:"
kubectl --context "$context" get nodes -o wide
# NodePool 상태
echo ""
echo "NodePools:"
kubectl --context "$context" get nodepools
# 시스템 Pod 상태
echo ""
echo "System Pods:"
kubectl --context "$context" get pods -n kube-system
# CoreDNS 확인
echo ""
echo "CoreDNS status:"
kubectl --context "$context" get pods -n kube-system -l k8s-app=kube-dns
# 클러스터 버전
echo ""
echo "Cluster version:"
kubectl --context "$context" version --short
}
# 함수: DNS 테스트
test_dns() {
local context=$1
echo ""
echo "--- DNS Resolution Test ---"
kubectl --context "$context" run dns-test \
--image=busybox:1.28 \
--rm -it --restart=Never \
-- nslookup kubernetes.default.svc.cluster.local
}
# 함수: 기본 워크로드 배포 테스트
test_workload() {
local context=$1
echo ""
echo "--- Workload Deployment Test ---"
# 테스트 네임스페이스 생성
kubectl --context "$context" create namespace smoke-test --dry-run=client -o yaml | \
kubectl --context "$context" apply -f -
# 테스트 Deployment 배포
cat <<EOF | kubectl --context "$context" apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-test
namespace: smoke-test
spec:
replicas: 1
selector:
matchLabels:
app: nginx-test
template:
metadata:
labels:
app: nginx-test
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
EOF
# Pod 시작 대기
echo "Waiting for pod to be ready..."
kubectl --context "$context" wait --for=condition=ready pod \
-l app=nginx-test -n smoke-test --timeout=120s
echo "Test deployment successful!"
# 정리
kubectl --context "$context" delete namespace smoke-test
}
# Blue 클러스터 테스트
echo "========================================="
echo "Testing Blue Cluster"
echo "========================================="
check_cluster "$BLUE_CONTEXT" "Blue"
test_workload "$BLUE_CONTEXT"
# Green 클러스터 테스트
echo ""
echo "========================================="
echo "Testing Green Cluster"
echo "========================================="
check_cluster "$GREEN_CONTEXT" "Green"
test_workload "$GREEN_CONTEXT"
echo ""
echo "========================================="
echo "All smoke tests passed!"
echo "========================================="
# Terraform 상태 확인
terraform state list
terraform state show aws_eks_cluster.example
# EKS 클러스터 상태 확인
aws eks describe-cluster --name eks-platform-prod-blue
# 클러스터 인증 문제 디버깅
aws eks get-token --cluster-name eks-platform-prod-blue
# Pod Identity 연결 확인
aws eks list-pod-identity-associations \
--cluster-name eks-platform-prod-blue
# CloudWatch 로그 확인
aws logs describe-log-groups \
--log-group-name-prefix /aws/eks/eks-platform-prod