메인 콘텐츠로 건너뛰기

Terragrunt 배포 가이드

이 가이드는 Terragrunt를 사용하여 AWS에서 EKB EKS 인프라를 전체 배포하는 과정을 안내합니다. 도구 설치, 환경 설정 및 모든 인프라 구성 요소 간 올바른 의존성 순서를 보장하도록 설계된 단계별 배포 순서를 다룹니다.

배포는 9단계로 구성됩니다:

  1. 상태 관리 — 환경의 Terraform 상태를 저장하는 S3 버킷을 부트스트랩합니다.
  2. EKS 인프라 — VPC, 서브넷, NAT Gateway, IAM 역할 및 EKS 클러스터와 매니지드 노드 그룹을 프로비저닝합니다.
  3. 스토리지 및 로드 밸런싱 — 퍼시스턴트 볼륨용 EBS CSI 드라이버와 ALB 인그레스용 AWS Load Balancer Controller를 배포합니다.
  4. Karpenter 자동 스케일링 — SQS 및 EventBridge를 통한 Spot 인스턴스 지원 및 중단 처리 기능을 갖춘 동적 노드 프로비저닝을 설정합니다.
  5. KEDA 자동 스케일링 — CPU 및 메모리 임계값 기반 Pod 수준 자동 스케일링을 위해 KEDA를 배포합니다.
  6. 데이터 서비스 — Supabase (셀프호스팅 또는 Cloud), ElastiCache Redis 및 Amazon MQ RabbitMQ를 프로비저닝합니다.
  7. EKB Services — Helm을 통해 EKB 애플리케이션 스택 (Web, FastAPI, Celery, Automator)을 배포합니다.
  8. SigNoz 관측 가능성 — SigNoz 및 k8s-infra 에이전트를 통해 분산 추적, 메트릭 및 로그 집계를 배포합니다.
  9. 최종 배포 — 남은 리소스를 조정하기 위해 전체 terragrunt apply를 실행합니다.

시작하기 전에 고객과 함께 사전 요구사항 체크리스트를 완료하고 환경 템플릿의 모든 <YOUR_*> 플레이스홀더가 채워져 있는지 확인하세요. VPC ID, EKS 클러스터 엔드포인트, Redis 및 RabbitMQ 엔드포인트를 포함한 여러 값은 특정 단계가 완료된 후에만 사용할 수 있으므로, 이 가이드는 정확히 언제 값을 캡처하고 적용해야 하는지 표시합니다.

사전 요구사항​

  • 적절한 권한으로 구성된 AWS CLI
  • Terraform (>= 1.0)
  • Terragrunt (최신 버전)
  • Kubernetes 관리를 위한 kubectl
  • Helm 차트 관리를 위한 helm

설치 가이드​

Terragrunt 설치​

macOS (Homebrew)

brew install terragrunt

Linux (apt)

# Add HashiCorp GPG key
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg

# Add HashiCorp repository
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

# Update and install
sudo apt update
sudo apt install terragrunt

Windows (Chocolatey)

choco install terragrunt

kubectl 설치​

macOS (Homebrew)

brew install kubectl

Linux

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

Windows (Chocolatey)

choco install kubernetes-cli

Helm 설치​

macOS (Homebrew)

brew install helm

Linux

curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

Windows (Chocolatey)

choco install kubernetes-helm

설치 확인​

terragrunt --version
terraform --version
kubectl version --client
helm version

AWS CLI 구성​

# Install AWS CLI
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

# Configure AWS credentials
aws configure

# Verify configuration
aws sts get-caller-identity

새 환경 생성​

1단계: 환경 템플릿 복사​

env-template-folder는 <YOUR_*> 플레이스홀더가 포함된 미리 구조화된 파일을 포함하고 있습니다. 새 환경 폴더를 만들려면 전체를 복사하세요.

# Navigate to the terragrunt environments directory
cd terragrunt/environments

# Copy the full template folder to a new environment (replace 'your-env-name')
cp -r env-template-folder your-env-name

# The folder structure is ready:
# your-env-name/
# ├── terragrunt.hcl # Core cluster configuration
# ├── state/
# │ └── terragrunt.hcl # S3 state bucket configuration
# └── values/
# ├── infrastructure.yaml # AWS Load Balancer Controller
# ├── karpenter-values.yaml # Karpenter controller settings
# ├── karpenter-nodeclasses.yaml # EC2NodeClass definitions
# ├── karpenter.yaml # Karpenter NodePool definitions
# ├── keda.yaml # KEDA autoscaler
# ├── aws-ebs-csi-driver.yaml # EBS CSI driver
# ├── odin-services.yaml # EKB application services
# ├── supabase.yaml # Supabase (if self-hosting)
# ├── ha-supabase-db.yaml # Supabase HA DB (if self-hosting)
# ├── cloudnative-pg.yaml # CloudNativePG operator (if self-hosting)
# ├── signoz.yaml # SigNoz observability (optional)
# └── signoz-k8s-infra.yaml # SigNoz k8s metrics (optional)

2단계: 모든 플레이스홀더 존재 확인​

cd your-env-name

# List all placeholders that need to be filled in
grep -r "<YOUR_" . --include="*.hcl" --include="*.yaml" | sort

모든 플레이스홀더는 <YOUR_*> 관례를 따릅니다. 아래 단계에서 파일별로 채우는 방법을 안내합니다.

3단계: SSL 인증서 프로비저닝 (AWS ACM)​

환경 변수를 설정하기 전에 인증서 ARN이 필요합니다. AWS 콘솔에서 AWS Certificate Manager (ACM)에서 환경이 서비스할 모든 도메인에 대한 SSL 인증서를 요청하세요.

옵션 A: 단일 와일드카드 인증서 (권장)

단일 와일드카드 인증서는 하나의 ARN으로 모든 하위 도메인을 커버합니다. 예를 들어 기본 도메인이 app.example.com인 경우, 단일 *.app.example.com 인증서가 다음을 커버합니다:

서비스도메인
Webapp.example.com
FastAPIapi-app.example.com
Automatorautomations-app.example.com
Supabasesupabase-app.example.com
SigNozsignoz-app.example.com

옵션 B: 서비스별 인증서

와일드카드를 사용할 수 없는 경우 도메인별로 하나의 인증서를 요청하세요. 각 도메인에 대해 아래 단계를 반복하세요: <YOUR_WEB_DOMAIN>, <YOUR_API_DOMAIN>, <YOUR_AUTOMATOR_DOMAIN>, <YOUR_SUPABASE_DOMAIN> (Supabase Cloud 사용 시만), <YOUR_SIGNOZ_DOMAIN> (SigNoz 활성화 시만).

AWS 콘솔에서 인증서 요청

  1. AWS Certificate Manager 콘솔을 엽니다
  2. 올바른 리전으로 전환 (우측 상단) — <YOUR_AWS_REGION>과 일치해야 합니다
  3. 인증서 요청 클릭 → 퍼블릭 인증서 요청 → 다음
  4. 완전한 도메인 이름 아래에 와일드카드 (예: *.app.example.com) 또는 특정 도메인을 입력
  5. 검증 방법을 DNS 검증으로 설정
  6. 요청 클릭 — 인증서가 Pending validation 상태로 생성됨

DNS CNAME 검증 레코드 추가

ACM은 도메인 소유권을 증명하기 위해 DNS 제공자에 추가해야 하는 CNAME 레코드를 생성합니다. 인증서를 열고 Domains 아래 도메인을 펼쳐서 ACM 콘솔에서 값을 확인하세요.

DNS 필드값
레코드 유형CNAME
이름 / 호스트예: _fa187f22ac17bce6f508bf3c56439c61.signoz-app.example.com.
값 / 연결 대상예: _c7c97325fe38061e168e232d122c7ff3.jkddzztszm.acm-validations.aws.
정보

DNS 제공자가 요구하는 경우 CNAME 값 끝에 마침표(.)를 포함하세요.

Cloudflare

  1. Cloudflare에 로그인 → 도메인 선택 → DNS → Records → Add record로 이동
  2. Type을 CNAME으로 설정
  3. ACM CNAME 이름을 Name에, ACM CNAME 값을 Target에 붙여넣기
  4. Proxy status를 DNS only (회색 구름 아이콘)로 설정 — Cloudflare 프록시를 통해 인증서가 검증되지 않습니다
  5. Save 클릭

Route 53

  1. Route 53 콘솔 열기 → Hosted zones → 해당 영역 선택 → Create record
  2. Record type을 CNAME으로 설정
  3. ACM CNAME 이름을 Record name에 (하위 도메인 부분만), 값을 Value에 붙여넣기
  4. TTL을 300으로 설정하고 Create records 클릭
팁

ACM에서 Create records in Route 53을 클릭하면 호스팅 영역이 동일한 계정에 있는 경우 ACM이 레코드를 자동으로 추가할 수 있습니다.

DNS 전파 후 (보통 1–5분), 인증서 상태가 Issued로 변경됩니다. 인증서 상단에서 ARN을 복사하세요 — arn:aws:acm:<region>:<account-id>:certificate/<uuid> 형태입니다. 다음 단계를 위해 ARN을 준비해 두세요.

4단계: 환경 변수 설정​

Terragrunt 명령어를 실행하기 전에 이 셸 환경 변수를 설정하세요. 이들은 terragrunt.hcl의 get_env()에 의해 직접 읽힙니다.

export AWS_REGION="<YOUR_AWS_REGION>" # e.g., "eu-west-2", "us-east-2"
export CLUSTER_NAME="<env_folder_name>" # e.g., "env-template-folder"

# Domain configuration
export WEB_DOMAIN="<YOUR_WEB_DOMAIN>" # e.g., "app.example.com"
export FASTAPI_DOMAIN="<YOUR_API_DOMAIN>" # e.g., "api-app.example.com"
export AUTOMATOR_DOMAIN="<YOUR_AUTOMATOR_DOMAIN>" # e.g., "automations-app.example.com"
export SUPABASE_DOMAIN="<YOUR_SUPABASE_DOMAIN>" # e.g., "supabase-app.example.com"
export SIGNOZ_DOMAIN="<YOUR_SIGNOZ_DOMAIN>" # e.g., "signoz-app.example.com"

# SSL Certificate ARNs — Option A: Single wildcard certificate (recommended)
export WILDCARD_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_WILDCARD_CERT_ID>"

# SSL Certificate ARNs — Option B: Per-service certificates
export WEB_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_WEB_CERT_ID>"
export FASTAPI_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_API_CERT_ID>"
export AUTOMATOR_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_AUTOMATOR_CERT_ID>"
export SUPABASE_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_SUPABASE_CERT_ID>"
export SIGNOZ_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_SIGNOZ_CERT_ID>"

# Service enablement flags
export ENABLE_ALB_CONTROLLER="true"
export ENABLE_AWS_SERVICES="true" # Set to "true" to enable ElastiCache and AmazonMQ

# Supabase stack (self-hosted) — enable all three together if self-hosting Supabase
export ENABLE_CNPG="true" # CloudNativePG operator (namespace: cnpg-system)
export ENABLE_HA_SUPABASE_DB="true" # Supabase HA database (namespace: ha-supabase-db)
export ENABLE_SUPABASE="true" # Supabase application (namespace: supabase)

export ENABLE_SIGNOZ="true" # Set to "true" to enable SigNoz observability
export SSL_TERMINATION="alb"

Spot 인스턴스 및 상태형 워크로드​

Spot 인스턴스는 환경 변수가 아닌 values/karpenter.yaml의 NodePool별로 구성됩니다. 각 NodePool은 자체 용량 전략을 선언합니다:

NodePoolworkload-type 레이블용량 유형이유
generalgeneralSpot → 온디맨드 폴백상태 비저장 배치/백그라운드 워크로드 비용 최적화
compute-intensivecompute-intensiveSpot → 온디맨드 폴백CPU 바인드 워크로드 비용 최적화
memory-intensivememory-intensive온디맨드 → Spot 폴백고메모리 Pod의 안정성 우선
gpugpuSpot → 온디맨드 폴백AI/ML 배치 워크로드 비용 최적화
applicationapplication온디맨드만안정적인 사용자 대면 서비스 (Supabase, Kong 등) — Spot 중단 없음
databasedatabase / node-type: database-dedicated온디맨드만상태형 — 데이터베이스에 Spot 중단은 안전하지 않음

application NodePool은 m/c 인스턴스 패밀리 (5세대 이상)를 사용하며 온디맨드만 사용합니다. Supabase 서비스 Pod는 nodeSelector: workload-type: "application"을 통해 여기에 고정되어 Spot 회수 이벤트에 의해 절대 중단되지 않도록 보장합니다.

database-dedicated NodePool은 Spot을 사용하지 않습니다. consolidationPolicy: WhenEmpty를 사용하여 Karpenter가 아직 실행 중인 Pod가 있는 노드를 추방하지 않도록 하여 PostgreSQL 및 CloudNativePG 복제본과 같은 상태형 워크로드에 안전합니다.

Spot에서의 상태형 애플리케이션 가이드라인:

  • 데이터베이스, 퍼시스턴트 큐 또는 PersistentVolumeClaim이 있는 Pod를 Spot NodePool에 스케일링하지 마세요.
  • 데이터베이스 Pod에는 node-type: database-dedicated를 대상으로 하는 nodeSelector와 일치하는 database-workload: "true" 톨러레이션을 사용하세요.
  • 중단 없이 계속 가용해야 하는 사용자 대면 상태 비저장 서비스에는 nodeSelector: workload-type: "application"을 사용하세요.
  • 백그라운드 워크로드 (Web, API, Celery, Automator)의 경우 general Spot NodePool이 적합합니다 — Karpenter의 SQS 중단 처리기가 AWS가 회수하기 전에 Spot 노드를 우아하게 드레인하고, KEDA의 최소 복제본 수 (≥ 2)가 노드 교체 중 가용성을 보장합니다.
  • Spot을 전역적으로 비활성화하려면 values/karpenter.yaml의 모든 NodePool에서 값 목록에서 "spot"을 제거하세요.

Karpenter가 Spot 중단 경고를 처리하는 방법:

AWS는 Spot 인스턴스를 종료하기 전에 2분 중단 통지를 제공합니다. Karpenter는 EventBridge와 SQS를 사용하여 이에 자동으로 대응합니다:

AWS Spot Interruption Event
│
▼
Amazon EventBridge (CloudWatch Events)
Rule: EC2 Spot Instance Interruption Warning
│
▼
SQS Queue (Karpenter interruption queue)
│
▼
Karpenter Controller (polls SQS continuously)
│
├── Cordons the node (no new pods scheduled)
├── Drains existing pods (respects PodDisruptionBudgets)
├── Provisions a replacement node in parallel
└── Pods reschedule onto the new node before the 2-min window closes

이는 terragrunt.hcl의 karpenter 블록에서 구성됩니다:

karpenter = {
spot_interruption_handling = true # creates the SQS queue and EventBridge rule
enable_spot_instances = true # allows Spot in NodePool capacity requirements
}

5단계: 환경별 파일 값 업데이트​

새 환경 폴더의 모든 파일에서 다음 플레이스홀더를 검색하여 대체하세요.

플레이스홀더설명예시
<YOUR_ENV_NAME>고유 환경 식별자app-eks-prod
<YOUR_AWS_REGION>클러스터의 AWS 리전eu-west-2, us-east-2
<YOUR_AWS_ACCOUNT_ID>12자리 AWS 계정 ID123456789012
<YOUR_ENVIRONMENT>환경 태그 값prod, staging, dev
<YOUR_PROJECT>프로젝트 태그 값ekb, ekb
# Run from your new env folder to find all remaining placeholders
grep -r "<YOUR_" .

5.1 terragrunt.hcl — 핵심 클러스터 구성​

nano terragrunt.hcl
필드플레이스홀더비고
cluster_name<YOUR_ENV_NAME>EKS 클러스터 이름과 일치해야 합니다
cluster_region<YOUR_AWS_REGION>AWS 리전
aws_account_id<YOUR_AWS_ACCOUNT_ID>12자리 계정 ID
vpc_cidr<YOUR_VPC_CIDR>예: 192.168.0.0/16
availability_zones<YOUR_REGION>a/b/c해당 리전의 3개 AZ
tags.Environment<YOUR_ENVIRONMENT>예: prod
tags.Project<YOUR_PROJECT>예: ekb
aws_services.amazon_mq.rabbitmq.username<YOUR_RABBITMQ_USERNAME>RabbitMQ 관리자 사용자 이름 (ENABLE_AWS_SERVICES=true일 때만)
aws_services.amazon_mq.rabbitmq.password<YOUR_RABBITMQ_PASSWORD>최소 12자; 대문자, 소문자, 숫자 및 특수 문자 포함 필요

5.2 state/terragrunt.hcl — S3 상태 버킷​

nano state/terragrunt.hcl
필드플레이스홀더비고
bucket_nameodin-terraform-state-<YOUR_ENV_NAME>전역적으로 고유해야 합니다
region<YOUR_AWS_REGION>클러스터와 동일한 리전

5.3 values/infrastructure.yaml — AWS Load Balancer Controller​

경고

AWS Load Balancer Controller를 배포하기 전에 EKS 클러스터 생성 이후에 VPC ID를 확보하세요.

# Get VPC ID after EKS cluster is created
aws eks describe-cluster --name <YOUR_ENV_NAME> \
--query "cluster.resourcesVpcConfig.vpcId" --output text
nano values/infrastructure.yaml
필드플레이스홀더비고
clusterName<YOUR_ENV_NAME>EKS 클러스터 이름
region<YOUR_AWS_REGION>AWS 리전
vpcId<YOUR_VPC_ID>ALB 배포 전에 필요
serviceAccount.annotations.eks.amazonaws.com/role-arn<YOUR_AWS_ACCOUNT_ID>, <YOUR_ENV_NAME>ALB 컨트롤러용 IAM 역할

5.4 values/karpenter-values.yaml — Karpenter 컨트롤러​

경고

Karpenter를 배포하기 전에 EKS 클러스터 생성 이후에 EKS 클러스터 엔드포인트를 확보하세요.

# Get cluster endpoint after EKS cluster is created
aws eks describe-cluster --name <YOUR_ENV_NAME> \
--query "cluster.endpoint" --output text
nano values/karpenter-values.yaml
필드플레이스홀더비고
serviceAccount.annotations.eks.amazonaws.com/role-arn<YOUR_AWS_ACCOUNT_ID>, <YOUR_ENV_NAME>Karpenter용 IAM 역할
env.CLUSTER_NAME<YOUR_ENV_NAME>EKS 클러스터 이름
env.CLUSTER_ENDPOINT<YOUR_EKS_CLUSTER_ENDPOINT>Karpenter 배포 전에 필요
settings.aws.defaultInstanceProfile<YOUR_ENV_NAME>Karpenter 노드 인스턴스 프로필

5.5 values/karpenter-nodeclasses.yaml — Karpenter 노드 클래스​

nano values/karpenter-nodeclasses.yaml
필드플레이스홀더비고
모든 kubernetes.io/cluster/<YOUR_ENV_NAME> 태그<YOUR_ENV_NAME>서브넷/SG 선택자를 위한 클러스터 태그
user_data 부트스트랩 클러스터 이름<YOUR_ENV_NAME>노드 부트스트랩 스크립트
tags.Environment<YOUR_ENVIRONMENT>예: prod
tags.Project<YOUR_PROJECT>예: ekb

5.6 values/aws-ebs-csi-driver.yaml — EBS CSI Driver​

nano values/aws-ebs-csi-driver.yaml
필드플레이스홀더비고
controller.serviceAccount.annotations.eks.amazonaws.com/role-arn<YOUR_AWS_ACCOUNT_ID>, <YOUR_ENV_NAME>EBS CSI 컨트롤러용 IAM 역할
node.serviceAccount.annotations.eks.amazonaws.com/role-arn<YOUR_AWS_ACCOUNT_ID>, <YOUR_ENV_NAME>EBS CSI 노드용 IAM 역할
controller.env.AWS_DEFAULT_REGION<YOUR_AWS_REGION>AWS 리전
controller.env.AWS_REGION<YOUR_AWS_REGION>AWS 리전
node.env.AWS_DEFAULT_REGION<YOUR_AWS_REGION>AWS 리전
node.env.AWS_REGION<YOUR_AWS_REGION>AWS 리전

5.7 values/karpenter.yaml — Karpenter NodePools​

nano values/karpenter.yaml
필드플레이스홀더비고
*.labels.Environment<YOUR_ENVIRONMENT>모든 NodePool 레이블에 적용
*.requirements topology.kubernetes.io/zone["<YOUR_REGION>a", "<YOUR_REGION>b", "<YOUR_REGION>c"]모든 NodePool의 AZ

노드 클래스 이름 (general, compute-intensive, memory-intensive, gpu, database)은 karpenter-nodeclasses.yaml의 항목과 일치해야 합니다.

5.8 values/keda.yaml — KEDA Autoscaler​

환경별 플레이스홀더가 필요하지 않습니다. 리소스 제한 및 복제본 수는 적절한 기본값으로 사전 구성되어 있습니다. 검토 후 필요에 따라 조정하세요.

5.9 values/supabase.yaml — Supabase 애플리케이션 (ENABLE_SUPABASE=true일 때만)​

경고

아래 모든 키는 일관되게 생성하고 ha-supabase-db.yaml과 공유해야 합니다. 한 번 생성하고 두 파일에서 동일한 값을 사용하세요.

# Generate JWT secret
openssl rand -hex 32

# Generate anon/service role JWTs (requires Supabase CLI)
brew install supabase/tap/supabase
supabase gen-keys

# Generate passwords and tokens
openssl rand -hex 24 # for passwords
openssl rand -base64 64 # for secretKeyBase
nano values/supabase.yaml
필드플레이스홀더비고
secret.jwt.anonKey<YOUR_SUPABASE_ANON_KEY>ha-supabase-db.yaml anonKey와 일치해야 합니다
secret.jwt.serviceKey<YOUR_SUPABASE_SERVICE_ROLE_KEY>ha-supabase-db.yaml serviceRoleKey와 일치해야 합니다
secret.jwt.secret<YOUR_SUPABASE_JWT_SECRET>ha-supabase-db.yaml jwtSecret과 일치해야 합니다
secret.db.password<YOUR_SUPABASE_DB_PASSWORD>ha-supabase-db.yaml postgresPassword와 일치해야 합니다
secret.analytics.publicAccessToken<YOUR_SUPABASE_ANALYTICS_PUBLIC_TOKEN>내부 Logflare 토큰
secret.analytics.privateAccessToken<YOUR_SUPABASE_ANALYTICS_PRIVATE_TOKEN>내부 Logflare 토큰
secret.dashboard.username<YOUR_SUPABASE_DASHBOARD_USERNAME>Studio UI 로그인
secret.dashboard.password<YOUR_SUPABASE_DASHBOARD_PASSWORD>Studio UI 로그인
secret.realtime.secretKeyBase<YOUR_SUPABASE_REALTIME_SECRET_KEY_BASE>Phoenix 시크릿 키
secret.meta.cryptoKey<YOUR_SUPABASE_META_CRYPTO_KEY>openssl rand -hex 32
secret.s3.keyId<YOUR_MINIO_KEY_ID>secret.minio.user와 일치해야 합니다 (openssl rand -hex 16)
secret.s3.accessKey<YOUR_MINIO_ACCESS_KEY>secret.minio.password와 일치해야 합니다 (openssl rand -hex 32)
secret.minio.user<YOUR_MINIO_KEY_ID>secret.s3.keyId와 동일한 값
secret.minio.password<YOUR_MINIO_ACCESS_KEY>secret.s3.accessKey와 동일한 값

5.10 values/ha-supabase-db.yaml — Supabase HA Database (ENABLE_HA_SUPABASE_DB=true일 때만)​

경고

여기의 시크릿은 supabase.yaml과 일치해야 합니다. postgresPassword, jwtSecret, anonKey 및 serviceRoleKey에 동일한 생성된 값을 사용하세요.

nano values/ha-supabase-db.yaml
필드플레이스홀더비고
secrets.inline.postgresPassword<YOUR_SUPABASE_DB_PASSWORD>supabase.yaml secret.db.password와 일치해야 합니다
secrets.inline.authenticatorPassword<YOUR_SUPABASE_DB_PASSWORD>postgresPassword와 동일해야 합니다
secrets.inline.pgbouncerPassword<YOUR_SUPABASE_DB_PASSWORD>postgresPassword와 동일해야 합니다
secrets.inline.jwtSecret<YOUR_SUPABASE_JWT_SECRET>supabase.yaml secret.jwt.secret과 일치해야 합니다
secrets.inline.anonKey<YOUR_SUPABASE_ANON_KEY>supabase.yaml secret.jwt.anonKey와 일치해야 합니다
secrets.inline.serviceRoleKey<YOUR_SUPABASE_SERVICE_ROLE_KEY>supabase.yaml secret.jwt.serviceKey와 일치해야 합니다

스토리지 클래스 (ebs-csi-gp2), 인스턴스 수 및 리소스 제한은 사전 구성되어 있습니다. 예상 데이터 볼륨에 따라 postgres.storage.size 및 postgres.walStorage.size를 조정하세요.

5.11 values/cloudnative-pg.yaml — CloudNativePG 오퍼레이터 (ENABLE_CNPG=true일 때만)​

환경별 플레이스홀더가 필요하지 않습니다. CNPG 오퍼레이터 컨트롤러만 배포합니다. 기본 설정 (3개 복제본, 리소스 제한)은 대부분의 환경에 적합합니다.

5.12 values/odin-services.yaml — EKB 애플리케이션 서비스​

경고

Redis 및 RabbitMQ 엔드포인트는 Terraform이 해당 AWS 리소스를 생성한 후에만 사용할 수 있습니다. 인증서 ARN은 배포 전에 ACM에서 프로비저닝되어야 합니다.

nano values/odin-services.yaml

일반 설정:

필드플레이스홀더비고
server<YOUR_WEB_DOMAIN>메인 웹 도메인
toolkitEncryptionKey<YOUR_TOOLKIT_ENCRYPTION_KEY>생성: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

Supabase (dataServiceConfig) — 셀프호스팅 (ENABLE_SUPABASE=true):

필드플레이스홀더출처
supabase.projectUrlhttp://supabase-kong:8000고정 — 내부 Supabase Kong
supabase.key<YOUR_SUPABASE_SERVICE_ROLE_KEY>supabase.yaml의 secret.jwt.serviceKey와 동일
supabase.postgres.userpostgres셀프호스팅 시 고정
supabase.postgres.hostha-supabase-db-postgres-pooler-rw.ha-supabase-db.svc.cluster.local고정 — 클러스터 내 DB Pool 서비스
supabase.postgres.password<YOUR_SUPABASE_DB_PASSWORD>supabase.yaml의 secret.db.password와 동일
supabase.projectId(비워 두기)셀프호스팅 모드에서는 사용되지 않음

Supabase (dataServiceConfig) — Supabase Cloud (ENABLE_SUPABASE=false):

필드플레이스홀더출처
supabase.projectUrl<YOUR_SUPABASE_PROJECT_URL>Supabase 대시보드 → Project Settings → API
supabase.key<YOUR_SUPABASE_SERVICE_ROLE_KEY>Supabase 대시보드 → API → service_role 키
supabase.postgres.user<YOUR_SUPABASE_DB_USER>Supabase 대시보드 → Project Settings → Database
supabase.postgres.host<YOUR_SUPABASE_DB_HOST>Supabase 대시보드 → Database (예: aws-0-eu-west-2.pooler.supabase.com)
supabase.postgres.password<YOUR_SUPABASE_DB_PASSWORD>Supabase 대시보드 → Project Settings → Database
supabase.projectId<YOUR_SUPABASE_PROJECT_ID>Supabase 프로젝트 URL에서 확인

Redis:

필드플레이스홀더비고
redis.urlrediss://<YOUR_REDIS_HOST>:6379?ssl_cert_reqs=noneTerraform이 ElastiCache를 생성한 후
redis.host<YOUR_REDIS_HOST>ElastiCache 프라이머리 엔드포인트
# Get Redis endpoint after Terraform apply
aws elasticache describe-cache-clusters \
--show-cache-node-info \
--query "CacheClusters[?starts_with(CacheClusterId,'<YOUR_ENV_NAME>')].CacheNodes[0].Endpoint.Address" \
--output text

RabbitMQ:

필드플레이스홀더비고
rabbitmq.urlamqps://<YOUR_RABBITMQ_USERNAME>:<YOUR_RABBITMQ_PASSWORD>@<YOUR_RABBITMQ_HOST>:5671Terraform이 AmazonMQ를 생성한 후
rabbitmq.host<YOUR_RABBITMQ_HOST>AmazonMQ 브로커 엔드포인트
rabbitmq.username<YOUR_RABBITMQ_USERNAME>terragrunt.hcl에서 설정
rabbitmq.password<YOUR_RABBITMQ_PASSWORD>terragrunt.hcl에서 설정
# Get RabbitMQ endpoint after Terraform apply
aws mq list-brokers \
--query "BrokerSummaries[?BrokerName=='odin-rabbitmq'].BrokerId" --output text | \
xargs -I{} aws mq describe-broker --broker-id {} \
--query "BrokerInstances[0].Endpoints[0]" --output text

SSL / 인증서 ARN:

필드플레이스홀더비고
ssl.services.web.domain<YOUR_WEB_DOMAIN>예: app.example.com
ssl.services.web.certificateArn<YOUR_WEB_CERTIFICATE_ARN>ACM 인증서 ARN
ssl.services.fastapiBackend.domain<YOUR_API_DOMAIN>예: api-app.example.com
ssl.services.fastapiBackend.certificateArn<YOUR_API_CERTIFICATE_ARN>ACM 인증서 ARN
ssl.services.automator.domain<YOUR_AUTOMATOR_DOMAIN>예: automations-app.example.com
ssl.services.automator.certificateArn<YOUR_AUTOMATOR_CERTIFICATE_ARN>ACM 인증서 ARN
ssl.services.supabase.domain<YOUR_SUPABASE_DOMAIN>예: supabase-app.example.com
ssl.services.supabase.certificateArn<YOUR_SUPABASE_CERTIFICATE_ARN>ACM 인증서 ARN
# List ACM certificates in your region
aws acm list-certificates --region <YOUR_AWS_REGION> \
--query "CertificateSummaryList[*].[DomainName,CertificateArn]" --output table

웹 프론트엔드 Supabase 키 — 셀프호스팅 (ENABLE_SUPABASE=true):

필드플레이스홀더출처
web.supabase.urlhttps://<YOUR_SUPABASE_DOMAIN>ALB 인그레스를 통해 라우팅되는 외부 URL
web.supabase.anonKey<YOUR_SUPABASE_ANON_KEY>supabase.yaml의 secret.jwt.anonKey와 동일
web.supabase.serviceRoleKey<YOUR_SUPABASE_SERVICE_ROLE_KEY>supabase.yaml의 secret.jwt.serviceKey와 동일
web.supabase.clientanonKey<YOUR_SUPABASE_SERVICE_ROLE_KEY>supabase.yaml의 secret.jwt.serviceKey와 동일

웹 프론트엔드 Supabase 키 — Supabase Cloud (ENABLE_SUPABASE=false):

필드플레이스홀더출처
web.supabase.url<YOUR_SUPABASE_PROJECT_URL>Supabase 대시보드 → Project Settings → API
web.supabase.anonKey<YOUR_SUPABASE_ANON_KEY>Supabase 대시보드 → API → anon 키
web.supabase.serviceRoleKey<YOUR_SUPABASE_SERVICE_ROLE_KEY>Supabase 대시보드 → API → service_role 키
web.supabase.clientanonKey<YOUR_SUPABASE_CLIENT_ANON_KEY>service_role 키와 동일

5.13 values/signoz.yaml — SigNoz 관측 가능성 (ENABLE_SIGNOZ=true일 때만)​

nano values/signoz.yaml
필드플레이스홀더비고
global.clusterName<YOUR_ENV_NAME>EKS 클러스터 이름
signoz.ingress.annotations.alb.ingress.kubernetes.io/certificate-arn<YOUR_AWS_REGION>, <YOUR_AWS_ACCOUNT_ID>, <YOUR_SIGNOZ_CERTIFICATE_ID>SigNoz용 ACM 인증서
signoz.ingress.hosts[0].host<YOUR_SIGNOZ_DOMAIN>예: signoz-app.example.com

5.14 values/signoz-k8s-infra.yaml — SigNoz K8s 메트릭 (ENABLE_SIGNOZ=true일 때만)​

nano values/signoz-k8s-infra.yaml
필드플레이스홀더비고
global.clusterName<YOUR_ENV_NAME>메트릭 레이블링을 위한 EKS 클러스터 이름

OTel 컬렉터 엔드포인트 (signoz-otel-collector.monitoring.svc.cluster.local:4317)는 SigNoz와 k8s-infra가 모두 monitoring 네임스페이스에 배포된 것으로 가정하고 사전 구성되어 있습니다. 커스텀 릴리스 이름을 사용하지 않는 한 변경이 필요하지 않습니다.

배포 순서 알림​

일부 값은 특정 인프라가 배포된 후에만 사용할 수 있습니다. 다음 순서를 따르세요:

  1. 모든 배포 전 — 설정: <YOUR_ENV_NAME>, <YOUR_AWS_REGION>, <YOUR_AWS_ACCOUNT_ID>, <YOUR_ENVIRONMENT>, <YOUR_PROJECT>, <YOUR_VPC_CIDR>, 모든 도메인 이름, 모든 인증서 ARN, 모든 Supabase 값, <YOUR_TOOLKIT_ENCRYPTION_KEY>, RabbitMQ 사용자 이름/비밀번호
  2. EKS 클러스터 생성 후 — 설정: <YOUR_VPC_ID> (infrastructure.yaml), <YOUR_EKS_CLUSTER_ENDPOINT> (karpenter-values.yaml)
  3. AWS 서비스에 대한 terraform apply 후 — 설정: <YOUR_REDIS_HOST>, <YOUR_RABBITMQ_HOST> (odin-services.yaml)

6단계: 남은 플레이스홀더가 없는지 확인​

grep -r "<YOUR_" . --include="*.hcl" --include="*.yaml"

예상 출력은 비어 있거나 곧 생성될 리소스 (VPC, Redis, MQ, EKS)에 대한 참조만 포함해야 합니다. 남은 플레이스홀더가 있는 경우 위의 5단계 하위 섹션을 참조하세요.

파일 체크리스트:

파일단계필요 조건
terragrunt.hcl5.1항상
state/terragrunt.hcl5.2항상
values/infrastructure.yaml5.3항상
values/karpenter-values.yaml5.4항상
values/karpenter-nodeclasses.yaml5.5항상
values/karpenter.yaml5.7항상
values/keda.yaml5.8항상
values/aws-ebs-csi-driver.yaml5.6항상
values/odin-services.yaml5.12항상
values/cloudnative-pg.yaml5.11ENABLE_CNPG=true일 때만
values/ha-supabase-db.yaml5.10ENABLE_HA_SUPABASE_DB=true일 때만
values/supabase.yaml5.9ENABLE_SUPABASE=true일 때만
values/signoz.yaml5.13ENABLE_SIGNOZ=true일 때만
values/signoz-k8s-infra.yaml5.14ENABLE_SIGNOZ=true일 때만

1단계: 상태 관리 설정​

목적: Terraform 상태를 위한 S3 버킷 생성.

각 환경의 상태 관리 모듈은 odin-terraform-state-{environment-name} 패턴의 S3 버킷을 생성하고, 암호화, 버전 관리 및 퍼블릭 접근 차단을 구성하며, 상태 모듈 자체에는 로컬 상태를 사용합니다 (부트스트랩 패턴).

cd terragrunt/environments/{your-env-name}/state
terragrunt init
terragrunt plan
terragrunt apply

2단계: EKS 인프라 배포​

목적: 핵심 네트워킹 (VPC, 서브넷, NAT Gateway), IAM 역할 및 정책, EKS 클러스터 및 매니지드 노드 그룹.

2.1 드라이 런 — EKS 인프라​

핵심 인프라

cd terragrunt/environments/your-env-name
terragrunt plan -target="aws_vpc.main" \
-target="aws_internet_gateway.main" \
-target="aws_subnet.public" \
-target="aws_subnet.private" \
-target="aws_eip.nat" \
-target="aws_nat_gateway.main" \
-target="aws_route_table.public" \
-target="aws_route_table.private" \
-target="aws_route_table_association.public" \
-target="aws_route_table_association.private"

IAM 역할 및 정책

terragrunt plan -target="aws_iam_role.cluster" \
-target="aws_iam_role_policy_attachment.cluster_AmazonEKSClusterPolicy" \
-target="aws_iam_openid_connect_provider.eks" \
-target="aws_iam_role.node" \
-target="aws_iam_role_policy_attachment.node_AmazonEKSWorkerNodePolicy" \
-target="aws_iam_role_policy_attachment.node_AmazonEKS_CNI_Policy" \
-target="aws_iam_role_policy_attachment.node_AmazonEC2ContainerRegistryReadOnly"

EKS 클러스터 및 노드 그룹

terragrunt plan -target="aws_eks_cluster.main" \
-target="aws_eks_node_group.main" \
-target="kubernetes_secret.regcred"

커스텀 / 프라이빗 Docker 레지스트리 사용​

기본적으로 EKB 이미지는 regcred라는 시크릿을 사용하여 Docker Hub에서 풀됩니다. 고객이 다른 레지스트리에 이미지를 호스팅하는 경우 odin-services를 배포하기 전에 다음 단계를 따르세요.

1단계 — 대상 네임스페이스에 imagePullSecret 생성

# Generic private registry (Docker Hub, Quay, self-hosted, etc.)
kubectl create secret docker-registry regcred \
--namespace default \
--docker-server=<YOUR_REGISTRY_HOST> \
--docker-username=<YOUR_REGISTRY_USERNAME> \
--docker-password=<YOUR_REGISTRY_PASSWORD> \
--docker-email=<YOUR_EMAIL>

# AWS ECR — token expires every 12h; refresh via a CronJob or use ECR pull-through cache
aws ecr get-login-password --region <YOUR_AWS_REGION> | \
kubectl create secret docker-registry regcred \
--namespace default \
--docker-server=<YOUR_AWS_ACCOUNT_ID>.dkr.ecr.<YOUR_AWS_REGION>.amazonaws.com \
--docker-username=AWS \
--docker-password-stdin

2단계 — values/odin-services.yaml에 시크릿 이름 설정

# values/odin-services.yaml
imagePullSecrets:
- name: regcred # must match the secret name created above
# - name: customer-registry-secret # add additional registries if needed

3단계 — 이미지 참조 업데이트

web:
image: <YOUR_REGISTRY_HOST>/<YOUR_ORG>/web:<TAG>

fastapiBackend:
image: <YOUR_REGISTRY_HOST>/<YOUR_ORG>/server:<TAG>

4단계 — 전체 배포 전에 풀 접근 확인

kubectl run registry-test \
--image=<YOUR_REGISTRY_HOST>/<YOUR_ORG>/web:<TAG> \
--overrides='{"spec":{"imagePullSecrets":[{"name":"regcred"}]}}' \
--restart=Never --rm -it -- echo "Pull successful"

2.2 EKS 인프라 배포​

1단계: 핵심 인프라

cd terragrunt/environments/your-env-name
terragrunt apply -target="aws_vpc.main" \
-target="aws_internet_gateway.main" \
-target="aws_subnet.public" \
-target="aws_subnet.private" \
-target="aws_eip.nat" \
-target="aws_nat_gateway.main" \
-target="aws_route_table.public" \
-target="aws_route_table.private" \
-target="aws_route_table_association.public" \
-target="aws_route_table_association.private"
경고

이 단계 후에 AWS Load Balancer Controller를 배포하기 전에 values/infrastructure.yaml의 vpcId를 업데이트하세요.

2단계: EKS 클러스터 및 IAM 역할 및 정책

terragrunt apply -target="aws_iam_role.cluster" \
-target="aws_iam_role_policy_attachment.cluster_AmazonEKSClusterPolicy" \
-target="aws_iam_openid_connect_provider.eks" \
-target="aws_iam_role.node" \
-target="aws_iam_role_policy_attachment.node_AmazonEKSWorkerNodePolicy" \
-target="aws_iam_role_policy_attachment.node_AmazonEKS_CNI_Policy" \
-target="aws_iam_role_policy_attachment.node_AmazonEC2ContainerRegistryReadOnly"

3단계: 노드 그룹 및 애드온

terragrunt apply -target="aws_eks_cluster.main" \
-target="aws_eks_node_group.main" \
-target="kubernetes_secret.regcred"
경고

이 단계 후에 Karpenter를 배포하기 전에 values/karpenter-values.yaml의 CLUSTER_ENDPOINT를 업데이트하세요.

EKS 클러스터 연결 확인

aws eks update-kubeconfig --region $AWS_REGION --name $CLUSTER_NAME

kubectl cluster-info
kubectl get nodes
kubectl get secret regcred -n default

3단계: 스토리지 및 로드 밸런싱​

목적: 퍼시스턴트 볼륨을 위한 EBS CSI 드라이버, 매니지드 노드 그룹에서 실행되는 AWS Load Balancer Controller.

3.1 드라이 런 — 스토리지 및 로드 밸런싱​

EBS CSI Driver

cd terragrunt/environments/your-env-name
terragrunt plan -target="aws_iam_role.ebs_csi_driver" \
-target="aws_iam_role_policy_attachment.ebs_csi_driver" \
-target="helm_release.ebs_csi_driver"

AWS Load Balancer Controller

terragrunt plan -target="aws_iam_role.aws_load_balancer_controller" \
-target="aws_iam_role_policy_attachment.aws_load_balancer_controller" \
-target="aws_iam_policy.aws_load_balancer_controller" \
-target="helm_release.infrastructure"

3.2 스토리지 및 로드 밸런싱 배포​

1단계: EBS CSI Driver

cd terragrunt/environments/your-env-name
terragrunt apply -target="aws_iam_role.ebs_csi_driver" \
-target="aws_iam_role_policy_attachment.ebs_csi_driver" \
-target="helm_release.ebs_csi_driver"

검증

helm list -n kube-system | grep ebs
kubectl get pods -n kube-system | grep ebs-csi
kubectl get storageclass
aws iam get-role --role-name $CLUSTER_NAME-ebs-csi-driver-role --region $AWS_REGION
kubectl get sa -n kube-system | grep ebs-csi
kubectl describe sa ebs-csi-controller-sa -n kube-system

2단계: AWS Load Balancer Controller

terragrunt apply -target="aws_iam_role.aws_load_balancer_controller" \
-target="aws_iam_role_policy_attachment.aws_load_balancer_controller" \
-target="aws_iam_policy.aws_load_balancer_controller" \
-target="helm_release.infrastructure"

검증

helm list -n infrastructure
kubectl get pods -n infrastructure | grep aws-load-balancer-controller
kubectl get sa -n infrastructure
kubectl describe sa aws-load-balancer-controller -n infrastructure
aws iam get-role --role-name $CLUSTER_NAME-aws-load-balancer-controller --region $AWS_REGION
kubectl logs -n infrastructure -l app.kubernetes.io/name=aws-load-balancer-controller
kubectl get ingressclass

4단계: Karpenter 자동 스케일링​

목적: Karpenter용 IAM 역할, Spot 중단 처리, Karpenter 컨트롤러 및 노드 풀.

4.1 드라이 런 — Karpenter​

Karpenter IAM 리소스

cd terragrunt/environments/your-env-name
terragrunt plan -target="aws_iam_role.karpenter_controller" \
-target="aws_iam_policy.karpenter_controller" \
-target="aws_iam_role_policy_attachment.karpenter_controller" \
-target="aws_iam_role.karpenter_node" \
-target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKSWorkerNodePolicy" \
-target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKS_CNI_Policy" \
-target="aws_iam_role_policy_attachment.karpenter_node_AmazonEC2ContainerRegistryReadOnly" \
-target="aws_iam_role_policy_attachment.karpenter_node_AmazonEBSCSIDriverPolicy" \
-target="aws_iam_instance_profile.karpenter_node"

EC2 Spot 서비스 링크 역할 (Spot 인스턴스가 활성화된 경우)

terragrunt plan -target="aws_iam_service_linked_role.ec2_spot[0]"

Karpenter Spot 중단 처리 (terragrunt.hcl에서 활성화된 경우)

terragrunt plan -target="aws_sqs_queue.karpenter_interruption_queue" \
-target="aws_sqs_queue_policy.karpenter_interruption_queue" \
-target="aws_cloudwatch_event_rule.karpenter_interruption" \
-target="aws_cloudwatch_event_target.karpenter_interruption"

Karpenter Helm 차트

terragrunt plan -target="helm_release.karpenter"

Karpenter NodePools 및 EC2NodeClasses

terragrunt plan -target="kubernetes_manifest.karpenter_nodepool" \
-target="kubernetes_manifest.karpenter_nodeclass" \
-target="kubernetes_config_map.aws_auth"
정보

plan 중에 예상되는 오류가 나타날 수 있습니다: API did not recognize GroupVersionKind from manifest (CRD may not be installed). 이는 무시해도 안전합니다 — Kubernetes는 CRD가 설치되기 전에 plan 시간에 라이브 API에 대해 리소스를 검증합니다.

4.2 Karpenter 배포​

1단계: Karpenter IAM 리소스

cd terragrunt/environments/your-env-name
terragrunt apply -target="aws_iam_role.karpenter_controller" \
-target="aws_iam_policy.karpenter_controller" \
-target="aws_iam_role_policy_attachment.karpenter_controller" \
-target="aws_iam_role.karpenter_node" \
-target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKSWorkerNodePolicy" \
-target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKS_CNI_Policy" \
-target="aws_iam_role_policy_attachment.karpenter_node_AmazonEC2ContainerRegistryReadOnly" \
-target="aws_iam_role_policy_attachment.karpenter_node_AmazonEBSCSIDriverPolicy" \
-target="aws_iam_instance_profile.karpenter_node"

검증

aws iam get-role --role-name $CLUSTER_NAME-karpenter-controller --region $AWS_REGION
aws iam get-role --role-name $CLUSTER_NAME-karpenter-node --region $AWS_REGION
aws iam get-instance-profile --instance-profile-name $CLUSTER_NAME-karpenter-node --region $AWS_REGION
aws iam list-attached-role-policies --role-name $CLUSTER_NAME-karpenter-node --region $AWS_REGION

2단계: EC2 Spot 서비스 링크 역할 (Spot 인스턴스가 활성화된 경우)

경고

EC2 Spot 서비스 링크 역할은 계정 전체 (AWS 계정당 하나만)이며 Karpenter가 Spot 인스턴스를 시작하기 전에 존재해야 합니다.

옵션 A: Terraform이 생성하도록 함 (새 배포에 권장)

terragrunt apply -target="aws_iam_service_linked_role.ec2_spot[0]"

옵션 B: 역할이 이미 존재하는 경우 가져오기

# Check if the role exists
aws iam get-role --role-name AWSServiceRoleForEC2Spot --region $AWS_REGION

# If it doesn't exist, create it manually
aws iam create-service-linked-role --aws-service-name spot.amazonaws.com --region $AWS_REGION

# Import into Terraform (replace ACCOUNT_ID with your 12-digit AWS account ID)
terragrunt import 'aws_iam_service_linked_role.ec2_spot[0]' \
arn:aws:iam::ACCOUNT_ID:role/aws-service-role/spot.amazonaws.com/AWSServiceRoleForEC2Spot
Spot 인스턴스 생성 문제 해결

Karpenter 로그에서 Spot 관련 오류 확인:

kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter -f | grep -i spot

일반적인 오류: AuthFailure.ServiceLinkedRoleCreationNotPermitted, UnfulfillableCapacity, InsufficientInstanceCapacity.

서비스 링크 역할 존재 확인:

aws iam get-role --role-name AWSServiceRoleForEC2Spot --region $AWS_REGION

IAM 정책에 Spot 권한 포함 확인:

aws iam get-policy-version \
--policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`YOUR_CLUSTER_NAME-karpenter-controller`].Arn' --output text) \
--version-id $(aws iam get-policy --policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`YOUR_CLUSTER_NAME-karpenter-controller`].Arn' --output text) --query 'Policy.DefaultVersionId' --output text) \
--region $AWS_REGION | grep -i "CreateServiceLinkedRole"

Spot 인스턴스 가용성 확인:

aws ec2 describe-spot-price-history \
--instance-types r6a.4xlarge r6a.large \
--product-descriptions "Linux/UNIX" \
--region $AWS_REGION \
--max-items 10

NodePool 용량 유형 확인:

kubectl get nodepool -o yaml | grep -A 5 "capacity-type"

Spot vs 온디맨드 노드 확인:

kubectl get nodes -o json | jq -r '.items[] | "\(.metadata.name)\t\(.metadata.labels."karpenter.sh/capacity-type")\t\(.metadata.labels."node.kubernetes.io/instance-type")"'

3단계: Karpenter Spot 중단 처리 (terragrunt.hcl에서 활성화된 경우)

terragrunt apply -target="aws_sqs_queue.karpenter_interruption_queue" \
-target="aws_sqs_queue_policy.karpenter_interruption_queue" \
-target="aws_cloudwatch_event_rule.karpenter_interruption" \
-target="aws_cloudwatch_event_target.karpenter_interruption"

검증

aws events describe-rule --name $CLUSTER_NAME-karpenter-interruption --region $AWS_REGION
aws events list-targets-by-rule --rule $CLUSTER_NAME-karpenter-interruption --region $AWS_REGION
aws sqs get-queue-url --queue-name $CLUSTER_NAME-karpenter-interruption-queue --region $AWS_REGION

4단계: Karpenter Helm 차트

terragrunt apply -target="helm_release.karpenter"

검증

helm list -n kube-system | grep karpenter
kubectl get pods -n kube-system | grep karpenter
kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter
kubectl describe sa karpenter -n kube-system

5단계: Karpenter Kubernetes 매니페스트

terragrunt apply -target="kubernetes_manifest.karpenter_nodepool" \
-target="kubernetes_manifest.karpenter_nodeclass"

# Import the existing aws-auth ConfigMap
# Note: Use quotes to prevent zsh from interpreting brackets as glob patterns
terragrunt import 'kubernetes_config_map.aws_auth[0]' kube-system/aws-auth

# Then apply
terragrunt apply -target='kubernetes_config_map.aws_auth[0]'

검증

kubectl get nodepools -o wide
kubectl describe nodepool general
kubectl describe nodepool application
kubectl describe nodepool database
kubectl get ec2nodeclasses -o wide
kubectl get configmap aws-auth -n kube-system -o jsonpath='{.data.mapRoles}' | grep karpenter-node
kubectl get nodepools -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.conditions[?(@.type=="Ready")].status}{"\n"}{end}'
kubectl get nodes -l karpenter.sh/nodepool --show-labels
kubectl get events -n kube-system --field-selector involvedObject.name=karpenter --sort-by='.lastTimestamp'

5단계: KEDA 자동 스케일링​

목적: 애플리케이션 수준 자동 스케일링을 위한 KEDA.

5.1 드라이 런 — KEDA​

cd terragrunt/environments/your-env-name
terragrunt plan -target="helm_release.keda"

5.2 KEDA 배포​

cd terragrunt/environments/your-env-name
terragrunt apply -target="helm_release.keda"

검증

helm list -n keda
kubectl get pods -n keda
kubectl get deployment -n keda
kubectl get crd | grep keda
kubectl get validatingwebhookconfigurations | grep keda
kubectl get svc -n keda

6단계: 데이터 서비스​

목적: Supabase (데이터베이스), ElastiCache (Redis), RabbitMQ (메시지 큐).

정보

CloudNativePG 오퍼레이터를 먼저 배포한 다음 HA Supabase DB 클러스터, 그 다음 Supabase 애플리케이션을 배포하세요. DB 클러스터는 Supabase가 시작되기 전에 준비되어야 합니다.

6.1 드라이 런 — 데이터 서비스​

1단계: CloudNativePG 오퍼레이터 (활성화된 경우)

cd terragrunt/environments/your-env-name
ENABLE_CNPG=true terragrunt plan \
--target='helm_release.additional_charts["cloudnative-pg"]'

2단계: HA Supabase DB (활성화된 경우)

ENABLE_HA_SUPABASE_DB=true terragrunt plan \
--target='helm_release.additional_charts["ha-supabase-db"]'

3단계: Supabase 애플리케이션 (활성화된 경우)

if [ "${ENABLE_SUPABASE:-false}" = "true" ]; then
ENABLE_SUPABASE=true terragrunt plan \
--target='helm_release.supabase[0]'
fi

4단계: AWS Services — ElastiCache 및 RabbitMQ (활성화된 경우)

if [ "${ENABLE_AWS_SERVICES:-false}" = "true" ]; then
terragrunt plan -target="aws_elasticache_subnet_group.redis" \
-target="aws_security_group.redis" \
-target="aws_elasticache_replication_group.redis" \
-target="aws_security_group.rabbitmq" \
-target="aws_mq_broker.rabbitmq"
fi

6.2 데이터 서비스 배포​

1단계: CloudNativePG 오퍼레이터 (활성화된 경우)

cd terragrunt/environments/your-env-name
ENABLE_CNPG=true terragrunt apply --auto-approve \
--target='helm_release.additional_charts["cloudnative-pg"]'

2단계: HA Supabase DB (활성화된 경우)

ENABLE_HA_SUPABASE_DB=true terragrunt apply --auto-approve \
--target='helm_release.additional_charts["ha-supabase-db"]'

배포 후 PgBouncer 풀러 및 자격증명 확인:

kubectl get svc -n ha-supabase-db | grep pooler
kubectl get secrets -n ha-supabase-db
kubectl get secret ha-supabase-db-authenticator-credentials -n ha-supabase-db \
-o jsonpath='{.data.username}' | base64 -d && echo ""

풀러 ClusterIP (또는 LoadBalancer인 경우 EXTERNAL-IP)를 values/odin-services.yaml의 SUPABASE_POSTGRES_HOST 값과 values/supabase.yaml의 secret.db.postgresHost로 사용하세요.

3단계: Supabase 애플리케이션 (활성화된 경우)

모든 Supabase 서비스 Pod는 Spot 중단을 방지하기 위해 Karpenter application NodePool (온디맨드만)에서 전용으로 실행됩니다.

if [ "${ENABLE_SUPABASE:-false}" = "true" ]; then
ENABLE_SUPABASE=true terragrunt apply --auto-approve \
--target='helm_release.supabase[0]'
fi

4단계: AWS Services — ElastiCache 및 RabbitMQ (활성화된 경우)

if [ "${ENABLE_AWS_SERVICES:-false}" = "true" ]; then
terragrunt apply \
-target="aws_elasticache_subnet_group.redis" \
-target="aws_security_group.redis" \
-target="aws_elasticache_replication_group.redis" \
-target="aws_security_group.rabbitmq" \
-target="aws_mq_broker.rabbitmq"
fi

검증

# Get connection details from Terraform outputs
terragrunt output elasticache_endpoint
terragrunt output elasticache_port
terragrunt output rabbitmq_endpoint
terragrunt output rabbitmq_port

# Test Redis connectivity from EKS cluster
kubectl run redis-test --image=redis:7-alpine --restart=Never -- \
sh -c "redis-cli -h <redis-endpoint> -p 6379 --tls --insecure ping && echo 'Redis connection successful'"
kubectl logs redis-test
kubectl delete pod redis-test

# Check Redis encryption status
aws elasticache describe-replication-groups \
--replication-group-id $CLUSTER_NAME-redis \
--region $AWS_REGION \
--query 'ReplicationGroups[0].{AtRestEncryption:AtRestEncryptionEnabled,TransitEncryption:TransitEncryptionEnabled}'
경고

EKB Services를 배포하기 전에 이 단계에서 확보한 Redis 엔드포인트, RabbitMQ 엔드포인트 및 모든 인증서 ARN으로 values/odin-services.yaml을 업데이트하세요.

7단계: EKB Services​

목적: Helm을 통한 애플리케이션 배포.

정보

배포 전에 초기 데이터베이스 마이그레이션 실행을 위해 fastapiBackend를 일시적으로 1개 복제본으로 축소하세요 — replicaCount: 1, workers: 1 및 keda.minReplicas: 1을 설정하세요. 마이그레이션이 성공적으로 완료되면 다시 배포하기 전에 이 값들을 프로덕션 기본값으로 되돌리세요.

7.1 드라이 런 — EKB Services​

cd terragrunt/environments/your-env-name
terragrunt plan -target="helm_release.odin_services"

7.2 EKB Services 배포​

cd terragrunt/environments/your-env-name
terragrunt apply -target="helm_release.odin_services"

검증

kubectl get pods
kubectl get ingress # Add the ALB endpoints to your DNS provider

8단계: SigNoz 관측 가능성​

목적: 로그 및 메트릭 모니터링.

8.1 드라이 런 — SigNoz 차트​

cd terragrunt/environments/your-env-name
terragrunt plan -target='helm_release.additional_charts["signoz"]'
terragrunt plan -target='helm_release.additional_charts["k8s-infra"]'

8.2 SigNoz 차트 배포​

cd terragrunt/environments/your-env-name
terragrunt apply -target='helm_release.additional_charts["signoz"]'
terragrunt apply -target='helm_release.additional_charts["k8s-infra"]'

검증

kubectl get pods -n monitoring
kubectl get ingress -n monitoring # Add the ALB endpoints to your DNS provider

9단계: 최종 배포​

9.1 전체 배포​

cd terragrunt/environments/your-env-name
terragrunt apply

이 최종 apply는 이전 단계에서 명시적으로 지정되지 않은 남은 리소스를 처리합니다.

9.2 배포 검증​

# Update kubeconfig
aws eks update-kubeconfig --region us-east-2 --name your-env-name

# Check cluster status
kubectl get nodes
kubectl get pods --all-namespaces

# Check Karpenter
kubectl get pods -n kube-system -l app.kubernetes.io/name=karpenter
kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter

# Check AWS Load Balancer Controller
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller

# Check KEDA
kubectl get pods -n keda

# Check EKB Services
kubectl get pods -n default
kubectl get services -n default
kubectl get ingress -n default

# Check all Helm releases
helm list --all-namespaces

문제 해결​

상태 잠금 문제

terragrunt force-unlock <lock-id>

Karpenter 작동 불가

kubectl describe nodes
kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter

로드 밸런서 문제

kubectl describe ingress -n default
kubectl logs -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller

Helm 차트 문제

helm status <release-name> -n <namespace>
helm rollback <release-name> <revision> -n <namespace>

정리​

# Destroy infrastructure
cd terragrunt/environments/your-env-name
terragrunt destroy -auto-approve

# Destroy state bucket (use with caution)
cd terragrunt/environments/your-env-name/state
terragrunt destroy -auto-approve

모니터링 및 로깅​

# AWS Resources
aws eks describe-cluster --name your-env-name --region us-east-2
aws ec2 describe-instances --filters "Name=tag:kubernetes.io/cluster/your-env-name,Values=owned"

# Kubernetes Resources
kubectl top nodes
kubectl top pods --all-namespaces
kubectl get events --sort-by=.metadata.creationTimestamp

빠른 참조 — 모든 배포 명령어​

# Phase 1: State Management
cd terragrunt/environments/your-env-name/state
terragrunt apply

# Phase 2: EKS Infrastructure
cd terragrunt/environments/your-env-name

terragrunt apply -target="aws_vpc.main" -target="aws_internet_gateway.main" \
-target="aws_subnet.public" -target="aws_subnet.private" -target="aws_eip.nat" \
-target="aws_nat_gateway.main" -target="aws_route_table.public" \
-target="aws_route_table.private" -target="aws_route_table_association.public" \
-target="aws_route_table_association.private" -auto-approve

terragrunt apply -target="aws_iam_role.cluster" \
-target="aws_iam_role_policy_attachment.cluster_AmazonEKSClusterPolicy" \
-target="aws_iam_openid_connect_provider.eks" -target="aws_iam_role.node" \
-target="aws_iam_role_policy_attachment.node_AmazonEKSWorkerNodePolicy" \
-target="aws_iam_role_policy_attachment.node_AmazonEKS_CNI_Policy" \
-target="aws_iam_role_policy_attachment.node_AmazonEC2ContainerRegistryReadOnly" \
-auto-approve

terragrunt apply -target="aws_eks_cluster.main" \
-target="aws_eks_node_group.main" -target="kubernetes_secret.regcred" -auto-approve

# Phase 3: Storage and Load Balancing
terragrunt apply -target="aws_iam_role.ebs_csi_driver" \
-target="aws_iam_role_policy_attachment.ebs_csi_driver" \
-target="helm_release.ebs_csi_driver" -auto-approve

terragrunt apply -target="aws_iam_role.aws_load_balancer_controller" \
-target="aws_iam_role_policy_attachment.aws_load_balancer_controller" \
-target="aws_iam_policy.aws_load_balancer_controller" \
-target="helm_release.infrastructure" -auto-approve

# Phase 4: Karpenter Autoscaling
terragrunt apply -target="aws_iam_role.karpenter_controller" \
-target="aws_iam_policy.karpenter_controller" \
-target="aws_iam_role_policy_attachment.karpenter_controller" \
-target="aws_iam_role.karpenter_node" \
-target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKSWorkerNodePolicy" \
-target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKS_CNI_Policy" \
-target="aws_iam_role_policy_attachment.karpenter_node_AmazonEC2ContainerRegistryReadOnly" \
-target="aws_iam_role_policy_attachment.karpenter_node_AmazonEBSCSIDriverPolicy" \
-target="aws_iam_instance_profile.karpenter_node" -auto-approve

# Spot interruption handling (if spot_interruption_handling = true)
terragrunt apply -target="aws_sqs_queue.karpenter_interruption_queue" \
-target="aws_sqs_queue_policy.karpenter_interruption_queue" \
-target="aws_cloudwatch_event_rule.karpenter_interruption" \
-target="aws_cloudwatch_event_target.karpenter_interruption" -auto-approve

terragrunt apply -target="helm_release.karpenter" -auto-approve

terragrunt apply -target="kubernetes_manifest.karpenter_nodepool" \
-target="kubernetes_manifest.karpenter_nodeclass" \
-target='kubernetes_config_map.aws_auth[0]' -auto-approve

# Phase 5: KEDA Autoscaling
terragrunt apply -target="helm_release.keda" -auto-approve

# Phase 6: Data Services
ENABLE_CNPG=true terragrunt apply --target='helm_release.additional_charts["cloudnative-pg"]' -auto-approve
ENABLE_HA_SUPABASE_DB=true terragrunt apply --target='helm_release.additional_charts["ha-supabase-db"]' -auto-approve

if [ "${ENABLE_SUPABASE:-false}" = "true" ]; then
ENABLE_SUPABASE=true terragrunt apply --target='helm_release.supabase[0]' -auto-approve
fi

if [ "${ENABLE_AWS_SERVICES:-false}" = "true" ]; then
terragrunt apply -target="aws_elasticache_subnet_group.redis" \
-target="aws_security_group.redis" \
-target="aws_elasticache_replication_group.redis" \
-target="aws_security_group.rabbitmq" \
-target="aws_mq_broker.rabbitmq" -auto-approve
fi

# Phase 7: EKB Services
terragrunt apply -target="helm_release.odin_services" -auto-approve

# Phase 8: SigNoz (if enabled)
terragrunt apply -target='helm_release.additional_charts["signoz"]' -auto-approve
terragrunt apply -target='helm_release.additional_charts["k8s-infra"]' -auto-approve

# Phase 9: Final Deployment
terragrunt apply -auto-approve
정보

전체 과정에서 your-env-name을 실제 환경 이름으로 대체하세요. 변경 사항을 적용하기 전에 항상 드라이 런 (terragrunt plan)을 실행하여 구성을 검증하세요.