拓展 Chaos Daemon 接口
在新增混沌实验类型中,你实现了一种名为 HelloWorldChaos 的混沌实验,它的功能是在 Chaos Controller Manager 的日志中输出一行 "Hello world!"。为了让 HelloWorldChaos 真正有用,你还需要向 Chaos Daemon 添加接口,从而在目标 Pod 上注入一些故障。比方说,获取目标 Pod 中正在运行的进程信息。
一些关于 Chaos Mesh 架构的知识对于帮助你理解这一文档非常有用,例如 Chaos Mesh 架构。
本文档分为以下几部分:
选择器
回顾一下你在 api/v1alpha1/helloworldchaos_type.go
中定义的 HelloWorldSpec
这一结构,其中包括了一项 ContainerSelector
。
// HelloWorldChaosSpec is the content of the specification for a HelloWorldChaos
type HelloWorldChaosSpec struct {
// ContainerSelector specifies target
ContainerSelector `json:",inline"`
// Duration represents the duration of the chaos action
// +optional
Duration *string `json:"duration,omitempty"`
}
...
// GetSelectorSpecs is a getter for selectors
func (obj *HelloWorldChaos) GetSelectorSpecs() map[string]interface{} {
return map[string]interface{}{
".": &obj.Spec.ContainerSelector,
}
}
在 Chaos Mesh 中,混沌实验通过选择器来定义试验范围。选择器可以限定目标的命名空间、注解、标签等。选择器也可以是一些更特殊的值(如 AWSChaos 中的 AWSSelector)。通常来说,每个混沌实验只需要一个选择器,但也有例外,比如 NetworkChaos 有时需要两个选择器作为网络分区的两个对象。
实现 gRPC 接口
为了让 Chaos Daemon 能接受 Chaos Controller Manager 的请求,需要给它们加上新的 gRPC 接口。
-
在
pkg/chaosdaemon/pb/chaosdaemon.proto
中加上新的 RPC。service chaosDaemon {
...
rpc ExecHelloWorldChaos(ExecHelloWorldRequest) returns (google.protobuf.Empty) {}
}
message ExecHelloWorldRequest {
string container_id = 1;
}更新了 proto 文件后,需要重新生成 Golang 代码。
make proto
-
在 Chaos Daemon 中实现 gRPC 服务。
在
pkg/chaosdaemon
目录下新建一个名为helloworld_server.go
的文件,写入以下内容:package chaosdaemon
import (
"context"
"fmt"
"github.com/golang/protobuf/ptypes/empty"
"github.com/chaos-mesh/chaos-mesh/pkg/bpm"
"github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/pb"
)
func (s *DaemonServer) ExecHelloWorldChaos(ctx context.Context, req *pb.ExecHelloWorldRequest) (*empty.Empty, error) {
log := s.getLoggerFromContext(ctx)
log.Info("ExecHelloWorldChaos", "request", req)
pid, err := s.crClient.GetPidFromContainerID(ctx, req.ContainerId)
if err != nil {
return nil, err
}
cmd := bpm.DefaultProcessBuilder("sh", "-c", fmt.Sprintf("ps aux")).
SetNS(pid, bpm.MountNS).
SetContext(ctx).
Build(ctx)
out, err := cmd.Output()
if err != nil {
return nil, err
}
if len(out) != 0 {
log.Info("cmd output", "output", string(out))
}
return &empty.Empty{}, nil
}在
chaos-daemon
收到ExecHelloWorldChaos
请求后, 它会输出当前容器的进程列表. -
在应用混沌实验中发送 gRPC 请求。
每个混沌实验都有其生命周期,首先被应用,然后被恢复。有一些混沌实验无法被恢复(如 PodChaos 中的 PodKill,以及 HelloWorldChaos),我们称之为 OneShot 实验,你可以在 HelloWorldChaos 结构的定义上方找到一行
+chaos-mesh:oneshot=true
。Chaos Controller Manager 需要在应用 HelloWorldChaos 时给 Chaos Daemon 发送请求。为此,你需要对
controllers/chaosimpl/helloworldchaos/types.go
稍作修改。package helloworldchaos
import (
"context"
"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
impltypes "github.com/chaos-mesh/chaos-mesh/controllers/chaosimpl/types"
"github.com/chaos-mesh/chaos-mesh/controllers/chaosimpl/utils"
"github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/pb"
"github.com/go-logr/logr"
"go.uber.org/fx"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type Impl struct {
client.Client
Log logr.Logger
decoder *utils.ContainerRecordDecoder
}
// Apply applies KernelChaos
func (impl *Impl) Apply(ctx context.Context, index int, records []*v1alpha1.Record, obj v1alpha1.InnerObject) (v1alpha1.Phase, error) {
impl.Log.Info("Apply helloworld chaos")
decodedContainer, err := impl.decoder.DecodeContainerRecord(ctx, records[index], obj)
if err != nil {
return v1alpha1.NotInjected, err
}
pbClient := decodedContainer.PbClient
containerId := decodedContainer.ContainerId
_, err = pbClient.ExecHelloWorldChaos(ctx, &pb.ExecHelloWorldRequest{
ContainerId: containerId,
})
if err != nil {
return v1alpha1.NotInjected, err
}
return v1alpha1.Injected, nil
}
// Recover means the reconciler recovers the chaos action
func (impl *Impl) Recover(ctx context.Context, index int, records []*v1alpha1.Record, obj v1alpha1.InnerObject) (v1alpha1.Phase, error) {
mpl.Log.Info("Recover helloworld chaos")
return v1alpha1.NotInjected, nil
}
func NewImpl(c client.Client, log logr.Logger, decoder *utils.ContainerRecordDecoder) *impltypes.ChaosImplPair {
return &impltypes.ChaosImplPair{
Name: "helloworldchaos",
Object: &v1alpha1.HelloWorldChaos{},
Impl: &Impl{
Client: c,
Log: log.WithName("helloworldchaos"),
decoder: decoder,
},
ObjectList: &v1alpha1.HelloWorldChaosList{},
}
}
var Module = fx.Provide(
fx.Annotated{
Group: "impl",
Target: NewImpl,
},
)