Notes
K8s 12. Pod와 Control Loop
Kubernetes에서 Pod가 생성될 때 kubectl, kube-apiserver, etcd, scheduler, kubelet, container runtime, CNI가 어떤 순서로 동작하는지 운영 관점에서 정리한다.
- Published
- Updated
- Area
- Cloud Infrastructure
- Type
- concept
- Series
- Kubernetes Essentials
- Category
- Notes
Kubernetes에서 Pod를 생성하는 과정은 단순히 kubectl이 worker node에 접속해서 container를 실행하는 과정이 아니다. 사용자는 Pod가 존재해야 한다는 desired state를 Kubernetes API에 제출하고, Kubernetes의 여러 control plane component와 node component가 각자 맡은 역할을 수행하면서 이 desired state를 실제 running container로 바꾼다.
즉 다음 명령은 worker node에서 직접 docker run을 실행하는 명령이 아니다.
kubectl apply -f pod.yaml
실제 흐름은 다음에 가깝다.
User / kubectl
↓
kube-apiserver
↓
authentication / authorization / admission / validation
↓
etcd에 Pod object 저장
↓
scheduler가 적절한 Node 선택
↓
Pod가 Node에 bind됨
↓
해당 Node의 kubelet이 Pod spec 감지
↓
container runtime에 container 생성 요청
↓
image pull, volume mount, network 설정
↓
container 실행
↓
kubelet이 Pod status를 API Server에 보고
이 과정은 Kubernetes의 핵심 architecture를 거의 모두 관통한다.
핵심 요약
Pod 생성은 “명령 실행”이 아니라 “상태 선언과 reconciliation”의 결과다.
kubectl
↓
kube-apiserver
↓
authn / authz / admission / validation
↓
etcd
↓
kube-scheduler
↓
Pod binding
↓
kubelet
↓
container runtime
↓
image pull / volume mount / network setup
↓
container start
↓
Pod status report
한 문장으로 정리하면 다음과 같다.
Kubernetes는 사용자가 제출한 Pod spec을 API Server와 etcd에 저장하고, Scheduler가 Node를 선택한 뒤, 해당 Node의 kubelet이 container runtime과 CNI를 통해 실제 container와 network를 구성함으로써 Pod를 생성한다.
Pod 생성은 상태 선언이다
Kubernetes를 처음 배울 때 가장 중요한 mental model은 다음이다.
Kubernetes는 명령형 실행기가 아니라 desired state를 맞추는 control system이다.
사용자가 Pod YAML을 작성한다.
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
그리고 적용한다.
kubectl apply -f pod.yaml
이때 사용자가 실제로 한 일은 다음이다.
“nginx라는 Pod가 존재해야 한다”는 desired state를 Kubernetes API에 제출했다.
그 이후 Kubernetes 내부 component들이 이 desired state를 actual state로 바꾼다.
desired state:
Pod/nginx should exist and run nginx:1.25
actual state:
아직 Pod 없음
Kubernetes action:
Pod object 저장
Node 선택
container 생성
상태 보고
즉 Pod 생성은 단순한 단일 함수 호출이 아니라, 여러 component가 API Server를 중심으로 협력하는 control loop의 결과다.
전체 흐름 먼저 보기
Pod가 생성되는 전체 과정은 다음처럼 정리할 수 있다.
1. 사용자가 kubectl로 Pod 생성 요청
2. kube-apiserver가 요청 수신
3. 인증, 권한 확인, admission, validation 수행
4. Pod object를 etcd에 저장
5. scheduler가 unscheduled Pod를 발견
6. scheduler가 적절한 Node를 선택
7. Pod가 Node에 bind됨
8. 해당 Node의 kubelet이 자신에게 할당된 Pod를 감지
9. kubelet이 container runtime에 Pod 실행 요청
10. container runtime이 image pull 및 container 생성
11. CNI plugin이 Pod network 구성
12. kubelet이 Pod 상태를 API Server에 보고
13. API Server가 status를 저장하고 사용자에게 상태 노출
한 줄로 압축하면 다음이다.
kubectl → API Server → etcd → Scheduler → kubelet → container runtime → Pod Running
이 구조를 이해하면 Kubernetes troubleshooting도 훨씬 쉬워진다. Pod가 안 뜨는 문제를 봤을 때, “Kubernetes가 이상하다”로 뭉뚱그리는 대신 어느 단계에서 막혔는지 분해할 수 있기 때문이다.
등장하는 주요 component
Pod 생성 과정에는 control plane component와 worker node component가 모두 관여한다.
| Component | 위치 | 역할 |
|---|---|---|
kubectl | 사용자 측 CLI | Kubernetes API Server에 요청을 보냄 |
kube-apiserver | Control Plane | Kubernetes API의 진입점 |
etcd | Control Plane | cluster state를 저장하는 key-value store |
kube-scheduler | Control Plane | 아직 Node가 정해지지 않은 Pod를 적절한 Node에 배치 |
kube-controller-manager | Control Plane | 여러 controller를 실행하여 desired state 유지 |
kubelet | Worker Node | 자신에게 배정된 Pod를 실제로 실행하고 상태 보고 |
container runtime | Worker Node | container를 실제로 실행 |
CNI plugin | Worker Node | Pod network 구성 |
kube-proxy 또는 data plane | Worker Node | Service traffic routing에 관여 |
여기서 중요한 것은 모든 component가 서로 직접 명령을 주고받는 방식으로 강하게 결합되어 있지 않다는 점이다. 대부분의 component는 API Server를 중심으로 cluster state를 읽고 쓰며, 자신이 담당하는 상태를 맞춘다.
Scheduler watches Pods
Controller watches Deployments / ReplicaSets / Pods
kubelet watches Pods assigned to its Node
EndpointSlice controller watches Services and Pods
1단계: kubectl이 요청을 보낸다
사용자는 보통 다음처럼 Pod를 만든다.
kubectl apply -f pod.yaml
또는 직접 실행형 명령을 사용할 수도 있다.
kubectl run nginx --image=nginx:1.25
하지만 중요한 점은 kubectl이 container runtime에 직접 명령을 보내지 않는다는 것이다.
kubectl
≠ worker node에 SSH 접속해서 docker run 실행
kubectl
= Kubernetes API Server에 HTTP request를 보내는 client
즉 kubectl은 Kubernetes API와 통신한다.
kubectl apply
↓
HTTP request
↓
kube-apiserver
이때 kubectl은 현재 kubeconfig를 사용한다.
kubectl config current-context
kubectl config view --minify
kubeconfig에는 보통 다음 정보가 들어 있다.
어떤 cluster에 접속할지
어떤 user credential을 사용할지
어떤 namespace를 기본으로 사용할지
API Server endpoint가 무엇인지
따라서 Pod 생성 문제가 생겼을 때 가장 먼저 확인해야 하는 것도 context다.
kubectl config current-context
kubectl get namespace
잘못된 cluster나 namespace에 적용하고 있을 수 있기 때문이다.
2단계: kube-apiserver가 요청을 받는다
모든 Kubernetes API 요청은 kube-apiserver를 통과한다.
kubectl
↓
kube-apiserver
kube-apiserver는 Kubernetes control plane의 front door다. 사용자는 물론이고 scheduler, controller, kubelet도 API Server를 통해 cluster state를 읽고 쓴다.
Pod 생성 요청을 받으면 API Server는 대략 다음을 수행한다.
Authentication
Authorization
Admission control
Schema validation
Defaulting
etcd write
이 단계에서 중요한 점은 API Server가 단순 저장소 앞의 proxy가 아니라는 것이다. API Server는 Kubernetes API의 일관성과 정책을 지키는 중심 component다.
Authentication: 너는 누구인가?
먼저 API Server는 요청자가 누구인지 확인한다.
요청자:
user?
service account?
certificate?
token?
예를 들어 사용자가 kubeconfig에 저장된 certificate 또는 token으로 요청할 수 있다.
kubectl request
↓
credential 확인
↓
user identity 확인
여기서 실패하면 다음과 같은 유형의 오류가 발생할 수 있다.
You must be logged in to the server
Unauthorized
certificate signed by unknown authority
이 경우 Pod YAML 문제가 아니라 cluster 인증 문제다.
Authorization: 이 작업을 할 수 있는가?
인증이 끝나면 다음은 권한 확인이다.
예를 들어 어떤 user가 default namespace에 Pod를 만들 수 있는지 확인해야 한다.
Can user X create pods in namespace default?
Kubernetes에서는 보통 RBAC로 권한을 제어한다.
확인 명령:
kubectl auth can-i create pods
kubectl auth can-i create pods -n production
권한이 없으면 다음과 같은 오류가 난다.
Error from server (Forbidden): pods is forbidden:
User "..." cannot create resource "pods" in API group "" in the namespace "..."
이 단계에서 실패하면 Pod는 아직 생성되지 않는다.
Admission control: cluster 정책을 적용한다
인증과 권한 확인을 통과해도 바로 저장되는 것은 아니다. Admission controller가 요청을 검사하거나 변경할 수 있다.
Admission 단계에서는 다음과 같은 일이 가능하다.
namespace에 resource quota가 있는지 확인
Pod Security 정책 확인
image registry 정책 확인
default value 주입
sidecar injection
label/annotation 강제
LimitRange 기반 resource 기본값 적용
예를 들어 namespace에 ResourceQuota가 걸려 있다면, Pod 생성이 거절될 수 있다.
exceeded quota: compute-resources
또는 보안 정책상 privileged container가 거절될 수 있다.
violates PodSecurity "restricted"
이 단계는 운영 환경에서 매우 중요하다. 같은 YAML이 개발 cluster에서는 되지만 production cluster에서는 거절될 수 있기 때문이다.
Validation과 Defaulting
API Server는 요청 object가 Kubernetes API schema에 맞는지도 확인한다.
예를 들어 다음처럼 잘못된 YAML을 적용했다고 하자.
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
wrongField: true
이런 경우 schema validation에서 문제가 될 수 있다.
또한 Kubernetes는 일부 field에 기본값을 채울 수 있다. 예를 들어 restartPolicy를 명시하지 않으면 Pod의 기본값은 일반적으로 Always다.
spec:
restartPolicy: Always
사용자가 모든 field를 직접 쓰지 않아도 Kubernetes API가 기본값을 채워 object를 완성한다.
3단계: Pod object가 etcd에 저장된다
검증을 통과하면 API Server는 Pod object를 etcd에 저장한다.
kube-apiserver
↓
etcd
etcd는 Kubernetes cluster state의 source of truth다.
여기서 중요한 점은 이 시점의 Pod가 아직 실행된 것은 아니라는 점이다.
etcd에 Pod object 저장됨
≠ container가 실행됨
이 시점의 Pod는 보통 아직 Node가 정해지지 않은 상태다.
spec:
nodeName: null
또는 kubectl get pod를 보면 Pending으로 보일 수 있다.
kubectl get pods
예시:
NAME READY STATUS RESTARTS AGE
nginx 0/1 Pending 0 2s
이 Pending은 “Pod object는 존재하지만 아직 실행 준비가 끝나지 않았다”는 의미다.
4단계: Scheduler가 unscheduled Pod를 발견한다
kube-scheduler는 API Server를 watch하면서 아직 Node가 정해지지 않은 Pod를 찾는다.
kube-scheduler
↓ watches API Server
Pod with no nodeName 발견
scheduler가 찾는 것은 대략 이런 Pod다.
spec.nodeName이 비어 있음
PodScheduled condition이 아직 True가 아님
Scheduler는 “이 Pod를 어떤 Node에서 실행할 것인가?”를 결정한다.
Pending Pod
↓
Scheduler
↓
Node 선택
Scheduling은 단순 랜덤 배치가 아니다
Scheduler는 아무 Node나 고르지 않는다.
예를 들어 cluster에 다음 Node가 있다고 하자.
node-a: CPU 4 core, Memory 8Gi
node-b: CPU 8 core, Memory 16Gi
node-c: GPU 있음
Pod가 다음 resource request를 갖고 있다.
resources:
requests:
cpu: "2"
memory: "4Gi"
Scheduler는 Node의 여유 resource와 Pod의 요구조건을 고려한다.
대표적으로 다음 조건들이 scheduling에 관여한다.
CPU request
memory request
nodeSelector
node affinity
pod affinity / anti-affinity
taints / tolerations
topology spread constraints
volume binding
node condition
priority
예를 들어 특정 Node label이 있는 곳에만 배치하고 싶다면 다음처럼 설정할 수 있다.
spec:
nodeSelector:
node-role: gpu
특정 taint가 있는 Node에 배치하려면 toleration이 필요하다.
tolerations:
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"
스케줄링 실패 시 Pod는 계속 Pending으로 남는다.
kubectl describe pod nginx
Events에 다음과 같은 메시지가 나올 수 있다.
0/3 nodes are available: insufficient memory.
0/3 nodes are available: node(s) had untolerated taint.
0/3 nodes are available: persistentvolumeclaim is not bound.
이 경우 container 실행 단계까지 가지도 못한 것이다.
5단계: Scheduler가 Pod를 Node에 bind한다
Scheduler가 Node를 선택하면 Pod를 해당 Node에 bind한다.
개념적으로는 다음이 채워진다.
spec:
nodeName: node-a
흐름은 다음과 같다.
Scheduler:
Pod/nginx는 node-a에서 실행하는 것이 적절하다.
API Server:
Pod/nginx를 node-a에 bind한다.
etcd:
binding 결과 저장
이제 Pod는 특정 Node에 할당된 상태가 된다.
kubectl get pod nginx -o wide
예시:
NAME READY STATUS NODE
nginx 0/1 Pending node-a
여전히 Pending일 수 있지만, 이제 Node는 정해졌다.
6단계: kubelet이 자신에게 할당된 Pod를 감지한다
각 worker node에는 kubelet이 실행된다.
node-a
└─ kubelet
kubelet은 API Server를 통해 자신에게 할당된 Pod를 watch한다.
kubelet on node-a
↓
API Server watch
↓
nodeName=node-a인 Pod 발견
이제 kubelet이 실제 실행 작업을 시작한다.
즉 kubelet은 Node 안에서 “Kubernetes desired state를 실제 container 실행 상태로 바꾸는 agent”다.
kubelet은 container를 직접 실행하지 않는다
kubelet은 container를 직접 실행하는 low-level runtime이 아니다.
kubelet은 container runtime과 통신한다.
kubelet
↓ CRI
container runtime
↓
container
현대 Kubernetes에서는 container runtime이 CRI(Container Runtime Interface)를 구현해야 한다.
대표 runtime은 다음과 같다.
containerd
CRI-O
주의할 점은 “Kubernetes가 Docker를 직접 호출한다”는 식으로 이해하면 오래된 모델이 섞일 수 있다는 것이다.
정리하면 다음이다.
과거에 흔한 오해:
kubelet → Docker 직접 호출
현재 일반적인 구조:
kubelet → CRI → containerd / CRI-O → OCI runtime
7단계: image pull
kubelet은 container runtime에 image를 준비하라고 요청한다.
image: nginx:1.25
container runtime은 image registry에서 image를 pull한다.
container runtime
↓
registry
↓
image layers download
여기서 문제가 생기면 Pod는 ImagePullBackOff 또는 ErrImagePull 상태가 될 수 있다.
NAME READY STATUS RESTARTS
nginx 0/1 ImagePullBackOff 0
가능한 원인:
image 이름 오타
tag가 존재하지 않음
private registry 인증 실패
imagePullSecret 누락
registry 접근 불가
Node의 DNS 문제
방화벽 또는 proxy 문제
확인 명령:
kubectl describe pod nginx
Events에 다음과 같은 메시지가 있을 수 있다.
Failed to pull image
pull access denied
manifest unknown
Back-off pulling image
이 단계에서 실패하면 container는 아직 시작되지 않는다.
8단계: volume 준비
Pod spec에 volume이 있으면 kubelet은 container 실행 전에 volume을 준비해야 한다.
예시:
volumes:
- name: config
configMap:
name: nginx-config
또는 PVC를 사용할 수 있다.
volumes:
- name: data
persistentVolumeClaim:
claimName: data-pvc
volume 관련 문제가 있으면 Pod가 ContainerCreating 또는 Pending에 오래 머물 수 있다.
가능한 원인:
ConfigMap not found
Secret not found
PVC not bound
volume attach 실패
mount permission 문제
CSI driver 문제
확인:
kubectl describe pod nginx
kubectl get pvc
kubectl describe pvc data-pvc
이 단계는 특히 stateful workload에서 중요하다.
9단계: Pod network 구성
Pod는 cluster 내부에서 고유한 Pod IP를 갖는다. 이를 위해 CNI plugin이 관여한다.
개념 흐름은 다음과 같다.
kubelet
↓
container runtime
↓
Pod sandbox 생성
↓
CNI plugin 호출
↓
Pod network namespace 구성
↓
Pod IP 할당
CNI plugin은 cluster 구현에 따라 다를 수 있다.
Calico
Cilium
Flannel
Weave Net
Antrea
cloud provider CNI
Pod network가 제대로 구성되지 않으면 다음 문제가 생긴다.
Pod IP 없음
Pod끼리 통신 불가
Service 통신 실패
DNS 조회 실패
NetworkPolicy 예상과 다르게 동작
확인 명령:
kubectl get pod nginx -o wide
kubectl describe pod nginx
kubectl exec -it nginx -- ip addr
kubectl exec -it nginx -- nslookup kubernetes.default
네트워크 문제는 Pod 생성 과정 중에서도 특히 디버깅이 복잡한 영역이다.
10단계: init container 실행
Pod에 initContainers가 있으면 일반 application container보다 먼저 실행된다.
예시:
initContainers:
- name: wait-db
image: busybox
command: ["sh", "-c", "until nc -z db 5432; do sleep 2; done"]
containers:
- name: app
image: registry.example.com/app:1.0.0
흐름은 다음이다.
init container 1 실행 완료
↓
init container 2 실행 완료
↓
main container 실행
init container가 실패하면 main container는 시작되지 않는다.
STATUS: Init:CrashLoopBackOff
확인:
kubectl logs nginx -c wait-db
kubectl describe pod nginx
init container는 application 시작 전 준비 작업에 유용하지만, 잘못 만들면 Pod가 영원히 시작되지 않을 수 있다.
11단계: main container 실행
image, volume, network 준비가 끝나면 container runtime이 main container를 실행한다.
container runtime
↓
OCI runtime
↓
container process start
이제 Pod 상태는 Running으로 바뀔 수 있다.
kubectl get pods
예시:
NAME READY STATUS RESTARTS
nginx 1/1 Running 0
하지만 Running과 Ready는 다르다.
Running:
container process가 시작됨
Ready:
application이 traffic을 받을 준비가 됨
운영에서 실제로 중요한 것은 Running만이 아니라 Ready까지 확인하는 것이다.
12단계: kubelet이 상태를 보고한다
kubelet은 Pod와 container의 상태를 계속 관찰한다.
container running?
container terminated?
restart count?
readiness probe 성공?
liveness probe 실패?
Pod IP는 무엇인가?
그리고 API Server에 status를 보고한다.
kubelet
↓
kube-apiserver
↓
etcd
사용자가 다음 명령을 실행하면 이 status를 보는 것이다.
kubectl get pod nginx
kubectl describe pod nginx
kubectl get pod nginx -o yaml
Pod status에는 다음 정보가 들어간다.
phase
conditions
containerStatuses
podIP
hostIP
startTime
restartCount
lastState
예를 들어 container가 죽었다면 containerStatuses에서 reason을 볼 수 있다.
containerStatuses:
- name: nginx
ready: false
restartCount: 3
state:
waiting:
reason: CrashLoopBackOff
lastState:
terminated:
reason: Error
exitCode: 1
운영에서 이 status 정보는 매우 중요하다.
Pod phase와 condition
Pod 생성 과정을 이해하려면 phase와 condition을 구분해야 한다.
대표적인 Pod phase는 다음과 같다.
| Phase | 의미 |
|---|---|
Pending | Pod가 API에 생성되었지만 container가 아직 모두 실행 준비되지 않음 |
Running | Pod가 Node에 bound되었고, 적어도 하나 이상의 container가 실행 중이거나 시작/재시작 중 |
Succeeded | 모든 container가 성공적으로 종료되고 재시작되지 않음 |
Failed | 모든 container가 종료되었고, 하나 이상이 실패로 종료 |
Unknown | Pod 상태를 확인할 수 없음 |
Pod condition은 더 세부적인 상태다.
PodScheduled
Initialized
ContainersReady
Ready
예를 들어 Pod가 Node에 할당되면 PodScheduled=True가 된다.
PodScheduled=True
→ scheduler가 Node를 정했고 bind가 완료됨
container가 실행 중이어도 readiness probe가 실패하면 Ready=False일 수 있다.
Running but not Ready
→ process는 떠 있지만 traffic 받을 준비는 안 됨
Probe는 kubelet이 수행한다
Pod가 실행된 뒤 kubelet은 probe를 수행한다.
대표 probe는 다음이다.
startupProbe
readinessProbe
livenessProbe
예시:
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
각 probe의 의미는 다음이다.
| Probe | 목적 |
|---|---|
startupProbe | 느리게 시작하는 application의 초기 기동 보호 |
readinessProbe | traffic을 받을 준비가 되었는지 판단 |
livenessProbe | application이 살아 있는지 판단하고 재시작 여부 결정 |
kubelet이 probe를 수행하고, 그 결과를 Pod status에 반영한다.
주의할 점:
readinessProbe 실패
→ Pod는 Service endpoint에서 제외될 수 있음
livenessProbe 실패
→ container가 재시작될 수 있음
startupProbe 설정 부재
→ 느리게 뜨는 app이 livenessProbe 때문에 계속 죽을 수 있음
Service와 EndpointSlice는 Pod 생성 이후 연결된다
Pod가 Running/Ready 상태가 되면 Service와 연결될 수 있다.
예를 들어 Service가 있다.
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
Pod label이 맞으면 다음처럼 연결된다.
metadata:
labels:
app: nginx
Service는 해당 Pod를 backend endpoint로 볼 수 있다.
Service selector:
app=nginx
Pod label:
app=nginx
확인:
kubectl get endpoints nginx
kubectl get endpointslice
이 단계에서 중요한 것은 Pod 생성과 Service routing이 별도 layer라는 점이다.
Pod Running
≠ Service traffic 정상
Service traffic 정상 조건:
Pod label이 Service selector와 일치
Pod가 Ready
targetPort가 application listen port와 일치
network policy가 허용
따라서 Pod는 정상인데 Service 접속이 안 된다면 Pod 생성 과정이 아니라 Service/Endpoint/Network 문제일 수 있다.
Deployment로 Pod를 만들 때는 한 단계가 더 있다
지금까지는 사용자가 Pod를 직접 만든 경우를 설명했다.
하지만 실무에서는 Pod를 직접 만들기보다 Deployment를 만든다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
이 경우 흐름이 조금 더 길어진다.
kubectl apply Deployment
↓
API Server가 Deployment 저장
↓
Deployment Controller가 ReplicaSet 생성
↓
ReplicaSet Controller가 Pod 생성
↓
API Server가 Pod 저장
↓
Scheduler가 Pod를 Node에 할당
↓
kubelet이 Pod 실행
즉 Deployment를 적용했을 때 실제 Pod를 만드는 주체는 사람이 아니라 controller다.
User
→ Deployment desired state 제출
Deployment Controller
→ ReplicaSet desired state 생성
ReplicaSet Controller
→ Pod desired state 생성
Scheduler / kubelet
→ 실제 Node에서 Pod 실행
이것이 Kubernetes control loop의 핵심이다.
Pod는 한 번만 scheduling된다
중요한 개념이 있다.
Pod는 lifetime 동안 한 번만 특정 Node에 scheduling된다.
이 말은 다음을 의미한다.
Pod가 node-a에 scheduling됨
↓
node-a에서 계속 실행
↓
node-a가 죽음
↓
그 Pod 자체가 node-b로 이사하는 것이 아님
대신 controller가 새 Pod를 만든다.
Deployment desired replicas = 3
actual available Pods = 2
↓
ReplicaSet이 replacement Pod 생성
↓
새 Pod가 다른 Node에 scheduling됨
즉 “Pod가 이동한다”기보다 “새 Pod가 생성된다”고 이해해야 정확하다.
Node 장애 시 Pod는 어떻게 되는가?
Node가 죽으면 그 Node 위의 Pod도 정상적으로 계속 실행될 수 없다.
node-a down
↓
node-a 위의 Pods unreachable
↓
control plane이 Node 상태 감지
↓
해당 Pod들을 삭제/종료 대상으로 봄
↓
controller가 replacement Pod 생성
여기서 중요한 것은 controller가 없는 bare Pod는 자동으로 동일하게 복구되지 않을 수 있다는 점이다.
bare Pod
→ 그 Pod가 죽으면 끝일 수 있음
Deployment-managed Pod
→ ReplicaSet이 replacement Pod 생성
StatefulSet-managed Pod
→ identity를 유지하는 방식으로 replacement 생성
그래서 운영에서는 bare Pod보다 Deployment, StatefulSet, DaemonSet, Job 같은 workload controller를 사용한다.
왜 etcd에 직접 쓰면 안 되는가?
etcd가 source of truth라고 해서 사람이 직접 etcd에 Pod 정보를 쓰면 안 된다.
정상 경로는 다음이다.
Client
↓
kube-apiserver
↓
etcd
API Server는 authentication, authorization, validation, admission, defaulting, version conversion, audit 등 많은 역할을 한다.
직접 etcd를 수정하면 다음 문제가 생길 수 있다.
schema validation 우회
admission policy 우회
audit 기록 누락
object version 불일치
cluster state 손상
controller가 예상하지 못한 상태 발생
따라서 Kubernetes state 변경은 API Server를 통해야 한다.
Pod 생성 과정으로 보는 troubleshooting
Pod가 정상 생성되지 않을 때는 어디서 막혔는지를 단계별로 보면 된다.
kubectl 요청 단계
API Server 단계
Admission 단계
etcd 저장 단계
Scheduling 단계
kubelet 실행 단계
image pull 단계
volume mount 단계
network 단계
container runtime 단계
application startup 단계
readiness 단계
각 단계별 대표 증상은 다음과 같다.
| 단계 | 대표 증상 | 확인 명령 |
|---|---|---|
| kubeconfig/context | 다른 cluster/namespace에 적용 | kubectl config current-context |
| API schema | YAML 적용 실패 | kubectl apply -f output |
| RBAC | Forbidden | kubectl auth can-i ... |
| Admission | Pod Security, quota 위반 | kubectl describe pod, admission error |
| Scheduling | Pending | kubectl describe pod |
| Image pull | ImagePullBackOff | kubectl describe pod |
| Volume | ContainerCreating 지연 | kubectl describe pod, kubectl get pvc |
| Runtime crash | CrashLoopBackOff | kubectl logs, kubectl logs --previous |
| Readiness | Running but 0/1 | kubectl describe pod, probe endpoint 확인 |
| Service 연결 | endpoint 없음 | kubectl get endpoints, label 확인 |
Pending 상태 깊게 보기
Pod가 Pending이면 아직 container가 정상 실행되지 않은 상태다.
kubectl get pod nginx
NAME READY STATUS RESTARTS
nginx 0/1 Pending 0
가능한 원인:
Scheduler가 Node를 못 찾음
PVC가 아직 bound되지 않음
image pull 전 단계에서 대기
resource 부족
taint/toleration 불일치
nodeSelector/affinity 조건 불만족
가장 중요한 명령은 다음이다.
kubectl describe pod nginx
Events를 확인한다.
Events:
Warning FailedScheduling default-scheduler 0/3 nodes are available: insufficient memory.
이 경우 해결은 Pod YAML 수정 또는 cluster resource 확보다.
resource requests 줄이기
Node 추가
taint/toleration 수정
nodeSelector 수정
PVC 확인
ContainerCreating 상태 깊게 보기
ContainerCreating은 Node는 정해졌고 kubelet이 container를 만들고 있다는 뜻이다.
STATUS: ContainerCreating
이 상태가 오래 지속되면 다음을 의심한다.
image pull 지연
volume mount 지연
CNI network setup 실패
Secret/ConfigMap mount 문제
CSI driver 문제
확인:
kubectl describe pod nginx
Events에 다음이 나올 수 있다.
FailedMount
FailedCreatePodSandBox
Failed to setup network for sandbox
이 경우 문제는 application code가 아니라 Node/runtime/CNI/CSI 쪽일 가능성이 크다.
CrashLoopBackOff 상태 깊게 보기
CrashLoopBackOff는 container가 시작되었다가 죽고, kubelet이 재시작을 반복하면서 backoff 중이라는 뜻이다.
STATUS: CrashLoopBackOff
가능한 원인:
entrypoint 오류
필수 env 누락
Secret 값 오류
DB 연결 실패 후 process exit
application config 오류
permission 문제
잘못된 command/args
확인:
kubectl logs nginx
kubectl logs nginx --previous
kubectl describe pod nginx
특히 --previous가 중요하다.
kubectl logs nginx --previous
현재 container가 이미 재시작되었을 수 있기 때문에, 직전 container가 죽기 전 남긴 log를 봐야 한다.
Running but not Ready 상태 깊게 보기
Pod가 Running인데 READY가 0/1인 경우가 있다.
NAME READY STATUS RESTARTS
nginx 0/1 Running 0
이는 container process는 실행 중이지만 readiness 조건을 통과하지 못했다는 뜻이다.
가능한 원인:
readinessProbe path 오류
readinessProbe port 오류
application 초기화 지연
dependency 연결 실패
health endpoint가 500 반환
확인:
kubectl describe pod nginx
kubectl logs nginx
kubectl port-forward pod/nginx 8080:80
curl http://localhost:8080/ready
운영에서 중요한 것은 Running이 아니라 Ready다.
Pod Running
≠ traffic 받을 준비 완료
Pod Ready
= Service endpoint로 들어갈 수 있는 상태
Pod 생성과 DNS
Pod가 실행되면 cluster DNS를 사용할 수 있다.
보통 CoreDNS가 cluster DNS 역할을 한다.
Pod 내부에서 Service 이름으로 접근할 수 있다.
kubectl exec -it nginx -- nslookup kubernetes.default
Service DNS 예시:
my-service.default.svc.cluster.local
DNS 문제가 있으면 application이 dependency를 찾지 못할 수 있다.
DB hostname resolve 실패
internal service 호출 실패
external name lookup 실패
이 경우 확인할 것:
kubectl get pods -n kube-system
kubectl get svc -n kube-system
kubectl logs -n kube-system -l k8s-app=kube-dns
cluster에 따라 CoreDNS label이 다를 수 있으므로 확인 필요하다.
Pod 생성과 kube-proxy / Service traffic
Pod 생성 자체와 Service traffic은 분리된 개념이다.
Pod가 생성되면 Pod IP가 생긴다.
Pod/nginx
IP: 10.244.1.25
Service는 selector로 Pod를 찾는다.
selector:
app: nginx
그 결과 endpoint가 생긴다.
kubectl get endpoints nginx
Service traffic은 kube-proxy나 CNI/eBPF data plane 등에 의해 Pod로 전달된다.
따라서 다음 두 문제는 다르다.
Pod가 생성되지 않음
→ scheduling, kubelet, runtime, image, volume 문제
Pod는 생성됐지만 Service 접속 실패
→ selector, endpoint, targetPort, network policy, kube-proxy/CNI 문제
문제를 분리해야 한다.
Pod 생성 과정을 실제로 확인하는 명령 흐름
실습할 때는 다음 순서로 보면 좋다.
1. Pod YAML 적용
kubectl apply -f pod.yaml
2. Pod phase 확인
kubectl get pod nginx
3. Node 배정 확인
kubectl get pod nginx -o wide
4. 전체 YAML 확인
kubectl get pod nginx -o yaml
볼 것:
spec.nodeName
status.phase
status.conditions
status.containerStatuses
status.podIP
5. Events 확인
kubectl describe pod nginx
6. container log 확인
kubectl logs nginx
7. 재시작 전 log 확인
kubectl logs nginx --previous
8. Node 상태 확인
kubectl get nodes
kubectl describe node <node-name>
이렇게 보면 Pod 생성 과정 중 어디에서 막혔는지 단계적으로 알 수 있다.
kubectl apply와 watch 기반 control loop
Kubernetes component들은 API Server를 지속적으로 watch한다.
Scheduler watches Pods
Controller watches Deployments / ReplicaSets / Pods
kubelet watches Pods assigned to its Node
EndpointSlice controller watches Services and Pods
이 구조는 polling과 event-driven watch가 결합된 control loop다.
예를 들어 scheduler는 다음과 같이 동작한다.
watch:
nodeName 없는 Pod 발견
decide:
적절한 Node 선택
act:
Pod binding 생성
kubelet은 다음과 같이 동작한다.
watch:
nodeName이 자기 Node인 Pod 발견
decide:
local runtime 상태와 비교
act:
container 생성/재시작/상태 보고
이런 구조 때문에 Kubernetes는 loosely coupled system이다.
API Server를 중심으로 component들이 상태를 읽고 쓴다.
component들은 서로 직접 강하게 묶이지 않는다.
각 component는 자신이 담당하는 desired state를 actual state로 맞춘다.
Pod가 “생성”된 시점은 언제인가?
“Pod가 생성되었다”는 말은 문맥에 따라 다르다.
| 표현 | 실제 의미 |
|---|---|
| Pod object 생성 | API Server가 Pod object를 받고 etcd에 저장함 |
| Pod scheduled | scheduler가 Node를 선택하고 binding 완료 |
| Pod sandbox 생성 | kubelet/runtime이 Pod network namespace와 sandbox 구성 |
| container 생성 | runtime이 container process를 만들 준비 완료 |
| container started | application process가 시작됨 |
| Pod Running | 하나 이상의 primary container가 실행 중 |
| Pod Ready | readiness 조건을 통과해 traffic 수신 가능 |
따라서 운영에서는 “Pod 생성됨”만으로 충분하지 않다.
Pod object exists
≠
container is running
container running
≠
application is ready
application ready
≠
Service routing is correct
이 구분이 troubleshooting에서 중요하다.
Pod 생성 실패를 layer별로 분해하기
Pod 생성 과정을 layer별로 나누면 다음과 같다.
API layer
→ kubectl, API Server, auth, admission, validation
State layer
→ etcd에 object 저장
Scheduling layer
→ scheduler, Node 선택, binding
Node agent layer
→ kubelet, Pod spec 감지
Runtime layer
→ CRI, containerd/CRI-O, image pull, container start
Network layer
→ CNI, Pod IP, DNS, Service endpoint
Application layer
→ process start, health check, readiness
각 layer별 대표 오류:
| Layer | 대표 오류 |
|---|---|
| API layer | Forbidden, schema validation error |
| State layer | API Server/etcd 장애 |
| Scheduling layer | FailedScheduling, Pending |
| Node agent layer | kubelet down, node NotReady |
| Runtime layer | ImagePullBackOff, CrashLoopBackOff |
| Network layer | FailedCreatePodSandBox, DNS failure |
| Application layer | readiness failure, app 500, wrong config |
이렇게 보면 “Pod가 안 떠요”라는 문제를 훨씬 구체적으로 나눌 수 있다.
전체 mental model
Pod 생성 과정을 이해하는 흐름은 다음과 같다.
1. 사용자가 kubectl로 Pod 생성 요청을 보낸다.
2. kubectl은 worker node에 직접 container를 실행하지 않고,
kube-apiserver에 API request를 보낸다.
3. API Server는 authentication, authorization, admission, validation을 수행한다.
4. 요청이 유효하면 Pod object를 etcd에 저장한다.
5. 이 시점의 Pod는 object로는 존재하지만,
아직 container가 실행된 것은 아니다.
6. Scheduler는 nodeName이 없는 Pod를 watch하고,
resource와 scheduling constraint를 고려해 적절한 Node를 선택한다.
7. Pod가 특정 Node에 bind된다.
8. 해당 Node의 kubelet은 자신에게 할당된 Pod를 watch하고,
container runtime에 Pod 실행을 요청한다.
9. container runtime은 image pull, container 생성, 실행을 수행한다.
10. CNI plugin은 Pod network를 구성하고 Pod IP를 부여한다.
11. kubelet은 container 상태와 probe 결과를 API Server에 보고한다.
12. 사용자는 kubectl get/describe/logs로 이 상태를 확인한다.
13. Pod는 lifetime 동안 한 번 scheduling되며,
Node 장애 시 Pod 자체가 이동하는 것이 아니라 controller가 replacement Pod를 만든다.
14. Pod 생성 문제는 API, scheduling, kubelet, runtime, network,
application readiness layer로 나눠서 디버깅해야 한다.
요약
Pod 생성은 단순히 kubectl run이 container를 실행하는 과정이 아니다.
Kubernetes에서 Pod 생성은 API 중심의 desired state 선언과 여러 controller/agent의 reconciliation이 결합된 과정이다.
kubectl
↓
kube-apiserver
↓
authn / authz / admission / validation
↓
etcd
↓
kube-scheduler
↓
Pod binding
↓
kubelet
↓
container runtime
↓
image pull / volume mount / network setup
↓
container start
↓
Pod status report
운영 관점에서는 Pod가 “생성됐다”는 표현을 더 세분화해서 봐야 한다.
Pod object가 API에 저장되었는가?
Scheduler가 Node를 선택했는가?
kubelet이 Pod spec을 감지했는가?
image pull이 성공했는가?
volume mount가 되었는가?
CNI network가 구성되었는가?
container process가 시작되었는가?
readinessProbe를 통과했는가?
Service endpoint에 포함되었는가?
한 문장으로 정리하면 다음과 같다.
Kubernetes는 사용자가 제출한 Pod spec을 API Server와 etcd에 저장하고, Scheduler가 Node를 선택한 뒤, 해당 Node의 kubelet이 container runtime과 CNI를 통해 실제 container와 network를 구성함으로써 Pod를 생성한다.