LabHub
배우기 러닝패스 코스

There Were Two Leaders

The same thing happens in a real implementation

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

앞의 세 실습에서 손으로 쓴 규칙이 실제 구현에서도 그대로인지 확인합니다. 같은 파드 안에 etcd 세 멤버를 띄워 리더를 옮기고, 하나를 죽이고, 둘을 죽여 봅니다.

왜 중요한가

습작은 배우기 좋지만 믿을 근거가 되지는 않습니다. 쿠버네티스의 모든 상태가 얹혀 있는 etcd 에서 같은 숫자와 같은 말(term, index, quorum)이 나오는 것을 보아야, 앞에서 만든 규칙이 장난감의 규칙이 아니라는 것이 확인됩니다.

가장 중요한 단계는 여섯 번째입니다. 셋 중 둘이 멈추면 쓰기가 막히는 것은 누구나 예상하지만, 읽기도 함께 막힙니다. etcd 의 기본 읽기는 선형화 읽기라 리더가 '나는 아직 리더다' 를 과반에게 확인받은 뒤에야 답하기 때문입니다. 과반을 잃은 클러스터는 느려지는 것이 아니라 멈춥니다 — 이것이 홀수 대를 쓰고 정족수를 계산하는 진짜 이유입니다.

이 파드에는 다른 실습용 etcd 가 2379 에서 따로 돌고 있습니다. 그래서 이 실습은 22379 부터 시작하는 포트를 씁니다.

단계

  1. /root/etcd/start.sh 를 만들어 a, b, c 를 띄웁니다.
  2. 지금 리더와 임기를 /root/etcd/leader.json 에 적습니다.
  3. /raft/labhello-raft 를 쓰고 다른 멤버에서 읽어 /root/etcd/kv.json 에 적습니다.
  4. move-leader 로 리더를 옮기고 /root/etcd/move.json 에 적습니다.
  5. 팔로워 하나를 멈춘 채 써 보고 /root/etcd/one-down.json 에 적습니다.
  6. b 와 c 를 멈춘 채 쓰기와 읽기를 시도하고 /root/etcd/quorum-loss.json 에 적습니다.
  7. 모두 되살린 뒤 /root/etcd/recover.json 에 적습니다.
  8. 앞의 실습과 맞춰 본 판정을 /root/etcd/verdict.json 에 적습니다.

참고

세 멤버를 정적 부트스트랩으로 띄운다

/root/etcd/start.sh 를 만들어 a, b, c 세 멤버를 클라이언트 포트 22379, 22479, 22579 와 피어 포트 22380, 22480, 22580 에 띄우세요. 여러 번 돌려도 안전해야 합니다.

정적 부트스트랩은 세 멤버가 똑같은 --initial-cluster 문자열을 갖는 것이 전부입니다. 한 글자라도 다르면 서로를 같은 클러스터로 보지 않습니다. 이 파드에는 kwok 이 띄운 다른 etcd 가 2379 를 쓰고 있으니 그 포트는 피합니다. 데이터 디렉터리는 /root/etcd/<이름> 으로 두고, 이미 돌고 있으면 다시 띄우지 않게 pgrep 으로 막으세요.

누가 리더인지 읽어 낸다

endpoint status 로 지금 리더와 raft 임기를 확인하고 /root/etcd/leader.json 에 leader 와 raft_term 으로 적으세요.

etcdctl --endpoints=... endpoint status --write-out=table 은 IS LEADER, RAFT TERM, RAFT INDEX 를 한 줄씩 보여 줍니다. 앞 실습에서 손으로 만든 것과 같은 값입니다 — 임기는 선거를 셀 때마다 오르고, 인덱스는 로그의 자리 번호입니다.

어디에 물어도 같은 값

/raft/lab 열쇠에 hello-raft 를 쓰고, 쓴 곳이 아닌 다른 멤버 둘에서도 읽어 본 뒤 /root/etcd/kv.json 에 key, value, read_from 을 적으세요.

쓰기는 어느 엔드포인트에 보내도 리더로 넘어갑니다. 읽기도 기본은 선형화 읽기(linearizable read) 라 팔로워에게 물어도 리더의 확인을 거칩니다 — 그래서 어디에 물어도 같은 값이 나옵니다. 이 성질이 6단계에서 어떻게 깨지는지 보게 됩니다.

리더를 옮기면 임기가 오른다

move-leader 로 리더를 다른 멤버에게 넘기고, 넘기기 전과 뒤의 이름·임기를 /root/etcd/move.json 에 before, after, before_term, after_term 으로 적으세요.

etcdctl move-leader <멤버 id>지금 리더인 엔드포인트에 보내야 합니다. 다른 곳에 보내면 거절합니다. 멤버 id 는 endpoint status 의 ID 열에 16진수로 나옵니다. 넘긴 뒤 임기를 다시 보세요 — 리더가 바뀌면 임기도 함께 오릅니다. 앞 실습에서 새 리더가 임기를 올리고 뽑히던 것과 같은 일입니다.

하나가 죽어도 쓰기는 된다

팔로워 하나를 멈춘 채 값을 써 보고, /root/etcd/one-down.json 에 stopped, write_ok, healthy 를 적으세요.

멈추는 것은 pkill -9 -f /root/etcd/b 처럼 데이터 디렉터리 경로로 고르세요 — 이름만 보면 이 파드에서 함께 도는 다른 etcd 까지 걸립니다. 셋 중 둘이면 과반이므로 쓰기는 그대로 됩니다. healthy 에는 살아 있는 멤버 수를 적습니다.

둘이 죽으면 읽기도 멈춘다

b 와 c 를 멈춘 채 /raft/ghost 에 쓰기와 읽기를 모두 시도하고, /root/etcd/quorum-loss.json 에 stopped, write_ok, read_ok, error 를 적으세요. 확인이 끝나면 다시 띄우세요.

쓰기가 막히는 것은 예상대로입니다. 놀라운 것은 읽기도 막힌다는 것입니다 — etcd 의 기본 읽기는 선형화 읽기라 리더가 '나는 아직 리더다' 를 과반에게 확인받아야 답할 수 있기 때문입니다. error 에는 etcdctl 이 낸 메시지를 그대로 적으세요. 되살릴 때는 --initial-cluster-state new 가 붙어 있어도 데이터 디렉터리가 있으면 etcd 가 그쪽을 씁니다.

되살아난 뒤에 남는 것

세 멤버를 모두 되살리고 /raft/lab/raft/ghost 를 각각 읽어 본 뒤, /root/etcd/recover.json 에 healthy, survived, ghost_present 를 적으세요.

과반이 있을 때 확정한 값은 살아남고, 과반 없이 시도한 값은 흔적도 남지 않습니다. 앞 실습에서 ghost 가 옛 리더의 로그에서 사라지던 것과 같은 일이고, 다른 점은 여기서는 클라이언트가 애초에 ok 를 받지 못했다는 것입니다 — 그래서 이쪽이 더 안전합니다.

손으로 만든 규칙과 같은가

/root/etcd/verdict.json 에 quorum_of_3, tolerated_failures, write_without_quorum, linearizable_read_without_quorum, raft_index_seen 을 적으세요.

앞의 세 실습에서 규칙으로 쓴 것과 이 실습에서 본 것을 맞춰 보는 단계입니다. 세 노드에서 과반은 몇이고, 그래서 견딜 수 있는 고장은 몇 대인가. 과반이 없을 때 쓰기와 읽기는 어떻게 되는가. raft_index_seen 에는 endpoint status 의 RAFT INDEX 를 그대로 적으세요 — 이 값은 줄어들지 않습니다.