서론
쿠버네티스 API Server가 500 에러를 뱉으며 반복적으로 재시작되는 치명적인 장애가 발생했다.
클라이언트의 호출 패턴(Watch)과 관측 도구(OTEL)의 설정 오류가 함께 발생학 복합적인 문제였다.
이번 포스팅에서는 GET과 WATCH의 차이를 이해하고 어떻게 클러스터 마비시켰는지 분석한다.
(공식 사이트에서 발췌한 내용을 기반으로 작성하였으나,명확하지 않은 부분은 경험을 토대로 작성하였습니다.
이는 정확한 정보가 아닐 수 있음을 알려드립니다.)
공식 사이트
광고 클릭은 큰 힘이 됩니다!
OpenTelemetry
High-quality, ubiquitous, and portable telemetry to enable effective observability
opentelemetry.io
GET (Snapshot)
동작: 클라이언트가 요청한 시점의 리소스 상태를 일회성으로 조회한다.
부하: 요청 시점에만 처리 비용이 발생하며, 연결은 응답 후 종료된다.
Audit: 요청 1회당 로그 1줄이 생성된다.
WATCH (Stream)
동작: 클라이언트와 API 서버가 장기 연결(Long-lived connection)을 맺고, 리소스의 변경 사항(Event)이 있을 때마다 실시간으로 스트림을 보낸다.
부하: 연결 유지 비용이 발생하며, 변경이 잦을 경우 지속적인 CPU/Memory를 소모한다.
Audit & Risk: WATCH 연결이 맺어지는 순간(RequestReceived/ResponseComplete) 감사 로그가 남는다. 만약 클라이언트가 '연결-해제-재연결'을 수초 단위로 반복한다면, GET보다 훨씬 빠르고 방대하게 감사 로그를 생성하여 스토리지와 버퍼를 잠식한다.
GET은 일회성이지만, WATCH는 이벤트를 지속 생성하여 감사 로그의 양을 폭발시킨다.
1. OTEL 수집과 WATCH 폭격
OTEL 컬렉터는 감사 로그 파일(audit.log)에 새로운 줄이 생기자마자 이를 읽어들인다.
이때 클라이언트가 생성하는 방대한 양의 WATCH 요청이 실시간으로 수집 대상이 되었다.
[kube-apiserver 로그]
AUDIT: id="..." stage="ResponseComplete" ip="클라이언트" method="watch" user="유저" groups="[system:authenticated]" as="<self>" as-groups="<nil>" namespace="default" uri="/apis/apps/v1/deployments?allowWatchBookmarks=true&resourceVersion=1015325243&timeoutSeconds=340&watch=true" response="200"
deployments 리소스에 대해 WATCH 연결을 맺었으며, 이는 즉시 감사 로그로 기록되었다.
+----------------+-----------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+----------------+-----------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 0.0.0.0:2379 | --------------- | 3.5.17 | 20 MB | true | false | 7 | 110951329| 110951329| |
+----------------+-----------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
이 과정에서 etcd의 Raft Index가 1억 이상까지 급증했으며, 마스터 노드의 감사 로그 파일(/var/log/kubernetes/audit.log)에 엄청난 속도로 로그가 쌓이기 시작했다.
팁
만약 자신의 클러스터 ETCD의 DB사이즈는 작은데 RAFT INDEX가 이상하리 만치 높다면, 잦은 요청에 의한 변화를 의심해보는게 좋다. 저렇게 RAFT INDEX 급격하게 늘어나면 ETCD의 Virtual Memory 사용량이 급증하게 된다.
2. 파싱 실패
OTEL이 읽은 로그를 외부 저장소로 보내려는 순간 문제가 발생한다.
로그 내의 날짜 필드(2025-12...)를 숫자(Long)로 변환하려다 타입 에러(Mapping Exception)가 발생하여 전송이 거부된다.
[opentelemetry 로그]
Exporting failed. Rejecting data. {"kind": "exporter", "data_type": "logs", "name": "otlp/opensearch", "error": "not retryable error: Permanent error: {\"type\":\"mapper_parsing_exception\",\"reason\":\"failed to parse field [attributes.requestObject.value] of type [long] ... Preview of field's value: '2025-'\"}"}
데이터 형식이 맞지 않아 외부 저장소가 데이터를 거부했고, OTEL은 에러를 뱉어냈다.
3. 재시도와 병목
OTEL은 전송에 실패했으므로 해당 로그를 버리지 않고 재시도한다.
OTEL이 에러로 인해 로그 처리를 멈칫하며 재시도하는 그 순간에도, CAPI의 WATCH 요청은 멈추지 않고 계속해서 새로운 감사 로그를 쏟아내고 있었다.
[kube-apiserver 로그]
AUDIT: id="..." ip="클라이언트" method="watch" user-agent="cluster-api/v0.0.0" uri="/api/v1/namespaces/kube-system/secrets?..."
AUDIT: id="..." ip="클라이언트" method="watch" user-agent="cluster-api/v0.0.0" uri="/api/v1/namespaces/default/replicasets?..."
4.Buffer Overflow
API 서버 내부의 감사 로그 버퍼가 순식간에 가득 차버렸고, 결국 API 서버는 "더 이상 로그를 기록할 수 없는 임계치"에 도달했다.
[kube-apiserver 로그]
Error in audit plugin 'buffered' affecting 1 audit events: audit backend shut down
5. 시스템 중단
병목 현상으로 인해 API 서버가 응답을 멈추자, kubelet이 수행하는 헬스체크(Liveness Probe)가 타임아웃되었다.
결국 kubelet은 API 서버 컨테이너를 죽이고 강제로 재시작한다.
[kubelet.log]
"Liveness probe failed" probeType="Liveness" pod="kube-apiserver" ... statuscode: 500
"Killing container with a grace period" pod="kube-apiserver" containerName="kube-apiserver"
결론 및 조치
OTEL이 비정상적인 로그를 수집하려다 데이터 파싱 오류로 인해 시스템을 마비시킨 것.
임시 조치: telemetry-collector.yaml에서 /data/k8s-audit/ 마운트 설정을 제거하여 감사 로그 수집을 중단.
데이터 수정: 외부 저장소의 인덱스 템플릿에서 attributes.requestObject.value 필드의 타입을 변경.
성능 회복: etcd의 Raft Index가 너무 높으므로, 모든 마스터 노드에서 etcdctl defrag를 실행하여 팽창된 메모리 정리.
중요
잘못된 정보나, 문의등은 댓글로 메일과 함께 적어주시면 감사하겠습니다.
'Kubernetes' 카테고리의 다른 글
| Kubestronaut 여정과 그 마지막 (0) | 2026.01.13 |
|---|---|
| Disk Thin Provisioning 방식과 Node Deadlock 상관관계 (1) | 2026.01.01 |
| I/O 병목과 ETCD 그리고 API 서버 (0) | 2025.12.09 |
| Ingress NGINX의 공식 은퇴 (0) | 2025.11.27 |
| Kubernetes Node Life Cycle - 노드의 생명주기 (0) | 2025.11.26 |