2022-12-26-TIL
2022-12-26-TIL
Today I Learned
스프린트의 정의에서 시작해, 운영 관점의 주제들(Kafka 컨슈머 랙, Redis 고가용성, Kubernetes 환경의 graceful shutdown과 프로브, nginx와 Tomcat 연동, CORS preflight)을 정리했다.
Sprint
Scrum Guide는 스프린트를 한 달 이하의 고정된 길이로 반복되는 이벤트로 정의하고, 이전 스프린트가 끝나면 바로 다음 스프린트가 시작된다고 설명한다. 스프린트 플래닝, 데일리 스크럼, 스프린트 리뷰, 회고가 모두 스프린트 안에서 일어나며, 매 스프린트마다 사용 가능한 결과물(Increment)을 만드는 것이 목표다. 기간을 고정하는 이유는 일정한 리듬으로 계획과 점검을 반복해 방향을 자주 조정하기 위해서다.
- https://www.atlassian.com/ko/agile/scrum/sprints#:~:text=%EC%8A%A4%ED%94%84%EB%A6%B0%ED%8A%B8%EB%9E%80%3F,%ED%95%98%EB%8A%94%20%EB%8D%B0%20%EB%8F%84%EC%9B%80%EC%9D%B4%20%EB%90%A9%EB%8B%88%EB%8B%A4.
- https://go-sprint.co.kr/journal?opts_bc_key=AJDBFIM
- https://cloudmt.co.kr/?p=4054
Kafka Topic Lag Monitor
컨슈머 랙(consumer lag)은 파티션에 마지막으로 생산된 메시지의 오프셋과 컨슈머 그룹이 마지막으로 커밋한 오프셋의 차이다. Confluent 문서는 이를 메시지 생산과 소비 사이의 지연으로 설명한다. 랙이 계속 커지면 컨슈머 처리 속도가 생산 속도를 따라가지 못한다는 뜻이므로, 컨슈머 수를 늘리거나(파티션 수 이내에서) 처리 로직을 개선해야 한다.
자바에서는 AdminClient로 그룹의 커밋 오프셋을, 컨슈머의 endOffsets()로 파티션 끝 오프셋을 가져와 차이를 계산할 수 있다. 커맨드라인에서는 kafka-consumer-groups.sh --describe가 파티션별 LAG 컬럼을 보여 준다.
- https://www.baeldung.com/java-kafka-consumer-lag
Redis HA
Redis 고가용성 구성은 크게 Sentinel과 Cluster 두 가지다. Redis 문서에 따르면 Sentinel은 마스터와 레플리카를 계속 감시하고, 문제가 생기면 알림을 보내며, 마스터가 정상이 아니면 레플리카 하나를 마스터로 승격하는 자동 장애 조치를 수행하고, 클라이언트에게 현재 마스터 주소를 알려 주는 설정 제공자 역할을 한다. 데이터를 여러 노드에 나눠 저장(샤딩)해야 한다면 Redis Cluster를 쓴다.
- https://lifeplan-b.tistory.com/13
Graceful Shutdown of Spring Boot Applications in Kubernetes
Kubernetes는 Pod를 삭제할 때 컨테이너의 메인 프로세스에 먼저 SIGTERM을 보내고, 유예 기간(terminationGracePeriodSeconds, 기본 30초)이 지나도 끝나지 않으면 강제로 종료한다. preStop 훅이 있으면 그 전에 실행한다. 문제는 SIGTERM을 받는 시점과 Service 엔드포인트에서 Pod가 빠지는 시점이 비동기라서, 종료 중인 Pod로 요청이 잠깐 더 들어올 수 있다는 점이다. 그래서 흔히 preStop에서 몇 초 대기해 엔드포인트 제거가 먼저 반영되게 하고, 애플리케이션은 SIGTERM을 받으면 진행 중인 요청을 마무리한 뒤 종료하도록 설정한다. Trendyol 글이 Spring Boot 애플리케이션 기준으로 이 조합을 다룬다.
함께 본 Kubernetes 1.24 블로그 글은 다른 주제다. 노드가 하드웨어 장애처럼 정상적으로 종료되지 못한 상황에서 StatefulSet 같은 워크로드를 다른 노드로 옮길 수 있게 하는 Non-Graceful Node Shutdown 기능을 알파로 소개한다.
- https://medium.com/trendyol-tech/graceful-shutdown-of-spring-boot-applications-in-kubernetes-f80e0b3a30b0
- https://kubernetes.io/blog/2022/05/20/kubernetes-1-24-non-graceful-node-shutdown-alpha/
Graceful Shutdown in Spring Boot
Spring Boot 2.3부터 server.shutdown=graceful로 내장 웹 서버의 graceful shutdown을 켤 수 있다. 문서에 따르면 Jetty, Reactor Netty, Tomcat은 네트워크 계층에서 새 요청을 받지 않고, Undertow는 요청을 받되 503으로 응답한다. 진행 중인 요청을 기다리는 최대 시간은 spring.lifecycle.timeout-per-shutdown-phase로 정하며, Tomcat은 9.0.33 이상이 필요하다.
1
2
3
4
5
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 20s
Kubernetes에서 쓸 때는 이 대기 시간과 preStop 대기 시간의 합이 terminationGracePeriodSeconds보다 짧아야 강제 종료 전에 정리가 끝난다.
- https://bkjeon1614.tistory.com/729
- https://bravenamme.github.io/2020/10/06/graceful-shutdown/
Kubernetes Readiness and Liveness
두 프로브는 실패했을 때의 동작이 다르다. Kubernetes 문서에 따르면 liveness probe가 실패하면 컨테이너를 재시작하고, readiness probe가 실패하면 Pod를 unready로 표시해 Service 트래픽에서 제외한다. 따라서 liveness는 “프로세스가 회복 불가능하게 멈췄는가”, readiness는 “지금 요청을 받을 준비가 되었는가”를 확인하도록 나눠야 한다. 외부 DB 장애 같은 일시적 문제를 liveness에 걸면 모든 Pod가 함께 재시작되는 사고로 이어질 수 있다. Spring Boot Actuator는 /actuator/health/liveness, /actuator/health/readiness 엔드포인트를 제공한다.
- https://bcho.tistory.com/1264
Nginx to Tomcat AJP Connector
AJP(Apache JServ Protocol)는 웹 서버와 Tomcat 사이에 쓰는 바이너리 프로토콜로, Tomcat 문서는 AJP 커넥터를 기존 웹 서버 뒤에 Tomcat을 보이지 않게 통합할 때 쓴다고 설명한다. Apache httpd는 mod_proxy_ajp로 바로 연결할 수 있지만, nginx는 공식 모듈에 AJP가 없어 서드파티 모듈을 빌드해 넣어야 한다. 그래서 nginx 앞단에서는 보통 Tomcat의 HTTP 커넥터로 proxy_pass하는 구성을 쓴다.
- https://www.lesstif.com/linux-core/linux-web-server-web-application/nginx-apache-tomcat
CORS Preflight
브라우저는 다른 출처(origin)로 요청을 보낼 때 CORS 규칙을 적용한다. MDN에 따르면 서버 데이터에 부수 효과를 줄 수 있는 요청(GET이 아니거나 특정 MIME 타입이 아닌 POST 등)은 실제 요청 전에 OPTIONS 메서드로 preflight 요청을 보내, 사용할 메서드와 헤더를 서버가 허용하는지 먼저 확인한다. 서버가 Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers로 허용해야 본 요청이 전송된다. 인증 필터가 OPTIONS 요청까지 막으면 preflight가 실패하므로 서버 설정에서 따로 신경 써야 한다.
- https://hwanchang.tistory.com/3
댓글
아직 댓글이 없습니다