IT/Kubernets

PreStop sleep, 아직도 sh -c 'sleep 10' 쓰시나요

gfrog 2026. 6. 23. 15:42
SMALL

오늘 알게 된 건데, 의외로 모르는 분 꽤 많더라. Pod의 graceful shutdown 늘리려고 PreStop hook에 sleep 박는 그 패턴 말이다. 우리도 한참 그렇게 썼는데, 사실 1.29부터는 그럴 필요가 없어졌다.

기존 방식의 함정

전형적인 zero-downtime 배포 트릭이다. Service에서 endpoint가 빠지는 동안에도 잠깐 트래픽이 계속 들어오니까, SIGTERM 받기 전에 몇 초 버텨주자는 거다.

lifecycle:
  preStop:
    exec:
      command: ["/bin/sh", "-c", "sleep 10"]

근데 이거 distroless 이미지 쓰는 순간 깨진다. sh도 없고 sleep도 없으니까. 우리 팀은 작년에 보안팀이 모든 이미지를 distroless로 밀어붙이면서 이게 죄다 터졌다. CrashLoopBackOff까진 아니어도 PreStop 실패 로그가 매 deploy마다 쏟아졌고, 결국 BusyBox sidecar를 끼우거나 base 이미지를 alpine으로 되돌리거나 했다. 멘탈 잡고 보면 둘 다 별로다.

그래서 sleep action

KEP-3960. 1.29에서 alpha, 1.30부터 default enable, 그리고 1.34에서 드디어 GA 도장 찍혔다. 중간에 한 번 1.32에서 GA 시도했다가 테스트 flake 때문에 되돌려진 사연도 있다.

쓰는 법은 간단하다.

lifecycle:
  preStop:
    sleep:
      seconds: 10

이게 전부다. 컨테이너 안에 sleep 바이너리가 있든 없든 상관없다. kubelet이 직접 타이머를 들고 있다가 SIGTERM 보내기 전에 그 시간만큼만 기다린다. 프로세스 fork도 없고, shell escape 걱정도 없고, 메모리도 안 먹는다.

한 가지 주의할 점

terminationGracePeriodSeconds보다 PreStop sleep이 길면 의미가 없다. kubelet은 grace period가 만료되면 PreStop이 아직 끝나지 않았어도 SIGKILL을 때린다. 그러니까 sleep 10초를 박을 거면 grace period는 최소 15~20초는 줘야 한다. 우리는 기본값 30초로 두고 sleep은 10~15초로 잡는다.

그리고 1.32부터는 seconds: 0도 허용된다 (PodLifecycleSleepActionAllowZero feature gate). 이건 처음 봤을 때 "이걸 왜?" 싶었는데, 조건부로 PreStop을 끼워야 하는 Helm chart 같은 데서 if 분기 안 타고 0초로 깔아두는 용도라고 한다. 솔직히 우리 팀은 아직 쓸 일이 없었다.

마이그레이션

기존 exec: sleep 패턴 다 찾으려면:

kubectl get pods -A -o json | jq '.items[] | select(.spec.containers[].lifecycle.preStop.exec.command // [] | tostring | contains("sleep")) | .metadata.namespace + "/" + .metadata.name'

찾아서 yaml만 바꾸면 끝이다. 다운타임 없이 롤링 업데이트로 처리하면 된다.

배포 파이프라인 점검할 때 같이 들여다보시길.

BIG