kubernetes

kubernetes Pod의 Graceful Shutdown (feat. Kafka)

downfa11 2024. 11. 19. 14:20

Graceful Shutdown

실행중인 어플리케이션이 새로운 애플리케이션을 위해 종료될때 자원 정리를 포함한 모든 조치가 취해진 ShutDown

배포때마다 수많은 오류가 뜬다는 것은 대체로 Graceful Shutdown이 제대로 이뤄지지 않고 있는 경우 발생한다.

 

배포 자동화 도구에서 대부분 해주지만 원리를 이해해야 트러블 슈팅에 대한 주체적인 근거를 댈 수 있다.

일단 들어온 트래픽은 모두 처리하고 종료해야 누락되지 않는다.

 

예상되는 문제 상황

  • 요청에 대한 task 수행 도중에 Pod가 종료된다면 응답이 비정상적으로 처리될 수 있다.
  • Pod가 종료될때 요청이 들어오면 connection refuesed 에러가 뜬다.

기존 로컬 환경에서의 해결 방법 - JVM

Java Warm up을 3-5s 기다린다. 빈이 올라오기전에 컨트롤러가 올라와서 오류가 뜰 수 있다.

server.shutdown=graceful

 

 

Spring Boot 애플리케이션에서 정상적인 종료를 활성화하기 위해서 제공하는 구성인데, k8s 환경에선 ConfigMaps에서 추가해도 된다.

Kubernetes 환경에서의 해결 방법

Signal : OS에서 프로세스에 보내는 신호로 주로 프로세스의 생명 주기를 관리한다. (중단, 종료 등)

프로세스 종료를 위한 시그널

  • SIGINT(Interrupt) : 사용자가 프로세스를 인터럽트하는 시그널
    • Ctrl+C 키보드 조합으로 프로세스의 정상 종료를 유도
  • SIGTERM(Terminate) : 프로세스에게 종료 요청하는 시그널
    • Graceful Shutdown을 위해 사용되는 시그널
    • 프로세스 종료 전 프로세스에서 해당 시그널을 핸들링
  • SIGKILL: 프로세스를 강제로 즉각 종료

OS가 SIGTERM,SIGINT를 날려서 JVM에서 내부적으로 Graceful Shutdown을 위한 코드를 수행한다.

 

Graceful Shutdown 진행 순서

  1. SIGTERM 시그널 수신
    • Kubernetes는 Pod를 종료할때 먼저 SIGTERM 시그널을 컨테이너에 전달한다.
    • 컨테이너는 데이터베이스 연결된 세션 종료 등 처리중인 요청을 끝낸다
  2. 처리중인 요청 완료
    • 애플리케이션은 처리중이던 요청을 마무리하고 새로운 요청을 받지 않아야한다.
    • kubernetes는 preStop 훅을 활용해서 준비하거나 Readiness Probe를 통해 트래픽 중단
  3. 리소스 정리
    • 안전하게 리소스 해제

방법1 - TerminationGracePeriodSeconds 설정

  • Pod가 SIGTERM 시그널을 받고도 종료되지 않을때 강제로 SIGKILL 신호를 보내기 전까지 대기하는 시간을 의미한다.
  • defualt=30으로, 이 기간동안 graceful shutdown이 완료되지 않으면 강제 종료(SIGKILL)

deployment.yaml에서 terminationGracePeriodSeconds 필드를 설정해서 graceful shutdown 대기 시간을 지정한다.

spec:
	terminationGracePeriodSeconds: 30

 

 

방법2 - preStop Hook 설정

당연하게도 Graceful Shutdown는 배포 과정의 일부일 뿐이다.

그러나 클라우드에선 일반적으로 k8s가 시그널을 발생시키는 주체이다.

liveness probe의 이상 감지로 Pod가 종료 상태로 변경될 때, 컨테이너에서 진행중인 프로세스가 모두 완료된걸 보고 나서야 종료되도록 설정해줘야 한다.

Pod가 종료될때 kubelet이 컨테이너에 SIGTERM 신호를 보내게 된다.

애플리케이션에서 설정한다면 SIGTERM 명령어가 들어와도 진행 중인 작업이 다 끝나고 종료되도록 시간을 설정하면 된다.

preStop Hook을 이용해서 Pod가 종료 요청을 받고, 컨테이너가 종료되기 전에 커맨드를 실행할 수 있다.

이때 애플리케이션에서 가장 오래 실행되는 프로세스의 완료가 보장되는 최대 시간보다 길게 대기(유휴) 시간을 갖도록 해준다.

 

ex) 가장 오래 걸리는 로직이 간격 30s의 batch라면, 적어도 마지막으로 들어온 요청을 해결하기 위해 30s를 최대 실행 시간으로 둬야한다.

 

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

 

 

Graceful Shutdown의 방법간의 비교

preStop Hook
TerminationGracePeriodSeconds
역할
종료 전 추가 작업을 수행
전체 종료 시간 설정
트래픽 제어
트래픽 즉시 차단(readiness=false)
readiness에 영향 없음
대기 시간
사용자가 원하는 시간 동안 실행
전체 종료 절차의 대기 시간
강제 종료
강제 종료 시점에 영향 없음
지나면 강제 종료

 

 

 

Kubernetes 환경 Spring 공식 문서에서 제공하는 모범 사례 중 Graceful Shutdown

https://spring.io/guides/topicals/spring-on-kubernetes

 

Getting Started | Spring on Kubernetes

Getting a Spring Boot application running on Kubernetes requires nothing more than a visit to start.spring.io. The goal of Spring Boot has always been to make building and running Java applications as easy as possible, and we try to enable that, no matter

spring.io

 

 

그럼 kafka Consumer는 어떻게 Graceful ShutDown을 할까?

kafka Consumer는 Message queuing을 이용하는데, 어떻게 우아한 연결 종료를 할까

이 부분은 특히 매우 중요하다. 특히 더 신경써야하기에..

메시지 브로커의 종류에 따라 어떤 아키텍쳐를 쓰는지 상이하기에, 각 플랫폼에 맞는 우아한 종료 처리를 따로 해줘야한다.

새로운 Consumer를 띄울때 중복이나 누락이 발생하지 않도록 해야한다.

Apache Kafka의 경우는 auto commit하면 중복된다.

이미 들어온 것에 대해 commit을 하지 못한 채로 종료되어서 중복 소비된다.

 

 

 

3개 마이크로 서비스 replica를 배포하다가 1개를 shutdown하는 상황을 가정해보자.

나머지 하나에 가는 부하는 33/33/33 에서 50/50가 된다.

그럼 하나 더 죽인다면 부하는 어떻게 되겠는가? 결국 100을 혼자서 트래픽 받아야하게 된다.

약 200% 를 혼자서 버틸 수 없으니, 내가 관리하는 애플리케이션이 일반적으로 혹은 피크 시간대에 어느 정도 리소스를 점유하는지 알아둬야한다.

MSA의 본질적인 목적은 높은 Business Capabillity

고객의 니즈를 더 많이, 더 빠르게 만족시키는 것이다

 

 

 

 

출처 및 인용

https://foxutech.com/kubernetes-pod-graceful-shutdown-how/