소개
Running은 컨테이너 프로세스가 존재한다는 뜻일 뿐입니다. 애플리케이션이 트래픽을 받을 준비가 되었거나 스스로 복구할 수 있다는 의미는 아닙니다. Kubernetes 프로브는 애플리케이션별 상태 점검 결과를 컨트롤러와 Service 의 동작으로 연결합니다.
세 가지 프로브 유형을 모두 추가하고, 준비 상태가 Pod 를 Service 엔드포인트에서 제외하는 과정을 확인한 뒤 이를 복구합니다. 마지막으로 정상 종료 훅도 추가합니다.
각 프로브의 역할을 명확히 구분해야 합니다. 시작 프로브는 초기화가 오래 걸리는 애플리케이션을 보호하고, 준비 상태 프로브는 Pod 가 Service 트래픽을 받을지 결정하며, 활성 상태 프로브는 멈춘 컨테이너를 kubelet 이 재시작할 시점을 판단합니다. 모든 목적에 하나의 프로브만 사용하면 불필요한 재시작이나 너무 이른 트래픽 전달이 발생할 수 있습니다.
프로브가 적용된 애플리케이션 배포
이 단계에서는 이름이 http인 포트에서 / 경로를 대상으로 시작, 준비 상태, 활성 상태 점검을 수행하도록 NGINX 를 배포합니다.
세 프로브가 모두 컨테이너 사양 내부에 들어가기 때문에 매니페스트가 다소 길어졌습니다. 적용하기 전에 Deployment 와 레이블, 컨테이너와 이름이 지정된 포트, 그리고 시작·준비 상태·활성 상태 프로브 순서로 읽어 보세요. 이 실습에서는 세 점검이 같은 HTTP 엔드포인트를 사용하지만, 실패했을 때의 결과는 서로 다릅니다.
한 줄로 작성된 일부 매핑과 목록은 {app: healthy-web}처럼 YAML 의 간결한“플로 스타일”을 사용합니다. 중괄호는 매핑을, 대괄호는 목록을 나타내며 여러 줄로 작성한 YAML 과 같은 의미입니다. 여기서는 전체 값을 읽기 쉽게 유지할 수 있을 때만 사용합니다.
시작 프로브의 임계값은 Kubernetes 가 시작 실패로 판단하기 전까지 최대 24 초 (12 × 2) 를 허용합니다. 시작에 성공하면 준비 상태 프로브는 3 초마다, 활성 상태 프로브는 5 초마다 실행됩니다.
cd /home/labex/project/application-health
cat <<'EOF' > healthy-web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: healthy-web
namespace: practice-health
spec:
replicas: 2
selector:
matchLabels: {app: healthy-web}
template:
metadata:
labels: {app: healthy-web}
spec:
containers:
- name: web
image: nginx:1.27-alpine
imagePullPolicy: IfNotPresent
ports:
- {name: http, containerPort: 80, protocol: TCP}
startupProbe:
httpGet: {path: /, port: http}
failureThreshold: 12
periodSeconds: 2
readinessProbe:
httpGet: {path: /, port: http}
periodSeconds: 3
livenessProbe:
httpGet: {path: /, port: http}
periodSeconds: 5
failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
name: healthy-web
namespace: practice-health
spec:
selector: {app: healthy-web}
ports:
- {name: http, port: 80, targetPort: http, protocol: TCP}
EOF
네임스페이스를 생성하고 매니페스트를 적용합니다. rollout status는 프로세스가 시작되기만 기다리는 것이 아니라 두 복제본이 모두 준비 상태를 만족할 때까지 기다립니다.
kubectl create namespace practice-health
kubectl apply -f healthy-web.yaml
kubectl rollout status deployment/healthy-web -n practice-health --timeout=60s
시작 프로브는 느린 초기화를 보호하고, 준비 상태 프로브는 트래픽 수신 가능 여부를 제어하며, 활성 상태 프로브는 복구할 수 없는 장애가 발생했을 때 재시작을 허용합니다.
롤아웃이 완료되었다면 단순히“컨테이너가 실행 중”이라는 사실보다 더 많은 것을 확인한 것입니다. 두 Pod 가 모두 준비 상태 요청에 성공적으로 응답했다는 뜻이기 때문입니다.
마지막 롤아웃 출력은 첫 번째 상태 확인 증거입니다.
Waiting for deployment "healthy-web" rollout to finish: 0 of 2 updated replicas are available...
Waiting for deployment "healthy-web" rollout to finish: 1 of 2 updated replicas are available...
deployment "healthy-web" successfully rolled out
중간 출력은 정상입니다. Kubernetes 는 각 Pod 가 시작 및 준비 상태 점검을 통과할 때마다 진행 상황을 보고합니다. 마지막 줄에 도달해야 두 복제본을 모두 사용할 수 있다는 의미입니다.
준비 상태를 Service 엔드포인트에 연결
이 단계에서는 준비 상태 조건과 EndpointSlice 를 확인합니다.
먼저 익숙한 Pod 목록을 확인한 다음, 출력 범위를 준비 상태 조건과 재시작 횟수로 좁힙니다. 사용자 지정 표를 사용하면“준비되지 않음”과“재시작 중”을 쉽게 구분할 수 있습니다.
kubectl get pods -n practice-health -l app=healthy-web
kubectl get pods -n practice-health -l app=healthy-web -o custom-columns='POD:.metadata.name,READY_CONDITION:.status.conditions[?(@.type=="Ready")].status,RESTARTS:.status.containerStatuses[0].restartCount'
kubectl get endpointslices -n practice-health -l kubernetes.io/service-name=healthy-web -o jsonpath='{range .items[].endpoints[*]}{.addresses[0]} ready={.conditions.ready}{"\n"}{end}'
두 주소 모두 ready=true로 표시되어야 합니다. 준비 상태 점검에 실패해도 컨테이너 자체는 계속 실행되지만, 해당 주소는 일반 Service 라우팅에서 제외됩니다.
EndpointSlice 는 Service 선택자와 실제 Pod 주소를 연결하는 역할을 합니다. Pod 가 준비되지 않은 상태여도 Service 자체는 존재합니다. 일반 트래픽이 해당 Pod 로 전달되지 않도록 하는 것은 엔드포인트의 조건입니다.
정상적인 상태에서는 다음과 같은 결과를 확인할 수 있습니다.
POD READY_CONDITION RESTARTS
healthy-web-... True 0
healthy-web-... True 0
10.244.104.11 ready=true
10.244.104.12 ready=true
Pod 이름과 IP 주소는 환경에 따라 달라집니다. 중요한 것은 두 개의 Ready=True Pod 가 두 개의 ready=true 엔드포인트에 대응하고, 어느 컨테이너도 재시작하지 않았다는 관계입니다.
준비 상태 실패 관찰
이 단계에서는 준비 상태 경로만 존재하지 않는 URL 로 의도적으로 변경합니다.
이는 통제된 실패 상황입니다. JSON Patch 는 중첩된 단일 필드만 교체하므로 시작 프로브와 활성 상태 프로브는 그대로 유지됩니다. NGINX 는 /not-ready에 대해 HTTP 404 를 반환하고, 준비 상태 프로브는 이를 실패로 처리합니다.
kubectl patch deployment healthy-web -n practice-health --type='json' -p='[{"op":"replace","path":"/spec/template/spec/containers/0/readinessProbe/httpGet/path","value":"/not-ready"}]'
kubectl rollout status deployment/healthy-web -n practice-health --timeout=20s || true
kubectl get pods -n practice-health -l app=healthy-web
kubectl get endpointslices -n practice-health -l kubernetes.io/service-name=healthy-web -o jsonpath='{range .items[].endpoints[*]}{.addresses[0]} ready={.conditions.ready}{"\n"}{end}'
|| true는 롤아웃 시간 초과를 예상된 결과로 처리해 셸이 계속 실행되도록 합니다. 새 컨테이너는 실행되지만 준비 상태 점검에 실패하므로 Deployment 는 사용 가능한 상태가 되지 못합니다.
실제로 점검했을 때는 다음과 비슷한 상태가 나타납니다.
error: timed out waiting for the condition
NAME READY STATUS RESTARTS
healthy-web-old-... 1/1 Running 0
healthy-web-old-... 1/1 Running 0
healthy-web-new-... 0/1 Running 0
...
10.244.104.17 ready=false
새 Pod 하나는 Running 상태이지만 준비되지 않았고, 재시작도 하지 않았습니다. 기존의 준비된 두 Pod 가 계속 유지되는 이유는 Deployment 의 롤링 업데이트가 가용성을 보호하기 때문입니다. 새 버전이 준비 상태가 되지 못하는 동안 Kubernetes 는 정상적인 복제본을 더 교체하지 않습니다. 따라서 롤아웃 시간 초과를 통해 준비 상태 동작과 컨트롤러의 안전장치를 동시에 확인할 수 있습니다. EndpointSlice 출력에서 새 주소가 ready=false로 표시되므로 일반 Service 라우팅은 계속 정상 엔드포인트만 사용합니다.
준비 상태 복구 및 트래픽 확인
이 단계에서는 준비 상태 경로를 원래대로 복구하고 Service 엔드포인트가 다시 등록될 때까지 기다립니다.
이전 패치의 반대 작업을 수행해 실험에서 변경되는 변수를 하나로 제한합니다. 이제 교체된 Pod 가 /에 HTTP 200 으로 응답하므로 롤아웃 상태 확인이 성공합니다.
kubectl run health-client는 요청을 수행할 임시 독립 Pod 를 생성합니다. --image는 BusyBox 를 선택하고, --image-pull-policy=IfNotPresent는 캐시된 이미지를 재사용하며, --restart=Never는 컨트롤러가 아닌 Pod 를 생성하도록 지정합니다. --rm -i는 명령을 연결된 상태로 유지하고 종료 시 Pod 를 삭제합니다. 두 번째 --는 kubectl 옵션과 컨테이너 내부에서 실행할 wget 명령을 구분합니다.
wget -qO-에서 -q는 진행 메시지를 숨기고 -O-는 응답 본문을 표준 출력으로 기록합니다. 파이프는 이 본문을 head로 전달하고, head는 앞부분만 표시합니다. URL 에 사용하는 Service 이름 healthy-web은 클러스터 DNS 가 네임스페이스 내부에서 해석합니다.
kubectl patch deployment healthy-web -n practice-health --type='json' -p='[{"op":"replace","path":"/spec/template/spec/containers/0/readinessProbe/httpGet/path","value":"/"}]'
kubectl rollout status deployment/healthy-web -n practice-health --timeout=60s
kubectl run health-client -n practice-health --image=busybox:1.36 --image-pull-policy=IfNotPresent --restart=Never --rm -i -- wget -qO- http://healthy-web | head
임시 클라이언트는 클러스터 DNS 를 사용합니다. 표시되는 HTML 의 양을 제한하면 결과를 읽기 쉽게 유지하면서, 성공적인 응답을 통해 준비 상태와 Service 라우팅이 일치한다는 사실을 확인할 수 있습니다.
응답은 다음과 같이 NGINX 의 HTML 임을 알아볼 수 있는 내용으로 시작합니다.
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
롤아웃이 성공한 뒤 HTML 이 표시되었다면 전체 경로가 정상적으로 작동했다는 뜻입니다. DNS 가 Service 를 해석했고, Service 가 준비된 엔드포인트를 선택했으며, NGINX 가 요청을 처리했습니다.
진단용 임시 Pod 가 네임스페이스에 남지 않도록 사용 후 삭제됩니다.
정상 종료 추가
이 단계에서는 preStop 훅과 종료 유예 시간을 추가합니다. 훅은 종료를 잠시 지연해 프로세스가 끝나기 전에 엔드포인트 제거 정보가 전파될 시간을 확보합니다.
두 JSON Patch 작업을 각각 따로 살펴보세요. 첫 번째 작업은 Pod 종료에 20 초의 유예 시간을 추가합니다. 두 번째 작업은 컨테이너 인덱스 0에 5 초 동안 대기하는 훅을 추가합니다. 종료가 시작되면 Kubernetes 가 훅을 실행하며, 이때 남은 유예 시간도 계속 줄어듭니다.
kubectl patch deployment healthy-web -n practice-health --type=json -p='[
{"op":"add","path":"/spec/template/spec/terminationGracePeriodSeconds","value":20},
{"op":"add","path":"/spec/template/spec/containers/0/lifecycle","value":{"preStop":{"sleep":{"seconds":5}}}}
]'
kubectl rollout status deployment/healthy-web -n practice-health --timeout=60s
kubectl get deployment healthy-web -n practice-health -o jsonpath='grace={.spec.template.spec.terminationGracePeriodSeconds}s preStop={.spec.template.spec.containers[0].lifecycle.preStop.sleep.seconds}s{"\n"}'
JSON 패치는 지정한 두 필드만 변경하므로 프로브와 기타 컨테이너 설정은 그대로 유지됩니다. 정상 종료은 준비 상태와 상호 보완적입니다. 준비 상태는 트래픽 수신을 허용할지 제어하고, 정상 종료는 종료가 완료되기 전에 Kubernetes 가 Pod 를 트래픽에서 제거할 시간을 제공합니다.
최종 JSONPath 출력은 grace=20s preStop=5s여야 합니다. 5 초짜리 훅은 20 초의 유예 시간 안에 완료되므로 NGINX 가 정상적으로 종료할 여유가 남습니다.
새 preStop 훅이 종료 과정에 의도적으로 5 초를 추가하므로 1 old replicas are pending termination과 같은 메시지에서 롤아웃이 잠시 멈출 수 있습니다. 최종 출력은 다음과 같습니다.
deployment "healthy-web" successfully rolled out
grace=20s preStop=5s
요약
시작, 준비 상태, 활성 상태 점검을 Kubernetes 동작과 연결하고, 컨테이너를 중지하지 않은 채 준비 상태에 따라 엔드포인트가 제거되는 과정을 확인했습니다. 또한 트래픽을 복구하고 정상 종료를 추가했습니다. 이러한 메커니즘을 사용하면 애플리케이션 상태를 단순히 보여 주는 데 그치지 않고 실제 대응으로 연결할 수 있습니다.


