在真实实现中会发生同样的事
目标
确认前面三个实验中亲手写的规则,在真实实现里是否也是一样的。在同一个 Pod 里 启动 etcd 的三个成员,转移领导者,杀掉一个,再杀掉两个。
为什么重要
简易实现适合学习,但并不是可以相信的依据。只有在承载着 Kubernetes 全部状态的 etcd 中,看到同样的数字和同样的话(term、index、quorum),才能确认前面做出来的规则 不是玩具的规则。
最重要的一步是第六步。三个当中停了两个,写入会被阻塞,这是谁都能预料到的,但 读取也会一起被阻塞。 因为 etcd 默认的读取是线性一致读,领导者要先得到过半数对 “我仍然是领导者”的确认,才会作答。失去过半数的集群不是变慢,而是 停摆——这才是使用奇数台并计算法定人数的真正原因。
这个 Pod 里,另一个实验用的 etcd 正单独在 2379 上运行。所以本实验 使用从 22379 开始的端口。
步骤
- 创建
/root/etcd/start.sh,启动 a、b、c。 - 把当前的领导者和任期写入
/root/etcd/leader.json。 - 向
/raft/lab写入hello-raft,在另一个成员上读取,并写入/root/etcd/kv.json。 - 用
move-leader转移领导者,并写入/root/etcd/move.json。 - 停掉一个追随者再尝试写入,并写入
/root/etcd/one-down.json。 - 停掉 b 和 c,尝试写入和读取,并写入
/root/etcd/quorum-loss.json。 - 全部恢复之后,写入
/root/etcd/recover.json。 - 把与前面实验对照后的判定写入
/root/etcd/verdict.json。
参考
- 端点组合:
--endpoints=127.0.0.1:22379,127.0.0.1:22479,127.0.0.1:22579 - 状态表:
etcdctl --endpoints=... endpoint status --write-out=table - 健康检查:
etcdctl --endpoints=... endpoint health - 停止:
pkill -9 -f /root/etcd/b· 恢复:bash /root/etcd/start.sh - 如果温和地停止(不加
-9),领导者会去寻找可以退位的地方,最终却无法下线。这里是模拟故障的环节,所以要强制停止。 - 常见错误 1:三个成员的
--initial-cluster字符串各不相同。哪怕只差一个字符,也组不成集群。 - 常见错误 2:把
move-leader发给追随者的端点。必须发给当前的领导者。 - 没有响应时,加上
--command-timeout=4s。用默认值会等很久。
用静态引导方式启动三个成员
创建 /root/etcd/start.sh,把 a、b、c 三个成员分别启动在客户端端口 22379、22479、22579 和对等端口 22380、22480、22580 上。多次运行也必须是安全的。
静态引导的全部要点,就是三个成员持有完全相同的 --initial-cluster 字符串。哪怕只差一个字符,它们就不会把彼此视为同一个集群。这个 Pod 里有 kwok 启动的另一个 etcd 在使用 2379,所以要避开那个端口。数据目录定为 /root/etcd/<이름>(占位符为名称),并用 pgrep 防止已经在运行时重复启动。
读出谁是领导者
用 endpoint status 确认当前的领导者和 raft 任期,并以 leader 和 raft_term 写入 /root/etcd/leader.json。
etcdctl --endpoints=... endpoint status --write-out=table 会逐行显示 IS LEADER、RAFT TERM、RAFT INDEX。这与上一个实验里亲手做出来的是同样的值——任期每数一次选举就上升,索引是日志的位置编号。
问哪里都是同样的值
向 /raft/lab 这个键写入 hello-raft,并在写入处之外的另外两个成员上也读取,然后把 key、value、read_from 写入 /root/etcd/kv.json。
写入无论发到哪个端点,都会转交给领导者。读取默认也是线性一致读(linearizable read),所以即使问追随者,也要经过领导者的确认——因此问哪里都是同样的值。这一性质在第 6 步会怎样被打破,你会看到。
转移领导者,任期就会上升
用 move-leader 把领导者交给另一个成员,并把交接前后的名称和任期,以 before、after、before_term、after_term 写入 /root/etcd/move.json。
etcdctl move-leader <멤버 id>(占位符为成员 id)必须发给当前是领导者的端点。发到别处会被拒绝。成员 id 以十六进制显示在 endpoint status 的 ID 一列。交接之后再看任期——领导者一变,任期也会随之上升。这与上一个实验里新领导者提高任期再当选是同一回事。
死一个,写入照样可以
停掉一个追随者再尝试写入值,并把 stopped、write_ok、healthy 写入 /root/etcd/one-down.json。
停止时,要像 pkill -9 -f /root/etcd/b 这样按数据目录的路径来选——只看名称,就会连这个 Pod 里同时运行的另一个 etcd 也一并选中。三个当中有两个就是过半数,所以写入照常可以。healthy 写存活的成员数。
死两个,读取也会停止
停掉 b 和 c,对 /raft/ghost 同时尝试写入和读取,并把 stopped、write_ok、read_ok、error 写入 /root/etcd/quorum-loss.json。确认结束后,再把它们重新启动。
写入被阻塞,在意料之中。令人惊讶的是,读取也被阻塞——因为 etcd 默认的读取是线性一致读,领导者必须先得到过半数对“我仍然是领导者”的确认,才能作答。error 要原样写下 etcdctl 给出的消息。恢复时,即使带有 --initial-cluster-state new,只要数据目录还在,etcd 就会使用它。
恢复之后留下什么
把三个成员全部恢复,分别读取 /raft/lab 和 /raft/ghost,然后把 healthy、survived、ghost_present 写入 /root/etcd/recover.json。
在有过半数时确定的值会留下来,没有过半数时尝试写入的值则不会留下任何痕迹。这与上一个实验里 ghost 从旧领导者日志中消失是同一回事,不同的是,这里客户端一开始就没有收到 ok——所以这边更安全。
与亲手做的规则是一样的吗
把 quorum_of_3、tolerated_failures、write_without_quorum、linearizable_read_without_quorum、raft_index_seen 写入 /root/etcd/verdict.json。
这一步是把前面三个实验里写成规则的内容,与本实验所见的内容对照起来。三个节点的过半数是多少,所以能容忍几台故障。没有过半数时,写入和读取会怎样。raft_index_seen 要原样写下 endpoint status 的 RAFT INDEX——这个值不会减小。