파드 실패 정책으로 재시도 가능 및 재시도 불가 파드 실패 처리하기
파드 실패 정책으로 재시도 가능 및 재시도 불가 파드 실패 처리하기 (Handling retriable and non-retriable pod failures with Pod failure policy)
이것은 쿠버네티스의 안정적(stable) 기능이며, 1.31 릴리스부터 그렇게 되어 왔어요. 더 이상 이 기능을 토글할 수 없어요(관련 기능 게이트가 제거됨).
이 문서는 기본 파드 백오프 실패 정책과 함께 파드 실패 정책(Pod failure policy)을 사용해 Job 내의 컨테이너 또는 파드 레벨 실패 처리에 대한 제어를 개선하는 방법을 보여 줘요.
파드 실패 정책의 정의는 다음에 도움이 될 수 있어요:
- 불필요한 파드 재시도를 피해 컴퓨팅 리소스를 더 잘 활용하기.
- 파드 중단(선점(preemption), API 시작 퇴거(eviction), 테인트 기반 퇴거 같은)으로 인한 Job 실패를 피하기.
출처: 문서
본문
시작하기 전에
Job의 기본 사용에 이미 익숙해야 해요.
쿠버네티스 클러스터가 있어야 하고, kubectl 명령줄 도구가 클러스터와 통신하도록 구성되어 있어야 해요. 이 튜토리얼을 제어 플레인 호스트로 작동하지 않는 최소 두 개의 노드가 있는 클러스터에서 실행하는 것을 권장해요. 아직 클러스터가 없다면 minikube로 만들거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있어요:
- iximiuz Labs
- Killercoda
- KodeKloud
버전을 확인하려면 kubectl version을 입력하세요. 쿠버네티스 서버는 v1.25 이상이어야 해요.
사용 시나리오
파드 실패 정책을 정의하는 Job에 대해 다음 사용 시나리오를 고려해 봐요:
- 불필요한 파드 재시도 피하기
- 파드 중단 무시하기
- 사용자 지정 파드 조건에 기반한 불필요한 파드 재시도 피하기
- 인덱스별 불필요한 파드 재시도 피하기
파드 실패 정책을 사용해 불필요한 파드 재시도 피하기
다음 예시로, 파드 실패가 재시도 불가능한 소프트웨어 버그를 나타낼 때 파드 실패 정책을 사용해 불필요한 파드 재시작을 피하는 방법을 배울 수 있어요.
- 다음 매니페스트를 살펴봐요:
apiVersion: batch/v1
kind: Job
metadata:
name: job-pod-failure-policy-failjob
spec:
completions: 8
parallelism: 2
template:
spec:
restartPolicy: Never
containers:
- name: main
image: docker.io/library/bash:5
command: ["bash"]
args:
- -c
- echo "Hello world! I'm going to exit with 42 to simulate a software bug." && sleep 30 && exit 42
backoffLimit: 6
podFailurePolicy:
rules:
- action: FailJob
onExitCodes:
containerName: main
operator: In
values: [42]
- 매니페스트를 적용해요:
kubectl create -f https://k8s.io/examples/controllers/job-pod-failure-policy-failjob.yaml
- 약 30초 후 전체 Job이 종료되어야 해요. 다음을 실행해 Job의 상태를 검사해요:
kubectl get jobs -l job-name=job-pod-failure-policy-failjob -o yaml
Job 상태에서 다음 조건이 표시돼요:
FailureTarget조건:reason필드가PodFailurePolicy로 설정되고message필드에 종료에 대한 더 많은 정보, 예를 들어Container main for pod default/job-pod-failure-policy-failjob-8ckj8 failed with exit code 42 matching FailJob rule at index 0이 있어요. Job 컨트롤러는 Job이 실패로 간주되는 즉시 이 조건을 추가해요. 자세한 내용은 Job 파드 종료를 참조하세요.Failed조건:FailureTarget조건과 같은 reason과 message. Job 컨트롤러는 Job의 모든 파드가 종료된 후 이 조건을 추가해요.
비교를 위해 파드 실패 정책이 비활성화되어 있었다면, Job은 backoffLimit(실패 6회)에 도달할 때까지 재시도했을 거예요. 재시도는 지수 백오프를 사용하고 parallelism: 2로 실패가 쌍으로 발생하므로, 시도 사이의 지연은 재시도할 때마다 증가해요. 결과적으로 이 예시는 Job이 실패하기 전에 최소 9분이 걸렸을 거예요.
정리
만든 Job을 삭제해요:
kubectl delete jobs/job-pod-failure-policy-failjob
클러스터가 파드를 자동으로 정리해요.
파드 실패 정책을 사용해 파드 중단 무시하기
다음 예시로, .spec.backoffLimit 한도에 향하는 파드 재시도 카운터를 증가시키는 것으로부터 파드 중단을 무시하는 데 파드 실패 정책을 사용하는 방법을 배울 수 있어요.
주의:
- 다음 매니페스트를 살펴봐요:
apiVersion: batch/v1
kind: Job
metadata:
name: job-pod-failure-policy-ignore
spec:
completions: 4
parallelism: 2
template:
spec:
restartPolicy: Never
containers:
- name: main
image: docker.io/library/bash:5
command: ["bash"]
args:
- -c
- echo "Hello world! I'm going to exit with 0 (success)." && sleep 90 && exit 0
backoffLimit: 0
podFailurePolicy:
rules:
- action: Ignore
onPodConditions:
- type: DisruptionTarget
- 매니페스트를 적용해요:
kubectl create -f https://k8s.io/examples/controllers/job-pod-failure-policy-ignore.yaml
- 파드가 스케줄링된 nodeName을 확인하는 이 명령을 실행해요:
nodeName=$(kubectl get pods -l job-name=job-pod-failure-policy-ignore -o jsonpath='{.items[0].spec.nodeName}')
- 파드가 완료되기 전에(90초 안에) 퇴거하도록 노드를 드레인해요:
kubectl drain nodes/$nodeName --ignore-daemonsets --grace-period=0
- Job의 카운터가 증가하지 않았는지 확인하려면
.status.failed를 검사해요:
kubectl get jobs -l job-name=job-pod-failure-policy-ignore -o yaml
- 노드를 언코돈해요:
kubectl uncordon nodes/$nodeName
Job이 재개되고 성공해요.
비교를 위해 파드 실패 정책이 비활성화되어 있었다면 파드 중단은(.spec.backoffLimit이 0으로 설정되어 있으므로) 전체 Job을 종료시켰을 거예요.
정리
만든 Job을 삭제해요:
kubectl delete jobs/job-pod-failure-policy-ignore
클러스터가 파드를 자동으로 정리해요.
사용자 지정 파드 조건에 기반한 불필요한 파드 재시도 피하기 위해 파드 실패 정책 사용하기
다음 예시로, 사용자 지정 파드 조건에 기반한 불필요한 파드 재시작을 피하는 데 파드 실패 정책을 사용하는 방법을 배울 수 있어요.
참고:
- 다음 매니페스트를 살펴봐요:
apiVersion: batch/v1
kind: Job
metadata:
name: job-pod-failure-policy-config-issue
spec:
completions: 8
parallelism: 2
template:
spec:
restartPolicy: Never
containers:
- name: main
image: "non-existing-repo/non-existing-image:example"
backoffLimit: 6
podFailurePolicy:
rules:
- action: FailJob
onPodConditions:
- type: ConfigIssue
- 매니페스트를 적용해요:
kubectl create -f https://k8s.io/examples/controllers/job-pod-failure-policy-config-issue.yaml
이미지가 존재하지 않으므로 잘못 구성되어 있다는 점을 유의하세요.
- Job의 파드 상태를 검사해요:
kubectl get pods -l job-name=job-pod-failure-policy-config-issue -o yaml
다음과 비슷한 출력을 볼 수 있을 거예요:
containerStatuses:
- image: non-existing-repo/non-existing-image:example
...
state:
waiting:
message: Back-off pulling image "non-existing-repo/non-existing-image:example"
reason: ImagePullBackOff
...
phase: Pending
파드는 잘못 구성된 이미지를 당기지 못해 Pending 단계에 남는다는 점을 유의하세요. 이것은 원칙적으로 일시적인 문제일 수 있고 이미지가 당겨질 수 있어요. 그러나 이 경우 이미지가 존재하지 않으므로 사용자 지정 조건으로 이 사실을 나타내요.
- 사용자 지정 조건을 추가해요. 먼저 패치를 준비해요:
cat <<EOF > patch.yaml
status:
conditions:
- type: ConfigIssue
status: "True"
reason: "NonExistingImage"
lastTransitionTime: "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
EOF
둘째, job이 만든 파드 중 하나를 선택해요:
podName=$(kubectl get pods -l job-name=job-pod-failure-policy-config-issue -o jsonpath='{.items[0].metadata.name}')
그런 다음 다음 명령으로 파드 중 하나에 패치를 적용해요:
kubectl patch pod $podName --subresource=status --patch-file=patch.yaml
성공적으로 적용되면 다음과 같은 알림을 받게 돼요:
pod/job-pod-failure-policy-config-issue-k6pvp patched
- 파드를 삭제해 Failed 단계로 전환해요:
kubectl delete pods/$podName
- Job의 상태를 검사해요:
kubectl get jobs -l job-name=job-pod-failure-policy-config-issue -o yaml
Job 상태에서 reason이 PodFailurePolicy와 같은 job Failed 조건을 볼 수 있어요. 또한 message 필드에는 Job 종료에 대한 더 자세한 정보, 예를 들어 Pod default/job-pod-failure-policy-config-issue-k6pvp has condition ConfigIssue matching FailJob rule at index 0 같은 것이 포함돼 있어요.
정리
만든 Job을 삭제해요:
kubectl delete jobs/job-pod-failure-policy-config-issue
클러스터가 파드를 자동으로 정리해요.
파드 실패 정책을 사용해 인덱스별 불필요한 파드 재시도 피하기
인덱스별 불필요한 파드 재시작을 피하려면 파드 실패 정책과 인덱스별 백오프 한도 기능을 사용할 수 있어요. 이 페이지의 이 섹션은 이러한 기능을 함께 사용하는 방법을 보여 줘요.
- 다음 매니페스트를 살펴봐요:
apiVersion: batch/v1
kind: Job
metadata:
name: job-backoff-limit-per-index-failindex
spec:
completions: 4
parallelism: 2
completionMode: Indexed
backoffLimitPerIndex: 1
template:
spec:
restartPolicy: Never
containers:
- name: main
image: docker.io/library/python:3
command:
# The script:
# - fails the Pod with index 0 with exit code 1, which results in one retry;
# - fails the Pod with index 1 with exit code 42 which results
# in failing the index without retry.
# - succeeds Pods with any other index.
- python3
- -c
- |
import os, sys
index = int(os.environ.get("JOB_COMPLETION_INDEX"))
if index == 0:
sys.exit(1)
elif index == 1:
sys.exit(42)
else:
sys.exit(0)
backoffLimit: 6
podFailurePolicy:
rules:
- action: FailIndex
onExitCodes:
containerName: main
operator: In
values: [42]
- 매니페스트를 적용해요:
kubectl create -f https://k8s.io/examples/controllers/job-backoff-limit-per-index-failindex.yaml
- 약 15초 후 Job의 파드 상태를 검사해요. 다음을 실행해 그렇게 할 수 있어요:
kubectl get pods -l job-name=job-backoff-limit-per-index-failindex -o yaml
다음과 비슷한 출력을 볼 수 있을 거예요:
NAME READY STATUS RESTARTS AGE
job-backoff-limit-per-index-failindex-0-4g4cm 0/1 Error 0 4s
job-backoff-limit-per-index-failindex-0-fkdzq 0/1 Error 0 15s
job-backoff-limit-per-index-failindex-1-2bgdj 0/1 Error 0 15s
job-backoff-limit-per-index-failindex-2-vs6lt 0/1 Completed 0 11s
job-backoff-limit-per-index-failindex-3-s7s47 0/1 Completed 0 6s
출력이 다음을 보여 준다는 점에 유의하세요:
-
인덱스의 한 번 재시도가 허용되는 백오프 한도 때문에 두 파드가 인덱스 0을 가져요.
-
실패한 파드의 종료 코드가
FailIndex동작과 함께 파드 실패 정책과 일치했기 때문에 인덱스 1의 파드는 하나뿐이에요. -
Job의 상태를 검사해요:
kubectl get jobs -l job-name=job-backoff-limit-per-index-failindex -o yaml
Job 상태에서 두 인덱스가 모두 실패했으므로 failedIndexes 필드가 "0,1"을 보여 주는 것을 볼 수 있어요. 인덱스 1이 재시도되지 않았으므로 failed 상태 필드가 나타내는 실패한 파드 수는 3과 같아요.
정리
만든 Job을 삭제해요:
kubectl delete jobs/job-backoff-limit-per-index-failindex
클러스터가 파드를 자동으로 정리해요.
대안
Job의 .spec.backoffLimit 필드를 지정해 파드 백오프 실패 정책만에 전적으로 의존할 수 있어요. 그러나 많은 상황에서 불필요한 파드 재시도를 피하기 위해 .spec.backoffLimit을 낮은 값으로 설정하면서도, Job이 파드 중단으로 종료되지 않도록 보장할 만큼 충분히 높게 설정하는 균형을 찾는 것이 문제가 돼요.