拓展 Chaos Daemon 接口

新增混沌实验类型中,你实现了一种名为 HelloWorldChaos 的混沌实验,它的功能是在 Chaos Controller Manager 的日志中输出一行 “Hello world!”。为了让 HelloWorldChaos 真正有用,你还需要向 Chaos Daemon 添加接口,从而在目标 Pod 上注入一些故障。比方说,获取目标 Pod 中正在运行的进程信息。

注意" class="reference-link">拓展 Chaos Daemon 接口 - 图3注意

一些关于 Chaos Mesh 架构的知识对于帮助你理解这一文档非常有用,例如 Chaos Mesh 架构

本文档分为以下几部分:

选择器

回顾一下你在 api/v1alpha1/helloworldchaos_type.go 中定义的 HelloWorldSpec 这一结构,其中包括了一项 ContainerSelector

  1. // HelloWorldChaosSpec is the content of the specification for a HelloWorldChaos
  2. type HelloWorldChaosSpec struct {
  3. // ContainerSelector specifies target
  4. ContainerSelector `json:",inline"`
  5. // Duration represents the duration of the chaos action
  6. // +optional
  7. Duration *string `json:"duration,omitempty"`
  8. }
  9. ...
  10. // GetSelectorSpecs is a getter for selectors
  11. func (obj *HelloWorldChaos) GetSelectorSpecs() map[string]interface{} {
  12. return map[string]interface{}{
  13. ".": &obj.Spec.ContainerSelector,
  14. }
  15. }

在 Chaos Mesh 中,混沌实验通过选择器来定义试验范围。选择器可以限定目标的命名空间、注解、标签等。选择器也可以是一些更特殊的值(如 AWSChaos 中的 AWSSelector)。通常来说,每个混沌实验只需要一个选择器,但也有例外,比如 NetworkChaos 有时需要两个选择器作为网络分区的两个对象。

实现 gRPC 接口

为了让 Chaos Daemon 能接受 Chaos Controller Manager 的请求,需要给它们加上新的 gRPC 接口。

  1. pkg/chaosdaemon/pb/chaosdaemon.proto 中加上新的 RPC。

    ``` service chaosDaemon {

    1. ...
  1. rpc ExecHelloWorldChaos(ExecHelloWorldRequest) returns (google.protobuf.Empty) {}
  2. }
  3. message ExecHelloWorldRequest {
  4. string container_id = 1;
  5. }
  6. ```
  7. 更新了 proto 文件后,需要重新生成 Golang 代码。
  8. ```
  9. make proto
  10. ```
  1. 在 Chaos Daemon 中实现 gRPC 服务。

    pkg/chaosdaemon 目录下新建一个名为 helloworld_server.go 的文件,写入以下内容:

    ``` package chaosdaemon

  1. import (
  2. "context"
  3. "fmt"
  4. "github.com/golang/protobuf/ptypes/empty"
  5. "github.com/chaos-mesh/chaos-mesh/pkg/bpm"
  6. pb "github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/pb"
  7. )
  8. func (s *DaemonServer) ExecHelloWorldChaos(ctx context.Context, req *pb.ExecHelloWorldRequest) (*empty.Empty, error) {
  9. log.Info("ExecHelloWorldChaos", "request", req)
  10. pid, err := s.crClient.GetPidFromContainerID(ctx, req.ContainerId)
  11. if err != nil {
  12. return nil, err
  13. }
  14. cmd := bpm.DefaultProcessBuilder("sh", "-c", fmt.Sprintf("ps aux")).
  15. SetNS(pid, bpm.MountNS).
  16. SetContext(ctx).
  17. Build()
  18. out, err := cmd.Output()
  19. if err != nil {
  20. return nil, err
  21. }
  22. if len(out) != 0 {
  23. log.Info("cmd output", "output", string(out))
  24. }
  25. return &empty.Empty{}, nil
  26. }
  27. ```
  28. 在 `chaos-daemon` 收到 `ExecHelloWorldChaos` 请求后, 它会输出当前容器的进程列表.
  1. 在应用混沌实验中发送 gRPC 请求。

    每个混沌实验都有其生命周期,首先被应用,然后被恢复。有一些混沌实验无法被恢复(如 PodChaos 中的 PodKill,以及 HelloWorldChaos),我们称之为 OneShot 实验,你可以在 HelloWorldChaos 结构的定义上方找到一行 +chaos-mesh:oneshot=true

    Chaos Controller Manager 需要在应用 HelloWorldChaos 时给 Chaos Daemon 发送请求。为此,你需要对 controllers/chaosimpl/helloworldchaos/types.go 稍作修改。

    ``` package helloworldchaos

  1. import (
  2. "context"
  3. "github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
  4. "github.com/chaos-mesh/chaos-mesh/controllers/chaosimpl/utils"
  5. "github.com/chaos-mesh/chaos-mesh/controllers/common"
  6. "github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/pb"
  7. "github.com/go-logr/logr"
  8. "go.uber.org/fx"
  9. "sigs.k8s.io/controller-runtime/pkg/client"
  10. )
  11. type Impl struct {
  12. client.Client
  13. Log logr.Logger
  14. decoder *utils.ContianerRecordDecoder
  15. }
  16. // Apply applies KernelChaos
  17. func (impl *Impl) Apply(ctx context.Context, index int, records []*v1alpha1.Record, obj v1alpha1.InnerObject) (v1alpha1.Phase, error) {
  18. impl.Log.Info("Apply helloworld chaos")
  19. decodedContainer, err := impl.decoder.DecodeContainerRecord(ctx, records[index])
  20. if err != nil {
  21. return v1alpha1.NotInjected, err
  22. }
  23. pbClient := decodedContainer.PbClient
  24. containerId := decodedContainer.ContainerId
  25. _, err = pbClient.ExecHelloWorldChaos(ctx, &pb.ExecHelloWorldRequest{
  26. ContainerId: containerId,
  27. })
  28. if err != nil {
  29. return v1alpha1.NotInjected, err
  30. }
  31. return v1alpha1.Injected, nil
  32. }
  33. // Recover means the reconciler recovers the chaos action
  34. func (impl *Impl) Recover(ctx context.Context, index int, records []*v1alpha1.Record, obj v1alpha1.InnerObject) (v1alpha1.Phase, error) {
  35. impl.Log.Info("Recover helloworld chaos")
  36. return v1alpha1.NotInjected, nil
  37. }
  38. func NewImpl(c client.Client, log logr.Logger, decoder *utils.ContianerRecordDecoder) *common.ChaosImplPair {
  39. return &common.ChaosImplPair{
  40. Name: "helloworldchaos",
  41. Object: &v1alpha1.HelloWorldChaos{},
  42. Impl: &Impl{
  43. Client: c,
  44. Log: log.WithName("helloworldchaos"),
  45. decoder: decoder,
  46. },
  47. ObjectList: &v1alpha1.HelloWorldChaosList{},
  48. }
  49. }
  50. var Module = fx.Provide(
  51. fx.Annotated{
  52. Group: "impl",
  53. Target: NewImpl,
  54. },
  55. )
  56. ```
  57. ##### ![](/projects/chaos-mesh-2.0.4-zh/2914d7dac54ccf701b6a234575fdb587.svg)注意
  58. 在 HelloWorldChaos 中,恢复过程什么都没有做。这是因为 HelloWorldChaos 是一个 OneShot 实验。如果你的新实验需要恢复,你应该在其中实现相关逻辑。

验证实验效果

要验证实验效果,请进行以下操作:

  1. 重新编译 Docker 镜像,并推送到本地 Registry 上,然后加载进 kind(如果你使用 kind):

    1. make image
    2. make docker-push
    3. kind load docker-image localhost:5000/pingcap/chaos-mesh:latest
    4. kind load docker-image localhost:5000/pingcap/chaos-daemon:latest
    5. kind load docker-image localhost:5000/pingcap/chaos-dashboard:latest
  2. 更新 Chaos Mesh:

    1. helm upgrade chaos-mesh helm/chaos-mesh --namespace=chaos-testing
  3. 部署用于测试的目标 Pod(如果你已经在之前部署了这个 Pod,请跳过这一步):

    1. kubectl apply -f https://raw.githubusercontent.com/chaos-mesh/apps/master/ping/busybox-statefulset.yaml
  4. 新建一个 YAML 文件,写入:

    1. apiVersion: chaos-mesh.org/v1alpha1
    2. kind: HelloWorldChaos
    3. metadata:
    4. name: busybox-helloworld-chaos
    5. spec:
    6. selector:
    7. namespaces:
    8. - busybox
    9. mode: all
    10. duration: 1h
  5. 应用混沌实验:

    1. kubectl apply -f /path/to/helloworld.yaml
  6. 验证实验结果。有几份日志需要确认:

    • 查看 Chaos Controller Manager 的日志:

      1. kubectl logs chaos-controller-manager-{pod-post-fix} -n chaos-testing

      查找以下内容:

      1. 2021-06-25T06:02:12.754Z INFO records apply chaos {"id": "busybox/busybox-1/busybox"}
      2. 2021-06-25T06:02:12.754Z INFO helloworldchaos Apply helloworld chaos
    • 查看 Chaos Daemon 的日志:

      1. kubectl logs chaos-daemon-{pod-post-fix} -n chaos-testing

      查找以下内容:

      1. 2021-06-25T06:25:13.048Z INFO chaos-daemon-server ExecHelloWorldChaos {"request": "container_id:\"containerd://af1b99df3513c49c4cab4f12e468ed1d7a274fe53722bd883256d8f65bc9f681\""}
      2. 2021-06-25T06:25:13.050Z INFO background-process-manager build command {"command": "/usr/local/bin/nsexec -m /proc/243383/ns/mnt -- sh -c ps aux"}
      3. 2021-06-25T06:25:13.056Z INFO chaos-daemon-server cmd output {"output": "PID USER TIME COMMAND\n 1 root 0:00 sleep 3600\n"}
      4. 2021-06-25T06:25:13.070Z INFO chaos-daemon-server ExecHelloWorldChaos {"request": "container_id:\"containerd://88f6a469e5da82b48dc1190de07a2641b793df1f4e543a5958e448119d1bec11\""}
      5. 2021-06-25T06:25:13.072Z INFO background-process-manager build command {"command": "/usr/local/bin/nsexec -m /proc/243479/ns/mnt -- sh -c ps aux"}
      6. 2021-06-25T06:25:13.076Z INFO chaos-daemon-server cmd output {"output": "PID USER TIME COMMAND\n 1 root 0:00 sleep 3600\n"}

      可以看到两条 ps aux,对应两个不同的 Pod。

      注意" class="reference-link">拓展 Chaos Daemon 接口 - 图4注意

      如果你的集群有多个节点,你会发现有不止一个 Chaos Daemon Pod。试着查看每一个 Chaos Daemon Pod 的日志,寻找真正被调用的那一个。

探索更多

在完成上述步骤后,HelloWorldChaos 已经成为一种有实际作用的混沌实验。如果你在这一过程中遇到了问题,请在 GitHub 创建一个 issue 向 Chaos Mesh 团队反馈。

你可能很好奇这一切是如何生效的。可以试着看看 controllers 目录下的各类 controller,它们有自己的 README(如 controllers/common/README.md)。你可以通过这些 README 了解每个 controller 的功能,也可以阅读 Chaos Mesh 架构了解 Chaos Mesh 背后的原理。

你已经准备好成为一名真正的 Chaos Mesh 开发者了!到 Chaos Mesh 里找一找练手的任务吧!推荐你先从简单的入手,例如这些 good first issues