Helm 트러블슈팅과 운영팁
이 글의 목표: 실제로 자주 마주치는 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 store | Secrets Manager에 그 이름 없음 | values.yaml의 remoteRef.key 오타 확인 |
AccessDenied (IAM) | IRSA Role에 secretsmanager:GetSecretValue 권한 없음 | EKS 모듈에서 IAM Role policy 추가 |
WebIdentityErr | ServiceAccount에 IRSA annotation 누락 | SA의 eks.amazonaws.com/role-arn 확인 |
property not found | JSON 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-secretsIngress로 접속이 안 됨
증상: 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/backendhelm 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.yaml의 version | 차트 패키지 버전. 차트 코드 수정 시 올림. Helm이 추적함 |
Chart.yaml의 appVersion | 안에 들어있는 앱의 버전. 정보용. 보통 image tag랑 일치시킴 |
values.yaml의 image.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로 해결. 새 비밀값이 필요하면:
- AWS Secrets Manager에 등록
helm/backend/values.yaml의externalSecret.data에 매핑 추가helm upgrade→ ExternalSecret 갱신 → K8s Secret 갱신 → Pod restart 해야 새 값 반영
✅ Resource limits는 항상 설정
→ limits 없으면 한 Pod이 노드 메모리 다 먹고 다른 Pod까지 죽일 수 있다. requests는 보통 limits의 1/2 ~ 1/4.
✅ 차트 버전을 올려라
→ values만 바꿨다면 Chart.yaml을 안 건드려도 되지만, 템플릿 코드를 수정했으면 Chart.yaml의 version을 올리는 습관. 협업 시 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가 비어있지 않은가