DB 연동 — RDS · External Secrets
22장에서 외부 노출까지 만든 myshop-api는 데이터 저장소가 없는 빈 껍데기입니다. 이번 챕터는 그 빈 곳을 채웁니다. RDS PostgreSQL을 Terraform으로 띄우고, 마스터 비밀번호를 AWS Secrets Manager에 두고, External Secrets Operator로 그 비밀을 K8s Secret으로 자동 동기화하고, IRSA로 정적 자격 증명 없이 권한을 부여하고, PgBouncer로 커넥션 풀을 얹고, 스키마 마이그레이션을 Helm hook 기반 Job 패턴으로 자동화하는 흐름까지 한 사이클로 정리합니다.
22장 앱 배포 골격에서 myshop-api가 외부 HTTPS 진입점까지 잡혔지만, 그 컨테이너 안에는 아직 데이터가 들어갈 곳이 없습니다. /health/ready만 200을 돌려주는 빈 껍데기 상태입니다. 이번 챕터는 그 빈 곳을 채우는 단계입니다. RDS PostgreSQL을 Terraform으로 띄우고, 마스터 비밀번호를 AWS Secrets Manager에 두고, External Secrets Operator로 그 비밀을 K8s Secret으로 자동 동기화하고, IRSA로 정적 자격 증명 없이 권한을 부여하고, PgBouncer로 커넥션 풀을 얹고, 스키마 마이그레이션을 Job으로 자동화하는 흐름까지를 한 묶음으로 다룹니다.
이번 챕터의 목표는 myshop-api가 RDS PostgreSQL과 정상으로 통신하고, 비밀번호 회전이 자동화되고, 커넥션 풀의 한도 위협이 통제된 상태입니다. 다음 24장 CI / CD에서는 새 버전이 들어오는 길을 자동화합니다.
왜 매니지드 RDS 인가 #
K8s 안에 PostgreSQL StatefulSet을 띄우는 길도 있습니다. 8장 StatefulSet · DaemonSet · Job의 StatefulSet 모델이 자체 호스팅의 출발점입니다. 그러나 운영 환경의 표준은 매니지드 RDS입니다. 백업, Multi-AZ failover, 패치, 모니터링, 메이저 버전 업그레이드의 부담이 모두 AWS의 책임으로 빠져나가고, 우리는 클러스터의 stateless 워크로드 운영에만 집중할 수 있습니다.
| 항목 | K8s 자체 호스팅 (StatefulSet) | 매니지드 RDS |
|---|---|---|
| 데이터 신뢰성 | PV의 백업 · 복구를 직접 운영 | 자동 백업 · PITR · Multi-AZ |
| 메이저 버전 업그레이드 | 다운타임 · 마이그레이션 직접 | RDS 콘솔 또는 Terraform으로 자동 |
| HA | StatefulSet + 외부 운영 도구 | Multi-AZ 옵션 한 줄 |
| 비용 | 노드 자원 + 운영 인력 시간 | RDS 인스턴스 + Storage |
| 운영 부담 | 큼 | 작음 |
본 책의 결은 stateless 워크로드만 K8s에, stateful 시스템은 매니지드 서비스에 두는 분리입니다. EKS의 모범 사례이고, 거의 모든 운영 클러스터의 출발점입니다.
RDS — Terraform으로 PostgreSQL 띄우기 #
Terraform 모듈 #
module "rds" {
source = "terraform-aws-modules/rds/aws"
version = "~> 6.0"
identifier = "myshop-${var.env}"
engine = "postgres"
engine_version = "16.3"
family = "postgres16"
major_engine_version = "16"
instance_class = var.env == "prod" ? "db.m6g.large" : "db.t4g.medium"
allocated_storage = 50
max_allocated_storage = 500
storage_type = "gp3"
storage_encrypted = true
db_name = "myshop"
username = "myshop_admin"
port = 5432
manage_master_user_password = true
master_user_secret_kms_key_id = aws_kms_key.rds.arn
multi_az = var.env == "prod"
db_subnet_group_name = var.db_subnet_group_name
vpc_security_group_ids = [aws_security_group.rds.id]
backup_retention_period = var.env == "prod" ? 30 : 7
backup_window = "03:00-04:00"
maintenance_window = "Mon:04:00-Mon:05:00"
performance_insights_enabled = true
monitoring_interval = 60
monitoring_role_arn = aws_iam_role.rds_monitoring.arn
enabled_cloudwatch_logs_exports = ["postgresql"]
deletion_protection = var.env == "prod"
skip_final_snapshot = var.env != "prod"
}운영 표준이 한 매니페스트 안에 모여 있습니다. 핵심 옵션을 짚습니다.
manage_master_user_password = true— RDS가 마스터 비밀번호를 직접 만들고 Secrets Manager에 저장합니다. 사람이 비밀번호를 본 적이 없는 상태가 되는 패턴입니다.multi_az— prod는 Multi-AZ로 failover 가능, dev는 단일 AZ로 비용을 절약합니다. 21장의 VPC 멀티 AZ 결정과 묶입니다.storage_encrypted— KMS 암호화. 운영 표준입니다.performance_insights_enabled— PostgreSQL 쿼리 성능 분석. RDS 자체 비용에 거의 영향이 없습니다.enabled_cloudwatch_logs_exports— PostgreSQL의 slow query / error 로그를 CloudWatch로 내보냅니다. 25장 모니터링 · 알람의 알람 소스가 됩니다.deletion_protection— prod에는 반드시 켭니다.terraform destroy사고를 막는 마지막 보호선입니다.
보안 그룹 — EKS 노드만 접근 #
resource "aws_security_group" "rds" {
name_prefix = "myshop-${var.env}-rds-"
vpc_id = var.vpc_id
}
resource "aws_security_group_rule" "rds_from_eks" {
type = "ingress"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_group_id = aws_security_group.rds.id
source_security_group_id = var.eks_node_security_group_id
description = "Allow from EKS worker nodes"
}5432 포트를 EKS 노드의 보안 그룹에서만 받습니다. 다른 곳에서는 RDS에 직접 접근할 수 없습니다 — 운영 표준입니다. 사람이 임시로 RDS에 직접 들어가 SQL을 봐야 할 때는 26장 운영 체크리스트에서 다룰 bastion 또는 SSM Session Manager를 통합니다.
마스터 비밀번호 — Secrets Manager에 둔다 #
manage_master_user_password = true를 켜 두면 RDS가 비밀번호를 자동으로 만들고 Secrets Manager에 다음 형식으로 저장합니다.
{
"username": "myshop_admin",
"password": "<RDS-generated random>",
"engine": "postgres",
"host": "myshop-prod.abcdef.ap-northeast-2.rds.amazonaws.com",
"port": 5432,
"dbname": "myshop"
}이 비밀을 K8s 안의 Pod가 어떻게 읽을지가 다음 단계입니다. 정적 비밀번호를 K8s Secret에 직접 적어 두는 길은 6장 ConfigMap · Secret §“비밀 관리의 한계"에서 짚은 그 함정과 정확히 겹칩니다 — 매니페스트에 평문이 들어가거나, git에 base64로만 인코딩된 비밀이 들어가거나, 비밀번호 회전 시 매니페스트를 직접 갱신해야 합니다. 이 세 함정을 한 번에 푸는 도구가 External Secrets Operator입니다.
External Secrets Operator — K8s Secret과 클라우드 비밀의 동기화 #
18장 CRD와 Operator의 Operator 패턴이 본격적인 운영 도구로 이어지는 단계입니다. AWS Secrets Manager (또는 Parameter Store, Vault, GCP Secret Manager 등)의 비밀을 K8s Secret으로 자동 동기화 해 주는 컨트롤러입니다.
설치 #
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
-n external-secrets --create-namespace \
--set installCRDs=true설치 후 두 가지 새 CRD가 클러스터에 등록됩니다 — ClusterSecretStore와 ExternalSecret. 18장에서 다룬 그 CRD 패턴입니다. 매니페스트로 비밀의 출처와 매핑을 선언하면, Operator가 그 선언을 보고 실제 K8s Secret 객체를 만들고 갱신합니다.
IRSA로 Secrets Manager 접근 권한 #
External Secrets Operator의 ServiceAccount에 Secrets Manager의 read 권한을 주는 IAM Role을 IRSA로 부착합니다. 16장 RBAC / ServiceAccount 깊이의 그 모델이 그대로 적용됩니다.
data "aws_iam_policy_document" "secrets_read" {
statement {
actions = [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
]
resources = [
"arn:aws:secretsmanager:${var.region}:${var.account_id}:secret:rds!cluster-myshop-${var.env}/*",
"arn:aws:secretsmanager:${var.region}:${var.account_id}:secret:myshop/${var.env}/*",
]
}
}
resource "aws_iam_policy" "secrets_read" {
name = "myshop-${var.env}-external-secrets-read"
policy = data.aws_iam_policy_document.secrets_read.json
}
module "external_secrets_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "~> 5.0"
role_name = "myshop-${var.env}-external-secrets"
oidc_providers = {
main = {
provider_arn = var.oidc_provider_arn
namespace_service_accounts = ["external-secrets:external-secrets"]
}
}
role_policy_arns = {
main = aws_iam_policy.secrets_read.arn
}
}resources의 ARN 패턴이 핵심입니다 — myshop의 비밀에만 접근 권한을 주고, 다른 팀의 비밀은 읽지 못하게 막습니다. 최소 권한 원칙이고, 16장에서 trust policy와 함께 다룬 보안 결의 두 번째 축입니다.
ClusterSecretStore — 비밀 소스의 정의 #
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-2
auth:
jwt:
serviceAccountRef:
name: external-secrets
namespace: external-secrets이 객체가 “AWS Secrets Manager에서 비밀을 가져온다” 고 클러스터 차원에서 선언합니다. auth.jwt.serviceAccountRef가 IRSA가 부착된 그 ServiceAccount이고, External Secrets Operator는 그 ServiceAccount의 projected token으로 STS의 AssumeRoleWithWebIdentity를 호출해 Secrets Manager 권한을 받습니다. 16장 §“IRSA의 동작 원리"의 흐름이 본격적인 비밀 동기화로 이어지는 시점입니다.
ExternalSecret — 매니페스트로 비밀을 가져오기 #
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: myshop-api-db
namespace: myshop
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: myshop-api-db
creationPolicy: Owner
template:
data:
DATABASE_URL: "postgresql://{{ .username }}:{{ .password }}@{{ .host }}:{{ .port }}/{{ .dbname }}?sslmode=require"
data:
- secretKey: username
remoteRef:
key: rds!cluster-myshop-prod
property: username
- secretKey: password
remoteRef:
key: rds!cluster-myshop-prod
property: password
- secretKey: host
remoteRef:
key: rds!cluster-myshop-prod
property: host
- secretKey: port
remoteRef:
key: rds!cluster-myshop-prod
property: port
- secretKey: dbname
remoteRef:
key: rds!cluster-myshop-prod
property: dbname이 한 매니페스트로 일어나는 일을 정리하면 다음과 같습니다.
- External Secrets Operator가 1시간마다 Secrets Manager의
rds!cluster-myshop-prod비밀을 fetch 합니다. - 그 비밀의 5개 필드 (username, password, host, port, dbname)를 가져옵니다.
template.data.DATABASE_URL로 그 값들을 Connection String 형식으로 조립합니다.- 이름이
myshop-api-db인 K8s Secret을 만들어 그 안에DATABASE_URL키 한 개로 저장합니다.
myshop-api Pod는 envFrom으로 이 Secret을 환경변수로 주입받으면 끝입니다.
envFrom:
- configMapRef:
name: myshop-api
- secretRef:
name: myshop-api-db # External Secrets 가 자동 동기화22장에서 placeholder로 두었던 DATABASE_URL이 이 시점에 진짜 값으로 채워집니다. RDS의 비밀번호가 회전되면 1시간 안에 K8s Secret도 새 값으로 자동 갱신됩니다.
Pod 재시작의 필요성 #
K8s Secret이 갱신되어도 Pod 안의 환경변수는 자동으로 갱신되지 않습니다. envFrom으로 주입된 값은 Pod 시작 시점에만 고정됩니다. 비밀번호 회전 후에 Pod를 재시작해야 새 비밀번호가 적용됩니다.
kubectl rollout restart deployment/myshop-api -n myshopExternal Secrets에는 Reloader라는 별도 컴포넌트와의 통합이 있어서, Secret이 바뀌면 자동으로 rollout restart를 트리거할 수 있습니다. 운영 클러스터의 표준 셋업의 일부로 자주 같이 들어옵니다. 4장 Deployment / ReplicaSet의 롤링 업데이트가 비밀 갱신에도 그대로 활용되는 모양입니다.
커넥션 풀 — 왜, 그리고 PgBouncer #
myshop-api Pod가 5개로 떠 있고, 각 Pod 안의 애플리케이션이 자기 PostgreSQL 커넥션 풀을 50 개씩 들고 있다면, 클러스터 전체로 250개의 RDS 커넥션을 점유합니다. RDS 인스턴스 클래스마다 max_connections 한도가 있는데, db.t4g.medium은 기본 약 100, db.m6g.large도 기본 약 800입니다. Pod가 13장 오토스케일링의 HPA로 늘어나는 환경에서는 이 한도가 빠르게 위협받습니다.
이 빈 곳에 커넥션 풀러가 들어옵니다. 가장 표준적인 것이 PgBouncer입니다.
PgBouncer의 역할 #
[myshop-api Pod 5개] --> [PgBouncer 2개] --> [RDS PostgreSQL]
각 50 conn 20 backend connmyshop-api는 PgBouncer에 연결하고, PgBouncer가 그 연결을 적은 수의 backend 연결로 다중화합니다. transaction pooling 모드에서는 한 PostgreSQL 연결이 여러 클라이언트의 짧은 트랜잭션을 차례로 처리하므로 사용 효율이 매우 높습니다.
매니페스트 #
apiVersion: apps/v1
kind: Deployment
metadata:
name: pgbouncer
namespace: myshop
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: pgbouncer
template:
metadata:
labels:
app.kubernetes.io/name: pgbouncer
spec:
containers:
- name: pgbouncer
image: edoburu/pgbouncer:1.22.1
ports:
- containerPort: 6432
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: myshop-api-db
key: DATABASE_URL
- name: POOL_MODE
value: transaction
- name: MAX_CLIENT_CONN
value: "1000"
- name: DEFAULT_POOL_SIZE
value: "20"
- name: SERVER_RESET_QUERY
value: "DISCARD ALL"
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: pgbouncer
namespace: myshop
spec:
selector:
app.kubernetes.io/name: pgbouncer
ports:
- port: 5432
targetPort: 6432myshop-api의 DATABASE_URL은 이제 RDS를 직접 가리키는 게 아니라 pgbouncer.myshop.svc.cluster.local:5432를 가리키도록 환경별로 override 합니다. 5장 Service의 ClusterIP + DNS 모델 덕분에 RDS의 실제 호스트가 바뀌어도 myshop-api의 환경변수는 그대로 유지됩니다.
config:
DATABASE_URL: "postgresql://myshop_admin:$(DB_PASSWORD)@pgbouncer.myshop.svc.cluster.local:5432/myshop?sslmode=disable"transaction pooling의 함정 #
PgBouncer의 transaction pooling 모드에서는 PostgreSQL의 prepared statement, advisory lock, session 변수를 안전하게 쓸 수 없습니다. 한 트랜잭션이 끝나면 backend 커넥션이 다른 클라이언트로 넘어가므로 세션 단위 상태가 유지되지 않습니다. ORM이 prepared statement를 자동으로 쓰는 경우 (SQLAlchemy의 일부 설정 등) 옵션을 끄거나 session pooling 모드로 바꿔야 합니다.
대안으로 RDS Proxy가 같은 역할을 하는 매니지드 옵션입니다. AWS가 운영해 주고 IAM 인증과의 통합이 깊지만, 비용이 추가되고 transaction pooling의 함정은 동일합니다. 28장 비용 최적화에서 PgBouncer vs RDS Proxy의 비용 결을 다시 짚습니다.
스키마 마이그레이션 — Job 패턴 #
데이터베이스 스키마를 새 버전으로 옮기는 일은 K8s에서는 Job으로 다룹니다.
Job 매니페스트 #
apiVersion: batch/v1
kind: Job
metadata:
name: myshop-api-migrate-1.4.2
namespace: myshop
spec:
backoffLimit: 3
ttlSecondsAfterFinished: 86400
template:
spec:
serviceAccountName: myshop-api
restartPolicy: OnFailure
containers:
- name: migrate
image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myshop-api:1.4.2
command: ["alembic", "upgrade", "head"]
envFrom:
- secretRef:
name: myshop-api-db8장 StatefulSet · DaemonSet · Job의 Job 패턴이 운영 클러스터의 마이그레이션으로 이어집니다. 이미지는 myshop-api와 같은 것을 쓰고, 명령만 마이그레이션 도구 (alembic, flyway, golang-migrate 등)로 바꿉니다. ttlSecondsAfterFinished: 86400이 24시간 후 Job 객체를 자동 삭제합니다 — 끝난 Job이 etcd에 쌓이지 않게 만드는 표준 옵션입니다.
배포와의 결합 — Helm hook #
마이그레이션이 끝난 뒤에야 새 버전 Pod가 떠야 합니다. 순서를 강제하는 패턴이 Helm hook입니다.
metadata:
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded이 annotation이 붙은 Job은 helm upgrade 시 새 Deployment보다 먼저 실행되고, 성공해야 그다음 단계로 넘어갑니다. 마이그레이션 실패가 Pod 배포 실패로 자연스럽게 이어져, 잘못된 스키마 위에 새 코드가 떠오르는 사고를 막습니다. 24장 CI / CD의 ArgoCD Sync 흐름에서 이 Helm hook이 어떻게 PreSync hook으로 대응되는지가 자연스럽게 이어집니다.
initContainer와의 비교 #
마이그레이션을 Pod의 initContainer로 넣는 패턴도 있습니다. 그러나 myshop-api Pod가 5 개라면 마이그레이션이 5번 시도됩니다. 일부 마이그레이션 도구 (alembic, flyway)는 advisory lock으로 중복을 막지만, K8s의 관점으로는 마이그레이션은 한 번, Job 으로가 더 깔끔합니다. 책임의 결을 한 곳에 모으는 패턴이고, 27장 kubectl 디버깅 패턴에서도 마이그레이션 사고의 진단 경로가 단일 Job의 로그로 좁아진다는 운영 이점이 같이 풀립니다.
IAM 인증 — 비밀번호 자체를 없애는 길 #
가장 진보된 패턴은 RDS의 IAM 인증입니다. 비밀번호를 완전히 없애고, IRSA 토큰으로 RDS에 직접 접근합니다.
import boto3
import psycopg2
rds_client = boto3.client('rds')
token = rds_client.generate_db_auth_token(
DBHostname='myshop-prod.abcdef.ap-northeast-2.rds.amazonaws.com',
Port=5432,
DBUsername='myshop_app',
Region='ap-northeast-2',
)
conn = psycopg2.connect(
host='myshop-prod.abcdef.ap-northeast-2.rds.amazonaws.com',
port=5432,
user='myshop_app',
password=token, # 비밀번호가 아니라 IAM 토큰
dbname='myshop',
sslmode='require',
)generate_db_auth_token이 IAM 자격 증명으로 15분짜리 토큰을 만들어 줍니다. 그 토큰이 PostgreSQL의 비밀번호 위치에 들어갑니다. 비밀번호 회전을 신경 쓸 필요가 없고, 모든 접근이 CloudTrail에 기록됩니다.
다만 단점도 있습니다.
- 15분마다 새 토큰을 받아야 하므로 커넥션 풀과의 결합이 까다롭습니다.
- PostgreSQL 측에 IAM 인증을 위한 사용자 (
rds_iam그룹)와 grants 관리가 필요합니다. - PgBouncer transaction pooling과 함께 쓰기가 매우 어렵습니다.
전통 비밀번호 + Secrets Manager + External Secrets 모델이 운영 부담과 보안의 균형이 가장 좋은 목표이고, IAM 인증은 한층 더 보안이 엄격한 환경에서 추가 도입을 검토하는 결입니다. 본 책에서는 전자를 표준 경로로 두고, IAM 인증은 옵션으로 짚는 정도로 마무리합니다.
첫 연결 후 점검 #
마이그레이션 Job과 myshop-api 배포가 모두 끝난 시점에서 점검할 명령들입니다.
kubectl get secret myshop-api-db -n myshop -o jsonpath='{.data.DATABASE_URL}' | base64 -dkubectl exec -it deployment/myshop-api -n myshop -- \
psql "$DATABASE_URL" -c "SELECT version();"kubectl exec -it deployment/pgbouncer -n myshop -- \
psql -p 6432 pgbouncer -c "SHOW POOLS;"kubectl get externalsecret myshop-api-db -n myshop
kubectl describe externalsecret myshop-api-db -n myshop이 네 단계로 비밀 동기화 · DB 연결 · 커넥션 풀이 모두 정상 동작하는지 확인됩니다. PostgreSQL의 version()이 응답하면 myshop-api가 진짜 데이터를 받을 수 있는 상태에 도착한 시점입니다. 만약 DATABASE_URL이 비어 있다면 ExternalSecret의 동기화가 실패한 것이고, describe의 Events가 원인을 보여 줍니다 — IRSA Role의 ARN 오타, Secrets Manager의 비밀 키 이름 불일치, OIDC trust policy의 SA 네임스페이스 불일치가 가장 흔한 세 가지입니다.
연습문제 #
- 본 챕터의 RDS Terraform 모듈을 적용해 dev 환경에 PostgreSQL 인스턴스 한 대를 띄웁니다.
manage_master_user_password = true가 만든 Secrets Manager 비밀의 이름과 형식을 확인하고, 그 비밀의 ARN을 External Secrets IRSA Role의 resources 패턴에 일치시킨 뒤 ExternalSecret 매니페스트를 적용합니다. K8s Secret이 자동으로 만들어지는 시점까지의 시간을 측정하고,kubectl describe externalsecret의 Events를 한 단락으로 정리합니다. - PgBouncer의
POOL_MODE를transaction과session두 가지로 바꿔 가며 myshop-api가 어떻게 동작하는지 비교합니다. 사용하는 ORM (SQLAlchemy / Prisma / GORM 등)이 prepared statement를 자동으로 쓰는지 확인하고, transaction pooling에서 깨지는 케이스가 있다면 그 함정을 본인 코드로 재현해 봅니다. 발견한 한계를 25장 모니터링 · 알람에서 어떤 메트릭으로 감지할지 메모합니다. - 마이그레이션 Job을 Helm hook으로 셋업한 뒤, 일부러 실패하는 마이그레이션을 한 번 만들어 봅니다 (예: 존재하지 않는 컬럼을 ALTER).
helm upgrade가 어떻게 멈추는지, 새 버전의 Pod가 어떤 상태로 남는지, 그리고 24장 CI / CD의 ArgoCD Sync 흐름에서 이 실패가 어떻게 시각화되는지 한 단락으로 정리합니다.
한 줄 요약: 운영 워크로드의 DB 연동 표준은 매니지드 RDS + Secrets Manager + External Secrets + IRSA + PgBouncer + Helm hook 마이그레이션 Job의 여섯 가지 도구가 한 묶음으로 동작하는 흐름이다. RDS의 마스터 비밀번호는
manage_master_user_password로 사람의 눈을 거치지 않고 Secrets Manager에 저장되고, External Secrets Operator가 그 비밀을 K8s Secret으로 자동 동기화하며, IRSA가 정적 자격 증명 없이 권한을 부여하고, PgBouncer가 커넥션 풀 한도 위협을 막는다. IAM 인증은 한층 엄격한 환경의 옵션이다.
다음 챕터 #
이 시점에서 myshop-api는 외부 진입점 · 내부 워크로드 · DB 연결까지 갖춘 완전한 서비스이지만, 새 버전이 들어오는 길이 사람의 손 (helm upgrade)에 묶여 있습니다. 다음 챕터에서는 그 빈 곳을 자동화합니다.
24장 CI / CD 파이프라인에서는 GitHub Actions에서 컨테이너를 빌드해 ECR로 푸시하고, ArgoCD가 git의 매니페스트 변경을 감지해 클러스터로 자동 동기화하는 GitOps 파이프라인의 한 사이클을 다룹니다. 20장 GitOps의 모델이 본격적인 EKS 파이프라인으로 이어지는 단계입니다.