파드 수명주기
파드 수명주기 (Pod Lifecycle)
이 페이지는 파드의 수명주기를 설명해요. 파드는 정의된 수명주기를 따르며, Pending 단계(#pod-phase)에서 시작하고, 기본 컨테이너 중 최소 하나가 정상적으로 시작되면 Running을 거치고, 파드의 어떤 컨테이너가 실패로 종료되었는지에 따라 Succeeded 또는 Failed 단계를 거쳐요.
파드가 실행되는 동안 kubelet은 컨테이너를 관리하고 파드의 spec을 컨테이너 런타임에게 변환해요. kubelet은 또한 애플리케이션의 건강을 추적하는 #프로브 실행도 관리해요.
개별 애플리케이션 컨테이너와 마찬가지로 파드는 비교적 일시적(영속적이기보다는)인 엔티티로 간주돼요. 파드는 생성되고, 고유한 ID(UID(/docs/concepts/overview/working-with-objects/names/#uids))가 할당되며, (재시작 정책에 따라) 종료되거나 삭제될 때까지 머무르는 노드에서 실행되도록 스케줄링돼요. 노드가 죽으면 그 노드에서 실행 중인(또는 실행되도록 스케줄링된) 파드가 삭제 표시돼요(#pod-garbage-collection). 컨트롤 플레인은 제한 시간 후 파드를 제거 표시해요.
출처: 문서
본문
파드 수명 (#pod-lifetime)
파드가 실행되는 동안 kubelet은 일부 종류의 결함을 처리하기 위해 컨테이너를 재시작할 수 있어요. 파드 내에서 Kubernetes는 서로 다른 #컨테이너 상태를 추적하고 파드를 다시 건강하게 만들기 위해 취할 조치를 결정해요. 이것은 원하는 상태(파드 spec)와 실행 중인 컨테이너의 실제 상태를 주기적으로 조정하는 폴링 루프에서 수행돼요.
Kubernetes API에서 파드는 사양과 실제 상태를 모두 가져요. 파드 객체의 상태는 #파드 조건 집합으로 구성돼요. 또한 애플리케이션에 유용하다면 파드에 대한 조건 데이터에 커스텀 준비 정보(#pod-readiness-gate)를 주입할 수 있어요.
파드는 수명 동안 단 한 번만 스케줄링돼요. 파드를 특정 노드에 할당하는 것을 바인딩(binding), 어떤 노드를 사용할지 선택하는 과정을 스케줄링(scheduling)이라고 해요. 파드가 스케줄링되어 노드에 바인딩되면 Kubernetes는 그 파드를 노드에서 실행하려고 시도해요. 파드는 중지되거나 파드가 종료(#pod-termination)될 때까지 그 노드에서 실행돼요. Kubernetes가 선택된 노드에서 파드를 시작할 수 없으면(예: 파드가 시작되기 전에 노드가 크래시한 경우) 그 특정 파드는 결코 시작되지 않아요.
파드 스케줄링 준비를 사용해 모든 스케줄링 게이트가 제거될 때까지 파드의 스케줄링을 지연시킬 수 있어요. 예를 들어 파드 집합을 정의하되 모든 파드가 생성된 후에만 스케줄링을 트리거하고 싶을 수 있어요.
파드와 결함 복구 (#pod-fault-recovery)
파드의 컨테이너 중 하나가 실패하면 Kubernetes는 그 특정 컨테이너를 재시작하려고 시도할 수 있어요. 파드가 컨테이너 문제를 처리하는 방법을 읽어 더 배워요.
그러나 파드는 클러스터가 복구할 수 없는 방식으로 실패할 수 있고, 그 경우 Kubernetes는 파드를 더 치유하려고 시도하지 않아요. 대신 Kubernetes는 파드를 삭제하고 다른 컴포넌트가 자동 치유를 제공하도록 의존해요.
파드가 노드에 스케줄링되고 그 노드가 실패하면 파드는 비정상으로 취급되고 Kubernetes는 결국 파드를 삭제해요. 파드는 축출(리소스 부족 또는 노드 유지보수로 인한)을 견디지 못해요.
Kubernetes는 비교적 폐기 가능한 파드 인스턴스를 관리하는 작업을 처리하는 컨트롤러라는 더 높은 수준의 추상화를 사용해요.
주어진 파드(UID로 정의됨)는 다른 노드로 "재스케줄링"되지 않아요. 대신 그 파드는 새롭고 거의 동일한 파드로 대체될 수 있어요. 대체 파드를 만들면 이전 파드가 가졌던 것과 같은 이름(.metadata.name에서처럼)을 가질 수도 있지만, 대체 파드는 이전 파드와 다른 .metadata.uid를 가질 거예요.
Kubernetes는 기존 파드의 대체가 대체되는 이전 파드와 같은 노드에 스케줄링된다고 보장하지 않아요.
연관된 수명 (#associated-lifetimes)
어떤 것이 파드와 같은 수명을 가진다고 말할 때, 예를 들어 볼륨은 그 특정 파드(정확한 UID를 가진)가 존재하는 한 그 것이 존재한다는 뜻이에요. 그 파드가 어떤 이유로든 삭제되고, 동일한 대체가 생성되더라도 관련된 것(이 예시에서는 볼륨)도 파괴되고 새로 생성돼요.
파드 단계 (#pod-phase)
파드의 status 필드는 phase 필드를 가진 PodStatus 객체예요.
파드의 단계는 파드가 수명주기에서 어디에 있는지에 대한 간단하고 높은 수준의 요약이에요. 단계는 컨테이너나 파드 상태에 대한 관찰의 포괄적인 합계로 의도된 것이 아니며, 포괄적인 상태 머신으로도 의도된 것이 아니에요.
파드 단계 값의 수와 의미는 엄격히 관리돼요. 여기에 문서화된 것 외에는 주어진 단계 값을 가진 파드에 대해 아무것도 가정해서는 안 돼요.
다음은 phase에 대한 가능한 값이에요:
| 값 | 설명 |
|---|---|
| Pending | 파드가 Kubernetes 클러스터에 수락되었지만 하나 이상의 컨테이너가 설정되어 실행할 준비가 되지 않음. 여기에는 파드가 스케줄링되기를 기다리는 시간과 네트워크를 통해 컨테이너 이미지를 다운로드하는 데 소비된 시간이 포함됨 |
| Running | 파드가 노드에 바인딩되었고 모든 컨테이너가 생성됨. 최소 하나의 컨테이너가 여전히 실행 중이거나 시작 또는 재시작 중임 |
| Succeeded | 파드의 모든 컨테이너가 성공으로 종료되었고 재시작되지 않음 |
| Failed | 파드의 모든 컨테이너가 종료되었고 최소 하나의 컨테이너가 실패로 종료됨. 즉 컨테이너가 0이 아닌 상태로 종료되었거나 시스템에 의해 종료되었으며 자동 재시작으로 설정되지 않음 |
| Unknown | 어떤 이유로든 파드의 상태를 얻을 수 없음. 이 단계는 일반적으로 파드가 실행되어야 하는 노드와의 통신 오류로 인해 발생함 |
파드가 반복적으로 시작에 실패하면 일부 kubectl 명령의 Status 필드에 CrashLoopBackOff가 나타날 수 있어요. 마찬가지로 파드가 삭제 중일 때 일부 kubectl 명령의 Status 필드에 Terminating이 나타날 수 있어요.
사용자 직관을 위한 kubectl 표시 필드인 Status를 파드의 phase와 혼동하지 않도록 주의해요. 파드 단계는 Kubernetes 데이터 모델과 Pod API의 명시적 부분이에요.
NAMESPACE NAME READY STATUS RESTARTS AGE
alessandras-namespace alessandras-pod 0/1 CrashLoopBackOff 200 2d9h
파드에는 원활하게 종료할 기간(term)이 부여되며 기본값은 30초예요. --force 플래그를 사용해 파드를 강제 종료할 수 있어요(/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination-forced).
Kubernetes 1.27부터 kubelet은 삭제된 파드를 API 서버에서 삭제하기 전에 두 가지 예외와 함께 종료 단계(파드 컨테이너의 종료 상태에 따라 Failed 또는 Succeeded)로 전환해요:
노드가 죽거나 클러스터의 나머지 부분에서 연결이 끊기면 Kubernetes는 잃어버린 노드의 모든 파드의 단계를 Failed로 설정하는 정책을 적용해요.
컨테이너 상태 (#container-states)
파드 전체의 #단계뿐 아니라 Kubernetes는 파드 내부의 각 컨테이너의 상태도 추적해요. 컨테이너 수명주기 훅을 사용해 컨테이너 수명주기의 특정 지점에서 실행할 이벤트를 트리거할 수 있어요.
스케줄러가 파드를 노드에 할당하면 kubelet은 컨테이너 런타임을 사용해 그 파드에 대한 컨테이너 생성을 시작해요. 가능한 컨테이너 상태는 Waiting, Running, Terminated 세 가지가 있어요.
파드의 컨테이너 상태를 확인하려면 kubectl describe pod <name-of-pod>를 사용할 수 있어요. 출력은 그 파드 내 각 컨테이너의 상태를 보여줘요.
각 상태는 특정한 의미를 가져요:
Waiting (#container-state-waiting)
컨테이너가 Running 또는 Terminated 상태가 아니면 Waiting이에요. Waiting 상태의 컨테이너는 시작을 완료하는 데 필요한 작업(예: 컨테이너 이미지 레지스트리에서 컨테이너 이미지 가져오기, 또는 Secret 데이터 적용)을 여전히 실행 중이에요.
kubectl을 사용해 Waiting인 컨테이너가 있는 파드를 조회하면 컨테이너가 그 상태에 있는 이유를 요약하는 Reason 필드도 볼 수 있어요.
Running (#container-state-running)
Running 상태는 컨테이너가 문제 없이 실행 중임을 나타내요. postStart 훅이 구성되어 있었다면 이미 실행되어 끝났어요. kubectl을 사용해 Running인 컨테이너가 있는 파드를 조회하면 컨테이너가 Running 상태에 들어간 시점에 대한 정보도 볼 수 있어요.
Terminated (#container-state-terminated)
Terminated 상태의 컨테이너는 실행을 시작한 후 완료까지 실행되었거나 어떤 이유로 실패했어요. kubectl을 사용해 Terminated인 컨테이너가 있는 파드를 조회하면 그 컨테이너의 실행 기간에 대한 reason, exit code, 시작 및 종료 시간을 볼 수 있어요.
컨테이너에 preStop 훅이 구성되어 있으면 이 훅은 컨테이너가 Terminated 상태에 들어가기 전에 실행돼요.
파드가 컨테이너 문제를 처리하는 방법 (#container-restarts)
Kubernetes는 파드 spec에 정의된 #재시작-정책(restartPolicy)을 사용해 파드 내의 컨테이너 실패를 관리해요. 이 정책은 Kubernetes가 오류나 다른 이유로 종료되는 컨테이너에 어떻게 반응하는지 결정하며, 다음 순서로 이어져요:
- 초기 크래시 (Initial crash): Kubernetes는 파드 restartPolicy에 기반해 즉시 재시작을 시도해요.
- 반복 크래시 (Repeated crashes): 초기 크래시 후 Kubernetes는 후속 재시작에 지수 백오프 지연을 적용해요(#재시작-정책 참고). 이것은 빠르고 반복적인 재시작 시도가 시스템에 과부하를 주는 것을 방지해요.
- CrashLoopBackOff 상태 (CrashLoopBackOff state): 백오프 지연 메커니즘이 크래시 루프에 있는 주어진 컨테이너(반복적으로 실패하고 재시작되는)에 현재 적용 중임을 나타내요.
- 백오프 재설정 (Backoff reset): 컨테이너가 특정 기간(예: 10분) 동안 성공적으로 실행되면 Kubernetes는 백오프 지연을 재설정해 새 크래시를 첫 번째로 취급해요.
실제로 CrashLoopBackOff는 컨테이너가 제대로 시작하지 못하고 계속 시도하고 루프에서 실패할 때 파드를 설명하거나 나열하는 동안 kubectl 명령의 출력으로 보일 수 있는 조건 또는 이벤트예요.
즉 컨테이너가 크래시 루프에 들어가면 Kubernetes는 컨테이너 재시작 정책에 언급된 지수 백오프 지연을 적용해요. 이 메커니즘은 결함 있는 컨테이너가 계속된 실패한 시작 시도로 시스템을 압도하는 것을 방지해요.
CrashLoopBackOff는 다음과 같은 문제로 인해 발생할 수 있어요:
- 컨테이너를 종료하게 하는 애플리케이션 오류.
- 잘못된 환경 변수 또는 누락된 구성 파일 같은 구성 오류.
- 컨테이너가 제대로 시작할 충분한 메모리나 CPU를 가지지 못할 수 있는 리소스 제약.
- 애플리케이션이 예상 시간 내에 서빙을 시작하지 않으면 실패하는 헬스 체크.
- #프로브-섹션(#container-probes)에서 언급된 대로 Failure 결과를 반환하는 컨테이너 liveness 프로브 또는 startup 프로브.
CrashLoopBackOff 문제의 근본 원인을 조사하려면 사용자는 다음을 할 수 있어요:
- 로그 확인 (Check logs):
kubectl logs <name-of-pod>를 사용해 컨테이너의 로그를 확인해요. 이것은 크래시를 일으키는 문제를 진단하는 가장 직접적인 방법인 경우가 많아요. - 이벤트 검사 (Inspect events):
kubectl describe pod <name-of-pod>를 사용해 파드의 이벤트를 보면 구성이나 리소스 문제에 대한 힌트를 제공할 수 있어요. - 구성 검토 (Review configuration): 환경 변수와 마운트된 볼륨을 포함한 파드 구성이 올바른지, 필수 외부 리소스를 모두 사용할 수 있는지 확인해요.
- 리소스 한도 확인 (Check resource limits): 컨테이너에 충분한 CPU와 메모리가 할당되었는지 확인해요. 때로는 파드 정의에서 리소스를 늘리는 것이 문제를 해결할 수 있어요.
- 애플리케이션 디버깅 (Debug application): 애플리케이션 코드에 버그나 잘못된 구성이 있을 수 있어요. 이 컨테이너 이미지를 로컬이나 개발 환경에서 실행하면 애플리케이션 특유의 문제를 진단하는 데 도움이 될 수 있어요.
컨테이너 재시작 (#restart-policy)
파드의 컨테이너가 중지되거나 실패를 겪으면 Kubernetes가 그것을 재시작할 수 있어요. 재시작이 항상 적절한 것은 아니에요. 예를 들어 init 컨테이너는 (성공하면) 파드 시작 중에 한 번만 실행돼요.
재시작을 모든 파드에 적용되는 정책으로 구성하거나 컨테이너 수준 구성(예: 사이드카 컨테이너를 정의할 때) 또는 컨테이너 수준 오버라이드를 정의해 구성할 수 있어요.
컨테이너 재시작과 복원력 (#container-restart-resilience)
Kubernetes 프로젝트는 사전 통지 없이 임의의 재시작을 고려하는 복원력 있는 설계를 포함한 클라우드 네이티브 원칙을 따를 것을 권장해요. 파드를 실패시키고 자동 대체(/docs/concepts/workloads/controllers/)에 의존하거나, 컨테이너 수준 복원력을 위해 설계함으로써 이를 달성할 수 있어요. 두 접근 방식 모두 부분적 실패에도 전체 워크로드가 사용 가능하게 유지되도록 보장하는 데 도움이 돼요.
파드 수준 컨테이너 재시작 정책 (#pod-level-container-restart-policy)
파드의 spec에는 가능한 값이 Always, OnFailure, Never인 restartPolicy 필드가 있어요. 기본값은 Always예요.
파드에 대한 restartPolicy는 파드의 앱 컨테이너와 일반 init 컨테이너에 적용돼요. 사이드카 컨테이너는 파드 수준 restartPolicy 필드를 무시해요: Kubernetes에서 사이드카는 컨테이너 수준 restartPolicy가 Always로 설정된 initContainers 내부의 항목으로 정의돼요.
오류로 종료되는 init 컨테이너의 경우 파드 수준 restartPolicy가 OnFailure 또는 Always이면 kubelet이 init 컨테이너를 재시작해요:
Always: 어떤 종료 후에도 컨테이너를 자동으로 재시작함.OnFailure: 컨테이너가 오류로(0이 아닌 종료 상태) 종료된 경우에만 재시작함.Never: 종료된 컨테이너를 자동으로 재시작하지 않음.
다음 표는 컨테이너가 다른 재시작 정책과 종료 코드에서 어떻게 동작하는지 보여줘요:
| 종료 코드 | restartPolicy: Always | restartPolicy: OnFailure | restartPolicy: Never | 사이드카 컨테이너 |
|---|---|---|---|---|
| 0 (성공) | 재시작 | 재시작 안 함 | 재시작 안 함 | 항상 재시작 |
| 0이 아닌 값 (실패) | 재시작 | 재시작 | 재시작 안 함 | 항상 재시작 |
재시작 동작은 Deployment와 Jobs 사이를 선택할 때 특히 중요해요:
- Deployment는 일반적으로 애플리케이션을 계속 실행하기 위해
restartPolicy: Always(유일한 허용 값)를 사용해요. - Jobs는 일반적으로 배치 처리 작업을 적절히 처리하기 위해
restartPolicy: OnFailure또는restartPolicy: Never를 사용해요. - 사이드카 컨테이너는 자체 컨테이너 수준
restartPolicy: Always를 가지므로 파드의 restartPolicy와 관계없이 항상 재시작되는 init 컨테이너예요.
다음은 서로 다른 재시작 동작을 보여주는 구체적인 예시들이에요.
예시 1: restartPolicy: Always인 웹 서버 (Deployment에서 일반적)
apiVersion: v1
kind: Pod
metadata:
name: web-server
spec:
restartPolicy: Always # Container restarts regardless of exit code
containers:
- name: nginx
image: nginx:1.14.2
# If this container crashes or exits for any reason, it will be restarted
예시 2: restartPolicy: OnFailure인 배치 작업
apiVersion: batch/v1
kind: Job
metadata:
name: data-processor
spec:
template:
spec:
restartPolicy: OnFailure # Only restart on non-zero exit codes
containers:
- name: processor
image: busybox:1.28
command: ['sh', '-c', 'echo "Processing data..."; exit 0']
# Exit code 0: Job completes successfully, no restart
# Exit code 1+: Container restarts to retry the task
예시 3: restartPolicy: Never인 일회성 작업
apiVersion: v1
kind: Pod
metadata:
name: migration-task
spec:
restartPolicy: Never # Never restart, regardless of exit code
containers:
- name: migrate
image: busybox:1.28
command: ['sh', '-c', 'echo "Running migration..."; exit 1']
# Even with exit code 1 (failure), the container will not restart
# The Pod will remain in Failed state
사이드카 컨테이너는 일반 앱 컨테이너와 다른 특별한 재시작 동작을 가져요:
- 사이드카 컨테이너는 파드 수준 restartPolicy를 무시함: 자체 컨테이너 수준 restartPolicy 필드를 사용하며, 항상
Always로 설정됨. - 독립적 수명주기: 사이드카 컨테이너는 기본 애플리케이션 컨테이너와 독립적으로 재시작될 수 있음.
- 지속적 운영: 사이드카 컨테이너는 지원 서비스를 제공하기 위해 파드의 수명 내내 실행됨.
예시: 사이드카 컨테이너가 있는 파드
apiVersion: v1
kind: Pod
metadata:
name: app-with-sidecar
spec:
restartPolicy: OnFailure # Applies to main container only
initContainers:
- name: logging-sidecar # This is a sidecar container
image: fluent/fluent-bit:1.8
restartPolicy: Always # Sidecar always restarts regardless of exit code
# Provides logging services throughout Pod lifetime
containers:
- name: main-app # This follows Pod-level restartPolicy
image: nginx:1.14.2
# Will only restart on failure (non-zero exit) due to Pod's OnFailure policy
kubelet이 구성된 재시작 정책에 따라 컨테이너 재시작을 처리할 때 그것은 같은 파드 안에서 대체 컨테이너를 만들고 같은 노드에서 실행하는 재시작에만 적용돼요. 파드의 컨테이너가 종료된 후 kubelet은 지수 백오프 지연(10s, 20s, 40s, ...)으로 그것들을 재시작하며, 이것은 300초(5분)에서 최대가 돼요. 컨테이너가 문제 없이 10분 동안 실행되면 kubelet은 그 컨테이너의 재시작 백오프 타이머를 재설정해요.
사이드카 컨테이너와 파드 수명주기는 그것에 restartPolicy 필드를 지정할 때 init 컨테이너의 동작을 설명해요.
개별 컨테이너 재시작 정책과 규칙 (#container-restart-rules)
클러스터가 ContainerRestartRules 기능 게이트를 활성화했다면 개별 컨테이너에 restartPolicy와 restartPolicyRules를 지정해 파드 재시작 정책을 재정의할 수 있어요. 컨테이너 재시작 정책과 규칙은 파드의 앱 컨테이너와 일반 init 컨테이너에 적용돼요.
Kubernetes 네이티브 사이드카 컨테이너는 컨테이너 수준 restartPolicy가 Always로 설정돼 있어요.
컨테이너 재시작은 위에서 설명한 파드 재시작 정책과 같은 지수 백오프를 따를 거예요.
지원되는 컨테이너 재시작 정책:
Always: 어떤 종료 후에도 컨테이너를 자동으로 재시작함.OnFailure: 컨테이너가 오류로(0이 아닌 종료 상태) 종료된 경우에만 재시작함.Never: 종료된 컨테이너를 자동으로 재시작하지 않음.
추가로 개별 컨테이너는 restartPolicyRules를 지정할 수 있어요. restartPolicyRules 필드가 지정되면 컨테이너 restartPolicy도 반드시 지정되어야 해요. restartPolicyRules는 컨테이너 종료 시 적용할 규칙 목록을 정의해요. 각 규칙은 조건(condition)과 동작(action)으로 구성될 거예요. 지원되는 조건은 exitCodes로, 컨테이너의 종료 코드를 주어진 값 목록과 비교해요. 지원되는 동작은 Restart로, 컨테이너가 재시작됨을 의미해요. 규칙은 순서대로 평가돼요. 첫 일치 시 그 동작이 적용돼요. 규칙의 어떤 조건도 일치하지 않으면 Kubernetes는 컨테이너의 구성된 restartPolicy로 돌아가요.
예를 들어 OnFailure 재시작 정책을 가진 try-once 컨테이너가 있는 파드. 이것은 파드가 특정 컨테이너만 재시작하게 해줘요:
apiVersion: v1
kind: Pod
metadata:
name: on-failure-pod
spec:
restartPolicy: OnFailure
containers:
- name: try-once-container # This container will run only once because the restartPolicy is Never.
image: registry.k8s.io/busybox:1.27.2
command: ['sh', '-c', 'echo "Only running once" && sleep 10 && exit 1']
restartPolicy: Never
- name: on-failure-container # This container will be restarted on failure.
image: registry.k8s.io/busybox:1.27.2
command: ['sh', '-c', 'echo "Keep restarting" && sleep 1800 && exit 1']
오직 한 번만 실행하는 init 컨테이너가 있는 Always 재시작 정책의 파드. init 컨테이너가 실패하면 파드가 실패해요. 이것은 초기화가 실패하면 파드가 실패하게 하지만, 초기화가 성공하면 계속 실행하게 해줘요:
apiVersion: v1
kind: Pod
metadata:
name: fail-pod-if-init-fails
spec:
restartPolicy: Always
initContainers:
- name: init-once # This init container will only try once. If it fails, the pod will fail.
image: registry.k8s.io/busybox:1.27.2
command: ['sh', '-c', 'echo "Failing initialization" && sleep 10 && exit 1']
restartPolicy: Never
containers:
- name: main-container # This container will always be restarted once initialization succeeds.
image: registry.k8s.io/busybox:1.27.2
command: ['sh', '-c', 'sleep 1800 && exit 0']
Never 재시작 정책의 파드에서 특정 종료 코드에 대해 무시하고 재시작하는 컨테이너. 이것은 재시작 가능한 오류와 재시작 불가능한 오류를 구분하는 데 유용해요:
apiVersion: v1
kind: Pod
metadata:
name: restart-on-exit-codes
spec:
restartPolicy: Never
containers:
- name: restart-on-exit-codes
image: registry.k8s.io/busybox:1.27.2
command: ['sh', '-c', 'sleep 60 && exit 0']
restartPolicy: Never # Container restart policy must be specified if rules are specified
restartPolicyRules: # Only restart the container if it exits with code 42
- action: Restart
exitCodes:
operator: In
values: [42]
재시작 규칙은 훨씬 더 많은 고급 수명주기 관리 시나리오에 사용될 수 있어요. 재시작 규칙은 일반 재시작 정책과 같은 불일치의 영향을 받는다는 점을 유의해요. kubelet 재시작, 컨테이너 런타임 가비지 컬렉션, 컨트롤 플레인과의 간헐적 연결 문제가 상태 손실을 일으킬 수 있고, 컨테이너를 재시작하기를 기대하지 않을 때에도 컨테이너가 다시 실행될 수 있어요.
모든 컨테이너 재시작 (#restart-all-containers)
클러스터가 RestartAllContainersOnContainerExits 기능 게이트를 활성화했다면 컨테이너 수준에서 restartPolicyRules의 동작으로 RestartAllContainers를 지정할 수 있어요. 컨테이너의 종료가 이 동작을 가진 규칙과 일치하면 전체 파드가 종료되고 인플레이스로 재시작돼요.
이 "인플레이스" 재시작은 전체 삭제와 재생성에 비해 파드의 상태를 재설정하는 더 효율적인 방법을 제공해요. 이것은 배치 작업이나 AI/ML 훈련 작업 같은 재스케줄링 비용이 큰 워크로드에 특히 유용해요.
RestartAllContainers 동작이 트리거되면 kubelet은 다음 단계를 수행해요:
- 빠른 종료 (Fast Termination): 파드의 모든 실행 중인 컨테이너가 종료됨. 구성된 terminationGracePeriodSeconds는 존중되지 않고 구성된 preStop 훅도 실행되지 않음. 이것은 신속한 종료를 보장함.
- 파드 리소스 보존 (Preservation of Pod Resources): 파드의 필수 리소스가 보존됨: 파드 UID, IP 주소, 네트워크 네임스페이스, 파드 샌드박스, 연결된 디바이스, emptyDir과 마운트된 볼륨을 포함한 모든 볼륨.
- 파드 상태 업데이트 (Pod Status Update): 파드의 상태가 PodRestartInPlace 조건을 True로 설정해 업데이트됨. 이것은 재시작 과정을 관찰 가능하게 함.
- 전체 재시작 순서 (Full Restart Sequence): 모든 컨테이너가 종료되면 PodRestartInPlace 조건이 False로 설정되고 파드가 표준 시작 과정을 시작함: init 컨테이너가 순서대로 다시 실행됨. 사이드카와 일반 컨테이너가 시작됨.
이 기능의 핵심 측면은 이전에 성공적으로 완료되었거나 실패한 컨테이너를 포함해 모든 컨테이너가 재시작된다는 것이에요. RestartAllContainers 동작은 구성된 어떤 컨테이너 수준 또는 파드 수준 restartPolicy도 재정의해요.
이 메커니즘은 모든 컨테이너에 대한 깨끗한 출발점이 필요한 시나리오에서 유용해요. 예:
- init 컨테이너가 손상될 수 있는 환경을 설정하면 이 기능은 설정 과정이 다시 실행되도록 보장함.
- 사이드카 컨테이너가 기본 애플리케이션의 건강을 모니터링하고 애플리케이션이 복구 불가능한 상태에 들어가면 전체 파드 재시작을 트리거할 수 있음.
watcher 사이드카가 오류가 발생하면 알려진 양호한 상태에서 기본 애플리케이션을 재시작할 책임이 있는 워크로드를 고려해요. watcher는 특정 코드로 종료해 worker 파드의 전체 인플레이스 재시작을 트리거할 수 있어요.
apiVersion: v1
kind: Pod
metadata:
name: ml-worker
spec:
restartPolicy: Never # The pod itself should not restart unless explicitly told to.
initContainers:
- name: setup-environment
image: registry.k8s.io/busybox:1.27.2
command: ['sh', '-c', 'echo "Setting up environment"']
# This init container runs once to prepare the environment.
# It will run again after a RestartAllContainers action.
- name: watcher-sidecar
image: registry.k8s.io/busybox:1.27.2
# In a real-world scenario, this would be a dedicated watcher image.
# This command simulates the watcher exiting with a special code.
command: ['sh', '-c', 'sleep 60; exit 88']
restartPolicy: Always
restartPolicyRules:
- action: RestartAllContainers
exitCodes:
# Exit code 88 triggers a full pod restart.
operator: In
values: [88]
containers:
- name: main-application
image: registry.k8s.io/busybox:1.27.2
command: ['sh', '-c', 'echo "Application is running"; sleep 3600']
이 예시에서:
- 파드의 전체 restartPolicy는
Never임. - watcher-sidecar는 명령을 실행한 후 코드
88로 종료함. - 종료 코드가 규칙과 일치해 RestartAllContainers 동작을 트리거함.
- setup-environment init 컨테이너와 main-application 컨테이너를 포함한 전체 파드가 인플레이스로 재시작됨. 파드는 자신의 UID, 샌드박스, IP, 볼륨을 유지함.
줄어든 컨테이너 재시작 지연 (#reduced-container-restart-delay)
알파 기능 게이트 ReduceDefaultCrashLoopBackOffDecay가 활성화되면 클러스터 전체의 컨테이너 시작 재시도가 1s(10s 대신)에서 시작하고 60s(5분인 300s 대신)의 최대 지연까지 각 재시작마다 2배씩 지수적으로 증가하도록 줄어요.
이 기능을 아래 설명된 알파 기능 KubeletCrashLoopBackOffMax와 함께 사용하면 개별 노드가 다른 최대 지연을 가질 수 있어요.
구성 가능한 컨테이너 재시작 지연 (#configurable-container-restart-delay)
KubeletCrashLoopBackOffMax 기능 게이트가 활성화되면 기본 300s(5분)에서 컨테이너 시작 재시도 사이의 최대 지연을 재구성할 수 있어요. 이 구성은 kubelet 구성을 사용해 노드별로 설정돼요. kubelet 구성에서 crashLoopBackOff 아래에 maxContainerRestartPeriod 필드를 "1s"와 "300s" 사이로 설정해요. 위의 컨테이너 재시작 정책에서 설명한 대로 그 노드의 지연은 여전히 10s에서 시작하고 각 재시작마다 2배씩 지수적으로 증가하지만 이제 구성한 최대값에서 최대가 돼요. 구성한 maxContainerRestartPeriod가 기본 초기 값 10s보다 작으면 초기 지연이 대신 구성된 최대값으로 설정될 거예요.
다음 kubelet 구성 예시를 참고해요:
# container restart delays will start at 10s, increasing
# 2x each time they are restarted, to a maximum of 100s
kind: KubeletConfiguration
crashLoopBackOff:
maxContainerRestartPeriod: "100s"
# delays between container restarts will always be 2s
kind: KubeletConfiguration
crashLoopBackOff:
maxContainerRestartPeriod: "2s"
이 기능을 위에 설명된 알파 기능 ReduceDefaultCrashLoopBackOffDecay와 함께 사용하면 클러스터의 초기 백오프와 최대 백오프 기본값이 더 이상 10s와 300s가 아니라 1s와 60s가 돼요. 노드별 구성은 ReduceDefaultCrashLoopBackOffDecay가 설정한 기본값보다 우선하며, 그것이 클러스터의 다른 노드보다 노드가 더 긴 최대 백오프를 가지게 되더라도 그렇지.
파드 조건 (#pod-conditions)
파드는 파드를 통과했는지 통과하지 않았는지를 나타내는 PodConditions(/docs/reference/generated/kubernetes-api/v1.37/#podcondition-v1-core)의 배열을 가진 PodStatus를 가져요. kubelet은 다음 PodConditions를 관리해요:
PodScheduled: 파드가 노드에 스케줄링됨.PodReadyToStartContainers: 파드 샌드박스가 성공적으로 생성되고, 네트워킹이 구성되고, 스토리지 볼륨이 마운트되고, (요청된 경우) 동적 리소스가 할당됨.ContainersReady: 파드의 모든 컨테이너가 준비됨.Initialized: 모든 init 컨테이너가 성공적으로 완료됨.Ready: 파드가 요청을 서빙할 수 있고 모든 일치하는 Service의 로드 밸런싱 풀에 추가되어야 함.DisruptionTarget: 파드가 중단(선점, 축출 또는 가비지 컬렉션 같은 것)으로 인해 종료되려고 함.PodResizePending: 파드 크기 조정이 요청되었지만 적용할 수 없음. 크기 조정 상태를 참고.PodResizeInProgress: 파드가 크기 조정 과정 중임. 크기 조정 상태를 참고.
| 필드 이름 | 설명 |
|---|---|
| type | 이 Pod 조건의 이름 |
| status | 그 조건이 적용되는지 나타내며 가능한 값은 "True", "False", "Unknown" |
| lastProbeTime | Pod 조건이 마지막으로 프로브된 시점의 타임스탬프 |
| lastTransitionTime | 파드가 마지막으로 한 상태에서 다른 상태로 전환된 시점의 타임스탬프 |
| reason | 조건의 마지막 전환 이유를 나타내는 기계가 읽을 수 있는 UpperCamelCase 텍스트 |
| message | 마지막 상태 전환에 대한 세부 사항을 나타내는 사람이 읽을 수 있는 메시지 |
파드 준비 (#pod-readiness-gate)
애플리케이션은 PodStatus에 추가 피드백 또는 신호를 주입할 수 있어요: 파드 준비. 이것을 사용하려면 파드의 spec에서 readinessGates를 설정해 kubelet이 파드 준비에 대해 평가하는 추가 조건 목록을 지정해요.
준비 게이트는 파드의 status.condition 필드의 현재 상태로 결정돼요. Kubernetes가 파드의 status.conditions 필드에서 그러한 조건을 찾을 수 없으면 조건의 상태는 "False"로 기본 설정돼요.
다음은 예시예요:
kind: Pod
...
spec:
readinessGates:
- conditionType: "www.example.com/feature-1"
status:
conditions:
- type: Ready # a built-in PodCondition
status: "False"
lastProbeTime: null
lastTransitionTime: 2018-01-01T00:00:00Z
- type: "www.example.com/feature-1" # an extra PodCondition
status: "False"
lastProbeTime: null
lastTransitionTime: 2018-01-01T00:00:00Z
containerStatuses:
- containerID: docker://abcd...
ready: true
...
추가하는 Pod 조건은 Kubernetes 레이블 키 형식을 충족하는 이름을 가져야 해요.
파드 준비 상태 (#pod-readiness-status)
kubectl patch 명령은 객체 상태 패칭을 지원하지 않아요. 파드에 대해 이러한 status.conditions를 설정하려면 애플리케이션과 운영자가 PATCH 동작을 사용해야 해요. Kubernetes 클라이언트 라이브러리를 사용해 파드 준비에 대한 커스텀 Pod 조건을 설정하는 코드를 작성할 수 있어요.
커스텀 조건을 사용하는 파드의 경우 다음 두 명제가 모두 적용될 때만 그 파드가 준비된 것으로 평가돼요:
- 파드의 모든 컨테이너가 준비됨.
- readinessGates에 지정된 모든 조건이
True임.
파드의 컨테이너가 Ready이지만 커스텀 조건 중 최소 하나가 누락되거나 False이면 kubelet은 파드의 #조건을 ContainersReady로 설정해요.
컨테이너 시작을 위한 파드 준비 (#pod-ready-to-start-containers)
파드가 노드에 스케줄링된 후 kubelet에 의해 admission되고 필수 스토리지 볼륨이 마운트되어야 해요. 이러한 단계가 완료되면 kubelet은 (CRI(Container Runtime Interface)(/docs/concepts/architecture/cri)를 사용하는) 컨테이너 런타임과 협력해 런타임 샌드박스를 설정하고 파드의 네트워킹을 구성해요. 파드가 동적 리소스 할당을 사용하면 그 리소스도 이 단계에서 할당돼요. PodReadyToStartContainers 조건이 파드의 status.conditions 필드에 추가돼요.
파드가 네트워킹이 구성된 런타임 샌드박스를 가지지 않았다는 것을 감지하면 조건이 kubelet에 의해 False로 설정돼요. 이것은 다음 시나리오에서 발생해요:
- 파드 수명주기 초기에 kubelet이 아직 컨테이너 런타임을 사용해 파드에 대한 샌드박스를 설정하기 시작하지 않았을 때.
- 파드 수명주기 후반에 파드 샌드박스가 다음 중 하나로 인해 파괴되었을 때: 노드 재부팅, 격리에 가상 머신을 사용하는 컨테이너 런타임의 경우 파드가 축출되지 않고, 파드 샌드박스 가상 머신 재부팅으로 인해 새 샌드박스와 새로운 컨테이너 네트워크 구성을 만들어야 할 때.
샌드박스 생성, 네트워크 구성, 볼륨 마운트, (요청된 경우) 동적 리소스 할당이 완료된 후 kubelet은 PodReadyToStartContainers 조건을 True로 설정해요. 이미지 풀링과 컨테이너 생성은 이 지점 이후에 발생해요.
init 컨테이너가 있는 파드의 경우 kubelet은 init 컨테이너가 성공적으로 완료된 후(런타임 플러그인에 의한 성공적인 샌드박스 생성과 네트워크 구성 후에 발생) Initialized 조건을 True로 설정해요. init 컨테이너가 없는 파드의 경우 kubelet은 샌드박스 생성과 네트워크 구성이 시작되기 전에 Initialized 조건을 True로 설정해요.
파드 크기 조정 (#pod-resize)
Kubernetes는 파드가 생성된 후 파드에 할당된 CPU와 메모리 리소스를 변경하는 것을 지원해요. (다른 인프라 리소스의 경우 그 리소스에 특화된 다른 기술을 사용해야 해요.) CPU와 메모리 크기 조정에는 두 가지 주요 접근 방식이 있어요:
인플레이스 파드 크기 조정 (#pod-resize-inplace)
파드를 다시 만들지 않고 파드의 컨테이너 수준 CPU와 메모리 리소스를 크기 조정할 수 있어요. 이것은 인플레이스 파드 수직 확장이라고도 해요. 이것은 잠재적으로 애플리케이션 중단을 피하면서 실행 중인 컨테이너에 대한 리소스 할당을 조정할 수 있게 해줘요.
파드 수준에서 리소스를 지정했다면 그것들을 인플레이스로 크기 조정할 수도 있어요. 자세한 내용은 파드에 할당된 CPU·메모리 리소스 크기 조정을 참고해요.
인플레이스 크기 조정을 수행하려면 /resize 하위 리소스를 사용해 파드의 원하는 상태를 업데이트해요. 그런 다음 kubelet이 새 리소스 값을 실행 중인 컨테이너에 적용하려고 시도해요. #파드 조건인 PodResizePending과 PodResizeInProgress(#pod-conditions에서 설명)가 크기 조정 작업의 상태를 나타내요. 크기 조정 상태에 대한 자세한 내용은 컨테이너 크기 조정 상태를 참고해요.
인플레이스 크기 조정에 대한 핵심 고려 사항:
- CPU와 메모리 리소스만 인플레이스로 크기 조정할 수 있음.
- 파드의 QoS(서비스 품질) 클래스는 생성 시 결정되며 크기 조정으로 변경할 수 없음.
- 컨테이너 사양에서 resizePolicy를 사용해 크기 조정에 컨테이너 재시작이 필요한지 구성할 수 있음.
인플레이스 크기 조정 수행에 대한 자세한 지침은 컨테이너에 할당된 CPU·메모리 리소스 크기 조정을 참고해요.
대체 파드 시작으로 크기 조정 (#resizing-by-launching-replacement-pods)
파드의 리소스를 변경하는 더 클라우드 네이티브한 접근 방식은 파드를 관리하는 워크로드 리소스(Deployment 또는 StatefulSet 같은 것)를 통하는 것이에요. Pod 템플릿에서 리소스 사양을 업데이트하면 워크로드의 컨트롤러가 업데이트된 리소스로 새 파드를 만들고 업데이트 전략에 따라 이전 파드를 종료해요.
이 접근 방식은:
- 어떤 Kubernetes 버전에서도 동작함.
- 리소스뿐 아니라 어떤 파드 사양도 변경할 수 있음.
- 파드 대체를 초래하므로 계획된 중단을 처리하도록 워크로드를 설계해야 함. 가용성을 제어하려면 PodDisruptionBudget 사용을 고려해요.
- 파드가 워크로드 리소스에 의해 관리되도록 요구함.
또한 VerticalPodAutoscaler를 사용해 파드 리소스 권장 사항과 업데이트를 자동으로 관리할 수 있어요.
컨테이너 프로브 (#container-probes)
Kubernetes는 파드의 컨테이너 건강을 지속적으로 모니터링하기 위한 프로브를 정의하게 해줘요. 프로브는 kubelet이 컨테이너에 대해 주기적으로 수행하는 진단이에요. 진단을 수행하기 위해 kubelet은 컨테이너 내부에서 코드를 실행하거나 네트워크 요청을 해요.
프로브 결과에 기반해 Kubernetes는 건강하지 않은 컨테이너를 재시작하거나 준비되지 않은 컨테이너로의 트래픽 전송을 중지할 수 있어요.
kubelet은 선택적으로 실행 중인 컨테이너에 대해 각각 다른 목적을 제공하는 세 종류의 프로브를 수행하고 반응할 수 있어요. 프로브 메커니즘(exec, grpc, httpGet, tcpSocket), 구성 필드, 자세한 사용 지침에 대해서는 Liveness, Readiness, Startup Probes를 참고해요.
Startup probe (#startup-probe)
Startup 프로브는 컨테이너 내부의 애플리케이션이 시작되었는지 확인해요. Startup 프로브가 구성되면 Kubernetes는 startup 프로브가 성공할 때까지 liveness나 readiness 프로브를 실행하지 않아 애플리케이션이 초기화를 마칠 시간을 준다.
이 유형의 프로브는 주기적으로 실행되는 liveness와 readiness 프로브와 달리 시작 시에만 실행돼요.
Startup 프로브가 실패하면 kubelet이 컨테이너를 죽이고 컨테이너는 그 재시작 정책을 받아요.
Liveness probe (#liveness-probe)
Liveness 프로브는 언제 컨테이너를 재시작할지 결정해요. 예를 들어 liveness 프로브는 애플리케이션이 실행 중이지만 진행할 수 없는 교착 상태(deadlock)를 잡아낼 수 있어요. 그러한 상태의 컨테이너를 재시작하면 버그에도 불구하고 애플리케이션을 더 사용 가능하게 만드는 데 도움이 될 수 있어요.
컨테이너가 구성된 허용 오차보다 더 많이 liveness 프로브에 실패하면 kubelet이 그 컨테이너를 재시작해요. Liveness 프로브는 readiness 프로브가 성공할 때까지 기다리지 않아요. liveness 프로브 실행 전에 기다리려면 initialDelaySeconds를 정의하거나 startup 프로브를 사용할 수 있어요.
Readiness probe (#readiness-probe)
Readiness 프로브는 컨테이너가 언제 트래픽을 받아들일 준비가 되었는지 결정해요. 이것은 애플리케이션이 네트워크 연결 설정, 파일 로드, 캐시 워밍 같은 시간이 오래 걸리는 초기 작업을 수행할 때까지 기다릴 때 유용해요.
Readiness 프로브는 컨테이너 수명주기 후반, 예를 들어 일시적 결함이나 과부하에서 복구할 때도 유용할 수 있어요.
Readiness 프로브가 실패한 상태를 반환하면 EndpointSlice(/docs/concepts/services-networking/endpoint-slices/) 컨트롤러가 파드와 일치하는 모든 Service의 EndpointSlices에서 파드의 IP 주소를 제거해요.
Readiness 프로브는 컨테이너 전체 수명주기 동안 실행돼요.
파드 종료 (#pod-termination)
파드는 클러스터의 노드에서 실행되는 프로세스를 나타내므로 더 이상 필요하지 않을 때 그 프로세스가 원활하게 종료되도록(KILL 신호로 갑자기 중지되고 정리할 기회가 없는 것보다는) 허용하는 것이 중요해요.
설계 목표는 삭제를 요청하고 프로세스가 종료되는 시점을 알 수 있으면서도, 삭제가 결국 완료되도록 보장할 수 있게 하는 것이에요. 파드 삭제를 요청하면 클러스터가 파드가 강제로 죽는 것이 허용되기 전의 의도된 유예 기간(grace period)을 기록하고 추적해요. 그 강제 종료 추적이 마련된 상태에서 kubelet이 원활한 종료를 시도해요.
일반적으로 이 원활한 파드 종료로 kubelet은 컨테이너 런타임에 요청해 각 컨테이너의 기본 프로세스에 유예 기간 제한 시간으로 먼저 TERM(일명 SIGTERM) 신호를 보내 파드의 컨테이너를 중지하려고 시도해요. 컨테이너를 중지하라는 요청은 컨테이너 런타임이 비동기적으로 처리해요. 이러한 요청의 처리 순서에 대한 보장은 없어요. 많은 컨테이너 런타임이 컨테이너 이미지에 정의된 STOPSIGNAL 값을 존중하며, 다르면 TERM 대신 컨테이너 이미지가 구성한 STOPSIGNAL을 보내요. 유예 기간이 만료되면 KILL 신호가 남아 있는 프로세스에 보내지고 파드는 API 서버에서 삭제돼요. kubelet이나 컨테이너 런타임의 관리 서비스가 프로세스 종료를 기다리는 동안 재시작되면 클러스터는 전체 원래 유예 기간을 포함해 처음부터 다시 시도해요.
중지 신호 (#pod-termination-stop-signals)
컨테이너를 죽이는 데 사용되는 중지 신호는 컨테이너 이미지에서 STOPSIGNAL 지시문으로 정의할 수 있어요. 이미지에 중지 신호가 정의되지 않으면 컨테이너 런타임의 기본 신호(containerd와 CRI-O 모두 SIGTERM)가 컨테이너를 죽이는 데 사용될 거예요.
커스텀 중지 신호 정의 (#defining-custom-stop-signals)
ContainerStopSignals 기능 게이트가 활성화되면 컨테이너 Lifecycle에서 컨테이너에 대한 커스텀 중지 신호를 구성할 수 있어요. 컨테이너 수명주기에서 중지 신호를 정의하기 위한 요구 사항으로 파드의 spec.os.name 필드가 존재해야 해요. 유효한 신호 목록은 파드가 스케줄링된 OS에 따라 달라져요. Windows 노드에 스케줄링된 파드의 경우 SIGTERM과 SIGKILL만 유효한 신호로 지원해요.
다음은 커스텀 중지 신호를 정의하는 파드 spec의 예시예요:
spec:
os:
name: linux
containers:
- name: my-container
image: container-image:latest
lifecycle:
stopSignal: SIGUSR1
lifecycle에 중지 신호가 정의되면 이것이 컨테이너 이미지에 정의된 신호를 재정의해요. 컨테이너 spec에 중지 신호가 정의되지 않으면 컨테이너는 기본 동작으로 돌아가요.
파드 종료 흐름 (#pod-termination-flow)
예시로 설명되는 파드 종료 흐름:
- kubectl 도구를 사용해 기본 유예 기간(30초)으로 특정 파드를 수동으로 삭제해요.
- API 서버의 파드가 파드가 "dead"로 간주되는 시간과 유예 기간으로 업데이트돼요.
kubectl describe로 삭제하는 파드를 확인하면 그 파드가 "Terminating"으로 표시돼요. 파드가 실행되는 노드에서: kubelet이 파드가 terminating으로 표시된 것을 보자마자(원활한 종료 기간이 설정됨) kubelet이 로컬 파드 종료 과정을 시작해요. 파드의 컨테이너 중 하나가 preStop 훅을 정의했고 파드 spec의 terminationGracePeriodSeconds가 0으로 설정되지 않았다면 kubelet이 컨테이너 내부에서 그 훅을 실행해요. 기본 terminationGracePeriodSeconds 설정은 30초예요. preStop 훅이 유예 기간이 만료된 후에도 여전히 실행 중이면 kubelet이 2초의 작은 일회성 유예 기간 연장을 요청해요. 참고: preStop 훅이 기본 유예 기간이 허용하는 것보다 완료하는 데 더 오래 걸리면 이것을 맞추기 위해 terminationGracePeriodSeconds를 수정해야 해요. kubelet이 컨테이너 런타임을 트리거해 각 컨테이너 내부의 프로세스 1에 TERM 신호를 보내요. 파드에 사이드카 컨테이너가 정의되어 있으면 특별한 순서(#termination-with-sidecars)가 있어요. 그렇지 않으면 파드의 컨테이너는 서로 다른 시간과 임의의 순서로 TERM 신호를 받아요. 종료 순서가 중요하면 preStop 훅을 사용해 동기화(또는 사이드카 컨테이너 사용으로 전환)하는 것을 고려해요. - kubelet이 파드의 원활한 종료를 시작하는 동시에 컨트롤 플레인이 그 종료 중인 파드를 EndpointSlice 객체에서 제거할지 평가하며, 여기서 그 객체들은 구성된 셀렉터를 가진 Service를 나타내요. ReplicaSet(/docs/concepts/workloads/controllers/replicaset/)과 다른 워크로드 리소스는 더 이상 종료 중인 파드를 유효한 서비스 중 복제본으로 취급하지 않아요. 느리게 종료되는 파드는 정상 트래픽을 계속 서빙해서는 안 되고 종료를 시작하고 열린 연결 처리를 마쳐야 해요. 일부 애플리케이션은 열린 연결 처리를 마치는 것을 넘어 더 원활한 종료(예: 세션 드레이닝과 완료)가 필요해요. 종료되는 파드를 나타내는 어떤 엔드포인트도 EndpointSlices에서 즉시 제거되지 않고, EndpointSlice API에서 종료 상태를 나타내는 상태(/docs/concepts/services-networking/endpoint-slices/#conditions)가 노출돼요. 종료 엔드포인트는 항상 ready 상태가 false예요(1.26 이전 버전과의 역호환성 위해), 그래서 로드 밸런서가 정상 트래픽에 그것을 사용하지 않을 거예요. 종료되는 파드에서 트래픽 드레이닝이 필요하면 실제 준비 상태를
serving조건으로 확인할 수 있어요. 연결 드레이닝을 구현하는 방법에 대한 더 많은 세부 사항은 튜토리얼 파드와 엔드포인트 종료 흐름에서 찾을 수 있어요. - 유예 기간이 만료되면 파드 내부에 여전히 실행 중인 컨테이너가 있으면 kubelet은 강제 종료를 트리거해 파드가 종료되고 종료하도록 보장해요. 컨테이너 런타임이 파드의 어떤 컨테이너에서 여전히 실행 중인 어떤 프로세스에도 SIGKILL을 보내요. kubelet은 또한 그 컨테이너 런타임이 하나를 사용한다면 숨겨진 pause 컨테이너를 정리해요. kubelet은 파드를 (컨테이너의 종료 상태에 따라 Failed 또는 Succeeded인) 종료 단계로 전환해요. kubelet은 유예 기간을 0으로 설정(즉시 삭제)해 API 서버에서 파드 객체의 강제 제거를 트리거해요. API 서버가 파드의 API 객체를 삭제하며, 그 후 어떤 클라이언트에서도 더 이상 보이지 않아요.
강제 파드 종료 (#pod-termination-forced)
기본적으로 모든 삭제는 30초 내에 원활하게 수행돼요. kubectl delete 명령은 기본값을 재정의하고 자신의 값을 지정할 수 있는 --grace-period=<seconds> 옵션을 지원해요.
유예 기간을 0으로 설정하면 API 서버에서 파드가 강제로 그리고 즉시 삭제돼요. 파드가 아직 노드에서 실행 중이었다면 그 강제 삭제가 kubelet이 즉시 정리를 시작하도록 트리거해요.
kubectl을 사용할 때는 강제 삭제를 수행하기 위해 --grace-period=0과 함께 추가 플래그 --force를 지정해야 해요.
강제 삭제가 수행되면 API 서버는 파드가 실행 중이던 노드에서 종료되었다는 kubelet의 확인을 기다리지 않아요. API에서 파드를 즉시 제거하므로 같은 이름으로 새 파드를 만들 수 있어요. 노드에서 즉시 종료되도록 설정된 파드는 강제로 죽기 전에 여전히 작은 유예 기간이 주어질 거예요.
StatefulSet의 일부인 파드를 강제 삭제해야 한다면 StatefulSet에서 파드 삭제 작업 문서를 참고해요.
파드 종료와 사이드카 컨테이너 (#termination-with-sidecars)
파드에 하나 이상의 사이드카 컨테이너(Always 재시작 정책을 가진 init 컨테이너)가 포함되면 kubelet은 마지막 기본 컨테이너가 완전히 종료될 때까지 이러한 사이드카 컨테이너에 TERM 신호를 보내는 것을 지연시켜요. 사이드카 컨테이너는 파드 spec에 정의된 역순으로 종료돼요. 이것은 사이드카 컨테이너가 더 이상 필요하지 않을 때까지 파드의 다른 컨테이너를 계속 서빙하도록 보장해요.
이것은 기본 컨테이너의 느린 종료가 사이드카 컨테이너의 종료도 지연시킨다는 뜻이에요. 종료 과정이 완료되기 전에 유예 기간이 만료되면 파드가 강제 종료(#pod-termination-beyond-grace-period)에 들어갈 수 있어요. 이 경우 파드의 모든 남은 컨테이너가 짧은 유예 기간으로 동시에 종료돼요.
마찬가지로 파드가 종료 유예 기간을 초과하는 preStop 훅을 가지면 비상 종료가 발생할 수 있어요. 일반적으로 사이드카 컨테이너 없이 preStop 훅을 사용해 종료 순서를 제어했다면 이제 그것들을 제거하고 kubelet이 사이드카 종료를 자동으로 관리하게 할 수 있어요.
파드 가비지 컬렉션 (#pod-garbage-collection)
실패한 파드의 경우 API 객체는 사람이나 컨트롤러 프로세스가 명시적으로 제거할 때까지 클러스터의 API에 남아 있어요.
컨트롤 플레인의 컨트롤러인 파드 가비지 컬렉터(PodGC)는 파드 수가 구성된 임계값(kube-controller-manager의 terminated-pod-gc-threshold로 결정)을 초과하면 종료된 파드(Succeeded 또는 Failed 단계)를 정리해요. 이것은 시간이 지남에 따라 파드가 생성되고 종료됨에 따라 리소스 누수를 방지해요.
추가로 PodGC는 다음 조건 중 하나를 충족하는 파드도 정리해요:
- 더 이상 존재하지 않는 노드에 바인딩된 고아(orphan) 파드.
- 스케줄링되지 않은 종료 중인 파드.
node.kubernetes.io/out-of-service(/docs/reference/labels-annotations-taints/#node-kubernetes-io-out-of-service)로 테인트된 비준비 노드에 바인딩된 종료 중인 파드.
PodGC는 파드를 정리하는 것과 함께 비종료 단계이면 그것들을 실패로 표시하기도 해요. 또한 PodGC는 고아 파드를 정리할 때 파드 중단 조건을 추가해요. 자세한 내용은 파드 중단 조건을 참고해요.
kubelet 재시작 중 파드 동작 (#kubelet-restarts)
kubelet을 재시작하면 파드(와 그 컨테이너)는 재시작 중에도 계속 실행돼요. 노드에 실행 중인 파드가 있을 때 그 노드에서 kubelet을 중지하거나 재시작해도 kubelet이 kubelet 자신이 멈추기 전에 모든 로컬 파드를 중지하지는 않아요. 노드의 파드를 중지하려면 kubectl drain을 사용할 수 있어요.
kubelet 재시작 감지 (#detection-of-kubelet-restarts)
kubelet이 시작할 때 이미 바인딩된 파드가 있는 Node가 있는지 확인해요. Node의 Ready 조건(/docs/reference/node/node-status/#condition)이 변경되지 않은 상태, 즉 조건이 true에서 false로 전환되지 않았다면 Kubernetes는 이것을 kubelet 재시작으로 감지해요. (다른 방식으로 kubelet을 재시작할 수도 있습니다. 예를 들어 노드 버그를 고치기 위해. 하지만 이 경우 Kubernetes는 안전한 옵션을 선택하고 kubelet을 중지했다가 나중에 시작한 것처럼 취급해요.)
kubelet이 재시작되면 컨테이너 상태는 기능 게이트 설정에 따라 다르게 관리돼요:
- 기본적으로 kubelet은 재시작 후 컨테이너 상태를 변경하지 않아요.
ready: true상태로 설정된 컨테이너는 준비된 상태로 유지돼요. kubelet을 노드 하트비트 검사 시리즈에 실패할 만큼 오래 중지한 다음 kubelet을 다시 시작하기 전에 기다리면 Kubernetes가 그 노드에서 파드를 축출하기 시작할 수 있어요. 하지만 파드 축출이 시작되더라도 Kubernetes는 그 파드의 개별 컨테이너를ready: false로 표시하지 않아요. 파드 수준 축출은 컨트롤 플레인이 노드를node.kubernetes.io/not-ready로 테인트한 후(실패한 하트비트로 인해) 발생해요. - Kubernetes 1.37에서는 kubelet이 재시작 후 항상 컨테이너 ready 값을 false로 수정하는 레거시 동작에 선택할 수 있어요. 이 레거시 동작은 오랫동안 기본값이었지만 대규모 배포에서 특히 Kubernetes를 사용하는 사람들에게 문제를 일으켰어요. 기능 게이트가 이 레거시 동작으로 일시적으로 되돌릴 수 있게 해주지만, Kubernetes 프로젝트는 문제가 발생하면 버그 보고서를 제출할 것을 권장해요. ChangeContainerStatusOnKubeletRestart 기능 게이트(/docs/reference/command-line-tools-reference/feature-gates/#ChangeContainerStatusOnKubeletRestart)는 향후 제거될 거예요.
다음 단계 (What's next)
- 컨테이너 수명주기 이벤트에 핸들러 연결에 대한 실습 경험 얻기.
- Liveness, Readiness, Startup Probes 구성에 대한 실습 경험 얻기.
- 컨테이너 수명주기 훅에 대해 더 배우기.
- 사이드카 컨테이너에 대해 더 배우기.
- API에서 파드와 컨테이너 상태에 대한 자세한 정보는 파드의 status를 다루는 API 참조 문서를 참고.