프로젝트 차트 분석 — backend / ingress / external-secrets
이 글의 목표:
helm/폴더 안의 4개 차트가 각각 무엇을 하는지, values와 templates의 어느 부분이 어떻게 K8s 리소스로 변환되는지 이해한다.
우리 helm/ 폴더 전체 구조
helm/
├── backend/ # 우리가 만든 Spring Boot 백엔드
├── ingress/ # 외부 공개용 Nginx Ingress (NLB internet-facing)
├── ingress-internal/ # 내부망 Nginx Ingress (NLB internal)
└── external-secrets/ # AWS Secrets Manager 동기화 컨트롤러
설치 순서(권장):
1. external-secrets ← Secret을 동기화해주는 컨트롤러를 먼저
2. ingress ← 진입점(Ingress Controller)을 깔고
3. backend ← 그 위에 앱을 올림
helm/backend — 우리 서비스 본체
Chart.yaml
apiVersion: v2
name: meeny-backend
description: Hello meeny backend
type: application
version: 1.0.0
appVersion: "1.0.0"→ Subchart 의존성 없는 순수 우리 차트. 간단하다.
values.yaml의 핵심 부분
replicaCount: 2
image:
repository: 505947591451.dkr.ecr.ap-northeast-2.amazonaws.com/meeny/backend
tag: "0.0.1"
pullPolicy: IfNotPresent
springProfilesActive: "prod"
env:
DB_URL: "jdbc:postgresql://meeny-prod-postgres.xxxxx.rds.amazonaws.com:5432/meeny"
service:
type: ClusterIP
port: 80
targetPort: 8080읽는 법:
| 키 | 의미 |
|---|---|
replicaCount | Pod 2개 띄움 |
image.repository | ECR(우리 계정) 의 backend 리포지토리 |
image.tag | 컨테이너 이미지 버전 — 배포 때 가장 자주 바뀜 |
springProfilesActive | Spring prod 프로필 활성화 → RDS + Flyway 마이그레이션 활성화 |
env.DB_URL | DB 호스트:포트/이름 (시크릿 X — 호스트는 비밀이 아님) |
service.type | ClusterIP — 외부에 직접 노출 X (Ingress가 들어옴) |
service.port | Service 포트 (cluster 안에서 80) |
service.targetPort | 컨테이너 포트 (Spring 8080) |
Liveness / Readiness Probe
probes:
liveness:
path: /actuator/health/liveness
initialDelaySeconds: 60
periodSeconds: 10
readiness:
path: /actuator/health/readiness
initialDelaySeconds: 30
periodSeconds: 5| 종류 | 실패 시 동작 |
|---|---|
| Liveness | 컨테이너 재시작 (서비스 안 됨 → kill) |
| Readiness | Service에서 트래픽 제외 (Pod는 살아있음) |
→ Spring Boot가 시작되는 데 시간이 걸리므로 initialDelaySeconds를 충분히(30~60초) 준다.
externalSecret 블록 — AWS Secrets Manager → K8s Secret 자동 동기화
externalSecret:
enabled: true
refreshInterval: 1h
secretStore:
name: aws-secretsmanager
kind: ClusterSecretStore
data:
- secretKey: DB_USERNAME
remoteRef:
key: rds!db-9e6b35bd-...
property: username
- secretKey: DB_PASSWORD
remoteRef:
key: rds!db-9e6b35bd-...
property: password
- secretKey: JWT_SECRET
remoteRef:
key: meeny/prod/backend/jwt
property: secret핵심: DB 비밀번호를 values.yaml에 적지 않는다. 대신 ESO(External Secrets Operator)가 1시간마다 AWS Secrets Manager에서 가져와 K8s Secret으로 만들어준다.
→ templates/externalsecret.yaml 가 위 블록을 ExternalSecret 리소스로 변환한다.
→ templates/deployment.yaml 에서는 envFrom: secretRef: backend-secret 으로 그 Secret을 통째로 환경변수로 주입한다.
→ 결과: Pod 안에서 System.getenv("DB_PASSWORD") 처럼 읽을 수 있고, values.yaml/git/Helm history 어디에도 비밀번호가 남지 않는다.
templates/ 가 만드는 K8s 리소스
| 템플릿 파일 | 만드는 리소스 |
|---|---|
deployment.yaml | Deployment (Pod 2개) |
service.yaml | Service (ClusterIP, 80→8080) |
ingress.yaml | Ingress (className: nginx, path /) |
externalsecret.yaml | ExternalSecret (Secrets Manager → backend-secret) |
설치 후 클러스터에는 다음이 생성된다.
Deployment/backend
ReplicaSet/backend-xxxxx
Pod/backend-xxxxx (×2)
Service/backend
Ingress/backend
ExternalSecret/backend
Secret/backend-secret ← ESO가 자동 생성
helm/ingress — 외부 공개용 Nginx Ingress Controller
Chart.yaml
apiVersion: v2
name: meeny-ingress
description: Nginx Ingress Controller with AWS NLB
type: application
version: 1.0.0
appVersion: "1.9.6"
dependencies:
- name: ingress-nginx
version: 4.9.1
repository: https://kubernetes.github.io/ingress-nginx→ dependencies 가 핵심. 외부 ingress-nginx 차트를 그대로 가져다 씀, 우리는 values만 덮어쓰는 wrapper 차트다.
values.yaml (전체)
ingress-nginx: # ← subchart 이름
controller:
service:
type: LoadBalancer
annotations:
# NLB 사용
service.beta.kubernetes.io/aws-load-balancer-type: "external"
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
# 퍼블릭 (internet-facing)
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: 500m, memory: 256Mi }
replicaCount: 2
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
topologyKey: kubernetes.io/hostname읽는 법:
| 키 | 의미 |
|---|---|
service.type: LoadBalancer | 외부에 노출되는 Service. AWS Load Balancer Controller가 NLB를 자동 생성 |
aws-load-balancer-type: external | AWS Load Balancer Controller(외부 컨트롤러) 사용 (Legacy in-tree X) |
aws-load-balancer-nlb-target-type: ip | NLB가 Pod IP로 직접 라우팅 (NodePort 우회 = 빠름) |
aws-load-balancer-scheme: internet-facing | 퍼블릭 NLB — 외부 인터넷에서 접속 가능 |
cross-zone-load-balancing-enabled: true | AZ 간 트래픽 균등 분산 |
replicaCount: 2 | 컨트롤러 자체를 2개 (HA) |
podAntiAffinity | 가급적 다른 노드에 배치 — 한 노드 죽어도 다른 곳 살아있게 |
→ 이 차트를 깔면 인터넷에 노출된 NLB 1개 가 생기고, 그 NLB가 nginx 컨트롤러로 트래픽을 보낸다.
→ backend/templates/ingress.yaml 의 Ingress 리소스가 nginx 컨트롤러에 의해 라우팅 룰로 변환된다.
helm/ingress-internal — 내부망용 Nginx Ingress
helm/ingress 와 거의 같은데 2가지가 다르다.
ingress-nginx:
controller:
# 외부 ingress와 충돌 방지: 별도 IngressClass + controller class
ingressClass: nginx-internal
ingressClassResource:
name: nginx-internal
enabled: true
default: false
controllerValue: "k8s.io/ingress-nginx-internal"
electionID: ingress-controller-leader-internal
service:
annotations:
service.beta.kubernetes.io/aws-load-balancer-scheme: "internal" # ← 차이점① IngressClass 분리
같은 클러스터에 ingress-nginx 컨트롤러를 두 개 깔면 둘 다 모든 Ingress 리소스를 처리하려고 충돌한다.
→ 외부용은 ingressClassName: nginx, 내부용은 ingressClassName: nginx-internal 로 클래스를 나눠 각자 자기 거만 처리하게 한다.
→ electionID도 분리한다: 두 컨트롤러가 같은 leader election lock을 잡으려 다투면 안 되므로.
② Scheme: internal vs internet-facing
internet-facing→ 퍼블릭 IP 가진 NLB (인터넷에서 접속 가능)internal→ VPC 내부 IP만 (사내망/VPN/Bastion에서만 접속)
→ 우리는 외부 트래픽은 ingress, 내부 운영용 도구는 ingress-internal 로 분리한다.
→ Backend Ingress가 ingressClassName: nginx (외부) 또는 nginx-internal (내부) 를 고르면 어느 LB로 들어올지 결정된다.
helm/external-secrets — 비밀값 동기화 시스템
Chart.yaml
apiVersion: v2
name: meeny-external-secrets
description: External Secrets Operator + ClusterSecretStore for AWS Secrets Manager
type: application
version: 1.0.0
appVersion: "0.10.4"
dependencies:
- name: external-secrets
version: 0.10.4
repository: https://charts.external-secrets.io→ 외부 ESO(External Secrets Operator) 차트 + 우리가 추가한 ClusterSecretStore 정의.
values.yaml
# Subchart 설정
external-secrets:
installCRDs: true # ESO 전용 CRD 설치 (ExternalSecret 등)
replicaCount: 1
serviceAccount:
create: false # ← 미리 만든 SA 사용
name: external-secrets
# 우리 차트가 추가로 만들 ClusterSecretStore
clusterSecretStore:
enabled: true
name: aws-secretsmanager
region: ap-northeast-2
serviceAccount:
name: external-secrets
namespace: external-secrets흐름 ⭐ 중요
[ AWS Secrets Manager ]
│
│ ESO (Pod) 가 IRSA(IAM Role for SA)로 인증
│ → external-secrets ServiceAccount에 IAM Role이 묶여있음
▼
[ ClusterSecretStore: aws-secretsmanager ]
│
│ backend 차트의 ExternalSecret이 이 store를 참조
▼
[ ExternalSecret/backend ]
│
│ 주기적으로(refreshInterval=1h) 가져와
▼
[ Secret/backend-secret ] (K8s 표준 Secret)
│
│ Pod가 envFrom: secretRef 로 환경변수 주입
▼
[ backend Pod ]
핵심 포인트:
- ServiceAccount + IRSA: Pod에 AWS 인증을 안전하게 부여하는 K8s 표준 방식
create: false인 이유: SA는 Terraform(EKS 모듈)으로 미리 만들어 IAM Role과 묶어둠- ClusterSecretStore vs SecretStore: Cluster 는 클러스터 전역, Secret 는 네임스페이스 한정. 우리는 backend가 어느 ns에 있든 쓸 수 있게 ClusterSecretStore 사용
templates/clustersecretstore.yaml
{{- if .Values.clusterSecretStore.enabled -}}
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: {{ .Values.clusterSecretStore.name }}
spec:
provider:
aws:
service: SecretsManager
region: {{ .Values.clusterSecretStore.region }}
auth:
jwt:
serviceAccountRef:
name: {{ .Values.clusterSecretStore.serviceAccount.name }}
namespace: {{ .Values.clusterSecretStore.serviceAccount.namespace }}
{{- end }}→ "AWS Secrets Manager 서울 리전, 인증은 external-secrets/external-secrets ServiceAccount(IRSA로 IAM Role 부여됨)로" 라는 선언.
→ auth.jwt 는 K8s ServiceAccount 토큰을 STS에 제출 → IAM Role 가져옴 → AWS API 호출 가능.
차트 간 의존 관계 정리
[external-secrets 차트]
│ ESO Pod + ClusterSecretStore 설치
│
▼
[ingress / ingress-internal 차트]
│ Nginx Ingress Controller 설치 (NLB 생성됨)
│
▼
[backend 차트]
│ Deployment + Service + Ingress + ExternalSecret 설치
│
▼ (런타임에)
ExternalSecret → backend-secret(K8s Secret) → Pod env
Ingress → IngressClass(nginx) 컨트롤러 → NLB → 인터넷
한눈 요약
| 차트 | 무엇을 깔까 | 핵심 키 |
|---|---|---|
backend | 앱 Pod + Service + Ingress + ExternalSecret | image.tag, env.DB_URL, externalSecret.data |
ingress | nginx 컨트롤러 (퍼블릭 NLB) | aws-load-balancer-scheme: internet-facing |
ingress-internal | nginx 컨트롤러 (내부 NLB) | ingressClass: nginx-internal, scheme: internal |
external-secrets | ESO + ClusterSecretStore | clusterSecretStore.region, IRSA 사용 SA |
다음 글에서는 install / upgrade / rollback / template / lint 등 실전 명령어를 본다.