중단
중단 (Disruptions)
이 가이드는 고가용성 애플리케이션을 구축하고 싶어, Pod에 어떤 종류의 중단이 발생할 수 있는지 이해해야 하는 애플리케이션 소유자를 위한 문서예요.
또한 클러스터 업그레이드나 오토스케일링 같은 자동화된 클러스터 작업을 수행하고 싶은 클러스터 관리자도 대상으로 합니다.
자발적 및 비자발적 중단 (Voluntary and involuntary disruptions)
Pod는 누군가(사람 또는 컨트롤러)가 파괴하거나, 피할 수 없는 하드웨어나 시스템 소프트웨어 오류가 없는 한 사라지지 않아요.
이런 피할 수 없는 경우를 애플리케이션의 *비자발적 중단(involuntary disruptions)*이라고 부릅니다. 예를 들면:
- 노드를 뒷받침하는 물리 머신의 하드웨어 고장
- 클러스터 관리자가 VM(인스턴스)을 실수로 삭제
- 클라우드 프로바이더나 하이퍼바이저 고장으로 VM이 사라짐
- 커널 패닉(kernel panic)
- 클러스터 네트워크 분할로 노드가 클러스터에서 사라짐
- 노드가 [리소스 부족(out-of-resources)]이 되어 Pod가 축출됨
리소스 부족 조건을 제외하면, 이 모든 조건은 대부분의 사용자에게 익숙할 거예요. 쿠버네티스에만 특정한 것이 아니기 때문입니다.
나머지 경우를 *자발적 중단(voluntary disruptions)*이라고 불러요. 여기에는 애플리케이션 소유자가 시작하는 동작과 클러스터 관리자가 시작하는 동작이 모두 포함됩니다. 전형적인 애플리케이션 소유자 동작에는 다음이 있어요.
- Pod를 관리하는 deployment나 다른 컨트롤러 삭제
- 재시작을 유발하는 deployment의 pod 템플릿 업데이트
- Pod 직접 삭제(예: 실수로)
클러스터 관리자 동작에는 다음이 포함됩니다.
- 수리나 업그레이드를 위해 [노드 드레이닝]
- 클러스터 축소를 위해 노드를 클러스터에서 드레이닝 ([Node Autoscaling]에 대해 배우기)
- 다른 것을 노드에 맞추기 위해 노드에서 Pod 제거
이 동작들은 클러스터 관리자가 직접 취하거나, 클러스터 관리자가 실행하는 자동화 또는 클러스터 호스팅 프로바이더가 취할 수 있어요.
클러스터 관리자에게 묻거나 클라우드 프로바이더/배포판 문서를 확인해서, 클러스터에 어떤 자발적 중단 소스가 활성화되어 있는지 확인하세요. 아무것도 활성화되어 있지 않으면 Pod Disruption Budget을 만들지 않아도 됩니다.
주의: 모든 자발적 중단이 Pod Disruption Budget으로 제한되는 것은 아니에요. 예를 들어 deployment나 pod를 삭제하면 Pod Disruption Budget을 우회합니다.
중단 다루기 (Dealing with disruptions)
비자발적 중단을 완화하는 몇 가지 방법이 있어요.
- Pod가 [필요한 리소스를 요청]하도록 하세요.
- 더 높은 가용성이 필요하면 애플리케이션을 복제하세요. (복제된 [stateless] 및 [stateful] 애플리케이션 실행에 대해 배우기)
- 복제된 애플리케이션을 실행할 때 더 높은 가용성을 위해 애플리케이션을 랙(rack)에 걸쳐 확산하세요([anti-affinity] 사용) 또는 존(zone)에 걸쳐 확산하세요([multi-zone cluster] 사용 시).
자발적 중단의 빈도는 다양해요. 기본 쿠버네티스 클러스터에는 자동화된 자발적 중단이 없습니다 (사용자가 트리거하는 것만 있어요). 그러나 클러스터 관리자나 호스팅 프로바이더가 자발적 중단을 일으키는 몇 가지 추가 서비스를 실행할 수 있어요. 예를 들어 노드 소프트웨어 업데이트 롤아웃은 자발적 중단을 일으킬 수 있습니다. 또한 일부 클러스터(노드) 오토스케일링 구현은 노드를 조각 모음하고 압축하기 위해 자발적 중단을 일으킬 수 있어요. 클러스터 관리자나 호스팅 프로바이더는 기대할 수 있는 자발적 중단 수준(있다면)을 문서화했어야 합니다. Pod 스펙에서 [PriorityClasses 사용] 같은 특정 구성 옵션도 자발적(그리고 비자발적) 중단을 일으킬 수 있어요.
Pod disruption budgets
기능 상태: Kubernetes v1.21 [stable]
쿠버네티스는 잦은 자발적 중단이 있어도 고가용성 애플리케이션을 실행하도록 돕는 기능을 제공해요.
애플리케이션 소유자로서 각 애플리케이션에 대한 PodDisruptionBudget(PDB)을 만들 수 있어요. PDB는 복제된 애플리케이션의 자발적 중단으로 인해 동시에 다운되는 Pod 수를 제한합니다. 예를 들어 쿼럼 기반 애플리케이션은 실행 중인 복제본 수가 쿼럼에 필요한 수 이하로 내려가지 않도록 보장하고 싶을 거예요. 웹 프론트엔드는 부하를 처리하는 복제본 수가 전체의 특정 비율 이하로 내려가지 않도록 보장하고 싶을 수 있어요.
클러스터 관리자와 호스팅 프로바이더는 Pod를 직접 삭제하는 대신 [Eviction API]를 호출해서 PodDisruptionBudget을 존중하는 도구를 사용해야 합니다.
예를 들어 kubectl drain 하위 명령은 노드를 서비스에서 제외되는 것으로 표시할 수 있게 해줘요. kubectl drain을 실행하면 도구는 서비스에서 제외하는 노드의 모든 Pod를 축출하려고 시도합니다. kubectl이 대신해서 제출하는 축출 요청은 일시적으로 거부될 수 있으므로, 도구는 대상 노드의 모든 Pod가 종료되거나 구성 가능한 시간 제한에 도달할 때까지 실패한 요청을 주기적으로 재시도합니다.
PDB는 애플리케이션이 의도한 복제본 수 대비 허용할 수 있는 복제본 수를 지정합니다. 예를 들어 .spec.replicas: 5를 가진 Deployment는 주어진 시점에 5개의 Pod를 가져야 합니다. PDB가 한 번에 4개를 허용한다면, Eviction API는 한 번에 한 개(두 개는 아님)의 Pod 자발적 중단을 허용해요.
애플리케이션을 구성하는 Pod 그룹은 애플리케이션의 컨트롤러(deployment, stateful-set 등)가 사용하는 것과 같은 레이블 셀렉터로 지정됩니다.
"의도된" Pod 수는 그 Pod들을 관리하는 워크로드 리소스의 .spec.replicas에서 계산됩니다. 컨트롤 플레인은 Pod의 .metadata.ownerReferences를 검사해 소유 워크로드 리소스를 찾아냅니다.
비자발적 중단은 PDB로 예방할 수 없지만, 예산(budget)에 포함되어 계산됩니다.
롤링 업그레이드로 인해 삭제되거나 사용할 수 없게 된 Pod는 중단 예산에 포함되지만, Deployment와 StatefulSet 같은 워크로드 리소스는 롤링 업그레이드 시 PDB에 제한되지 않아요. 대신 애플리케이션 업데이트 중 실패 처리는 특정 워크로드 리소스의 스펙에 구성됩니다.
노드 드레이닝 중 잘못 동작하는 애플리케이션의 축출을 지원하려면 PodDisruptionBudget에 AlwaysAllow [Unhealthy Pod Eviction Policy]를 설정하는 것이 좋습니다. 기본 동작은 드레이닝이 진행되기 전에 애플리케이션 Pod가 [정상(healthy)]이 되기를 기다리는 것이에요.
Pod가 eviction API로 축출되면, [PodSpec]의 terminationGracePeriodSeconds 설정을 존중하며 정상적으로 [종료]됩니다.
PodDisruptionBudget 예시 (PodDisruptionBudget example)
3개의 노드 node-1부터 node-3까지 있는 클러스터를 생각해 볼게요. 클러스터는 여러 애플리케이션을 실행하고 있어요. 그중 하나는 처음에 pod-a, pod-b, pod-c라고 불리는 3개의 복제본을 가지고 있습니다. PDB가 없는 관련 없는 다른 Pod pod-x도 보여요. 처음에 Pod는 다음과 같이 배치되어 있습니다.
| node-1 | node-2 | node-3 |
|---|---|---|
| pod-a available | pod-b available | pod-c available |
| pod-x available |
3개의 Pod 모두 deployment에 속해 있으며, 함께 3개 중 2개 이상이 항상 사용 가능해야 한다는 PDB를 가집니다.
예를 들어 클러스터 관리자가 커널 버그를 고치기 위해 새 커널 버전으로 재부팅하고 싶다고 해볼게요. 클러스터 관리자는 먼저 kubectl drain 명령으로 node-1을 드레이닝하려고 합니다. 이 도구는 pod-a와 pod-x를 축출하려고 시도합니다. 이것은 즉시 성공합니다. 두 Pod 모두 동시에 terminating 상태로 들어갑니다. 이로써 클러스터는 다음과 같은 상태가 됩니다.
| node-1 draining | node-2 | node-3 |
|---|---|---|
| pod-a terminating | pod-b available | pod-c available |
| pod-x terminating |
deployment는 Pod 중 하나가 종료되고 있음을 알아차리고 pod-d라는 교체본을 만듭니다. node-1이 cordon되었으므로 다른 노드에 배치됩니다. pod-x의 교체본으로 pod-y라는 것도 생성되었습니다.
(참고: StatefulSet의 경우, pod-a(보통 pod-0 같은 이름)는 다른 UID를 가진 같은 이름(pod-0)의 교체본이 생성되기 전에 완전히 종료되어야 합니다. 그 외에는 이 예시가 StatefulSet에도 적용됩니다.)
이제 클러스터 상태는 다음과 같습니다.
| node-1 draining | node-2 | node-3 |
|---|---|---|
| pod-a terminating | pod-b available | pod-c available |
| pod-x terminating | pod-d starting | pod-y |
어느 시점에 Pod들이 종료되고, 클러스터는 다음과 같아집니다.
| node-1 drained | node-2 | node-3 |
|---|---|---|
| pod-b available | pod-c available | |
| pod-d starting | pod-y |
이 시점에서 성급한 클러스터 관리자가 node-2나 node-3을 드레이닝하려고 하면, 드레이닝 명령은 차단됩니다. deployment에 대해 사용 가능한 Pod가 2개뿐이고 PDB가 최소 2개를 요구하기 때문입니다. 잠시 후 pod-d가 사용 가능해집니다.
클러스터 상태는 이제 이렇게 보입니다.
| node-1 drained | node-2 | node-3 |
|---|---|---|
| pod-b available | pod-c available | |
| pod-d available | pod-y |
이제 클러스터 관리자는 node-2를 드레이닝하려고 합니다. 드레이닝 명령은 순서에 따라 두 Pod, 예를 들어 pod-b 먼저 그리고 pod-d를 축출하려고 할 거예요. pod-b를 축출하는 데는 성공합니다. 하지만 pod-d를 축출하려고 할 때, pod-d를 축출하면 deployment에 사용 가능한 Pod가 하나만 남게 되므로 거부됩니다.
deployment는 pod-b의 교체본인 pod-e를 만듭니다. 클러스터에 pod-e를 스케줄할 리소스가 충분하지 않기 때문에 드레이닝은 다시 차단됩니다. 클러스터는 이런 상태로 끝날 수 있어요.
| node-1 drained | node-2 | node-3 | no node |
|---|---|---|---|
| pod-b terminating | pod-c available | pod-e pending | |
| pod-d available | pod-y |
이 시점에서 클러스터 관리자는 업그레이드를 진행하려면 노드를 클러스터에 하나 다시 추가해야 합니다.
쿠버네티스가 중단이 발생할 수 있는 속도를 다음과 같은 요소에 따라 어떻게 달리하는지 볼 수 있어요.
- 애플리케이션이 필요로 하는 복제본 수
- 인스턴스를 정상적으로 종료하는 데 걸리는 시간
- 새 인스턴스가 시작되는 데 걸리는 시간
- 컨트롤러의 유형
- 클러스터의 리소스 용량
Pod 중단 조건 (Pod disruption conditions)
기능 상태: Kubernetes v1.31 [stable] (기본 활성화)
전용 Pod DisruptionTarget [조건]이 추가되어, Pod가 [중단]으로 인해 곧 삭제될 것임을 나타냅니다. 이 조건의 reason 필드는 Pod 종료의 다음 이유 중 하나를 추가로 나타내요.
PreemptionByScheduler— 더 높은 우선순위를 가진 새 Pod를 수용하기 위해 스케줄러가 Pod를 [선점]할 예정. 자세한 내용은 [Pod priority preemption]을 참고하세요.DeletionByTaintManager— Pod가 허용하지 않는NoExecutetaint로 인해 Taint Manager(kube-controller-manager안의 node lifecycle controller의 일부)가 Pod를 삭제할 예정. [taint] 기반 축출을 참고하세요.EvictionByEvictionAPI— [Kubernetes API를 사용한 축출]로 Pod가 표시됨.DeletionByPodGC— 더 이상 존재하지 않는 Node에 바인딩된 Pod가 [Pod garbage collection]으로 삭제될 예정.TerminationByKubelet— [노드 압력 축출], [정상 노드 종료], 또는 [시스템 중요 Pod]의 선점 중 하나로 인해 kubelet이 Pod를 종료함.
[Pod 컨테이너 한도 초과]로 인한 축출 같은 다른 모든 중단 시나리오에서는, 중단이 Pod에 의해 발생했을 가능성이 높고 재시도 시 다시 발생할 것이므로 Pod가 DisruptionTarget 조건을 받지 않아요.
참고: Pod 중단은 중단될 수 있어요. 컨트롤 플레인은 같은 Pod의 중단을 계속하려고 재시도할 수 있지만 보장되지는 않습니다. 그 결과
DisruptionTarget조건이 Pod에 추가될 수 있지만, 그 Pod가 실제로 삭제되지 않을 수도 있습니다. 이런 상황에서는 잠시 후 Pod 중단 조건이 지워집니다.
Pod garbage collector(PodGC)는 Pod를 정리하는 것과 함께, Pod가 비종료 단계(non-terminal phase)에 있으면 실패로 표시합니다 ([Pod garbage collection] 참고).
Job(또는 CronJob)을 사용할 때, Job의 [Pod failure policy]의 일부로 이러한 Pod 중단 조건을 사용하고 싶을 수 있어요.
종종 Cluster Manager와 Application Owner를 서로에 대한 지식이 제한된 별도의 역할로 생각하는 것이 유용해요. 이런 책임 분리는 다음 시나리오에서 타당할 수 있습니다.
- 많은 애플리케이션 팀이 쿠버네티스 클러스터를 공유하고 역할의 자연스러운 전문화가 있을 때
- 서드파티 도구나 서비스가 클러스터 관리를 자동화하는 데 사용될 때
Pod Disruption Budget은 역할 사이의 인터페이스를 제공함으로써 이런 역할 분리를 지원합니다.
조직에 이런 책임 분리가 없다면 Pod Disruption Budget을 사용할 필요가 없을 수도 있어요.
클러스터에서 중단 작업 수행하기 (How to perform Disruptive Actions on your Cluster)
클러스터 관리자이고 클러스터의 모든 노드에 대해 중단 작업(노드 또는 시스템 소프트웨어 업그레이드 같은)을 수행해야 한다면, 몇 가지 옵션이 있어요.
- 업그레이드 중 가동 중단(downtime)을 수용하기
- 완전한 복제 클러스터로 장애 조치하기
- 가동 중단이 없지만, 복제된 노드와 전환을 조율하는 인력 노력 모두 비용이 들 수 있어요.
- 중단 허용(disruption tolerant) 애플리케이션을 작성하고 PDB 사용하기
- 가동 중단 없음
- 최소한의 리소스 중복
- 클러스터 관리의 더 많은 자동화 허용
- 중단 허용 애플리케이션 작성은 까다롭지만, 자발적 중단을 허용하는 작업은 오토스케일링 지원 및 비자발적 중단 허용 작업과 크게 겹칩니다.
다음 내용 (What's next)
- [Pod Disruption Budget 구성] 단계로 애플리케이션 보호하기
- [노드 드레이닝]에 대해 더 배우기
- [deployment 업데이트]에 대해 배우기 (롤아웃 중 가용성 유지 단계 포함)