쿠버네티스 컨트롤 플레인이 느려지는 원인: API 서버, etcd, 스케줄러
서비스 트래픽은 정상인데 kubectl get이 몇 초씩 걸리고, 배포를 걸면 롤아웃이 굼뜨고, 가끔 웹훅이 타임아웃됩니다. 워크로드(데이터 플레인)가 아니라 클러스터를 조종하는 쪽(컨트롤 플레인)이 느려진 상황입니다. 이 글은 그 병목이 생기는 세 곳, API 서버, etcd, 스케줄러를 순서대로 진단합니다. Pod 단위 트러블슈팅은 Pending 편과 CKA 트러블슈팅 시리즈에서 다뤘고, 이 글은 클러스터 규모가 커진 뒤에 만나는 층을 다룹니다.
먼저 구분부터 합니다. 컨트롤 플레인이 느려도 이미 떠 있는 Pod의 트래픽 처리는 대체로 멀쩡합니다. 반대로 “서비스가 느리다"면 이 글이 아니라 서버가 느린 이유 시리즈의 영역입니다. 증상이 kubectl·배포·컨트롤러 반응 속도에 몰려 있을 때 이 글의 순서를 따릅니다.
1. API 서버: 누가 이렇게 두드리고 있는가 #
모든 요청은 API 서버를 지나므로, 컨트롤 플레인 지연의 첫 용의자입니다. 그리고 API 서버를 무겁게 만드는 것은 대부분 사용자의 kubectl이 아니라 자동화된 클라이언트입니다.
- 비싼 LIST의 반복: 전체 네임스페이스의 Pod를 라벨 필터 없이 LIST하는 호출은 etcd와 API 서버 메모리를 크게 먹습니다. 이것을 폴링 루프로 도는 오퍼레이터, CI 스크립트, 모니터링 에이전트가 전형적인 범인입니다. 잘 만든 컨트롤러는 LIST 한 번 + WATCH(informer 캐시)로 일합니다. 폴링 LIST가 보이면 교정 대상입니다.
- 웹훅 체인: 어드미션 웹훅이 느리면 모든 쓰기 요청이 그 웹훅의 지연을 상속합니다. 웹훅 백엔드 Pod가 성능이 부족한 노드에 있거나 재시작 중이면 클러스터 전체의 쓰기가 함께 느려집니다.
- 확인: API 서버 메트릭
apiserver_request_duration_seconds를 verb·resource별로 보면 무엇이 느린지,apiserver_flowcontrol_*(API Priority and Fairness)를 보면 누가 큐에 걸려 있는지 나옵니다. 감사 로그(audit log)를 켜 두었다면 클라이언트별 호출량 순위를 뽑을 수 있습니다. - 처방: 범인 클라이언트의 폴링을 informer 패턴으로 교정, LIST에 라벨 셀렉터·페이지네이션 적용, 느린 웹훅의 타임아웃 축소와 failurePolicy 재검토입니다. APF가 기본 제공하는 우선순위 덕에 폭주 클라이언트가 클러스터를 통째로 멈춰 세우는 일은 줄었지만, 그 클라이언트 자신과 같은 등급의 요청은 여전히 느려집니다.
2. etcd: 컨트롤 플레인의 디스크 문제 #
etcd는 클러스터의 유일한 저장소이고, 쓰기마다 디스크에 fsync합니다. 그래서 etcd의 성능 문제는 대부분 디스크 문제입니다. SSD 편에서 다룬 fsync 지연 이야기가 여기서 그대로 재등장합니다.
- fsync 지연: etcd가 권고하는 기준은 fsync p99가 한 자릿수 밀리초입니다.
etcd_disk_wal_fsync_duration_seconds지표가 이것을 넘고 있으면, 네트워크 스토리지의 IOPS 상한, 같은 디스크를 쓰는 이웃, PLP 없는 장치 같은 스토리지 쪽 원인을 봅니다. etcd 로그의 “apply entries took too long” 경고가 같은 신호입니다. - DB 비대화: 오브젝트가 많거나 변경이 잦으면(특히 Events, 그리고 상태를 자주 갱신하는 오퍼레이터의 CR) 리비전이 쌓여 DB가 커지고, 커질수록 느려지며, 쿼터(기본 2GB)에 도달하면 클러스터가 읽기 전용으로 떨어지는 사고가 납니다.
etcd_mvcc_db_total_size_in_bytes를 추이로 관측하고, compaction이 도는지, 조각 모음(defrag)이 필요한지 봅니다. - 큰 오브젝트: 거대한 ConfigMap(파일 뭉치를 통째로), 대형 CRD 오브젝트는 쓰기·워치 양쪽을 무겁게 합니다. 데이터 파일은 오브젝트 스토리지로 빼는 것이 맞습니다.
- 처방: etcd 전용의 낮은 지연 디스크(로컬 SSD 권장), 이벤트 TTL·오브젝트 다이어트, 주기적 컴팩션·조각 모음입니다. 매니지드 클러스터(EKS 등)라면 etcd는 공급자 몫이지만, 오브젝트 수와 변경률을 낮게 유지하는 것은 여전히 사용자 몫입니다.
3. 스케줄러: 큐가 길거나 계산이 무겁거나 #
스케줄러 지연은 “Pod 생성 → 노드 배정” 사이의 시간으로 나타납니다. 두 모양이 있습니다.
- 큐가 긴 경우: 대량 배치 작업이 수천 Pod를 한 번에 만들면 스케줄링 큐가 밀립니다. 이것은 병목이라기보다 자연스러운 대기이고, 배치 워크로드의 생성 속도 조절이 처방입니다.
- Pod 하나의 계산이 무거운 경우: 노드 수천 대 클러스터에서 복잡한 pod affinity/anti-affinity(특히 topology 기반)는 평가 비용이 큽니다.
scheduler_scheduling_attempt_duration_seconds가 늘어져 있으면, 꼭 필요하지 않은 anti-affinity를 topology spread constraints로 바꾸는 것이 정석 처방입니다.
스케줄러가 느린 것과 스케줄할 곳이 없는 것(Pending)은 다른 문제입니다. 전자는 시간이 걸려도 결국 배정되고, 후자는 이벤트에 FailedScheduling이 남습니다.
진단 순서 정리 #
- 데이터 플레인 문제와 구분: 증상이 kubectl·배포·컨트롤러 반응에 몰려 있는지 확인합니다. 서비스 응답이 느린 것이면 다른 글의 영역입니다.
- API 서버 지표:
apiserver_request_duration_seconds로 어떤 verb·resource가 느린지, APF 지표와 감사 로그로 폭주 클라이언트를 찾습니다. - etcd 지표: fsync p99와 DB 크기 추이를 봅니다. 디스크가 원인의 대부분입니다.
- 웹훅: 쓰기만 느리면 어드미션 웹훅 체인의 지연을 봅니다.
- 스케줄러: 배정 지연이면 큐 길이와 스케줄링 시도 시간을 보고, 무거운 affinity를 정리합니다.
정리 #
- 컨트롤 플레인의 느려짐은 워크로드 트래픽과 별개 층의 문제입니다. 증상이 kubectl·배포·컨트롤러에 몰려 있는지로 먼저 구분합니다.
- API 서버를 무겁게 하는 것은 대부분 자동화 클라이언트의 폴링 LIST와 느린 어드미션 웹훅입니다. informer 패턴과 셀렉터·페이지네이션이 처방입니다.
- etcd 문제는 대부분 디스크(fsync) 문제이고, 나머지는 오브젝트 비대화입니다. fsync p99 한 자릿수 ms와 DB 크기 추이를 지표로 잡습니다.
- 스케줄러는 대량 생성의 큐 대기와 무거운 affinity 계산이 지연을 만듭니다. topology spread로의 전환이 정석입니다.
- 매니지드 클러스터라도 오브젝트 수·변경률·웹훅 품질은 사용자 몫으로 남습니다.