在 Kubernetes 实施混沌工程—— Chaos Mesh® 原理分析与控制面开发

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

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
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值