IT/Kubernets

etcd defrag, CronJob에 그냥 박아놓으면 안 된다

gfrog 2026. 5. 18. 12:44
반응형

이거 모르는 분 의외로 많더라. 어제도 사내 슬랙에서 비슷한 질문이 올라왔다. "etcd defrag CronJob 돌리는데 가끔 API 서버가 잠깐 죽어요." 그럴 만했다.

오늘은 짧게 한 가지만 정리한다. etcd defragmentation을 CronJob으로 자동화할 때 빠뜨리기 쉬운 포인트.

compaction과 defrag는 다른 거다

먼저 용어부터. 둘이 같은 줄 알고 쓰는 사람이 꽤 있다.

  • compaction: 오래된 revision을 논리적으로 지운다. 디스크 파일은 안 줄어든다. 자동으로 돌릴 수 있다 (--auto-compaction-mode=periodic --auto-compaction-retention=8h 같은 식). kube-apiserver가 5분마다 자동 compaction을 호출하기도 한다.
  • defragmentation: 실제로 db 파일을 재작성해서 디스크 공간을 회수한다. 이건 자동이 아니다. 그리고 돌리는 동안 그 멤버는 블로킹된다.

여기서 핵심. compaction을 아무리 잘 돌려도 db 파일은 안 줄어든다. 디스크 사용량 보고 "왜 안 줄지?" 하다가 알게 되는 사람이 많다.

흔한 실수 — 동시에 다 돌린다

CronJob 하나로 모든 etcd 멤버를 한꺼번에 defrag하는 매니페스트를 종종 본다. 보통 이런 식이다.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: etcd-defrag
spec:
  schedule: "0 3 * * 0"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: defrag
            image: quay.io/coreos/etcd:v3.5.21
            command:
            - /bin/sh
            - -c
            - |
              etcdctl --endpoints=$ENDPOINTS \
                --cacert=/certs/ca.crt --cert=/certs/client.crt --key=/certs/client.key \
                defrag --cluster

defrag --cluster 플래그. 이게 함정이다. 모든 멤버에 순차적으로 보내긴 하는데, 각 멤버가 defrag 중에는 writes를 못 받는다. 3노드 클러스터에서 leader가 defrag 중이면 그 시간 동안 quorum이 흔들린다. db가 크면 (5GB+) 멤버 하나당 수십 초 멈출 수 있다.

운 좋으면 API 서버가 timeout만 좀 나고 끝난다. 운 나쁘면 controller-manager의 leader election이 깨지고 그 사이에 HPA나 GC가 멈춘다. 우리 팀에서도 한번 겪었다. 새벽 3시 alert에 잠 깬 게 누구였더라.

멤버별로 시간을 흩뿌리자

그래서 우리는 멤버마다 CronJob을 분리해서 시간을 5분씩 띄워뒀다. follower부터 먼저, leader는 마지막.

# member-0: 03:00, member-1: 03:10, member-2: 03:20
schedule: "0 3 * * 0"   # 멤버마다 다르게
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3

# command 부분만:
- |
  set -e
  # 1) 이 멤버가 leader면 step down
  IS_LEADER=$(etcdctl --endpoints=$THIS_ENDPOINT endpoint status --write-out=json | jq '.[0].Status.leader == .[0].Status.header.member_id')
  if [ "$IS_LEADER" = "true" ]; then
    etcdctl --endpoints=$THIS_ENDPOINT move-leader $OTHER_MEMBER_ID
    sleep 5
  fi
  # 2) defrag는 단일 endpoint로
  etcdctl --endpoints=$THIS_ENDPOINT defrag
  # 3) 알람 해제
  etcdctl --endpoints=$THIS_ENDPOINT alarm disarm

마지막 alarm disarm도 빼먹지 말자. db quota를 넘어서 NOSPACE 알람이 떠 있는 상태라면 defrag 후에 직접 해제해야 writes가 다시 들어간다.

한 가지 더 — etcd-defrag 도구

최근에 etcd 메인테이너 중 한 명이 만든 etcd-defrag 도구가 꽤 잘 만들어졌다 (ahrtr/etcd-defrag). leader-last 순서 자동, fragmentation ratio 기반 조건부 실행 같은 게 빌트인이다. 직접 쉘 스크립트 짜는 것보다 이걸 컨테이너로 말아서 CronJob 돌리는 게 깔끔하다.

etcd-defrag \
  --endpoints=... \
  --cacert=... --cert=... --key=... \
  --defrag-rule="dbQuotaUsage > 0.5 && dbSize - dbSizeInUse > 200*1024*1024"

위 룰은 "쿼터의 50% 이상 차 있고 단편화로 인한 낭비가 200MB 초과일 때만 실행". 매주 무지성으로 돌리는 것보단 이쪽이 안전하다.

추가로 신경 쓸 것

etcd_mvcc_db_total_size_in_bytesetcd_mvcc_db_total_size_in_use_in_bytes 두 지표를 Grafana에 띄워두자. 차이가 벌어지면 단편화가 쌓이고 있다는 뜻이다. quota의 70% 넘기 전에 알람.

오늘은 여기까지. 짧게만 정리하려고 했는데 또 길어졌네.

반응형