Chaos Mesh® 是由 TiDB 背后的 PingCAP 公司开发,运行在 Kubernetes 上的混沌工程(Chaos Engineering)系统。简而言之,Chaos Mesh® 通过运行在 K8s 集群中的“特权”容器,依据 CRD 资源中的测试场景,在集群中制造浑沌(模拟故障)[1]。
本文探索混沌工程在 Kubernetes 集群上的实践,基于源码分析了解 Chaos Mesh® 的工作原理,以代码示例阐述如何开发 Chaos Mesh® 的控制平面。如果你缺乏基础知识,要想对 Chaos Mesh® 的架构有宏观上的认识,请参阅文末尾注中的链接。
本文试验代码位于 mayocream/chaos-mesh-controlpanel-demo 仓库。
如何制造混沌
Chaos Mesh® 是在 Kubernetes 上实施混沌工程的利器,那它是如何工作的呢?
特权模式
上面提到 Chaos Mesh® 运行 Kubernetes 特权容器来制造故障。Daemon Set 方式运行的 Pod 授权了容器运行时的权能字(Capabilities)。
apiVersion:apps/v1kind:DaemonSetspec: template: metadata:... spec: containers: -name:chaos-daemon securityContext: {
{-if.Values.chaosDaemon.privileged}} privileged:true capabilities: add: -SYS_PTRACE {
{-else}} capabilities: add: -SYS_PTRACE -NET_ADMIN -MKNOD -SYS_CHROOT -SYS_ADMIN -KILL # CAP_IPC_LOCK is used to lock memory -IPC_LOCK {
{-end}}
这些 Linux 权能字用于授予容器特权,以创建和访问 /dev/fuse FUSE 管道[2](FUSE 是 Linux 用户空间文件系统接口,它使无特权的用户能够无需编辑内核代码而创建自己的文件系统)。
参阅 #1109 Pull Request,Daemon Set 程序使用 CGO 调用 Linux makedev 函数创建 FUSE 管道。
// #include <sys/sysmacros.h>// #include <sys/types.h>// // makedev is a macro, so a wrapper is needed// dev_t Makedev(unsigned int maj, unsigned int min) {// return makedev(maj, min);// }// EnsureFuseDev ensures /dev/fuse exists. If not, it will create onefunc EnsureFuseDev() { if _, err := os.Open("/dev/fuse"); os.IsNotExist(err) { // 10, 229 according to https://www.kernel.org/doc/Documentation/admin-guide/devices.txt fuse := C.Makedev(10, 229) syscall.Mknod("/dev/fuse", 0o666|syscall.S_IFCHR, int(fuse)) }}
同时在 #1103 PR 中,Chaos Daemon 默认启用特权模式,即容器的 securityContext 中设置 privileged: true。
杀死 Pod
PodKill、PodFailure、ContainerKill 都归属于 PodChaos 类别下,PodKill 是随机杀死 Pod。
PodKill 的具体实现其实是通过调用 API Server 发送 Kill 命令。
import ( "context" v1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client")type Impl struct { client.Client}func (impl *Impl) Apply(ctx context.Context, index int, records []*v1alpha1.Record, obj v1alpha1.InnerObject) (v1alpha1.Phase, error) { ... err = impl.Get(ctx, namespacedName, &pod) if err != nil { // TODO: handle this error return v1alpha1.NotInjected, err } err = impl.Delete(ctx, &pod, &client.DeleteOptions{ GracePeriodSeconds: &podchaos.Spec.GracePeriod, // PeriodSeconds has to be set specifically }) ... return v1alpha1.Injected, nil}
GracePeriodSeconds 参数适用于 K8s 强制终止 Pod。例如在需要快速删除 Pod 时,我们使用 kubectl delete pod --grace-period=0 --force 命令。
PodFailure 是通过 Patch Pod 对象资源,用错误的镜像替换 Pod 中的镜像。Chaos 只修改了 containers 和 initContainers 的 image 字段,这也是因为 Pod 大部分字段是无法更改的,详情可以参阅 Pod 更新与替换。
func (impl *Impl) Apply(ctx context.Context, index int, records []*v1alpha1.Record, obj v1alpha1.InnerObject) (v1alpha1.Phase, error) { ... pod := origin.DeepCopy() for index := range pod.Spec.Containers { originImage := pod.Spec.Containers[index].Image name := pod.Spec.Containers[index].Name key := annotation.GenKeyForImage(podchaos, name, false) if pod.Annotations == nil { pod.Annotations = make(map[string]string) } // If the annotation is already existed, we could skip the reconcile for this container if _, ok := pod.Annotations[key]; ok { continue } pod.Annotations[key] = originImage pod.Spec.Containers[index].Image = config.ControllerCfg.PodFailurePauseImage } for index := range pod.Spec.InitContainers { originImage := pod.Spec.InitContainers[index].Image name := pod.Spec.InitContainers[index].Name key := annotation.GenKeyForImage(podchaos, name, true) if pod.Annotations == nil { pod.Annotations = make(map[string]string) } // If the annotation is already existed, we could skip the reconcile for this container if _, ok := pod.Annotations[key]; ok { continue } pod.Annotations[key] = originImage pod.Spec.InitContainers[index].Image = config.ControllerCfg.PodFailurePauseImage } err = impl.Patch(ctx, pod, client.MergeFrom(&origin)) if err != nil { // TODO: handle this error return v1alpha1.NotInjected, err } return v1alpha1.Injected, nil}
默认用于引发故障的容器镜像是 gcr.io/google-containers/pause:latest, 如果在国内环境使用,大概率会水土不服,可以将 gcr.io 替换为 registry.aliyuncs.com。
ContainerKill 不同于 PodKill 和 PodFailure,后两个都是通过 K8s API Server 控制 Pod 生命周期,而 ContainerKill 是通过运行在集群 Node 上的 Chaos Daemon 程序操作完成。具体来说,ContainerKill 通过 Chaos Controller Manager 运行客户端向 Chaos Daemon 发起 grpc 调用。
func (b *ChaosDaemonClientBuilder) Build(ctx context.Context, pod *v1.Pod) (chaosdaemoncl

本文详细介绍了ChaosMesh的工作原理,包括特权模式下的 DaemonSet 容器、Pod 和 Container 的混沌注入、网络故障模拟、压力测试以及IO故障注入。ChaosMesh通过Kubernetes API 下发混沌实验配置,由ChaosControllerManager进行规则校验和下发,ChaosDaemon在节点上执行实际混沌操作。此外,文章还探讨了如何构建一个面向用户的混沌工程平台,并给出了代码示例。
最低0.47元/天 解锁文章
871

被折叠的 条评论
为什么被折叠?



