Sclae down 일때 연결이 끊기는 이유
사용자가 겪은 "'관계없는' 다른 서비스도 끊어지는 현상"은 정상적인 동작이며, 그 이유는 Ingress Controller 파드들이 노드별로 따로 동작하는 것이 아니라, 하나의 큰 '팀'으로 동작하기 때문입니다.
핵심 오해: Ingress는 노드별로 따로 동작하지 않습니다
가장 흔한 오해는 'Node A'에 있는 Ingress 파드는 서비스 A를 처리하고, 'Node B'에 있는 Ingress 파드는 서비스 B를 처리할 것이라는 생각입니다. 하지만 실제로는 그렇지 않습니다.
- 하나의 팀: nginx-ingress-controller는 보통 2~3개 이상의 파드가 여러 노드에 분산되어 실행됩니다. 이 모든 파드들은 동일한 라우팅 규칙을 공유하는 하나의 팀입니다.
- 무작위 분배: 맨 앞단의 로드밸런서(NLB 또는 ALB)는 들어오는 모든 요청(서비스 A, B, C에 대한 요청)을 어떤 Ingress 파드가 처리할지 구분하지 않고, 가용한 모든 Ingress 파드들에게 무작위로 분배합니다.
가장 쉬운 비유는 대형 마트의 계산대입니다.
- 계산대 (여러 개): 여러 노드에 흩어져 있는 NGINX Ingress 파드들
- 손님들: 서비스 A, B, C에 대한 요청들
- 입구의 안내 직원: 로드밸런서(NLB/ALB)
안내 직원은 손님이 카트에 무엇을 담았는지(어떤 서비스에 대한 요청인지) 신경 쓰지 않고, 가장 한가해 보이는 계산대(가용한 Ingress 파드)로 손님을 보냅니다.
ㅡ'관계없는 서비스'가 함께 끊기는 이유 (자세한 순서)
위의 마트 비유를 바탕으로 현상을 재구성해 보겠습니다. 계산대가 3개(Node A, Node B, Node C에 각각) 있다고 가정합니다.
- 계산대 C 폐쇄 결정: 마트 매니저(클러스터 오토스케일러)가 손님이 적으니 3번 계산대(Node C)를 닫기로 결정합니다.
- 안내 직원은 계속 손님을 보냄: 💀 이것이 핵심입니다. 입구의 안내 직원(로드밸런서)은 3번 계산대가 닫히고 있다는 공지를 아직 받지 못했습니다. 그래서 계속해서 손님들을 1, 2, 3번 계산대로 골고루 보냅니다.
- 연결 끊김 발생: A서비스 물건을 산 손님이 우연히 3번 계산대로 안내받습니다. 하지만 3번 계산대 직원(NGINX 파드)은 막 퇴근하려던 참이라 계산을 해주지 못하고, 이 손님은 계산을 못 하고 멈춰 섭니다. (연결 끊김 발생)
- 사용자 경험: 이 손님 입장에서는 "나는 A서비스 물건을 샀는데, 왜 상관없는 C계산대가 닫혔다고 내 계산이 안 되는 거지?"라고 느끼게 됩니다. 이것이 바로 '관계없는 서비스가 끊어지는' 현상의 실체입니다.
해결책: '우아한 종료 (Graceful Shutdown)'
이전 답변에서 설명해 드린 해결책이 바로 이 문제를 풀기 위한 것입니다. 계산대 직원이 퇴근하기 전에 "더 이상 손님 받지 마세요"라고 안내 직원에게 미리 알려줄 시간을 버는 것입니다.
- preStop Hook: 계산대 직원이 퇴근 버튼을 누르기 전에, 잠시(예: 30초) 동안 아무것도 안 하고 기다립니다.
- 등록 취소 지연: 안내 직원은 그 30초 동안 "아, 3번 계산대가 곧 닫히는구나"를 인지하고 더 이상 그쪽으로 손님을 보내지 않습니다.
- PDB (Pod Disruption Budget): 마트 매니저가 "아무리 손님이 없어도 최소 2개의 계산대는 항상 열어둬야 한다"는 규칙을 정하는 것과 같습니다.
결론
사용자의 생각처럼 "노드가 리스타트되면 Ingress도 리스타트되니 당연히 연결이 끊긴다"는 것은 맞습니다. 하지만 그 영향이 해당 노드에 국한되는 것이 아니라, Ingress 시스템 전체가 하나의 팀으로 작동하기 때문에 로드밸런서가 트래픽을 보내는 모든 서비스에 일시적으로 영향을 미칠 수 있다는 것이 핵심입니다.
원인은 노드(Node)가 축소(Scale-down)될 때 발생하는 '경쟁 상태(Race Condition)' 때문입니다. 간단히 말해, NLB가 해당 노드로 트래픽 보내는 것을 멈추기 전에, 그 노드 위에서 실행되던 NGINX 파드가 먼저 종료되어 버리는 현상입니다.
문제 발생 원인 (자세한 순서)
- 오토스케일러, 노드 제거 결정: 클러스터 오토스케일러가 사용량이 적은 노드(Node A)를 발견하고 제거하기로 결정합니다.
- 파드 종료 시작: 쿠버네티스는 Node A 위의 모든 파드(NGINX 파드 포함)에게 종료 신호(SIGTERM)를 보냅니다.
- NLB는 계속 트래픽 전송: 💀 이것이 핵심 문제입니다. NLB는 아직 Node A가 사라지는 것을 모릅니다. NLB의 헬스 체크(Health Check)가 실패하고, 타겟 그룹에서 해당 노드가 'unhealthy'로 판정되어 제거되기까지는 수십 초의 시간이 걸립니다.
- 연결 끊김 발생: NLB는 계속해서 새로운 사용자 트래픽을 종료 중인 Node A로 보냅니다. 하지만 Node A의 NGINX 파드는 이미 종료되었거나 종료 중이므로, 이 트래픽을 처리하지 못하고 연결이 끊겨버립니다. (502 Bad Gateway, Connection Refused 등 발생)
해결책 (Best Practice)
이 문제는 하나의 설정만으로 해결되지 않으며, 쿠버네티스와 AWS 양쪽에서 '우아한 종료(Graceful Shutdown)'를 위한 설정을 함께 적용해야 합니다. 아래 3가지 방법을 조합하여 사용하는 것이 가장 좋습니다.
1. 쿠버네티스 preStop Hook 사용 (가장 중요)
preStop Hook은 파드에게 종료 신호(SIGTERM)를 보내기 전에 실행되는 명령어입니다. 이 시간을 벌어주는 것이 핵심입니다.
- 원리: NGINX 파드가 즉시 종료되지 않도록 preStop에서 잠시 대기(sleep)시킵니다. 이 시간 동안 NLB 헬스 체크가 실패하고, NLB는 더 이상 이 파드로 트래픽을 보내지 않게 됩니다.
- 설정 방법: NGINX Ingress Controller의 Deployment YAML 파일에 lifecycle과 preStop을 추가합니다.
# NGINX Ingress Controller Deployment 설정 중 일부
...
spec:
template:
spec:
containers:
- name: controller
...
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- "sleep 30" # 30초 동안 대기하여 NLB가 타겟에서 제외할 시간을 줌
...
Tip: sleep 대신 NGINX의 우아한 종료 명령어 nginx -s quit을 사용하는 방법도 있지만, sleep이 더 직관적이고 확실하게 시간을 보장해 줍니다.
2. NLB 타겟 그룹의 '등록 취소 지연' 설정
이 설정은 NLB가 타겟을 'unhealthy'로 판단했을 때, 기존에 연결된 커넥션을 바로 끊지 않고 얼마나 기다려줄지를 결정합니다.
- 원리: preStop Hook 시간과 맞물려, 기존에 처리 중이던 요청들이 정상적으로 완료될 시간을 보장해 줍니다.
- 설정 방법: AWS 콘솔에서 EKS 서비스에 연결된 NLB의 타겟 그룹(Target Group)으로 이동하여 속성을 편집합니다.
- 등록 취소 지연 (Deregistration delay): preStop에서 설정한 sleep 시간보다 길게 설정합니다. (예: preStop이 30초면, 60초로 설정)
3. Pod Disruption Budget (PDB) 설정
PDB는 자발적인 중단(Voluntary Disruption), 즉 오토스케일러의 노드 제거 같은 상황에서 최소한으로 가용해야 하는 파드의 개수를 보장하는 기능입니다.
- 원리: NGINX 파드가 동시에 너무 많이 사라지는 것을 막아 서비스 전체의 안정성을 유지합니다. 예를 들어 NGINX 파드 3개 중 최대 1개만 동시에 중단되도록 설정할 수 있습니다.
- 설정 방법: 클러스터에 PDB 리소스를 생성합니다.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: nginx-ingress-pdb
spec:
minAvailable: 2 # 최소 2개의 파드는 항상 사용 가능해야 함
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx # NGINX Ingress의 Label
app.kubernetes.io/component: controller
요약: 최적의 조합
- NGINX 파드에 preStop Hook으로 30~60초의 sleep을 설정합니다.
- NLB 타겟 그룹의 '등록 취소 지연' 시간을 preStop 시간보다 길게 (예: 60~120초) 설정합니다.
- PDB를 설정하여 동시에 여러 개의 NGINX 파드가 사라지는 것을 방지합니다.
이 세 가지를 함께 적용하면, 클러스터 오토스케일러가 노드를 제거하더라도 사용자 요청이 끊김 없이 안정적으로 처리되는 'Zero-Downtime Scale-down' 환경을 구축할 수 있습니다.
'K8S' 카테고리의 다른 글
| Argo CD에서 이미지 변경 트리거 안되는 배포 트러블 슈팅 (0) | 2025.08.25 |
|---|---|
| 일반 서비스 (echo-service)와 Ingress Controller의 차이 (0) | 2025.08.07 |
| [kubernetes] API groups 를 활용하여 리소스 제어 (0) | 2025.03.04 |
| [Kubernetes] Service Account 개념 및 활용 (0) | 2025.03.04 |
| [kubernetes] rolebinding과 clusterrolebinding 할당하는 리소스 (0) | 2025.03.04 |
댓글