이미지 로딩 중...

K8s 트러블슈팅 완벽 가이드 - 슬라이드 1/9
A

AI Generated

2025. 11. 5. · 5 Views

K8s 트러블슈팅 완벽 가이드

Kubernetes 운영 중 자주 발생하는 문제들을 진단하고 해결하는 실전 가이드입니다. Pod 문제부터 네트워크, 스토리지, 성능 이슈까지 체계적으로 다룹니다.


목차

  1. Pod가 시작되지 않을 때
  2. Service 연결이 안 될 때
  3. 메모리/CPU 부족 문제
  4. 볼륨 마운트 실패
  5. DNS 해석 실패
  6. 컨테이너 이미지 Pull 실패
  7. ConfigMap/Secret 마운트 문제
  8. 네트워크 정책 차단

1. Pod가 시작되지 않을 때

시작하며

여러분이 새로운 애플리케이션을 배포했는데 Pod가 Pending 상태에서 멈춰있거나 CrashLoopBackOff 상태를 반복하는 상황을 겪어본 적 있나요? 배포는 완료됐다고 나오는데 정작 서비스는 접속이 안 되는 답답한 경험 말입니다.

이런 문제는 Kubernetes를 운영하다 보면 가장 흔하게 마주치는 상황입니다. Pod가 정상적으로 실행되지 않으면 전체 서비스가 마비될 수 있고, 특히 프로덕션 환경에서는 즉각적인 대응이 필요합니다.

바로 이럴 때 필요한 것이 체계적인 Pod 트러블슈팅 방법입니다. kubectl 명령어를 활용해 문제의 원인을 빠르게 파악하고, 로그를 분석하여 근본 원인을 찾아낼 수 있습니다.

개요

간단히 말해서, Pod 시작 실패는 리소스 부족, 이미지 문제, 설정 오류 등 다양한 원인으로 발생합니다. 각 상태 코드가 의미하는 바를 정확히 이해하면 문제를 빠르게 해결할 수 있습니다.

왜 이런 접근이 필요할까요? Pod의 상태와 이벤트를 체계적으로 확인하지 않으면 문제의 원인을 찾는 데 오랜 시간이 걸립니다.

예를 들어, ImagePullBackOff 에러를 단순히 네트워크 문제로 착각하고 시간을 낭비하는 경우가 많습니다. 기존에는 무작정 Pod를 삭제하고 재생성하는 방법을 반복했다면, 이제는 정확한 진단을 통해 근본 원인을 해결할 수 있습니다.

핵심은 세 가지입니다. 첫째, Pod의 현재 상태를 정확히 파악하기.

둘째, 이벤트 로그에서 단서 찾기. 셋째, 컨테이너 로그를 통해 애플리케이션 레벨 문제 확인하기.

이 세 가지를 순차적으로 진행하면 대부분의 문제를 해결할 수 있습니다.

코드 예제

# Pod 상태 상세 확인
kubectl describe pod <pod-name> -n <namespace>

# Pod 이벤트만 확인
kubectl get events --field-selector involvedObject.name=<pod-name>

# 컨테이너 로그 확인 (이전 인스턴스 포함)
kubectl logs <pod-name> -c <container-name> --previous

# 실시간 로그 스트리밍
kubectl logs -f <pod-name> --tail=100

# Pod가 시작되지 않을 때 init 컨테이너 확인
kubectl logs <pod-name> -c <init-container-name>

# 리소스 할당 상태 확인
kubectl top pod <pod-name> --containers

설명

이것이 하는 일: Pod 트러블슈팅은 문제의 레벨을 구분하여 접근하는 것이 핵심입니다. 클러스터 레벨, Pod 레벨, 컨테이너 레벨로 나누어 체계적으로 진단합니다.

첫 번째 단계로, kubectl describe 명령을 통해 Pod의 전체적인 상태를 파악합니다. Events 섹션에서는 Pod 생성부터 현재까지의 모든 이벤트를 시간순으로 확인할 수 있고, Conditions 섹션에서는 현재 Pod의 상태를 알 수 있습니다.

특히 'Message'와 'Reason' 필드가 문제 해결의 핵심 단서를 제공합니다. 두 번째로, get events 명령으로 특정 Pod와 관련된 이벤트만 필터링해서 봅니다.

FailedScheduling, FailedMount, FailedAttachVolume 같은 이벤트는 클러스터 레벨의 문제를 나타내고, Pulling, Pulled, Failed 같은 이벤트는 이미지 관련 문제를 의미합니다. 세 번째로, logs 명령을 통해 애플리케이션 레벨의 문제를 확인합니다.

--previous 옵션은 크래시된 이전 컨테이너의 로그를 보여주므로 CrashLoopBackOff 상태를 디버깅할 때 필수적입니다. -f 옵션으로 실시간 로그를 보면서 문제가 발생하는 시점을 정확히 파악할 수 있습니다.

여러분이 이 방법을 마스터하면 Pod 문제의 90% 이상을 10분 이내에 진단할 수 있습니다. 특히 프로덕션 환경에서 빠른 대응이 가능해지고, 불필요한 재시작을 줄여 서비스 안정성도 향상됩니다.

실전 팁

💡 Pod가 Pending 상태라면 먼저 노드의 리소스를 확인하세요. kubectl describe nodes로 CPU/메모리가 충분한지 체크합니다.

💡 ImagePullBackOff는 대부분 이미지 이름 오타, 프라이빗 레지스트리 인증 문제입니다. imagePullSecrets를 확인하세요.

💡 CrashLoopBackOff는 애플리케이션 자체의 문제일 가능성이 높습니다. 로컬에서 동일한 이미지로 테스트해보세요.

💡 Init 컨테이너가 있다면 메인 컨테이너보다 먼저 init 컨테이너의 로그를 확인해야 합니다.

💡 문제가 간헐적으로 발생한다면 kubectl get pod -w로 실시간 모니터링하면서 상태 변화를 관찰하세요.


2. Service 연결이 안 될 때

시작하며

여러분이 완벽하게 Pod를 배포했는데도 서비스에 접속이 안 되는 황당한 경험을 해보셨나요? curl로 테스트해봐도 connection refused나 timeout이 발생하고, 분명 Pod는 Running 상태인데 왜 연결이 안 되는지 막막했던 순간 말입니다.

이런 네트워크 문제는 Kubernetes에서 두 번째로 흔한 이슈입니다. Service, Endpoint, NetworkPolicy 등 여러 컴포넌트가 복잡하게 얽혀있어 문제의 원인을 찾기가 쉽지 않죠.

Service 연결 문제를 체계적으로 디버깅하는 방법을 알면, 복잡해 보이는 네트워크 문제도 단계별로 해결할 수 있습니다. 특히 Label Selector 매칭 문제는 생각보다 자주 발생하는 실수입니다.

개요

간단히 말해서, Service 연결 문제는 대부분 Label Selector 불일치, Endpoint 미생성, 포트 설정 오류에서 발생합니다. 각 컴포넌트의 연결 고리를 하나씩 점검하면 문제를 찾을 수 있습니다.

왜 이렇게 복잡할까요? Kubernetes는 Service가 직접 트래픽을 전달하는 것이 아니라, Label Selector로 Pod를 찾고, Endpoint를 생성하고, iptables 규칙을 만드는 여러 단계를 거치기 때문입니다.

예를 들어, Service의 selector와 Pod의 labels가 한 글자라도 다르면 연결이 안 됩니다. 전통적인 방법으로는 Service를 삭제하고 다시 만들기를 반복했다면, 이제는 endpoints를 확인하고 Port 매핑을 검증하는 체계적인 접근이 가능합니다.

핵심 체크포인트는 네 가지입니다. Service의 Selector 확인, Endpoints 생성 여부, Port/TargetPort 매핑, 그리고 NetworkPolicy 설정입니다.

이 네 가지만 순서대로 확인해도 대부분의 연결 문제를 해결할 수 있습니다.

코드 예제

# ServiceSelector 확인
kubectl get svc <service-name> -o yaml | grep -A 3 selector

# Endpoints 확인 (Pod IP가 있어야 함)
kubectl get endpoints <service-name>

# PodLabel 확인
kubectl get pods --show-labels | grep <app-name>

# ServicePod의 포트 매핑 확인
kubectl get svc <service-name> -o jsonpath='{.spec.ports[*]}'
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].ports[*]}'

# 네트워크 연결 테스트 (임시 Pod 생성)
kubectl run test-pod --image=busybox -it --rm -- sh
# Pod 내부에서: wget -O- <service-name>:<port>

# NetworkPolicy 확인
kubectl get networkpolicy -A

설명

이것이 하는 일: Service 트러블슈팅은 트래픽 흐름을 따라가며 각 단계를 검증하는 과정입니다. 클라이언트 → Service → Endpoints → Pod 순서로 연결 고리를 확인합니다.

첫 번째로, Service의 selector와 Pod의 labels가 정확히 일치하는지 확인합니다. kubectl get svc -o yaml로 Service의 selector를 보고, kubectl get pods --show-labels로 Pod의 labels를 확인합니다.

대소문자 하나, 띄어쓰기 하나도 정확히 일치해야 합니다. 실수하기 쉬운 부분은 'app: nginx'와 'app:nginx'처럼 공백 유무입니다.

두 번째로, Endpoints 객체가 제대로 생성되었는지 확인합니다. kubectl get endpoints 명령으로 Service와 같은 이름의 Endpoints가 있는지, 그리고 ENDPOINTS 컬럼에 Pod의 IP:Port가 표시되는지 확인합니다.

비어있다면 Selector 문제이고, IP는 있는데 포트가 없다면 컨테이너 포트 설정 문제입니다. 세 번째로, Service의 port와 targetPort가 올바르게 매핑되었는지 검증합니다.

Service의 port는 클라이언트가 접속하는 포트이고, targetPort는 Pod가 실제로 listening하는 포트입니다. 많은 경우 targetPort를 잘못 설정해서 연결이 안 됩니다.

마지막으로, NetworkPolicy가 설정되어 있다면 트래픽을 차단하고 있지 않은지 확인합니다. 특히 기본적으로 모든 트래픽을 차단하는 정책이 있다면, 명시적으로 허용 규칙을 추가해야 합니다.

여러분이 이 디버깅 프로세스를 따르면 Service 연결 문제의 95% 이상을 해결할 수 있습니다. 특히 마이크로서비스 아키텍처에서 서비스 간 통신 문제를 빠르게 해결할 수 있어 전체 시스템의 안정성이 향상됩니다.

실전 팁

💡 Service 이름으로 접속이 안 되면 먼저 ClusterIP로 직접 접속해보세요. DNS 문제인지 Service 문제인지 구분할 수 있습니다.

💡 headless Service (clusterIP: None)는 Endpoints를 직접 반환하므로 일반 Service와 디버깅 방법이 다릅니다.

💡 ExternalName Service는 CNAME을 반환하므로 nslookup으로 확인해야 합니다.

💡 NodePort Service가 외부에서 접속이 안 되면 방화벽과 Security Group을 확인하세요.

💡 임시 디버깅 Pod를 만들 때는 nicolaka/netshoot 이미지를 사용하면 다양한 네트워크 도구를 쓸 수 있습니다.


3. 메모리/CPU 부족 문제

시작하며

여러분의 애플리케이션이 갑자기 OOMKilled되거나, CPU throttling으로 느려지는 경험을 해보셨나요? 분명 충분한 리소스를 할당했다고 생각했는데, 트래픽이 조금만 증가해도 Pod가 죽어버리는 상황 말입니다.

이런 리소스 문제는 프로덕션 환경에서 가장 치명적인 이슈 중 하나입니다. 메모리 부족으로 인한 OOMKilled는 데이터 손실을 야기할 수 있고, CPU throttling은 사용자 경험을 크게 저하시킵니다.

리소스 관련 문제를 정확히 진단하고 적절한 requests/limits를 설정하는 방법을 알면, 안정적이고 효율적인 클러스터 운영이 가능합니다. 특히 HPA와 연계하면 자동으로 확장되는 탄력적인 시스템을 구축할 수 있습니다.

개요

간단히 말해서, 리소스 문제는 requests/limits 설정 미흡, 실제 사용량 모니터링 부재, 노드 리소스 부족에서 발생합니다. Metrics Server와 kubectl top 명령으로 실시간 사용량을 모니터링하고 적절한 값을 찾아야 합니다.

왜 이게 중요할까요? Kubernetes는 requests 기준으로 스케줄링하고, limits 기준으로 제한을 겁니다.

예를 들어, requests가 너무 낮으면 노드가 오버커밋되고, limits가 너무 낮으면 정상적인 피크 트래픽도 처리하지 못합니다. 기존에는 대충 큰 값을 설정하고 문제가 생기면 더 늘리는 방식이었다면, 이제는 실제 사용 패턴을 분석하여 최적값을 찾을 수 있습니다.

VPA(Vertical Pod Autoscaler)를 활용하면 자동으로 권장값을 제안받을 수도 있습니다. 핵심 원칙은 세 가지입니다.

첫째, requests는 평상시 사용량 기준으로 설정. 둘째, limits는 피크 사용량에 여유를 둔 값으로 설정.

셋째, 주기적인 모니터링으로 조정. 이 원칙을 지키면 리소스를 효율적으로 사용하면서도 안정성을 확보할 수 있습니다.

코드 예제

# 현재 리소스 사용량 확인
kubectl top pods -n <namespace> --sort-by=memory
kubectl top nodes

# Pod의 리소스 설정 확인
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].resources}'

# OOMKilled 이벤트 찾기
kubectl get events -A | grep OOMKilled

# 과거 리소스 사용 이력 확인 (Metrics Server 필요)
kubectl describe pod <pod-name> | grep -A 10 "Limits\|Requests"

# HPA 상태 확인
kubectl get hpa
kubectl describe hpa <hpa-name>

# VPA 권장사항 확인 (VPA 설치 필요)
kubectl describe vpa <vpa-name> | grep -A 20 "Recommendation"

설명

이것이 하는 일: 리소스 트러블슈팅은 현재 사용량 측정, 패턴 분석, 최적값 설정의 순환 과정입니다. 단순히 값을 늘리는 것이 아니라, 데이터 기반으로 의사결정을 합니다.

첫 번째로, kubectl top 명령으로 실시간 리소스 사용량을 확인합니다. --sort-by 옵션으로 메모리나 CPU 사용량 순으로 정렬하면 문제가 되는 Pod를 빠르게 찾을 수 있습니다.

노드 레벨에서도 확인하여 특정 노드에 부하가 집중되지 않았는지 체크합니다. 두 번째로, OOMKilled나 Evicted 이벤트를 분석합니다.

OOMKilled는 메모리 limits를 초과했다는 의미이고, Evicted는 노드의 리소스가 부족해서 Pod를 강제로 제거했다는 의미입니다. 각각의 원인이 다르므로 해결 방법도 달라야 합니다.

세 번째로, 실제 사용 패턴을 기반으로 requests/limits를 조정합니다. requests는 보장받고 싶은 최소 리소스이므로 평균 사용량의 110-120% 정도로 설정하고, limits는 피크 사용량의 120-150% 정도로 설정하는 것이 일반적입니다.

하지만 애플리케이션의 특성에 따라 달라질 수 있습니다. 네 번째로, HPA(Horizontal Pod Autoscaler)를 설정하여 자동 스케일링을 구현합니다.

CPU나 메모리 사용률이 특정 임계값을 넘으면 자동으로 Pod 수를 늘려서 부하를 분산시킵니다. 이때 requests 설정이 정확해야 HPA가 제대로 작동합니다.

여러분이 이 프로세스를 적용하면 리소스 사용 효율성을 30-50% 개선하면서도 안정성은 더 높일 수 있습니다. 특히 클라우드 환경에서는 비용 절감 효과도 큽니다.

실전 팁

💡 Java 애플리케이션은 JVM 힙 메모리 외에 추가 메모리가 필요하므로, -Xmx 값보다 30-50% 많은 메모리 limits를 설정하세요.

💡 CPU limits는 throttling을 일으킬 수 있으므로, 가능하면 requests만 설정하고 limits는 설정하지 않는 것도 고려해보세요.

💡 메모리 누수가 의심되면 kubectl exec로 들어가서 메모리 덤프를 떠서 분석하세요.

💡 노드의 Allocatable 리소스를 확인하여 시스템 예약분을 고려한 용량 계획을 세우세요.

💡 Prometheus + Grafana로 장기간 리소스 사용 추이를 모니터링하면 더 정확한 capacity planning이 가능합니다.


4. 볼륨 마운트 실패

시작하며

여러분이 StatefulSet을 배포했는데 PVC가 Pending 상태로 남아있거나, Volume mount 에러로 Pod가 시작조차 못하는 상황을 겪어보셨나요? 데이터베이스나 파일 서버처럼 영구 저장소가 필요한 서비스인데 볼륨이 연결이 안 되면 정말 난감합니다.

볼륨 관련 문제는 특히 복잡합니다. StorageClass, PV, PVC, 그리고 실제 스토리지 백엔드까지 여러 레이어가 관련되어 있어서 어디서부터 확인해야 할지 막막하죠.

AWS EBS, GCE PD, NFS 등 스토리지 종류에 따라 제약사항도 다릅니다. 체계적인 볼륨 트러블슈팅 방법을 익히면, 복잡해 보이는 스토리지 문제도 단계적으로 해결할 수 있습니다.

특히 Multi-Attach 에러나 Volume Affinity 문제는 원인만 알면 쉽게 해결됩니다.

개요

간단히 말해서, 볼륨 마운트 실패는 주로 StorageClass 미설정, PV/PVC 바인딩 실패, 노드 친화성 불일치, 권한 문제에서 발생합니다. 각 컴포넌트의 상태를 순차적으로 확인하면 문제를 찾을 수 있습니다.

왜 이런 문제가 자주 발생할까요? Kubernetes의 볼륨은 단순한 디스크 연결이 아니라, 동적 프로비저닝, 바인딩, 마운트, 권한 설정 등 여러 단계를 거치기 때문입니다.

예를 들어, AWS EBS 볼륨은 Pod와 같은 가용 영역(AZ)에 있는 노드에만 연결될 수 있습니다. 기존에는 PVC를 삭제하고 다시 만들기를 반복했다면, 이제는 각 단계별로 문제를 진단하고 정확한 해결책을 적용할 수 있습니다.

핵심 체크포인트는 다섯 가지입니다. StorageClass 존재 여부, PVC 상태, PV 바인딩 상태, Volume Attachment 상태, 그리고 파일시스템 권한입니다.

이 순서대로 확인하면 대부분의 볼륨 문제를 해결할 수 있습니다.

코드 예제

# PVC 상태 확인
kubectl get pvc -A
kubectl describe pvc <pvc-name>

# PV 상태 및 바인딩 확인
kubectl get pv
kubectl describe pv <pv-name>

# StorageClass 확인
kubectl get storageclass
kubectl get storageclass <sc-name> -o yaml

# VolumeAttachment 확인 (CSI 드라이버 사용 시)
kubectl get volumeattachments

# Pod의 볼륨 마운트 상태 확인
kubectl describe pod <pod-name> | grep -A 10 "Volumes\|Mounts"

# 노드의 볼륨 상태 확인
kubectl get nodes -o json | jq '.items[].status.volumesAttached'

# 파일시스템 권한 확인
kubectl exec <pod-name> -- ls -la /mount/path

설명

이것이 하는 일: 볼륨 트러블슈팅은 요청(PVC)부터 실제 스토리지 연결까지 전체 플로우를 추적하는 과정입니다. 각 단계에서 발생할 수 있는 문제를 체계적으로 점검합니다.

첫 번째로, PVC의 상태를 확인합니다. Pending 상태라면 describe 명령으로 Events를 확인합니다.

"no persistent volumes available" 메시지는 조건에 맞는 PV가 없다는 의미이고, "waiting for a volume to be created" 는 동적 프로비저닝을 기다리는 중입니다. 두 번째로, StorageClass가 올바르게 설정되었는지 확인합니다.

특히 provisioner 필드가 클러스터에 설치된 CSI 드라이버와 일치하는지, volumeBindingMode가 Immediate인지 WaitForFirstConsumer인지 확인합니다. WaitForFirstConsumer 모드는 Pod가 스케줄링될 때까지 PV 생성을 지연시킵니다.

세 번째로, PV와 PVC의 바인딩을 확인합니다. Access Modes(RWO, RWX, ROX)가 일치하는지, 용량이 충분한지, StorageClass가 맞는지 체크합니다.

수동으로 생성한 PV는 claimRef를 통해 특정 PVC와 바인딩할 수 있습니다. 네 번째로, 실제 볼륨이 노드에 attach되었는지 확인합니다.

VolumeAttachment 리소스나 노드의 status.volumesAttached를 확인하면 됩니다. Multi-Attach 에러가 발생한다면 이전 노드에서 detach가 완료되지 않은 것입니다.

마지막으로, 컨테이너 내부에서 볼륨이 제대로 마운트되었는지, 권한은 올바른지 확인합니다. fsGroup, runAsUser 설정에 따라 파일 권한이 달라질 수 있습니다.

여러분이 이 프로세스를 숙지하면 StatefulSet이나 데이터베이스 워크로드의 스토리지 문제를 빠르게 해결할 수 있습니다. 특히 데이터 손실 위험을 최소화하면서 안정적인 스토리지 운영이 가능합니다.

실전 팁

💡 EBS 볼륨은 단일 AZ에 종속되므로, Pod가 다른 AZ의 노드로 이동하면 연결이 안 됩니다. nodeSelector나 nodeAffinity를 사용하세요.

💡 RWO(ReadWriteOnce) 볼륨은 한 노드에만 연결 가능하므로, 여러 Pod가 사용하려면 같은 노드에 스케줄링되어야 합니다.

💡 동적 프로비저닝이 안 되면 CSI 드라이버 로그를 확인하세요: kubectl logs -n kube-system <csi-pod>

💡 PVC 크기 조정(resize)은 StorageClass에 allowVolumeExpansion: true가 설정되어 있어야 가능합니다.

💡 스냅샷을 이용한 백업/복구를 위해서는 VolumeSnapshotClass 설정이 필요합니다.


5. DNS 해석 실패

시작하며

여러분의 애플리케이션에서 다른 서비스를 호출할 때 "Name or service not known" 에러가 발생한 적 있나요? 또는 외부 API 호출이 갑자기 안 되기 시작했는데, 알고 보니 DNS 문제였던 경험이 있으신가요?

Kubernetes의 DNS는 서비스 디스커버리의 핵심입니다. 매우 중요하지만 문제가 발생했을 때 어디서부터 확인해야 할지 막막합니다.

CoreDNS 설정, Service 이름, 네임스페이스, 도메인 설정 등 확인할 포인트가 많죠. DNS 트러블슈팅 방법을 체계적으로 익히면, 서비스 간 통신 문제를 빠르게 해결할 수 있습니다.

특히 FQDN과 짧은 이름의 차이, Search 도메인 설정의 영향을 이해하면 많은 문제를 예방할 수 있습니다.

개요

간단히 말해서, DNS 문제는 CoreDNS Pod 장애, 잘못된 서비스 이름, 네임스페이스 설정 오류, DNS 정책 설정 미스에서 발생합니다. nslookup과 dig 명령으로 단계별로 검증하면 원인을 찾을 수 있습니다.

왜 DNS가 이렇게 복잡할까요? Kubernetes는 클러스터 내부 서비스용 DNS와 외부 도메인용 DNS를 모두 처리해야 하고, 네임스페이스별로 격리도 제공해야 하기 때문입니다.

예를 들어, 'mysql' 이라는 짧은 이름은 같은 네임스페이스의 서비스만 찾을 수 있습니다. 기존에는 IP 주소를 하드코딩하는 임시방편을 썼다면, 이제는 DNS 설정을 정확히 이해하고 문제를 근본적으로 해결할 수 있습니다.

핵심 구성요소는 네 가지입니다. CoreDNS Pod의 상태, Service의 DNS 레코드, Pod의 DNS 설정(resolv.conf), 그리고 DNS 정책입니다.

각 구성요소를 체계적으로 점검하면 DNS 문제를 해결할 수 있습니다.

코드 예제

# CoreDNS 상태 확인
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50

# Pod 내부에서 DNS 테스트
kubectl run dnstest --image=busybox:1.28 --rm -it -- sh
# nslookup kubernetes.default
# nslookup <service-name>.<namespace>.svc.cluster.local
# cat /etc/resolv.conf

# Service DNS 레코드 확인
kubectl get svc -A | grep <service-name>
# FQDN: <service>.<namespace>.svc.cluster.local

# CoreDNS 설정 확인
kubectl get configmap -n kube-system coredns -o yaml

# DNS 정책별 테스트
kubectl run test --image=busybox:1.28 --rm -it \
  --overrides='{"spec":{"dnsPolicy":"ClusterFirst"}}' -- nslookup google.com

설명

이것이 하는 일: DNS 트러블슈팅은 이름 해석 경로를 따라가며 각 단계를 검증하는 과정입니다. Pod → CoreDNS → 외부 DNS 순서로 문제를 격리시킵니다.

첫 번째로, CoreDNS Pod가 정상 작동하는지 확인합니다. kube-system 네임스페이스의 coredns 또는 kube-dns Pod가 Running 상태인지, 로그에 에러가 없는지 체크합니다.

CoreDNS가 CrashLoopBackOff 상태라면 전체 클러스터의 DNS가 마비됩니다. 두 번째로, 테스트용 Pod를 생성하여 DNS 쿼리를 직접 테스트합니다.

busybox 이미지의 nslookup을 사용하여 kubernetes.default 서비스부터 테스트합니다. 이게 작동하면 CoreDNS 기본 기능은 정상입니다.

그 다음 실제 문제가 되는 서비스를 FQDN으로 조회해봅니다. 세 번째로, Pod의 /etc/resolv.conf 파일을 확인합니다.

nameserver는 보통 CoreDNS Service의 ClusterIP이고, search 도메인들이 올바르게 설정되어 있는지 확인합니다. dnsPolicy 설정에 따라 이 파일의 내용이 달라집니다.

네 번째로, DNS 정책을 이해하고 적절히 설정합니다. ClusterFirst는 클러스터 DNS를 먼저 사용하고, Default는 노드의 DNS 설정을 사용합니다.

None으로 설정하면 dnsConfig로 직접 지정해야 합니다. 마지막으로, 서비스 이름 규칙을 정확히 이해해야 합니다.

같은 네임스페이스면 서비스 이름만, 다른 네임스페이스면 <service>.<namespace>, 완전한 이름은 <service>.<namespace>.svc.cluster.local 형식입니다. 여러분이 이 지식을 활용하면 마이크로서비스 간 통신 문제의 50% 이상을 해결할 수 있습니다.

특히 멀티 네임스페이스 환경에서 서비스 디스커버리가 안정적으로 작동합니다.

실전 팁

💡 busybox:1.28 버전을 사용하세요. 최신 버전은 nslookup 버그가 있을 수 있습니다.

💡 headless Service는 ClusterIP가 없으므로 A 레코드가 Pod IP를 직접 반환합니다.

💡 ExternalName Service는 CNAME을 반환하므로 외부 서비스를 클러스터 내부 이름으로 사용할 때 유용합니다.

💡 DNS 캐시 문제가 의심되면 Pod를 재시작하거나 dnsPolicy: None으로 TTL을 짧게 설정하세요.

💡 많은 DNS 쿼리가 발생하면 NodeLocal DNSCache를 도입하여 성능을 개선할 수 있습니다.


6. 컨테이너 이미지 Pull 실패

시작하며

여러분이 새 버전을 배포했는데 ImagePullBackOff 에러가 계속 발생하는 상황을 겪어보셨나요? 또는 프라이빗 레지스트리에서 이미지를 가져오려는데 인증 실패로 막혀본 경험이 있으신가요?

이미지 Pull 문제는 배포 과정에서 가장 먼저 마주치는 장애물입니다. 단순해 보이지만 레지스트리 종류, 인증 방식, 네트워크 설정 등 다양한 요인이 관련되어 있어 의외로 복잡합니다.

이미지 관련 문제를 체계적으로 해결하는 방법을 익히면, 배포 실패를 빠르게 복구하고 CI/CD 파이프라인의 안정성을 높일 수 있습니다. 특히 프라이빗 레지스트리 운영 시 필수적인 지식입니다.

개요

간단히 말해서, 이미지 Pull 실패는 이미지 이름/태그 오류, 레지스트리 인증 실패, 네트워크 연결 문제, 레지스트리 용량 제한에서 발생합니다. 각 원인별로 다른 해결 방법이 필요합니다.

왜 이런 문제가 자주 발생할까요? Kubernetes는 다양한 레지스트리(Docker Hub, ECR, GCR, Harbor 등)를 지원하고, 각각 다른 인증 방식을 사용하기 때문입니다.

예를 들어, Docker Hub는 최근 Rate Limit을 도입해서 인증 없이는 이미지 Pull이 제한됩니다. 기존에는 public 이미지로 바꾸거나 latest 태그만 사용하는 임시방편을 썼다면, 이제는 적절한 인증 설정과 이미지 관리 전략을 수립할 수 있습니다.

핵심 체크포인트는 다섯 가지입니다. 이미지 전체 경로 확인, imagePullSecrets 설정, 레지스트리 접근성, Rate Limit 확인, 그리고 이미지 존재 여부입니다.

이 순서로 점검하면 대부분의 이미지 문제를 해결할 수 있습니다.

코드 예제

# 이미지 Pull 에러 확인
kubectl describe pod <pod-name> | grep -A 10 "Events"

# ImagePullSecrets 생성 (Docker Hub)
kubectl create secret docker-registry regcred \
  --docker-server=docker.io \
  --docker-username=<username> \
  --docker-password=<password> \
  --docker-email=<email>

# Secret 확인
kubectl get secret regcred -o yaml
echo <base64-encoded-dockerconfigjson> | base64 -d

# Pod에 imagePullSecrets 적용
kubectl patch serviceaccount default \
  -p '{"imagePullSecrets": [{"name": "regcred"}]}'

# 레지스트리 연결 테스트
kubectl run test --image=curlimages/curl --rm -it -- \
  curl -I https://<registry-url>/v2/

# 이미지 Pull 정책 확인
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].imagePullPolicy}'

설명

이것이 하는 일: 이미지 Pull 트러블슈팅은 인증, 네트워크, 레지스트리 상태를 순차적으로 검증하는 과정입니다. 특히 프라이빗 레지스트리는 인증 설정이 핵심입니다.

첫 번째로, 이미지 이름이 정확한지 확인합니다. 레지스트리 URL, 네임스페이스(또는 프로젝트), 이미지 이름, 태그가 모두 정확해야 합니다.

docker.io/library/nginx:latest 처럼 전체 경로를 사용하면 모호함을 제거할 수 있습니다. 두 번째로, 프라이빗 레지스트리 인증을 설정합니다.

docker-registry 타입의 Secret을 생성하고, Pod spec에 imagePullSecrets를 추가하거나 ServiceAccount에 설정합니다. ServiceAccount에 설정하면 해당 SA를 사용하는 모든 Pod에 자동으로 적용됩니다.

세 번째로, 레지스트리 연결을 테스트합니다. 레지스트리의 /v2/ 엔드포인트에 접근 가능한지 확인하고, TLS 인증서가 유효한지 체크합니다.

자체 서명 인증서를 사용한다면 클러스터 노드에 CA 인증서를 설치해야 합니다. 네 번째로, Rate Limit이나 Quota를 확인합니다.

Docker Hub는 무료 계정에 대해 6시간당 100회로 제한하고, ECR이나 GCR도 리전별 제한이 있습니다. 헤더에서 RateLimit-Remaining 값을 확인할 수 있습니다.

마지막으로, imagePullPolicy를 적절히 설정합니다. Always는 항상 최신 이미지를 Pull하고, IfNotPresent는 로컬에 없을 때만 Pull합니다.

latest 태그는 기본적으로 Always이므로 주의가 필요합니다. 여러분이 이 프로세스를 마스터하면 배포 성공률을 크게 높일 수 있습니다.

특히 멀티 클라우드 환경에서 다양한 레지스트리를 효율적으로 관리할 수 있습니다.

실전 팁

💡 ECR 사용 시 aws ecr get-login-password를 주기적으로 실행해야 합니다. CronJob으로 자동화하세요.

💡 Harbor 같은 프라이빗 레지스트리는 프로젝트별로 Robot Account를 만들어 권한을 제한하세요.

💡 이미지 크기가 크면 Pull 시간이 오래 걸립니다. 멀티 스테이지 빌드로 이미지를 최적화하세요.

💡 미러 레지스트리를 구축하면 외부 의존성을 줄이고 Pull 속도를 개선할 수 있습니다.

💡 이미지 스캔 정책 때문에 차단될 수 있으므로, 레지스트리의 보안 정책을 확인하세요.


7. ConfigMap/Secret 마운트 문제

시작하며

여러분이 ConfigMap이나 Secret을 수정했는데 애플리케이션에 반영이 안 되는 경험을 해보셨나요? 또는 Secret을 마운트했는데 "permission denied" 에러가 발생하거나, 설정 파일이 보이지 않는 상황을 겪어보셨나요?

ConfigMap과 Secret은 애플리케이션 설정 관리의 핵심입니다. 하지만 볼륨 마운트, 환경변수 주입, 권한 설정 등 고려할 사항이 많아서 문제가 자주 발생합니다.

ConfigMap/Secret 관련 문제를 체계적으로 해결하는 방법을 익히면, 설정 변경을 안전하고 빠르게 배포할 수 있습니다. 특히 무중단 설정 업데이트는 프로덕션 환경에서 매우 중요합니다.

개요

간단히 말해서, ConfigMap/Secret 문제는 주로 이름 불일치, 키 오타, 마운트 경로 충돌, 권한 설정 오류에서 발생합니다. 볼륨 마운트와 환경변수 방식의 차이를 이해하면 대부분 해결됩니다.

왜 이런 문제가 복잡할까요? ConfigMap/Secret은 여러 방식(전체 마운트, 부분 마운트, 환경변수)으로 사용할 수 있고, 각각 업데이트 동작이 다르기 때문입니다.

예를 들어, 환경변수로 주입한 값은 Pod 재시작 없이는 업데이트되지 않습니다. 기존에는 Pod를 재시작하는 것으로 해결했다면, 이제는 적절한 마운트 방식을 선택하고 자동 리로드를 구현할 수 있습니다.

핵심 개념은 네 가지입니다. 볼륨 마운트 vs 환경변수, subPath 사용 시 주의점, 파일 권한 설정, 그리고 업데이트 전파 메커니즘입니다.

이를 정확히 이해하면 설정 관리가 훨씬 쉬워집니다.

코드 예제

# ConfigMap/Secret 확인
kubectl get configmap <name> -o yaml
kubectl get secret <name> -o jsonpath='{.data}' | base64 -d

# Pod의 마운트 상태 확인
kubectl describe pod <pod-name> | grep -A 20 "Mounts\|Environment"

# ConfigMap을 볼륨으로 마운트
kubectl exec <pod-name> -- ls -la /config
kubectl exec <pod-name> -- cat /config/application.yaml

# 실시간 업데이트 확인 (볼륨 마운트의 경우)
kubectl edit configmap <name>
# 약 1분 후 Pod 내부 파일 확인
kubectl exec <pod-name> -- cat /config/application.yaml

# subPath 사용 시 (업데이트 안 됨)
volumes:
- name: config
  configMap:
    name: app-config
volumeMounts:
- name: config
  mountPath: /app/config.yaml
  subPath: config.yaml  # 주의: 자동 업데이트 안 됨

설명

이것이 하는 일: ConfigMap/Secret 트러블슈팅은 데이터 전달 경로와 업데이트 메커니즘을 이해하는 과정입니다. 각 마운트 방식의 특성을 파악해야 합니다.

첫 번째로, ConfigMap/Secret이 올바르게 생성되었는지 확인합니다. 특히 Secret의 경우 base64 인코딩이 올바른지, 키 이름에 특수문자가 없는지 체크합니다.

YAML에서는 대시(-)가 포함된 키를 따옴표로 감싸야 합니다. 두 번째로, 마운트 방식을 이해합니다.

전체 볼륨 마운트는 ConfigMap의 모든 키가 파일로 생성되고, items를 사용한 부분 마운트는 선택한 키만 파일로 생성됩니다. 기존 디렉토리에 마운트하면 원래 파일들이 가려집니다.

세 번째로, subPath의 제약사항을 이해합니다. subPath를 사용하면 특정 파일만 마운트할 수 있지만, ConfigMap 업데이트가 자동으로 반영되지 않습니다.

실시간 업데이트가 필요하면 전체 디렉토리를 마운트해야 합니다. 네 번째로, 파일 권한을 적절히 설정합니다.

defaultMode로 파일 권한을 설정할 수 있고, Secret의 경우 보안을 위해 0400 같은 제한적인 권한을 사용합니다. 특정 사용자/그룹이 필요하면 fsGroup을 설정합니다.

마지막으로, 애플리케이션의 설정 리로드 방식을 고려합니다. 파일 변경을 감지하여 자동 리로드하는 애플리케이션도 있고, SIGHUP 시그널이 필요한 경우도 있습니다.

Reloader 같은 오퍼레이터를 사용하면 자동화할 수 있습니다. 여러분이 이 지식을 활용하면 설정 변경을 무중단으로 배포하고, 민감 정보를 안전하게 관리할 수 있습니다.

특히 환경별 설정 관리가 체계화됩니다.

실전 팁

💡 ConfigMap 크기 제한은 1MB입니다. 더 큰 파일은 PV를 사용하거나 Init Container로 다운로드하세요.

💡 Secret은 etcd에 평문으로 저장됩니다. Sealed Secrets나 External Secrets Operator 사용을 고려하세요.

💡 환경변수명은 대문자와 언더스코어만 사용하세요. 특수문자가 있으면 주입이 실패합니다.

💡 Kustomize나 Helm을 사용하면 환경별 ConfigMap 관리가 편리합니다.

💡 immutable: true로 설정하면 실수로 변경하는 것을 방지하고 성능도 향상됩니다.


8. 네트워크 정책 차단

시작하며

여러분이 마이크로서비스 간 통신을 테스트하는데 갑자기 특정 서비스만 연결이 안 되는 경험을 해보셨나요? 모든 설정이 올바른데도 connection timeout이 발생하고, 다른 Pod에서는 잘 되는데 특정 Pod에서만 안 되는 이상한 상황 말입니다.

NetworkPolicy는 Kubernetes의 마이크로 세그멘테이션을 구현하는 핵심 기능입니다. 보안을 강화하지만, 잘못 설정하면 정상적인 통신까지 차단하여 장애를 일으킵니다.

NetworkPolicy 트러블슈팅 방법을 체계적으로 익히면, 보안과 연결성의 균형을 맞출 수 있습니다. 특히 Zero Trust 네트워크를 구축할 때 필수적인 지식입니다.

개요

간단히 말해서, NetworkPolicy 문제는 주로 정책 우선순위 이해 부족, Label Selector 불일치, Ingress/Egress 규칙 혼동, 포트 지정 누락에서 발생합니다. 트래픽 방향과 정책 적용 범위를 정확히 파악해야 합니다.

왜 NetworkPolicy가 어려울까요? 기본적으로 허용(allow-all)인 상태에서 명시적 차단이 아닌, 하나라도 정책이 있으면 기본 차단(deny-all)으로 바뀌는 특성 때문입니다.

예를 들어, Ingress 정책만 만들면 Egress는 여전히 허용되지만, Egress 정책을 하나라도 만들면 나머지는 모두 차단됩니다. 기존에는 NetworkPolicy를 삭제하는 것으로 해결했다면, 이제는 정확한 규칙을 작성하여 보안을 유지하면서도 필요한 통신은 허용할 수 있습니다.

핵심 개념은 네 가지입니다. 정책 선택자(podSelector), 트래픽 방향(Ingress/Egress), 피어 선택자(from/to), 그리고 포트 규칙입니다.

이 네 가지를 조합하면 세밀한 네트워크 제어가 가능합니다.

코드 예제

# NetworkPolicy 조회
kubectl get networkpolicy -A
kubectl describe networkpolicy <policy-name>

# 특정 Pod에 적용된 정책 찾기
kubectl get networkpolicy -o json | \
  jq '.items[] | select(.spec.podSelector.matchLabels | to_entries[] | .key + "=" + .value | test("app=frontend"))'

# 트래픽 테스트용 Pod 생성
kubectl run netshoot --image=nicolaka/netshoot -it --rm

# 네트워크 연결 테스트
kubectl exec netshoot -- nc -zv <service-name> <port>
kubectl exec netshoot -- curl -I http://<service-name>:<port>

# 기본 거부 정책 생성
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes: ["Ingress", "Egress"]
EOF

설명

이것이 하는 일: NetworkPolicy 트러블슈팅은 트래픽 흐름을 분석하고 정책 적용 범위를 파악하는 과정입니다. 어떤 정책이 어떤 Pod에 영향을 주는지 정확히 매핑해야 합니다.

첫 번째로, 대상 Pod에 어떤 NetworkPolicy가 적용되는지 확인합니다. podSelector가 Pod의 Label과 매칭되는 모든 정책이 적용됩니다.

여러 정책이 있으면 OR 조건으로 작동합니다(하나라도 허용하면 통과). 두 번째로, 트래픽 방향을 명확히 합니다.

Ingress는 들어오는 트래픽, Egress는 나가는 트래픽을 제어합니다. policyTypes 필드에 명시된 방향만 제어되고, 명시되지 않은 방향은 영향받지 않습니다.

세 번째로, from/to 선택자를 정확히 이해합니다. podSelector는 같은 네임스페이스의 Pod를, namespaceSelector는 특정 네임스페이스를, ipBlock은 IP 대역을 지정합니다.

이들을 AND/OR 조합으로 사용할 수 있습니다. 네 번째로, 포트 규칙을 확인합니다.

포트를 명시하지 않으면 모든 포트가 허용됩니다. TCP/UDP 프로토콜도 명시해야 하며, 기본값은 TCP입니다.

마지막으로, DNS 트래픽을 잊지 마세요. Egress 정책을 적용할 때 kube-system 네임스페이스의 DNS (포트 53) 접근을 허용해야 서비스 이름 해석이 가능합니다.

여러분이 이 개념을 마스터하면 제로 트러스트 네트워크를 구현하면서도 운영 이슈를 최소화할 수 있습니다. 특히 컴플라이언스 요구사항을 만족시키는 보안 아키텍처를 구축할 수 있습니다.

실전 팁

💡 NetworkPolicy는 CNI 플러그인이 지원해야 작동합니다. Calico, Cilium은 지원하지만 Flannel은 미지원입니다.

💡 kubectl-np-viewer 같은 도구를 사용하면 정책을 시각화해서 볼 수 있습니다.

💡 개발 환경에서는 먼저 로깅 모드로 테스트하고, 확인 후 차단 모드로 전환하세요.

💡 Egress 정책 적용 시 반드시 DNS (UDP/TCP 53)를 허용하세요. 안 그러면 서비스 이름 해석이 안 됩니다.

💡 정책 작성 시 앱별로 하나씩 만들지 말고, 역할별(frontend, backend, database)로 그룹화하면 관리가 쉽습니다.


#Kubernetes#Troubleshooting#Debugging#Monitoring#DevOps#React

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.