Post

ETCD 테스트 기록

ETCD 테스트 기록

테스트 환경

3 Node Stacked ETCD 구성에서 멤버 삭제/추가, Leader 재선출 시나리오를 테스트했다.

테스트 환경 구성도

사전 준비 — 환경 변수 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# etcd pod 이름 확인
kubectl get pod -n kube-system | grep etcd

# 전체 클러스터 상태 조회
kubectl exec -it etcd-porthos-vnl-pub-kr1-dop-rl-cp-1 -n kube-system -- \
  etcdctl \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  --endpoints=https://192.168.1.91:2379,https://192.168.1.56:2379,https://192.168.1.16:2379 \
  endpoint status --write-out=table

# 개별 노드 조회 (cp-1)
kubectl exec -it etcd-porthos-vnl-pub-kr1-dop-rl-cp-1 -n kube-system -- \
  etcdctl \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  --endpoints=https://127.0.0.1:2379 \
  endpoint status --write-out=table

초기 상태

초기 클러스터 상태

항목
Leadercp-1
DB Size230 MB
RAFT TERM41 (Leader 선출이 41회 발생)


시나리오 1 — 멤버 삭제

cp-3의 member ID c302c00356f1848a를 클러스터에서 제거했다.

멤버 삭제 실행

Quorum 유지 확인

멤버 삭제 후 클러스터 상태

2/3 노드가 정상이므로 Quorum은 유지됐다. 단, kubectl 응답 latency가 증가했다.

발생한 현상과 로그 분석

1. etcd 내부 — ConfChange가 Raft로 합의되는 순간

member remove 실행 직후 etcd 로그에 다음이 찍혔다.

1
2
3
4
5
6
7
8
{"msg":"applied a configuration change through raft",
 "local-member-id":"2a40d8ba966d12fe",
 "raft-conf-change":"ConfChangeRemoveNode",
 "raft-conf-change-node-id":"a30526f5dacd19af"}

{"msg":"removed remote peer",
 "local-member-id":"2a40d8ba966d12fe",
 "removed-remote-peer-id":"a30526f5dacd19af"}

member remove는 단순히 목록에서 지우는 게 아니라 Raft 합의 과정을 거쳐 클러스터 전체에 반영된다. ConfChangeRemoveNode가 log entry로 기록되고, 쿼럼이 달성되면 commit된다. 이후 cp-3과의 TCP 연결을 즉시 끊는다.

2. cp-3이 계속 재연결을 시도하는 현상

commit 이후에도 cp-3은 자신이 제거됐다는 걸 모르고 계속 연결을 시도했다. Leader는 이를 거부했다.

1
2
3
4
{"msg":"rejected stream from remote peer because it was removed",
 "local-member-id":"2a40d8ba966d12fe",
 "remote-peer-id-stream-handler":"2a40d8ba966d12fe",
 "remote-peer-id-from":"a30526f5dacd19af"}

이 로그가 수십 초간 반복됐다. cp-3 입장에서는 자신이 클러스터에서 제거됐다는 신호를 직접 받지 못하기 때문이다.

3. kube-apiserver — Lease 갱신 타임아웃

ConfChange가 처리되는 순간 kube-apiserver의 etcd write 요청들이 동시에 타임아웃됐다.

1
2
3
4
5
6
7
8
E0328 00:03:55 timeout.go:140 method="PUT"
  path="/apis/coordination.k8s.io/v1/namespaces/kube-system/leases/kube-scheduler"

E0328 00:03:55 timeout.go:140 method="PUT"
  path="/apis/coordination.k8s.io/v1/namespaces/kube-system/leases/kube-controller-manager"

E0328 00:03:55 status.go:71 err="etcdserver: request timed out,
  possibly due to previous leader failure"

kube-schedulerkube-controller-managerLease 갱신이 동시에 실패했다. etcd가 ConfChange를 처리하는 동안 write 요청을 잠깐 받지 못했기 때문이다. 이게 kubectl hang의 실제 원인이다.

4. cp-1 재시작 — WAL flush 후 재초기화

멤버 구성 변경이 WAL에 flush되면서 cp-1이 재시작됐다. 이는 정상 동작이다. 재시작 후에도 멤버 구성이 유지되는 이유가 바로 WAL에 기록됐기 때문이다.


시나리오 2 — 멤버 추가

클러스터에 멤버 등록

1
2
3
4
5
6
7
8
kubectl exec -it etcd-porthos-vnl-pub-kr1-dop-rl-cp-1 -n kube-system -- \
  etcdctl \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  --endpoints=https://192.168.1.91:2379 \
  member add porthos-vnl-pub-kr1-dop-rl-cp-3 \
  --peer-urls=https://192.168.1.16:2380

출력 결과:

1
2
3
4
ETCD_NAME="porthos-vnl-pub-kr1-dop-rl-cp-3"
ETCD_INITIAL_CLUSTER="porthos-vnl-pub-kr1-dop-rl-cp-1=https://192.168.1.91:2380,porthos-vnl-pub-kr1-dop-rl-cp-2=https://192.168.1.56:2380,porthos-vnl-pub-kr1-dop-rl-cp-3=https://192.168.1.16:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.1.16:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"

멤버 추가 후 상태

member add 직후 cp-3은 unstarted 상태다. 멤버 등록만 됐을 뿐 etcd 프로세스가 아직 없는 상태로, 이 시점부터 쿼럼 기준이 3으로 증가한다.

cp-3 데이터 초기화 후 재기동

기존 데이터를 삭제하지 않으면 디스크에 남은 이전 member ID와 클러스터에 등록된 새 ID가 충돌해 CrashLoopBackOff가 발생한다.

1
2
3
4
5
{
  "msg": "rejected Raft message to mismatch member",
  "local-member-id": "c302c00356f1848a",
  "mismatch-member-id": "f383bce7688605f2"
}

데이터 디렉터리를 초기화한다.

1
sudo rm -rf /var/lib/etcd && sudo mkdir -p /var/lib/etcd && sudo chmod 700 /var/lib/etcd

삭제 후 kubelet이 etcd를 재기동하면 Leader가 즉시 snapshot 전송을 시작한다.

대형 snapshot 전송 확인 (Issue #13913)

1
2
kubectl logs etcd-porthos-vnl-pub-kr1-dop-rl-cp-1 -n kube-system | \
  grep -E "sending snapshot|sent database snapshot"
1
2
{"msg":"sending database snapshot","snapshot-index":15995741,"remote-peer-id":"a30526f5dacd19af","size":"230 MB"}
{"msg":"sent database snapshot","snapshot-index":15995741,"remote-peer-id":"a30526f5dacd19af","size":"230 MB"}

230 MB snapshot이 약 2초 만에 전송됐다. I/O 경합이 있는 환경에서는 이 구간이 수십 초로 늘어나 heartbeat 누락 -> 리더 재선출로 이어진다고 한다. (#13913)


시나리오 3 — Leader 강제 종료 및 재선출

Leader 삭제

1
kubectl delete pod etcd-porthos-vnl-pub-kr1-dop-rl-cp-1 -n kube-system --grace-period=0 --force

결과 — 1초 내 복구, RAFT TERM 증가

Leader 재선출 후 상태

Leader(cp-1)가 종료되자 나머지 Follower들이 election timeout을 감지하고 새 Leader를 선출했다. RAFT TERM이 42로 증가한 것이 그 증거다.

cp-1은 Static Pod이므로 kubelet이 즉시 재기동시켰고, 새 Leader로부터 heartbeat를 수신하자 자동으로 Follower로 전환됐다.

로그 확인

1
2
kubectl logs etcd-porthos-vnl-pub-kr1-dop-rl-cp-2 -n kube-system | \
  grep -E "became leader|elected leader" | tail -5
1
2
{"level":"info","ts":"2026-03-27T23:58:05.750027Z","logger":"raft","caller":"etcdserver/zap_raft.go:77","msg":"b9933c48f2424f14 became leader at term 42"}
{"level":"info","ts":"2026-03-27T23:58:05.750042Z","logger":"raft","caller":"etcdserver/zap_raft.go:77","msg":"raft.node: b9933c48f2424f14 elected leader b9933c48f2424f14 at term 42"}

주의 사항

Leader 장애 직전, 쿼럼 달성 전에 전송된 Write는 유실될 수 있다.

Write 유실 가능 구간 다이어그램

This post is licensed under CC BY 4.0 by the author.