破坏实验室 — 破坏五次,修好一次
目标
在临时 VM 内真正的 k3s 上进行五次混沌实验。先测出正常基线,在破坏之前一次性写下五个实验的假设,接着杀掉 Pod、限制 CPU、耗尽内存,然后把所学反映到设计中,再次承受同样的攻击。这是一个 65 分钟的实验,面向了解 kubectl 基本查询和 Deployment 结构的中级学习者。
为什么重要
注入故障本身只是一行命令。难的是让这个实验产出下周仍然留得下来的东西。所以本实验的产出不是故障,而是笔记。假设在实验之前写,测量在实验窗口内进行,结论则写成两者对照的结果。预期落空并不是失败——被证伪的假设恰恰是这个实验能带来的最昂贵的信息,把它记为 refuted 才是正确答案。
判定表(结论中要用的名称)
为了避免每个人对同一个数字有不同的解读,先约定好命名规则。
- 可用性:成功率在 0.98 以上为
ok,在 0.5 以上为degraded,低于 0.5 为down - 延迟:成功请求的延迟样本少于 5 个时为
unmeasured,p95 达到基线 p95 的 2 倍以上为slower,否则为same - 判定:假设中的两个值与观测到的两个值都相同时为
confirmed,只要有一个不同就是refuted
测量工具
python3 /opt/fixtures/chaos_lab.py 只做三件事。它既不替你制造故障,也不替你修复。
load <초> <원시.json>(占位符依次为秒数、原始结果 JSON 文件)——通过 Service 发送两路负载。可用性请求以每秒 10 次发往静态路径/,延迟请求以每秒 2 次发往会消耗 CPU 的/cgi-bin/work。让它在后台运行,并在这个窗口内制造故障。record <국면> <원시.json> <기록.json>(占位符依次为阶段、原始结果 JSON 文件、记录 JSON 文件)——把负载结果与当时的集群状态一并保存。如果所请求阶段的结构条件实际上并不成立,就会拒绝记录。grade——由评分器使用。
安全范围
只使用临时 VM 内的 API https://127.0.0.1:6443 和 labhub-chaos 命名空间。绝对不要在生产集群或他人的环境中运行。为了只保留一个原因,所提供的应用使用 Recreate 策略——这并不是“不中断运维”的推荐值。应用镜像已按摘要(digest)固定。
本实验安排为 65 分钟,比默认会话(60 分钟)更长。开始时先延长会话时间。会话结束后,VM 和记录文件都会消失,无法恢复。记录文件是可以编辑的实验笔记,而不是无法伪造的证据。
步骤
- 应用
/opt/fixtures/chaos-lab-app.json,等待滚动更新完成,然后在不触碰任何东西的状态下施加 20 秒负载,记录基线。用python3 /opt/fixtures/chaos_lab.py load 20 /root/chaos-lab/raw-01.json测量,并用python3 /opt/fixtures/chaos_lab.py record steady /root/chaos-lab/raw-01.json /root/chaos-lab/01-steady.json保存。 - 从基线文件中读取成功率和 p95,在
steady_state中写下稳态,并在/root/chaos-lab/hypothesis.json中写下五个实验(kill-one、kill-ha、cpu-squeeze、mem-squeeze、hardened)的假设。每个实验需要四项:availability、latency、为什么这样预期(why)、何时停止(abort_if)。availability_ratio_min取不低于 0.95 且不高于基线测量值,p95_ms_max取不低于基线 p95 且不超过其 3 倍。 - 在副本数为 1 的状态下,让负载在后台运行,并在测量窗口内删除那唯一的 Pod。窗口关闭后用
python3 /opt/fixtures/chaos_lab.py record kill-one /root/chaos-lab/raw-03.json /root/chaos-lab/03-kill-one.json保存。资源限制和 preStop 保持基线状态不变。 - 把副本增加到 3,等三个都变为 Ready 之后,用同样的方式在负载窗口内只删除一个 Pod。用
python3 /opt/fixtures/chaos_lab.py record kill-ha /root/chaos-lab/raw-04.json /root/chaos-lab/04-kill-ha.json保存。除了副本数,其他什么都不要改。 - 只把 shop 容器的 cpu 限制降到
100m(memory 保持96Mi,副本数仍为 3)。在滚动更新结束、三个都变为 Ready 之后,施加 20 秒负载,并用python3 /opt/fixtures/chaos_lab.py record cpu-squeeze /root/chaos-lab/raw-05.json /root/chaos-lab/05-cpu-squeeze.json保存。 - 把 cpu 限制恢复为
500m,同时把 memory 限制降到40Mi。应用启动时会一次性占用 48MiB,因此容器会因 OOM 被杀死。捕捉到重启后,施加 20 秒负载,并用python3 /opt/fixtures/chaos_lab.py record mem-squeeze /root/chaos-lab/raw-06.json /root/chaos-lab/06-mem-squeeze.json保存。 - 把资源限制恢复为正常值(cpu
500m、memory96Mi),这次再加上终止处理。用preStop钩子争取至少 3 秒,并把terminationGracePeriodSeconds提高到 10 以上。滚动更新结束后,再次发起与第 3 步、第 4 步相同的攻击,并用python3 /opt/fixtures/chaos_lab.py record hardened /root/chaos-lab/raw-07.json /root/chaos-lab/07-hardened.json保存。 - 按照说明中的判定表给五份记录中的数字命名,写入
/root/chaos-lab/conclusion.json。每个实验需要observed_availability、observed_latency、与假设对照得出的verdict(confirmed 或 refuted),以及据此要改变什么(action)。最后的评分会一并查看之前的记录和当前的集群——改进后的设计必须处于运行状态。
参考
- 用
kubectl -n labhub-chaos get pods,svc,endpointslices -o wide分别查看对象及其状态。 kubectl -n labhub-chaos get rs是过去部署留下的痕迹。最后的评分会检查它。- 用
kubectl -n labhub-chaos describe pod <이름>(占位符为 Pod 名称)确认终止原因和退出码。 - 让负载在后台运行(
&),制造故障后用wait等待窗口关闭,然后再记录。 - 如果需要重新测量基线,可以回到前面的步骤。但不要修改
hypothesis.json。 - 限制值除了
limits,还要看它与requests的关系。限制小于请求量时会被拒绝。
在破坏之前,把“正常”记成数字
应用 /opt/fixtures/chaos-lab-app.json,等待滚动更新完成,然后在不触碰任何东西的状态下施加 20 秒负载,记录基线。用 python3 /opt/fixtures/chaos_lab.py load 20 /root/chaos-lab/raw-01.json 测量,并用 python3 /opt/fixtures/chaos_lab.py record steady /root/chaos-lab/raw-01.json /root/chaos-lab/01-steady.json 保存。
Running、Ready 与实际请求成功是不同的事实。等到 Ready 的端点变为 1 个之后再测量。如果测量窗口内出现了新的 Pod,就不会被认定为基线。
先写下五个假设
从基线文件中读取成功率和 p95,在 steady_state 中写下稳态,并在 /root/chaos-lab/hypothesis.json 中写下五个实验(kill-one、kill-ha、cpu-squeeze、mem-squeeze、hardened)的假设。每个实验需要四项:availability、latency、为什么这样预期(why)、何时停止(abort_if)。availability_ratio_min 取不低于 0.95 且不高于基线测量值,p95_ms_max 取不低于基线 p95 且不超过其 3 倍。
预期不必一定正确。把错误的预期事后记为 refuted,正是本实验的正确答案。标签的含义见说明中的判定表。从这里开始不要再修改这个文件——最后的评分会检查它是否先于第一份实验记录写成。
杀掉只有一个副本的服务
在副本数为 1 的状态下,让负载在后台运行,并在测量窗口内删除那唯一的 Pod。窗口关闭后用 python3 /opt/fixtures/chaos_lab.py record kill-one /root/chaos-lab/raw-03.json /root/chaos-lab/03-kill-one.json 保存。资源限制和 preStop 保持基线状态不变。
要先启动负载,过几秒再杀掉 Pod,影响才会落在窗口之内。如果杀掉之后才开始测量,测到的就是替代 Pod 已经就绪之后的数字。窗口关闭时 Pod 必须重新变为 Ready,才会被记录。
把副本增加到三个,重复同样的攻击
把副本增加到 3,等三个都变为 Ready 之后,用同样的方式在负载窗口内只删除一个 Pod。用 python3 /opt/fixtures/chaos_lab.py record kill-ha /root/chaos-lab/raw-04.json /root/chaos-lab/04-kill-ha.json 保存。除了副本数,其他什么都不要改。
每次只改变一个变量,才能解释结果。确认三个全部 Ready 之后再测量。即使成功率没有恢复到基线水平,也照实记录即可——那就是这个实验的结果。
限制 CPU——不会被杀死,而是变慢的故障
只把 shop 容器的 cpu 限制降到 100m(memory 保持 96Mi,副本数仍为 3)。在滚动更新结束、三个都变为 Ready 之后,施加 20 秒负载,并用 python3 /opt/fixtures/chaos_lab.py record cpu-squeeze /root/chaos-lab/raw-05.json /root/chaos-lab/05-cpu-squeeze.json 保存。
资源限制在 Pod 模板中,所以修改后会启动新的 Pod。如果在滚动更新途中测量,部署的影响和限制的影响会混在一起。也要记住,限制不能小于请求量。
耗尽内存——这次会被杀死
把 cpu 限制恢复为 500m,同时把 memory 限制降到 40Mi。应用启动时会一次性占用 48MiB,因此容器会因 OOM 被杀死。捕捉到重启后,施加 20 秒负载,并用 python3 /opt/fixtures/chaos_lab.py record mem-squeeze /root/chaos-lab/raw-06.json /root/chaos-lab/06-mem-squeeze.json 保存。
这次滚动更新不会结束。不要等待 rollout status,而要查看重启次数或终止原因。退出码和原因与 CPU 那次有何不同,正是这个实验的关键。
把所学反映到设计中,再次承受同样的攻击
把资源限制恢复为正常值(cpu 500m、memory 96Mi),这次再加上终止处理。用 preStop 钩子争取至少 3 秒,并把 terminationGracePeriodSeconds 提高到 10 以上。滚动更新结束后,再次发起与第 3 步、第 4 步相同的攻击,并用 python3 /opt/fixtures/chaos_lab.py record hardened /root/chaos-lab/raw-07.json /root/chaos-lab/07-hardened.json 保存。
preStop 先于 TERM 信号执行,钩子结束后信号才会发出。在此期间,端点移除得以传播开。宽限期的倒计时在钩子之前就已开始,所以宽限期必须比钩子时间更宽裕。删除探针,或者再增加副本来让数字变好看,都是在规避这个实验的约定。
对照假设与测量,合上实验笔记
按照说明中的判定表给五份记录中的数字命名,写入 /root/chaos-lab/conclusion.json。每个实验需要 observed_availability、observed_latency、与假设对照得出的 verdict(confirmed 或 refuted),以及据此要改变什么(action)。最后的评分会一并查看之前的记录和当前的集群——改进后的设计必须处于运行状态。
被证伪的假设不是失败。与预期不同时,记为 refuted 才是正确的答案。如果只把数字写得像模像样,名称却随便乱写,评分器会按同样的规则重新计算并将其筛掉。五个 action 必须各不相同。