Kubernetes 애플리케이션 트러블슈팅
개요
이 문서는 Kubernetes 클러스터에 워크로드를 배포하는 과정이나 운영 도중 발생할 수 있는 문제의 원인과 해결 방법을 안내합니다.
워크로드 유형 중 가장 많이 사용되는 웹 애플리케이션을 기준으로 설명하며, 바닐라 Kubernetes와 퍼블릭 클라우드의 관리형 Kubernetes 서비스는 물론 Samsung Cloud Platform Kubernetes Engine(이하 SKE) 환경에서도 동일하게 참고할 수 있습니다.
애플리케이션 일반 구조
Kubernetes에서 웹 애플리케이션을 외부에 노출하는 방식에는 hostNetwork, NodePort, LoadBalancer 타입 Service 등 여러 가지가 있습니다. 이 문서에서는 HTTP/HTTPS 기반 도메인 호출에 가장 적합한 Ingress 방식을 기준으로 설명합니다.
다음은 nginx 웹 애플리케이션을 Kubernetes 워크로드로 배포할 때 사용하는 리소스 조합의 예입니다.
이 외에도 PersistentVolume이나 NetworkPolicy 등 다양한 리소스를 함께 사용할 수 있지만, 위 예시가 Ingress를 사용하는 가장 기본적인 리소스 구성입니다.
apiVersion: extensions/v1beta1과 kubernetes.io/ingress.class 어노테이션은 Kubernetes 1.22부터 제거된 구버전 API입니다. 실제 매니페스트를 작성할 때는 다음과 같이 networking.k8s.io/v1 API와 spec.ingressClassName 필드를 사용하세요.apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
ingressClassName: nginx
rules:
- host: foo.bar.com
http:
paths:
- path: /foo
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
ingressClassName: nginx
rules:
- host: foo.bar.com
http:
paths:
- path: /foo
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80여기서 중요한 것은 각 리소스 간의 참조 관계입니다. Deployment와 Service의 selector는 모두 Pod template의 label을 참조하고, Ingress의 backend에 지정한 서비스명은 Service 리소스의 name을 참조합니다. 이후 설명하는 대부분의 문제는 이 참조 관계가 어긋나면서 발생하므로 반드시 기억해 두세요.
이어지는 내용에서는 각 리소스가 배포되었을 때의 정상 상태 점검 방법과, 문제가 발생했을 때의 대처 방법을 살펴봅니다.
Pod 상태 점검
기본 명령어
Pod의 상태를 점검할 때 사용하는 kubectl 명령어는 다음과 같습니다.
Pod의 기본 상태를 확인하는 명령어입니다. IP와 할당된 노드 정보까지 함께 보려면 -owide 옵션을, YAML 매니페스트 형태의 원본 데이터를 보려면 -oyaml 옵션을 사용하세요.
kubectl get pod
kubectl get pod -owide
kubectl get pod <pod-name> -oyamlkubectl get pod
kubectl get pod -owide
kubectl get pod <pod-name> -oyaml문제가 발생한 Pod의 상세 정보와 Event를 확인할 때 사용하는 명령어입니다.
kubectl describe po <pod-name>kubectl describe po <pod-name>실행 중인 애플리케이션에 문제가 생겼을 때 로그를 확인하는 명령어입니다. Pod 안에서 여러 Container가 구동 중이라면 -c 옵션으로 특정 Container를 지정할 수 있고, -f 옵션으로 실시간 tailing을, --previous 옵션으로 재시작 이전 Container의 로그를 확인할 수 있습니다.
kubectl logs <pod-name> [-c <container-name>]
kubectl logs <pod-name> [-c <container-name>] -f
kubectl logs <pod-name> [-c <container-name>] --previouskubectl logs <pod-name> [-c <container-name>]
kubectl logs <pod-name> [-c <container-name>] -f
kubectl logs <pod-name> [-c <container-name>] --previous네임스페이스 안에서 발생한 Event를 조회하는 명령어입니다. --sort-by 옵션으로 발생 시간순 정렬이 가능합니다.
kubectl get ev --sort-by=.lastTimestampkubectl get ev --sort-by=.lastTimestamp--event-ttl 기본값)만 보관되므로, 오래된 장애는 Event로 확인되지 않을 수 있습니다. Kubernetes 1.26 이상에서는 kubectl events --for pod/<pod-name> 명령으로 특정 오브젝트의 Event만 시간순으로 조회할 수 있습니다.정상적인 Pod Lifecycle
비정상 상태를 판별하려면 먼저 Pod를 배포했을 때 정상적으로 거치는 상태 변화 흐름을 알아야 합니다.
일반적인 Pod는 다음과 같은 Lifecycle을 갖습니다.
- Pending: Pod가 스케줄될 노드가 결정되기 전, 또는 스케줄 가능한 노드가 없는 상태입니다.
- ContainerCreating: Pod가 특정 노드에 스케줄된 후 해당 노드의 Container Runtime이 Container를 생성하는 단계입니다. 이미지를 내려받거나 ConfigMap, PersistentVolume 등 연계 자원을 마운트하는 과정에서 표시될 수 있습니다.
- Running: Pod 내 Container가 모두 생성되고 프로세스가 구동 중인 상태입니다.
- Ready: Running 이후 Readiness Probe를 통과하여 Pod가 서비스할 준비가 되었음을 의미합니다.
- Terminating: Pod가 삭제되어 해당 노드에서 정리되는 과정에서 표시됩니다.
위 다섯 가지는 kubectl get pod의 STATUS/READY 컬럼에 표시되는 값을 기준으로 한 것으로, Kubernetes API가 정의한 Pod phase(status.phase)와는 다릅니다. Pod phase는 Pending, Running, Succeeded, Failed, Unknown 다섯 가지뿐이며, ContainerCreating이나 Terminating은 kubectl이 Container 상태와 삭제 시각(deletionTimestamp)을 조합해 보여 주는 값입니다.
또한 Ready는 STATUS 컬럼이 아니라 READY 컬럼(예: 1/1)으로 확인합니다. STATUS는 Running을 유지한 채 READY 값만 0/1에서 1/1로 바뀌는 것이 정상적인 흐름입니다.
kubectl get po 명령어에 -w(watch) 옵션을 사용하면 다음과 같이 Pod Lifecycle 변화를 확인할 수 있습니다. Pod의 정상 여부를 판단하는 가장 기본적인 컬럼은 READY와 STATUS입니다.
kubectl get po -owide -w
NAME READY STATUS AGE IP NODE
nginx-5d796fc999-qkgpp 0/1 Pending 0s <none> <none>
nginx-5d796fc999-qkgpp 0/1 Pending 0s <none> node1
nginx-5d796fc999-qkgpp 0/1 ContainerCreating 1s <none> node1
nginx-5d796fc999-qkgpp 0/1 Running 3s 10.44.0.2 node1
nginx-5d796fc999-qkgpp 1/1 Running 9s 10.44.0.2 node1kubectl get po -owide -w
NAME READY STATUS AGE IP NODE
nginx-5d796fc999-qkgpp 0/1 Pending 0s <none> <none>
nginx-5d796fc999-qkgpp 0/1 Pending 0s <none> node1
nginx-5d796fc999-qkgpp 0/1 ContainerCreating 1s <none> node1
nginx-5d796fc999-qkgpp 0/1 Running 3s 10.44.0.2 node1
nginx-5d796fc999-qkgpp 1/1 Running 9s 10.44.0.2 node1다음부터는 비정상 Pod 상태가 발생하는 사례별로 원인과 조치 방안을 알아보겠습니다.
Pending 상태가 지속되는 경우
Pod가 Pending 상태에 장시간 머무는 경우가 있습니다.
kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-6dcd8d4dff-njsrh 0/1 Pending 0 10mkubectl get po
NAME READY STATUS RESTARTS AGE
nginx-6dcd8d4dff-njsrh 0/1 Pending 0 10m이런 경우에는 먼저 kubectl describe po 명령어로 Events 메시지를 확인하세요.
kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 14s (x2 over 14s) default-scheduler 0/3 nodes are available: 3 Insufficient cpu, 3 Insufficient memory.kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 14s (x2 over 14s) default-scheduler 0/3 nodes are available: 3 Insufficient cpu, 3 Insufficient memory.클러스터의 할당 가능한 자원이 부족한 경우입니다. kubectl describe node 명령어로 각 노드의 Allocated resources를 확인하고, 필요하면 노드를 증설하거나 Pod의 자원 요청량을 줄이세요.
resources.requests 값의 합계를 기준으로 노드를 선택합니다. 따라서 kubectl top node로 확인한 실사용률이 낮더라도 requests 합계가 노드의 Allocatable을 초과하면 스케줄되지 않습니다. kubectl describe node의 Allocated resources 항목이 requests 기준 값입니다.kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 5s (x2 over 5s) default-scheduler 0/3 nodes are available: 3 node(s) didn't match Pod's node affinity/selector.kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 5s (x2 over 5s) default-scheduler 0/3 nodes are available: 3 node(s) didn't match Pod's node affinity/selector.Pod에 설정된 nodeSelector 또는 nodeAffinity와 일치하는 노드가 없는 상태입니다. Pod의 nodeSelector/nodeAffinity를 실제 노드 정보에 맞게 수정하거나, 대상 노드에 label을 추가해서 Pod가 정상적으로 스케줄되도록 조치하세요.
kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 6s (x2 over 6s) default-scheduler 0/3 nodes are available: 3 node(s) were unschedulable.kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 6s (x2 over 6s) default-scheduler 0/3 nodes are available: 3 node(s) were unschedulable.모든 노드가 Cordon(spec.unschedulable: true) 되어 스케줄 예약이 불가능한 상태입니다. kubectl get no 명령어로 STATUS가 SchedulingDisabled인 노드를 확인하고 kubectl uncordon <node-name> 명령어로 해제하세요.
3 node(s) had untolerated taint {node.kubernetes.io/not-ready: }처럼 taint를 언급하는 메시지가 표시됩니다. Taint 관련 메시지가 보인다면 해당 노드의 kubelet 및 CNI 상태를 먼저 점검하고, 의도적으로 지정한 taint라면 Pod에 toleration을 추가하세요.kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 53s (x2 over 60s) default-scheduler error while running "VolumeBinding" filter plugin for pod "nginx-9d8d9896-k7gzj": pod has unbound immediate PersistentVolumeClaimskubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 53s (x2 over 60s) default-scheduler error while running "VolumeBinding" filter plugin for pod "nginx-9d8d9896-k7gzj": pod has unbound immediate PersistentVolumeClaimsPVC(PersistentVolumeClaim)가 Pending 상태는 아닌지 다음과 같이 확인하세요.
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nginx-pvc Pending standard 3mkubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nginx-pvc Pending standard 3m정적 PV(PersistentVolume)를 사용하는 경우에는 Available 상태의 PV가 먼저 생성되어 있는지, PVC의 volumeName이 해당 PV name과 일치하는지, 그리고 용량과 accessModes가 PVC 요구 사항을 충족하는지 확인하세요.
동적 PV를 사용하는 경우에는 PVC에 지정한 storageClassName의 StorageClass가 존재하는지 확인하고, 해당 Volume Provisioner(CSI Driver) Pod가 정상 동작하는지도 함께 확인하세요. PVC를 describe하면 프로비저닝 실패 사유가 Event로 기록됩니다.
volumeBindingMode가 WaitForFirstConsumer인 경우에는 Pod가 스케줄되기 전까지 PVC가 Pending 상태로 유지되는 것이 정상 동작입니다. 이때는 위와 같은 unbound immediate PersistentVolumeClaims 메시지가 아니라 다른 스케줄링 사유가 함께 표시되므로, PVC가 아닌 그 사유를 먼저 확인하세요.kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 21s default-scheduler Successfully assigned default/nginx-5d796fc999-6ksbj to node1
kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE
nginx-5d796fc999-6ksbj 0/1 Pending 0 37s <none> node1kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 21s default-scheduler Successfully assigned default/nginx-5d796fc999-6ksbj to node1
kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE
nginx-5d796fc999-6ksbj 0/1 Pending 0 37s <none> node1노드에 정상적으로 할당되었다는 이벤트만 있고 이후 kubelet이 남기는 이벤트(Pulling, Created, Started 등)가 전혀 없다면, 해당 노드의 kubelet이 동작하지 않는 상태입니다. -owide 조회 결과에 표시된 노드의 kubelet 상태를 확인하고 조치하세요.
kubectl describe po <pod-name>
Events: <none>
kubectl get po -n kube-system
NAME READY STATUS RESTARTS AGE
kube-scheduler-master 0/1 CrashLoopBackOff 9 16mkubectl describe po <pod-name>
Events: <none>
kubectl get po -n kube-system
NAME READY STATUS RESTARTS AGE
kube-scheduler-master 0/1 CrashLoopBackOff 9 16mEvent가 전혀 기록되지 않고 노드 할당도 이루어지지 않았다면 kube-scheduler가 스케줄 작업을 수행하지 못하는 상태이므로, kube-scheduler의 상태와 로그를 확인하고 조치하세요.
ContainerCreating 상태가 지속되는 경우
다음은 Pod가 ContainerCreating 상태에 머물러 있는 경우입니다.
kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-6dcd8d4dff-njsrh 0/1 ContainerCreating 0 1mkubectl get po
NAME READY STATUS RESTARTS AGE
nginx-6dcd8d4dff-njsrh 0/1 ContainerCreating 0 1m이런 경우에도 먼저 kubectl describe po 명령어로 Events 메시지를 확인하세요.
kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 15s default-scheduler Successfully assigned default/nginx-58cd5b85f7-cmb6c to node1
Normal Pulling 12s kubelet Pulling image "nginx:1.13.9"kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 15s default-scheduler Successfully assigned default/nginx-58cd5b85f7-cmb6c to node1
Normal Pulling 12s kubelet Pulling image "nginx:1.13.9"Events가 Pulling image 단계에서 멈춰 있다면 아직 이미지를 내려받는 중입니다. 이미지 용량이 크거나 네트워크 대역폭이 부족하면 Pulling 시간이 길어질 수 있으며, 내려받기가 끝나면 자연스럽게 해소되므로 조금 더 기다려 보세요.
kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedMount 5s (x6 over 21s) kubelet MountVolume.SetUp failed for volume "config-volume" : configmap "nginx-cm" not foundkubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedMount 5s (x6 over 21s) kubelet MountVolume.SetUp failed for volume "config-volume" : configmap "nginx-cm" not foundPod가 사용하는 ConfigMap 또는 Secret이 생성되어 있지 않거나 이름을 잘못 지정한 경우입니다. Pod Specification의 volumes 필드에 지정된 리소스가 같은 네임스페이스에 실제로 존재하는지 확인하세요.
env.valueFrom.configMapKeyRef 등)로 참조했는데 해당 리소스나 key가 없으면, ContainerCreating이 아니라 CreateContainerConfigError 상태로 표시됩니다. 이 경우에도 확인 방법과 조치 방법은 동일합니다.kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedMount 18s kubelet MountVolume.SetUp failed for volume "pvc-d2def29a-d503-443e-9615-886713983216" : mount failed: exit status 32kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedMount 18s kubelet MountVolume.SetUp failed for volume "pvc-d2def29a-d503-443e-9615-886713983216" : mount failed: exit status 32Pod가 사용하는 PV가 노드에 마운트되지 않아 발생하는 문제입니다. 해당 PV를 사용하기 위한 CSI(Container Storage Interface) Driver나, NFS를 사용하는 경우 nfs-utils, rpcbind 같은 클라이언트 패키지가 노드에 설치되어 있는지 확인하세요. 추가로 노드에서 스토리지로의 접근 제어와 방화벽을 확인해서 마운트가 가능하도록 조치하세요.
kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedCreatePodSandBox 9s kubelet Failed to create pod sandbox: plugin type="calico" failed (add): failed to allocate for range 0: no IP addresses available in range setkubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedCreatePodSandBox 9s kubelet Failed to create pod sandbox: plugin type="calico" failed (add): failed to allocate for range 0: no IP addresses available in range set노드에 할당된 Pod CIDR의 IP가 모두 소진된 경우입니다. 노드당 Pod 수(maxPods)와 Pod CIDR 크기를 확인하고, 불필요한 Pod를 정리하거나 노드를 증설하세요. CNI Plugin Pod가 비정상일 때도 FailedCreatePodSandBox가 발생할 수 있으므로 Pod 통신 실패의 CNI 점검 방법도 함께 참고하세요.
ImagePullBackOff 상태로 표시되는 경우
이번에는 ImagePullBackOff가 발생하는 상황을 알아보겠습니다.
kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-6dcd8d4dff-njsrh 0/1 ImagePullBackOff 0 6skubectl get po
NAME READY STATUS RESTARTS AGE
nginx-6dcd8d4dff-njsrh 0/1 ImagePullBackOff 0 6sErrImagePull로 표시되고, kubelet이 재시도를 backoff하기 시작하면 ImagePullBackOff로 바뀝니다. 두 상태의 원인과 조치 방법은 동일합니다.마찬가지로 describe 명령어로 원인을 확인할 수 있습니다.
kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Failed 23s (x2 over 44s) kubelet Failed to pull image "nginx:invalid-tag": rpc error: code = Unknown desc = Error response from daemon: manifest for nginx:invalid-tag not foundkubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Failed 23s (x2 over 44s) kubelet Failed to pull image "nginx:invalid-tag": rpc error: code = Unknown desc = Error response from daemon: manifest for nginx:invalid-tag not foundImage Repository에 해당 Image:Tag가 없는 경우입니다. Pod Specification의 이미지 값을 올바른 Image:Tag로 수정하거나, 해당 Image:Tag를 Image Repository에 Push하세요.
kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Failed 9s kubelet Failed to pull image "myregistry.io/nginx:1.17.8": rpc error: code = Unknown desc = Error response from daemon: Get https://myregistry.io/v2/nginx/manifests/1.17.8: no basic auth credentialskubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Failed 9s kubelet Failed to pull image "myregistry.io/nginx:1.17.8": rpc error: code = Unknown desc = Error response from daemon: Get https://myregistry.io/v2/nginx/manifests/1.17.8: no basic auth credentialsPrivate Registry를 사용하는 경우에는 Credential 정보가 필요합니다. 올바른 Credential로 kubernetes.io/dockerconfigjson 타입의 Secret을 만들어 Pod Specification의 imagePullSecrets에 추가하세요. 이미 imagePullSecrets을 사용 중이라면 자격 증명이 만료되지 않았는지, 그리고 Secret이 Pod와 같은 네임스페이스에 있는지 함께 확인하세요.
kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Failed 12s kubelet Failed to pull image "nginx:1.17.8": rpc error: code = Unknown desc = Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Failed 12s kubelet Failed to pull image "nginx:1.17.8": rpc error: code = Unknown desc = Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)노드에서 Image Repository와의 통신이 실패한 경우입니다. 노드와 Image Repository 간 방화벽 및 프록시 설정, 그리고 Image Repository 서비스가 정상 운영 중인지 확인하세요.
toomanyrequests: You have reached your pull rate limit 메시지와 함께 실패할 수 있습니다. 이는 익명 사용자에 대한 이미지 내려받기 횟수 제한 때문이므로, 인증된 계정의 imagePullSecrets을 사용하거나 Container Registry에 이미지를 미러링해서 사용하세요.Init 컨테이너 단계에서 실패하는 경우
Pod에 초기화 컨테이너(initContainer)가 있는 경우, 애플리케이션 Container가 시작되기 전에 Init: 접두어가 붙은 상태로 멈출 수 있습니다.
kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-6dcd8d4dff-njsrh 0/1 Init:CrashLoopBackOff 4 2mkubectl get po
NAME READY STATUS RESTARTS AGE
nginx-6dcd8d4dff-njsrh 0/1 Init:CrashLoopBackOff 4 2mInit:0/1에서 진행되지 않으면 초기화 컨테이너가 아직 실행 중인 상태이고, Init:Error 또는 Init:CrashLoopBackOff는 초기화 컨테이너가 0이 아닌 종료 코드로 끝난 상태입니다. 초기화 컨테이너의 로그를 직접 지정해서 확인하세요.
kubectl logs <pod-name> -c <init-container-name>kubectl logs <pod-name> -c <init-container-name>초기화 컨테이너는 주로 설정 파일 생성, 스키마 마이그레이션, 선행 서비스 대기 용도로 사용되므로, 연계 서비스와의 통신 실패나 권한 부족이 원인인 경우가 많습니다. 로그에서 확인된 원인을 조치하세요.
CrashLoopBackOff와 Running이 반복되는 경우
Pod가 CrashLoopBackOff와 Running 상태를 주기적으로 반복하는 경우가 있습니다.
kubectl get po -w
NAME READY STATUS RESTARTS AGE
test-7f74c45f58-jm629 0/1 CrashLoopBackOff 8 16m
test-7f74c45f58-jm629 0/1 Running 9 16mkubectl get po -w
NAME READY STATUS RESTARTS AGE
test-7f74c45f58-jm629 0/1 CrashLoopBackOff 8 16m
test-7f74c45f58-jm629 0/1 Running 9 16m애플리케이션 프로세스가 기동 직후 비정상 종료되어 kubelet이 재시작을 반복하는 상태입니다. 다음 명령어로 현재 또는 이전 Container의 로그를 확인하고, 로그에서 원인이 확인되면 애플리케이션 문제를 해결하세요.
kubectl logs <pod-name> -f
kubectl logs <pod-name> --previouskubectl logs <pod-name> -f
kubectl logs <pod-name> --previous주로 발생하는 애플리케이션 이슈로는 DB 연결 실패, 잘못된 command 사용, 애플리케이션 설정 오류 등이 있습니다.
로그를 확인할 수 없거나 로그에 아무것도 남지 않는 경우에는 describe 명령어로 Last State의 종료 사유와 Exit Code를 확인하세요.
kubectl describe po <pod-name>
Last State: Terminated
Reason: ContainerCannotRun
Message: OCI runtime create failed: container_linux.go:344: starting container process caused "exec: \"ping\": executable file not found in $PATH": unknown
Exit Code: 127kubectl describe po <pod-name>
Last State: Terminated
Reason: ContainerCannotRun
Message: OCI runtime create failed: container_linux.go:344: starting container process caused "exec: \"ping\": executable file not found in $PATH": unknown
Exit Code: 127127은 실행 파일을 찾을 수 없는 경우, 126은 실행 권한이 없는 경우, 137은 SIGKILL(주로 OOMKilled), 143은 SIGTERM에 의한 종료를 의미합니다.CrashLoopBackOff와 Completed가 반복되는 경우
Pod가 CrashLoopBackOff와 Completed를 반복하는 경우도 있습니다.
kubectl get po -w
NAME READY STATUS RESTARTS AGE
test-6fd77b68b9-44hxs 0/1 CrashLoopBackOff 8 16m
test-6fd77b68b9-44hxs 0/1 Completed 9 16mkubectl get po -w
NAME READY STATUS RESTARTS AGE
test-6fd77b68b9-44hxs 0/1 CrashLoopBackOff 8 16m
test-6fd77b68b9-44hxs 0/1 Completed 9 16mContainer의 주 프로세스가 정상 종료 코드(0)로 끝났지만, Pod의 restartPolicy가 기본값인 Always이기 때문에 kubelet이 계속 재시작하는 상태입니다. 즉, Container 안에서 계속 떠 있는 Foreground 프로세스가 없는 경우입니다.
Dockerfile의 CMD 또는 ENTRYPOINT에 Foreground로 동작하는 프로세스를 명시하거나, Deployment의 command/args를 수정하세요. 한 번 실행하고 끝나는 배치성 프로세스라면 Deployment가 아니라 Job 또는 CronJob으로 생성해야 합니다.
Ready 상태로 변경되지 않는 경우
Pod가 Running 상태이지만 READY 값이 1/1로 바뀌지 않는 경우입니다.
kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-659484b897-mnq4l 0/1 Running 0 2m14skubectl get po
NAME READY STATUS RESTARTS AGE
nginx-659484b897-mnq4l 0/1 Running 0 2m14s이 경우에도 describe 명령어로 Events를 확인하세요.
kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Unhealthy 5s (x7 over 65s) kubelet Readiness probe failed: HTTP probe failed with statuscode: 404kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Unhealthy 5s (x7 over 65s) kubelet Readiness probe failed: HTTP probe failed with statuscode: 404Readiness Probe가 실패하고 있다면 Probe에 설정한 path, port, scheme이 애플리케이션이 실제로 응답하는 값과 일치하는지 확인하세요. 위 예시처럼 404가 반환된다면 Probe의 path가 잘못 지정된 경우입니다.
initialDelaySeconds를 늘리는 대신 startupProbe를 사용하는 것을 권장합니다. startupProbe가 성공할 때까지 Readiness/Liveness Probe가 비활성화되므로, 기동은 충분히 기다리면서도 기동 후에는 짧은 주기로 상태를 감지할 수 있습니다.Events에 Probe 실패 메시지가 없는데도 READY가 0/1이라면, Readiness Probe 자체가 정의되지 않았는지 또는 Pod 안의 여러 Container 중 일부만 준비된 상태(예: 1/2)는 아닌지 확인하세요.
Terminating 상태가 지속되는 경우
이번에는 Terminating 상태가 지속되는 경우를 알아보겠습니다.
kubectl get po -owide
NAME READY STATUS RESTARTS AGE NODE
nginx-6dcd8d4dff-njsrh 0/1 Terminating 0 10m node2
nginx-6dcd8d4dff-fvlmf 1/1 Running 0 43s node1kubectl get po -owide
NAME READY STATUS RESTARTS AGE NODE
nginx-6dcd8d4dff-njsrh 0/1 Terminating 0 10m node2
nginx-6dcd8d4dff-fvlmf 1/1 Running 0 43s node1먼저 Pod가 할당된 노드가 NotReady 상태는 아닌지 확인하세요.
kubectl get no
NAME STATUS ROLES AGE VERSION
master Ready master 39d v1.21.2
node1 Ready worker 39d v1.21.2
node2 NotReady worker 39d v1.21.2kubectl get no
NAME STATUS ROLES AGE VERSION
master Ready master 39d v1.21.2
node1 Ready worker 39d v1.21.2
node2 NotReady worker 39d v1.21.2노드가 NotReady 상태이면 kubelet이 Pod 정리를 완료했다고 API Server에 보고할 수 없기 때문에, Pod가 Terminating 상태로 남습니다. 이때 워크로드 종류에 따라 대체 Pod의 생성 정책이 다르다는 점도 알아 두어야 합니다.
- Deployment: 다른 노드에 대체 Pod가 생성됩니다. Failover가 이루어졌는지 확인하세요.
- StatefulSet: 동일 identity의 Pod가 중복 실행되지 않도록 다른 노드로 이동하지 않습니다. 원본 Pod를 강제 삭제해야 이동합니다.
- DaemonSet: 노드마다 하나씩 배치되는 특성상 다른 노드로 이동하지 않습니다.
node.kubernetes.io/not-ready:NoExecute taint를 부여하고, Pod에 자동으로 추가되는 tolerationSeconds(기본 300초)만큼 기다린 후 Eviction을 시작합니다. 따라서 대체 Pod는 노드 이상 감지 후 약 5분 뒤에 생성됩니다.Pod가 할당된 노드가 Ready 상태인데도 Terminating이 지속된다면 다음을 순서대로 확인하세요.
- Finalizer 잔존: Pod에 finalizer가 남아 있으면 삭제가 완료되지 않습니다.
kubectl get po <pod-name> -oyaml로metadata.finalizers를 확인하고, 해당 finalizer를 처리하는 컨트롤러(Operator, CSI Driver 등)가 정상 동작 중인지 점검하세요. - 종료 유예 시간 및 preStop Hook:
terminationGracePeriodSeconds가 크게 설정되어 있거나preStopHook이 끝나지 않으면 그 시간만큼 Terminating이 유지됩니다. 이는 정상 동작이므로 설정값을 먼저 확인하세요. - 볼륨 정리 실패: Pod 종료 과정에서 PV의 unmount 또는 detach가 처리되지 않았을 수 있으므로 스토리지 연결 상태와 CSI Driver 로그를 확인하세요.
- 좀비 프로세스: 해당 노드에 좀비(defunct) 프로세스가 남아 Container가 정리되지 않는 경우가 있으므로 함께 확인하세요.
kubectl delete po <pod-name> --force --grace-period=0 명령은 실제 Container가 종료되었는지 확인하지 않고 API Server의 오브젝트만 제거합니다. StatefulSet처럼 identity가 중요한 워크로드에서는 동일한 Pod가 두 노드에서 동시에 실행되어 데이터가 손상될 수 있으므로, 노드가 확실히 정지되었음을 확인한 후에만 사용하세요.Evicted된 경우
Pod가 Evicted된 경우입니다.
kubectl get po -owide
NAME READY STATUS RESTARTS AGE NODE
nginx-58cd5b85f7-4fz88 0/1 Evicted 0 1h node1
nginx-58cd5b85f7-ts7ps 1/1 Running 0 12m node2kubectl get po -owide
NAME READY STATUS RESTARTS AGE NODE
nginx-58cd5b85f7-4fz88 0/1 Evicted 0 1h node1
nginx-58cd5b85f7-ts7ps 1/1 Running 0 12m node2Evicted는 노드의 자원이 부족해 kubelet이 Pod를 강제로 축출한 상태입니다. 해당 노드를 describe해서 어떤 Condition이 발생했는지 확인하세요.
kubectl describe no node1
Taints: node.kubernetes.io/disk-pressure:NoSchedule
Conditions:
Type Status Reason Message
---- ------ ------ -------
MemoryPressure False KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure True KubeletHasDiskPressure kubelet has disk pressurekubectl describe no node1
Taints: node.kubernetes.io/disk-pressure:NoSchedule
Conditions:
Type Status Reason Message
---- ------ ------ -------
MemoryPressure False KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure True KubeletHasDiskPressure kubelet has disk pressureDiskPressure가 발생했다면 노드의 디스크 사용량을 확인하고 공간을 확보하세요.
df -h /var/lib/kubelet
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/VGROOT-LV_root 50G 46G 4.7G 91% /df -h /var/lib/kubelet
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/VGROOT-LV_root 50G 46G 4.7G 91% /kubelet의 기본 hard eviction 임계값은 nodefs.available<10%, imagefs.available<15%, memory.available<100Mi입니다. 따라서 /var/lib/kubelet이 속한 디스크의 사용률이 90%를 넘으면 DiskPressure가 발생하고 Eviction이 시작됩니다. 사용하지 않는 컨테이너 이미지 정리, 애플리케이션 로그 로테이션 설정, emptyDir 사용량 점검 등으로 공간을 확보하세요.
MemoryPressure로 인한 Eviction도 자주 발생합니다. 이때는 resources.requests.memory를 초과해 사용하는 Pod가 우선적으로 축출되므로, requests/limits를 실제 사용량에 맞게 설정하는 것이 중요합니다.
Evicted 상태의 Pod는 자동으로 삭제되지 않고 목록에 남습니다. 원인을 확인한 뒤에는 다음 명령어로 정리하세요.
kubectl get po --field-selector=status.phase=Failed -o name | xargs -r kubectl delete
Restart가 자주 발생하는 경우
Pod가 현재는 정상 구동 중이지만 RESTARTS 값이 계속 증가하는 경우가 있습니다.
kubectl get po -n calico-system
NAME READY STATUS RESTARTS AGE
calico-node-kwzvz 1/1 Running 5 5d1hkubectl get po -n calico-system
NAME READY STATUS RESTARTS AGE
calico-node-kwzvz 1/1 Running 5 5d1h--previous 옵션으로 재시작 이전 Container의 로그를 확인해서 원인을 파악하세요.
kubectl logs calico-node-kwzvz -n calico-system -c calico-node --previous
2024-01-15T04:25:03.297881Z [FATAL][1] daemon.go 216: Failed to initialize Calico datastore error=Get "https://172.24.0.1:443/api/v1/nodes": dial tcp 172.24.0.1:443: i/o timeoutkubectl logs calico-node-kwzvz -n calico-system -c calico-node --previous
2024-01-15T04:25:03.297881Z [FATAL][1] daemon.go 216: Failed to initialize Calico datastore error=Get "https://172.24.0.1:443/api/v1/nodes": dial tcp 172.24.0.1:443: i/o timeout주로 애플리케이션과 연계된 외부 DB 서비스나 클러스터 내부 Pod와의 통신 실패가 원인이며, 계획된 노드 작업이나 네트워크 작업으로 인해 발생하기도 합니다. 이전 로그로 원인이 확인되지 않는다면 describe 명령어로 Last State를 확인하세요.
kubectl describe po <pod-name>
Last State: Terminated
Reason: OOMKilled
Exit Code: 137kubectl describe po <pod-name>
Last State: Terminated
Reason: OOMKilled
Exit Code: 137OOM(Out Of Memory)으로 종료된 경우입니다. Container의 메모리 사용량이 resources.limits.memory를 초과하면 커널의 OOM Killer가 프로세스를 종료합니다. kubectl top po 명령어로 실제 사용량을 확인한 후, 서비스 가용성을 고려하여 limits를 상향하거나 애플리케이션의 힙 크기 설정을 limits보다 작게 조정하세요.
kubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Unhealthy 48m (x6 over 49m) kubelet Liveness probe failed: localhost:5432 - no responsekubectl describe po <pod-name>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Unhealthy 48m (x6 over 49m) kubelet Liveness probe failed: localhost:5432 - no responseLiveness Probe가 실패하면 kubelet이 Container를 재시작합니다. 실제로는 애플리케이션이 살아 있는데 부하가 몰려 응답이 늦어져 재시작되는 경우가 많으므로, 부하 상황을 고려하여 timeoutSeconds, periodSeconds, failureThreshold 값을 조정하세요.
Pod 자체가 조회되지 않는 경우
Deployment 등의 워크로드를 생성했는데도 Pod가 조회되지 않는 경우입니다. 이때는 Pod가 아니라 ReplicaSet 단계에서 생성이 거부된 것이므로, 먼저 Event를 조회하세요.
kubectl get ev
LAST SEEN TYPE REASON OBJECT MESSAGE
3m1s Warning FailedCreate replicaset/nginx-847f85d779 Error creating: pods "nginx-847f85d779-j484f" is forbidden: exceeded quota: resourcequota, requested: requests.cpu=100m,requests.memory=2Gi, used: requests.cpu=4,requests.memory=128Mi, limited: requests.cpu=2,requests.memory=1Gikubectl get ev
LAST SEEN TYPE REASON OBJECT MESSAGE
3m1s Warning FailedCreate replicaset/nginx-847f85d779 Error creating: pods "nginx-847f85d779-j484f" is forbidden: exceeded quota: resourcequota, requested: requests.cpu=100m,requests.memory=2Gi, used: requests.cpu=4,requests.memory=128Mi, limited: requests.cpu=2,requests.memory=1Gi네임스페이스에 설정된 ResourceQuota를 초과했거나 LimitRange 범위를 벗어난 경우입니다. ResourceQuota/LimitRange를 조정하거나 Pod의 resources 값을 조정하세요.
requests.cpu 등이 설정된 네임스페이스에서는 모든 Container가 해당 항목을 반드시 선언해야 합니다. resources를 지정하지 않은 Pod는 must specify requests.cpu 메시지와 함께 생성이 거부됩니다.kubectl get ev
LAST SEEN TYPE REASON OBJECT MESSAGE
3s Warning FailedCreate replicaset/nginx-56b5449445 Error creating: pods "nginx-56b5449445-" is forbidden: error looking up service account default/my-sa: serviceaccount "my-sa" not foundkubectl get ev
LAST SEEN TYPE REASON OBJECT MESSAGE
3s Warning FailedCreate replicaset/nginx-56b5449445 Error creating: pods "nginx-56b5449445-" is forbidden: error looking up service account default/my-sa: serviceaccount "my-sa" not foundPod 구동에 필요한 리소스를 만들지 않은 경우입니다. Pod Specification을 조회해서 참조하는 ServiceAccount 등 선행 리소스를 같은 네임스페이스에 생성하세요.
kubectl get ev
LAST SEEN TYPE REASON OBJECT MESSAGE
5s Warning FailedCreate replicaset/nginx-6b7f9c8d55 Error creating: admission webhook "validation.example.io" denied the request: privileged containers are not allowedkubectl get ev
LAST SEEN TYPE REASON OBJECT MESSAGE
5s Warning FailedCreate replicaset/nginx-6b7f9c8d55 Error creating: admission webhook "validation.example.io" denied the request: privileged containers are not allowed클러스터에 설치된 정책 엔진 등 Admission Webhook이 Pod 생성을 거부한 경우입니다. 메시지에 표시된 웹훅 이름으로 어떤 정책에 위배되었는지 확인하고 매니페스트를 수정하세요. Pod Security Admission이 적용된 네임스페이스에서는 violates PodSecurity "restricted:latest" 형태의 메시지가 표시됩니다.
failed calling webhook ...: connection refused 메시지가 발생하며, failurePolicy: Fail 설정에 따라 모든 Pod 생성이 차단될 수 있습니다. 이 경우 웹훅 서버 Pod의 상태를 먼저 확인하세요.Event가 전혀 조회되지 않고 ReplicaSet조차 생성되지 않았다면, kube-controller-manager가 정상 동작하지 않는 상태이므로 해당 컴포넌트의 상태와 로그를 확인하고 조치하세요.
Service 상태 점검
Pod 구동 상태에 문제가 없다면 Service 상태를 점검합니다. 다만 그전에 Pod로의 통신 자체에 문제가 없는지부터 확인해야 원인을 좁힐 수 있습니다.
Pod 통신 확인
Kubernetes 노드에서는 Pod 네트워크에 직접 접근할 수 있으므로, <Pod IP>:<Port>로 통신 요청을 보내어 확인할 수 있습니다.
kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP
nginx-58cd5b85f7-sn5n9 1/1 Running 0 2m15s 10.36.0.1
curl 10.36.0.1:80
<title>Welcome to nginx!</title>kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP
nginx-58cd5b85f7-sn5n9 1/1 Running 0 2m15s 10.36.0.1
curl 10.36.0.1:80
<title>Welcome to nginx!</title>Kubernetes 노드가 아닌 환경에서는 kubectl port-forward로 통신을 확인할 수 있습니다. port-forward는 TCP만 지원합니다.
kubectl port-forward <pod-name> 8888:80
Forwarding from 127.0.0.1:8888 -> 80
Forwarding from [::1]:8888 -> 80
curl localhost:8888
<title>Welcome to nginx!</title>kubectl port-forward <pod-name> 8888:80
Forwarding from 127.0.0.1:8888 -> 80
Forwarding from [::1]:8888 -> 80
curl localhost:8888
<title>Welcome to nginx!</title>Pod 통신이 확인되었다면 Service 통신 점검으로 넘어가면 됩니다.
Pod 통신 실패
Pod와의 통신이 되지 않는 경우에는 먼저 Pod가 해당 Port를 Listen하고 있는지 확인하세요. Pod 안에서 netstat, ss, curl 같은 명령어를 사용할 수 있다면 내부에서 먼저 점검해 보는 것이 좋습니다.
kubectl exec -it <pod-name> -- ss -lnt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 4096 0.0.0.0:80 0.0.0.0:*kubectl exec -it <pod-name> -- ss -lnt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 4096 0.0.0.0:80 0.0.0.0:*애플리케이션이 127.0.0.1에만 바인딩되어 있으면 Pod 내부에서는 통신되지만 외부에서는 접근할 수 없습니다. 위 예시처럼 Local Address가 0.0.0.0 또는 [::]인지 확인하세요.
디버깅 도구가 포함되지 않은 경량 이미지(distroless 등)에서는 kubectl debug -it <pod-name> --image=busybox --target=<container-name> 명령으로 임시 컨테이너를 붙여 점검할 수 있습니다.
Port Listen은 정상인데 통신이 안 된다면 NetworkPolicy에 의해 차단되었을 가능성을 확인하세요. 정책에서 허용된 다른 Pod에서는 통신이 되는지 비교해 보면 판단하기 쉽습니다.
kubectl get netpol
NAME POD-SELECTOR AGE
default-deny <none> 1dkubectl get netpol
NAME POD-SELECTOR AGE
default-deny <none> 1d또는 CNI(Container Network Interface) Plugin이 정상 동작하는지 확인하고 조치하세요.
kubectl get po -n calico-system | grep calico-node
NAME READY STATUS RESTARTS AGE
calico-node-97gcr 0/1 CrashLoopBackOff 8 16mkubectl get po -n calico-system | grep calico-node
NAME READY STATUS RESTARTS AGE
calico-node-97gcr 0/1 CrashLoopBackOff 8 16mService 통신 확인
Pod와 마찬가지로 Kubernetes 노드에서는 <Service IP>:<Port>로 직접 통신을 확인할 수 있습니다.
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP 10.100.215.120 <none> 80/TCP 54m
curl 10.100.215.120:80
<title>Welcome to nginx!</title>kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP 10.100.215.120 <none> 80/TCP 54m
curl 10.100.215.120:80
<title>Welcome to nginx!</title>Kubernetes 노드가 아닌 환경에서는 kubectl port-forward로 확인할 수 있습니다. Service를 대상으로 지정할 때의 원격 포트는 Pod의 포트가 아니라 Service의 port 값입니다.
kubectl port-forward service/<service-name> 8888:80
Forwarding from 127.0.0.1:8888 -> 80
Forwarding from [::1]:8888 -> 80
curl localhost:8888
<title>Welcome to nginx!</title>kubectl port-forward service/<service-name> 8888:80
Forwarding from 127.0.0.1:8888 -> 80
Forwarding from [::1]:8888 -> 80
curl localhost:8888
<title>Welcome to nginx!</title>클러스터 내부의 다른 Pod에서 DNS 이름으로 접근할 수 있는지도 함께 확인하면 좋습니다.
kubectl exec -it <pod-name> -- nslookup <service-name>.<namespace>.svc.cluster.localkubectl exec -it <pod-name> -- nslookup <service-name>.<namespace>.svc.cluster.localDNS 조회가 실패한다면 kube-system 네임스페이스의 CoreDNS Pod 상태를 확인하세요.
여기까지 Service 통신이 확인되었다면 Ingress 통신 점검으로 넘어가면 됩니다.
Service 통신 실패
Service로의 통신이 실패한 경우에는 다음 항목을 순서대로 점검하세요.
kubectl describe svc <service-name>
Selector: name=nginx
Port: http 80/TCP
TargetPort: 8080/TCP
Endpoints: <none>kubectl describe svc <service-name>
Selector: name=nginx
Port: http 80/TCP
TargetPort: 8080/TCP
Endpoints: <none>Service를 describe해서 Endpoints를 확인하세요. <none>이라면 트래픽을 전달할 대상 Pod가 없다는 뜻이며, 원인은 크게 두 가지입니다.
- Selector 불일치: Service의
selector와 Pod의 label이 일치하지 않는 경우입니다.kubectl get po --show-labels명령어로 Pod의 label을 확인하고 두 값이 일치하도록 수정하세요. - Ready 상태인 Pod 없음: Selector는 일치하지만 Readiness Probe를 통과한 Pod가 없는 경우입니다. Endpoints에는 Ready 상태의 Pod만 등록되므로, Ready 상태로 변경되지 않는 경우를 참고해 조치하세요.
kubectl get endpointslice -l kubernetes.io/service-name=<service-name> 명령어로 조회하면 Ready 상태(conditions.ready)까지 함께 확인할 수 있습니다.다음은 Endpoints가 있는데도 통신이 되지 않는 경우입니다.
kubectl describe svc <service-name>
Selector: name=nginx
Port: http 80/TCP
TargetPort: 8080/TCP
Endpoints: 10.36.0.1:8080kubectl describe svc <service-name>
Selector: name=nginx
Port: http 80/TCP
TargetPort: 8080/TCP
Endpoints: 10.36.0.1:8080Service의 targetPort가 애플리케이션이 실제로 Listen하는 포트와 다른 경우입니다. 위 예시에서는 Endpoints가 8080으로 등록되어 있으므로, 애플리케이션이 80을 Listen하고 있다면 통신이 실패합니다. Pod 통신 확인에서 확인한 실제 Listen 포트에 맞게 targetPort를 수정하세요.
포트가 일치하는데도 통신이 되지 않는다면 NetworkPolicy에 의한 차단 여부를 확인하고, kube-proxy 데몬셋이 모든 노드에서 정상 동작하는지도 점검해서 조치하세요.
Ingress 상태 점검
Service까지 점검이 끝났다면 Ingress 통신을 확인하여 Kubernetes 워크로드 점검을 마무리할 수 있습니다.
Ingress 통신 확인
정상적인 경우라면 kubectl describe ing 결과의 Backends에 Pod IP 정보가 표시됩니다.
kubectl describe ing <ingress-name>
Name: nginx
Address: 172.28.128.11
Rules:
Host Path Backends
---- ---- --------
nginx.example.io
/ nginx:80 (10.36.0.1:80,10.44.0.2:80)kubectl describe ing <ingress-name>
Name: nginx
Address: 172.28.128.11
Rules:
Host Path Backends
---- ---- --------
nginx.example.io
/ nginx:80 (10.36.0.1:80,10.44.0.2:80)Ingress에 설정한 도메인으로 웹 브라우저에서 접속하거나 curl 명령어로 확인할 수 있습니다. 해당 도메인이 DNS 서버에 등록되지 않은 경우에는 다음과 같이 Host 헤더를 직접 지정해서 확인할 수 있습니다.
curl -H 'Host: <host-domain>' http://<ingress-controller-external-ip>
<title>Welcome to nginx!</title>curl -H 'Host: <host-domain>' http://<ingress-controller-external-ip>
<title>Welcome to nginx!</title>여기까지 통신이 되었다면 Kubernetes 클러스터 내부에는 이슈가 없다고 판단할 수 있습니다.
Ingress 통신 실패
Ingress로의 통신이 실패하는 경우를 알아보겠습니다.
먼저 Ingress의 Backends에 Pod IP 정보가 없는 경우입니다.
kubectl describe ing <ingress-name>
Name: nginx
Address: 172.28.128.11
Rules:
Host Path Backends
---- ---- --------
nginx.example.io
/ nginx:80 (<none>)kubectl describe ing <ingress-name>
Name: nginx
Address: 172.28.128.11
Rules:
Host Path Backends
---- ---- --------
nginx.example.io
/ nginx:80 (<none>)Ingress Specification의 backend(spec.rules[].http.paths[].backend.service)에 지정한 서비스명(name)과 포트(port.number)가 실제 Service 리소스와 일치하는지 확인하세요. 서비스명은 맞는데 <none>이라면 Service의 Endpoints가 비어 있는 것이므로 Service 통신 실패를 참고하세요.
extensions/v1beta1 API의 backend.serviceName, backend.servicePort 필드는 현재 API(networking.k8s.io/v1)에서 backend.service.name, backend.service.port.number로 변경되었습니다.
Ingress의 Address가 비어 있다면 Ingress Controller가 해당 Ingress를 처리하지 않고 있는 상태입니다. spec.ingressClassName 값이 클러스터에 설치된 IngressClass(kubectl get ingressclass)와 일치하는지 확인하세요.
다음은 Backends 정보가 있는데도 통신이 되지 않는 경우들입니다.
curl -H 'Host: nginx.example.io' http://172.28.128.11
<title>504 Gateway Time-out</title>curl -H 'Host: nginx.example.io' http://172.28.128.11
<title>504 Gateway Time-out</title>Ingress Controller가 백엔드 Pod로 요청을 전달했지만 제한 시간 안에 응답을 받지 못한 경우입니다. 애플리케이션의 처리 시간이 Ingress Controller의 타임아웃(nginx의 경우 proxy-read-timeout, 기본 60초)을 초과하지 않는지 확인하고, 필요하면 애플리케이션을 개선하거나 어노테이션으로 타임아웃을 조정하세요. NetworkPolicy로 Ingress Controller에서 백엔드 Pod로의 통신이 차단된 경우에도 동일한 오류가 발생하므로 함께 확인하세요.
curl -H 'Host: nginx.example.io' http://172.28.128.11
<title>503 Service Temporarily Unavailable</title>curl -H 'Host: nginx.example.io' http://172.28.128.11
<title>503 Service Temporarily Unavailable</title>전달 가능한 정상 백엔드가 하나도 없는 경우입니다. Pod가 모두 비정상이거나 Ready 상태가 아니어서 Service의 Endpoints가 비어 있을 가능성이 높으므로 Pod 상태를 확인하세요.
curl -H 'Host: nginx.example.io' http://172.28.128.11
<title>404 Not Found</title>curl -H 'Host: nginx.example.io' http://172.28.128.11
<title>404 Not Found</title>404는 두 가지 경우에 발생합니다. 첫째, 요청한 Host나 Path와 일치하는 Ingress 규칙이 없어 Ingress Controller의 기본 백엔드가 응답한 경우입니다. Host 헤더 값과 spec.rules[].host, 그리고 path/pathType 설정이 요청과 일치하는지 확인하세요. 둘째, 규칙은 일치했지만 애플리케이션이 해당 경로를 서비스하지 않는 경우입니다. Ingress에 설정한 path가 실제 애플리케이션의 context path와 맞는지 확인하세요.
curl -H 'Host: nginx.example.io' http://172.28.128.11
curl: (28) Failed to connect to 172.28.128.11 port 80: Connection timed outcurl -H 'Host: nginx.example.io' http://172.28.128.11
curl: (28) Failed to connect to 172.28.128.11 port 80: Connection timed outIngress Controller까지 요청이 도달하지 못한 경우입니다. 클라이언트와 Ingress Controller 사이의 방화벽 및 Security Group에서 80/443 포트가 열려 있는지 확인하세요.
curl -H 'Host: nginx.example.io' http://172.28.128.11
curl: (7) Failed to connect to 172.28.128.11 port 80: Connection refusedcurl -H 'Host: nginx.example.io' http://172.28.128.11
curl: (7) Failed to connect to 172.28.128.11 port 80: Connection refused목적지까지 패킷은 도달했지만 해당 포트를 Listen하는 프로세스가 없는 경우입니다. Ingress Controller Pod가 정상 구동 중인지, LoadBalancer Service의 포트 설정이 올바른지 확인하세요.
방화벽에 의한 차단인지 확인하려면 다음 명령어로 포트 연결 여부를 점검한 후, <ingress-controller-external-ip>의 80, 443 포트에 대한 방화벽을 여세요.
nc -zv <ingress-controller-external-ip> 80
# nc 명령어를 사용할 수 없는 경우(bash 전용)
timeout 3 bash -c '</dev/tcp/<ingress-controller-external-ip>/80' && echo "open" || echo "closed"nc -zv <ingress-controller-external-ip> 80
# nc 명령어를 사용할 수 없는 경우(bash 전용)
timeout 3 bash -c '</dev/tcp/<ingress-controller-external-ip>/80' && echo "open" || echo "closed"도메인이 DNS에 등록되지 않은 경우에는 Could not resolve host 오류가 발생합니다. 다음 명령어로 도메인이 resolve되는지 확인한 후, DNS 서버에 도메인을 등록하거나 클라이언트의 hosts 파일에 등록해서 사용하세요.
nslookup <host-domain>
# 또는
getent hosts <host-domain>nslookup <host-domain>
# 또는
getent hosts <host-domain>빠른 점검 가이드
지금까지의 내용을 토대로, 문제가 발생했을 때 가장 빠르게 원인 구간을 좁히는 점검 순서는 다음과 같습니다. 앞 단계가 실패하면 뒤 단계는 반드시 실패하므로, 반드시 순서대로 확인하세요.
- Pod Running & Pod Ready 확인
kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-58cd5b85f7-84xlr 1/1 Running 0 31skubectl get po
NAME READY STATUS RESTARTS AGE
nginx-58cd5b85f7-84xlr 1/1 Running 0 31s- Pod 통신 확인
curl <Pod IP>:<port>curl <Pod IP>:<port>- Service 통신 확인
curl <Service IP>:<port>curl <Service IP>:<port>- Ingress 통신 확인
curl -H 'Host: <host-domain>' http://<ingress-controller-external-ip>curl -H 'Host: <host-domain>' http://<ingress-controller-external-ip>

