Helm 차트 구축기

Helm 트러블슈팅과 운영팁

2026. 06. 19.
KubernetesHelmDevOps

이 글의 목표: 실제로 자주 마주치는 Helm/K8s 에러와 해결 절차를 빠르게 짚어본다.


디버깅의 황금 순서 ⭐

어떤 문제든 이 순서로 좁혀가야 한다.

1. helm template 으로 렌더링 결과 보기      → 템플릿 자체가 잘못 만든 건지?
2. helm get manifest 로 실제 적용된 것 보기   → Helm은 잘 보냈는지?
3. kubectl get / describe / logs              → K8s 측에서 받았는지, Pod 상태?
4. 외부 의존성 (RDS, Secrets Manager, IAM)    → 외부에서 막혔는지?

→ 순서를 안 지키면 엉뚱한 곳을 파게 된다.


[흔함] Pod가 ImagePullBackOff / ErrImagePull

증상

kubectl get pods
# NAME        READY   STATUS             RESTARTS   AGE
# backend-1   0/1     ImagePullBackOff   0          2m

원인 후보

원인어떻게 확인
이미지 태그 오타kubectl describe pod → Events에 "manifest unknown"
ECR 인증 실패Events에 "no basic auth credentials"
노드의 IAM 권한 부족EC2 Instance Profile에 AmazonEC2ContainerRegistryReadOnly 필요
잘못된 아키텍처M1 Mac에서 빌드 → 노드는 amd64 → ECR push는 amd64여야

가이드

  • 빌드 시 --platform linux/amd64 명시 (멀티아치 CI 글 참고)
  • 노드 IAM에는 EKS 모듈에서 ECR 읽기 권한이 이미 부여돼 있어야 함

[흔함] Pod가 CrashLoopBackOff

증상

kubectl get pods
# backend-1   0/1   CrashLoopBackOff   5   3m

→ 컨테이너가 켜지자마자 죽고 다시 켜지고를 반복.

1차 점검 — 로그

kubectl logs backend-1
kubectl logs backend-1 --previous   # 직전 종료된 컨테이너 로그

자주 보이는 원인

원인단서
환경변수 누락 (DB_URL 등)Spring Boot Cannot determine embedded database driver
DB 비밀번호 틀림password authentication failed for user
DB 호스트 도달 불가connection refused / could not translate host name
Liveness Probe 실패Events에 Liveness probe failed: ... 404
메모리 부족 (OOMKilled)kubectl describe pod → Last State: OOMKilled

[흔함] Liveness/Readiness 실패

증상: Pod는 Running인데 Service에서 트래픽이 안 오거나, 자꾸 재시작한다.

kubectl describe pod backend-1
# Liveness probe failed: HTTP probe failed with statuscode: 503

대처

  • initialDelaySeconds 충분히 줬는지 (Spring Boot 시작 30~60초 필요)
  • Probe path가 정확한지 (/actuator/health/liveness 등)
  • failureThreshold 너무 작으면 잠깐 느려져도 죽음 → 5~10 권장
  • timeoutSeconds 너무 짧으면 GC 잠깐 돌 때 실패 → 3~5

backend values.yaml의 기본값은 보수적으로 잡혀있으나, 실제 동작 보고 조정한다.


ExternalSecret이 K8s Secret을 만들지 않음

증상

kubectl get secret backend-secret
# Error from server (NotFound)
 
kubectl get externalsecret backend
# NAME     STORE                READY   STATUS
# backend  aws-secretsmanager   False   SecretSyncedError

점검 순서

# 1. ExternalSecret 상세
kubectl describe externalsecret backend
# .status.conditions 와 Events 확인

자주 보이는 원인:

메시지원인해결
unable to retrieve secret from storeSecrets Manager에 그 이름 없음values.yaml의 remoteRef.key 오타 확인
AccessDenied (IAM)IRSA Role에 secretsmanager:GetSecretValue 권한 없음EKS 모듈에서 IAM Role policy 추가
WebIdentityErrServiceAccount에 IRSA annotation 누락SA의 eks.amazonaws.com/role-arn 확인
property not foundJSON Secret인데 property 이름 틀림Secrets Manager 콘솔에서 키 이름 확인
# 2. ClusterSecretStore 상태
kubectl get clustersecretstore aws-secretsmanager -o yaml | grep -A5 conditions
 
# 3. ESO Pod 로그
kubectl logs -n external-secrets deploy/external-secrets

Ingress로 접속이 안 됨

증상: NLB 주소를 받았는데 curl 시 timeout 또는 503.

층층이 점검

[ Internet ] → [ NLB ] → [ ingress-nginx Pod ] → [ backend Service ] → [ backend Pod ]

① NLB가 만들어지긴 했는가

kubectl get svc -n ingress-nginx ingress-nginx-controller
# EXTERNAL-IP 에 NLB DNS가 떠야 함

→ 안 떴다면 AWS Load Balancer Controller가 클러스터에 안 깔려있을 가능성.

② Ingress가 컨트롤러에 매핑됐는가

kubectl describe ingress backend
# Address: a1b2c3d4...elb.amazonaws.com   ← 채워져 있어야 함
# Events: Sync ... successfully synced

→ Address가 비어있으면 ingressClassName 이 컨트롤러의 IngressClass와 안 맞는 것.

  • 외부 노출: ingressClassName: nginx
  • 내부 노출: ingressClassName: nginx-internal

③ Service의 Endpoints가 비었는가

kubectl get endpoints backend
# NAME      ENDPOINTS              AGE
# backend   10.100.x.y:8080        1h

→ 비어있으면 Pod가 Ready 상태가 아니거나, Service의 selector(app: backend)가 Pod label과 안 맞는 것.

④ Pod 자체

kubectl get pod -l app=backend
kubectl exec -it backend-xxxxx -- curl localhost:8080/actuator/health

→ Pod 안에서는 동작하는데 Service로는 안 되면 위 ③번 다시.


helm upgrade 가 멈췄다 (pending-upgrade)

증상

helm list
# STATUS: pending-upgrade

원인: 직전 upgrade 중 Helm 프로세스가 죽었거나, 누군가 Ctrl+C 함.

복구

# 1. 무엇이 적용됐는지 봄
helm history backend
# 마지막 deployed revision 확인 (예: 5)
 
# 2. 마지막 정상 revision으로 롤백
helm rollback backend 5
 
# 3. 이후 정상적으로 upgrade
helm upgrade backend ./helm/backend

helm rollback 도 안 먹으면 마지막 수단:

# 매니페스트 secret 직접 삭제 (위험)
kubectl delete secret -n default sh.helm.release.v1.backend.v6

⚠️ Release 이력 일부를 잃는다. 그 후 helm upgrade --install 로 다시 시작.


helm template 결과가 이상한 YAML이다

증상: 들여쓰기가 깨져있거나, 빈 줄이 잔뜩 있거나, 키가 중복.

원인

증상원인
빈 줄이 많음{{ 대신 {{- 를 안 씀
키 중복range 안에서 condition 분기 잘못
들여쓰기 깨짐nindent N 의 N 값 틀림
따옴표 사라짐quote 없이 숫자처럼 보이는 값

→ 항상 helm template > out.yaml 로 파일에 저장하고 에디터로 검토.


차트 버전 vs 앱 버전 헷갈림

어디서무엇
Chart.yamlversion차트 패키지 버전. 차트 코드 수정 시 올림. Helm이 추적함
Chart.yamlappVersion안에 들어있는 앱의 버전. 정보용. 보통 image tag랑 일치시킴
values.yamlimage.tag실제 컨테이너 이미지 태그. Pod에 박힘

→ "버전을 올렸는데 왜 새 이미지가 안 깔려?" 라면 보통 image.tag 안 바꾼 경우다.


운영 팁 모음

✅ CI/CD에서 항상 helm upgrade --install 사용

→ 새 환경/기존 환경 분기 필요 없음. 멱등.

✅ 매니페스트는 kubectl apply 와 섞지 말자

→ Helm이 만든 리소스를 kubectl edit/patch 하면 다음 helm upgrade에서 덮어씌워진다. 변경은 values.yaml로만.

--atomic 옵션 활용

helm upgrade --install --atomic --timeout 5m backend ./helm/backend

→ 업그레이드가 5분 안에 완전히 성공하지 못하면 자동 롤백. 운영에서 안전.

--wait 옵션

helm upgrade --install --wait backend ./helm/backend

→ Pod가 Ready 될 때까지 명령어가 안 끝난다. CI에서 다음 단계 진행 전에 기다리고 싶을 때.

✅ values.yaml에 비밀값 적지 않기

→ 우리는 ExternalSecrets로 해결. 새 비밀값이 필요하면:

  1. AWS Secrets Manager에 등록
  2. helm/backend/values.yamlexternalSecret.data 에 매핑 추가
  3. helm upgrade → ExternalSecret 갱신 → K8s Secret 갱신 → Pod restart 해야 새 값 반영

✅ Resource limits는 항상 설정

→ limits 없으면 한 Pod이 노드 메모리 다 먹고 다른 Pod까지 죽일 수 있다. requests는 보통 limits의 1/2 ~ 1/4.

✅ 차트 버전을 올려라

→ values만 바꿨다면 Chart.yaml을 안 건드려도 되지만, 템플릿 코드를 수정했으면 Chart.yamlversion을 올리는 습관. 협업 시 PR 리뷰가 명확해진다.


빠른 진단 체크리스트

문제 생기면 위에서부터 차례로:

  • kubectl config current-context 가 맞는 클러스터인가
  • helm list -A 에 Release가 보이는가, status가 deployed 인가
  • helm history 에 최근 변경이 있는가
  • kubectl get pod -A | grep -v Running 에 비정상 Pod가 있는가
  • kubectl describe pod 의 Events에 단서가 있는가
  • kubectl logs --previous 로 죽기 전 로그를 봤는가
  • ExternalSecret이라면 kubectl get externalsecret -A 에 SecretSyncedError 있는가
  • Ingress라면 NLB 주소가 채워져 있고, Endpoints가 비어있지 않은가

도움 되는 외부 자료