混沌工程之 ChaosBlade 故障注入百寶箱

前言

上文中對 ChaosBlade 進行了整體介紹,其中主要分爲 ChaosBlade Box 以及 ChaosBlade Tool 兩大部分,ChaosBlade Box 提供了可視化的管理控制檯。真正的故障注入能力是由 ChaosBlade Tool 提供的。 本文將重點介紹 ChaosBlade Tool 的架構以及實現細節。

ChaosBlade Tool

ChaosBlade Tool 是一個故障注入工具箱,它本身並不是一個實際項目/工程,它是由一個 CLI 項目和多個故障執行器組成,ChasoBlade Tool 架構如下:

  1. ChaosBlade Cli

    ChaosBlade Cli 項目:主要負責故障指令生成,解析故障注入指令,故障指令分發,以及記錄故障的狀態

  2. ChaosBlade OS

    ChaosBlade OS 執行器:主要負責基礎資源類的故障注入,例如 CPU,內存,網絡,磁盤等

  3. ChaosBlade JVM

    ChaosBlade JVM 執行器:主要負責 Java 應用的故障注入,例如 Dubbo,Http,JVM 等等

  4. ChaosBlade Docker

    ChaosBlade Docker 執行器:主要負責針對 Docker 環境下的故障注入,例如針對某個 Docker 容器進行基礎資源故障,刪除指定 Docker 容器等

  5. ChaosBlade K8S

    ChaosBlade K8S 執行器:主要負責針對 K8S 環境下的 Pod,Node,Container 進行故障注入,支持基礎資源故障,刪除 Pod 等。K8S 的執行器和其他執行器稍有不同,是在 Cli 項目中直接調用 K8S 集羣創建 CRD,通過 Operator 來實現的故障注入。這裏先大概瞭解,後面會在 K8S 故障注入源碼分析中詳細介紹

  6. ChaosBlade CRI

    ChaosBlade CRI 執行器:主要負責對於容器運行插件(兼容 CRI)的容器進行故障注入,容器運行時插件(Container Runtime Interface,簡稱 CRI)是 Kubernetes v1.5 引入的容器運行時接口,它將 Kubelet 與容器運行時解耦,例如 Docker、Containerd 等容器運行時都是 CRI 的實現,後續社區計劃將 ChaosBlade Docker 執行器廢棄,改用 ChaosBlade CRI 執行器

  7. ChaosBlade C++

    ChaosBlade C++ 執行器:主要負責對 C++服務進行故障注入,利用 GDB 實現。提供修改返回值,變量值,調用延遲等故障注入能力

  8. ChaosBlade Middleware

    ChaosBlade Middleware 執行器: 主要負責對中間件進行故障注入,目前支持 nginx 的重啓,銷燬,配置變更,修改返回值等

  9. ChaosBlade Cloud

    ChaosBlade Cloud 執行器:主要負責對各種雲平臺進行故障注入,例如阿里雲的 ECS 的上線/下線/重啓、公網 IP 的解綁、安全組的修改等等。

ChaosBlade Cli

介紹

Cli 是一個獨立的項目,打包編譯後生成二進制可執行程序,它主要負責故障指令生成,解析故障注入指令,故障指令分發,以及記錄故障的狀態。用戶可以直接運行它進行各種場景的故障注入以及故障恢復等操作。

下面我將通過實際使用案例對 CLI 項目進行詳細介紹。

故障注入命令

我們可以在 Release 頁面下載最新版本的 ChaosBlade,解壓後的結構如下:

下面以創建 "指定隨機兩個核滿載" 實驗爲例。直接運行 blade 二進制文件,執行後會返回執行結果以及當前故障的唯一 id。

./blade create cpu load --cpu-count 2
{"code":200,"success":true,"result":"d05c13c7101933c7"}


運行成功後在當前機器上將會啓動一個 chaos_burncpu 的進程,將佔用機器的 2 核 CPU。

其他場景的故障注入命令,可以參考官網說明,具體的故障場景能力以及實現細節,將在後續的執行器篇詳細介紹。

故障模型

通過上面的使用樣例,我們來聊一下故障模型,在上面的 CPU 滿載的執行命令中,可以拆分爲四部分。

  1. 對什麼做混沌實驗(CPU)

  2. 混沌實驗實施的範圍是是什麼(默認當前機器 Host)

  3. 具體實施什麼實驗(滿載 Load)

  4. 實驗生效的匹配條件有哪些(僅對其中兩核 CPU 滿載,cpu-list 2)

Chaosblade 通過以上四個部分將,將故障注入抽象出以下模型:

  • Target:實驗靶點,指實驗發生的組件,例如 容器、應用框架(Dubbo、Redis、Zookeeper)等。

  • Scope:實驗實施的範圍,指具體觸發實驗的機器或者集羣等。

  • Matcher:實驗規則匹配器,根據所配置的 Target,定義相關的實驗匹配規則,可以配置多個。由於每個 Target 可能有各自特殊的匹配條件,比如 RPC 領域的 HSF、Dubbo,可以根據服務提供者提供的服務和服務消費者調用的服務進行匹配,緩存領域的 Redis,可以根據 set、get 操作進行匹配。

  • Action:指實驗模擬的具體場景,Target 不同,實施的場景也不一樣,比如磁盤,可以演練磁盤滿,磁盤 IO 讀寫高,磁盤硬件故障等。如果是應用,可以抽象出延遲、異常、返回指定值(錯誤碼、大對象等)、參數篡改、重複調用等實驗場景。

CLI 指令

目前在 CLi 項目中支持多種操作指令,可以通過如下命令進行查看(對於這些 CLI 指令,如何生成的將在源碼分析中介紹)

./blade help


An easy to use and powerful chaos engineering experiment toolkit


Usage:
  blade [command]


Available Commands:
  check       Check the environment for chaosblade
  create      Create a chaos engineering experiment
  destroy     Destroy a chaos experiment
  help        Help about any command
  prepare     Prepare to experiment
  query       Query the parameter values required for chaos experiments
  revoke      Undo chaos engineering experiment preparation
  server      Server mode starts, exposes web services
  status      Query preparation stage or experiment status
  version     Print version info


Flags:
  -d, --debug   Set client to DEBUG mode
  -h, --help    help for blade


Use "blade [command] --help" for more information about a command.




故障狀態

可以通過如下命令,查詢某一次故障注入的執行狀態

./blade status 726c10d2bbffdbf7


{
        "code": 200,
        "success": true,
        "result": {
                "Uid": "726c10d2bbffdbf7",
                "Command": "cpu",
                "SubCommand": "fullload",
                "Flag": " --cpu-count=2",
                "Status": "Success",
                "Error": "",
                "CreateTime": "2023-02-18T13:07:18.801277194+08:00",
                "UpdateTime": "2023-02-18T13:07:19.921206569+08:00"
        }
}


也可以查詢某中類型指令的故障執行狀態

./blade status --type create


指令的執行結果狀態一共分爲如下幾種:

  1. Created:故障注入

  2. Success:故障注入命令執行成功

  3. Running:給 Java 和 C++故障場景使用的,當 java agent 掛載成功後,狀態變爲 Running。

  4. Error:故障注入命令執行失敗

  5. Destroyed:銷燬故障

  6. Revoked:撤銷 Java 和 C++故障注入的前置工作,例如將 Java agent 卸載。

ChaosBlade Cli 源碼分析

初始化數據庫

Chaosblade 使用SQLite記錄執行故障模型數據,例如狀態,錯誤信息等。在執行銷燬和查詢狀態等命令時可以從SQLite中直接獲取已存儲的數據。(SQLite是一個進程內的庫,實現了自給自足的、無服務器的、零配置的、事務性的 SQL 數據庫。)

func (s *Source) init() {
   s.CheckAndInitExperimentTable()
   s.CheckAndInitPreTable()
}


func getConnection() *sql.DB {
   database, err := sql.Open("sqlite3", path.Join(util.GetProgramPath(), dataFile))
   if err != nil {
      log.Fatalf(context.Background(), "open data file err, %s", err.Error())
      //log.Error(err, "open data file err")
      //os.Exit(1)
   }
   return database
}


指令生成

在CLi項目啓動時,會利用開源項目Cobra(Cobra是一個用於創建強大的現代CLI應用程序的庫)將項目中的yaml文件夾下的各個執行器中spec.yaml轉換成Cobra Cli指令。

關鍵代碼如下:

啓動時運行CmdInit()構建CLi動作指令,例如Create Command

func CmdInit() *baseCommand {
   cli := NewCli()
   baseCmd := &baseCommand{
      command: cli.rootCmd,
   }
   // 省略......
   // add revoke command
   baseCmd.AddCommand(&RevokeCommand{})
   // add create command
   createCommand := &CreateCommand{}
   baseCmd.AddCommand(createCommand)
   // 省略......
   return baseCmd
}


然後對Create指令添加Sub Command,主要就是添加各個故障場景的Command, 例如基礎資源中的cpu、mem,Java中的dubbo、jvm,K8S等等

func newBaseExpCommandService(actionService actionCommandService) *baseExpCommandService {
   service := &baseExpCommandService{
      commands:           make(map[string]*modelCommand, 0),
      executors:          make(map[string]spec.Executor, 0),
      bindFlagsFunc:      actionService.bindFlagsFunction(),
      actionRunEFunc:     actionService.actionRunEFunc,
      actionPostRunEFunc: actionService.actionPostRunEFunc,
   }
   // 註冊SubCommands
   service.registerSubCommands()
   for _, command := range service.commands {
      // 添加到Create Command下
      actionService.CobraCmd().AddCommand(command.CobraCmd())
   }
   return service
}


以註冊OS SubCommands舉例子,

  1. 首先將會解析os-spec.yaml(文件中是各種基礎資源類故障場景的描述文件),

  2. 生成指令model,併爲每一個基礎資源類的故障場景都分配OS執行器客戶端。

  3. 將指令model轉換爲Corba Sub Command指令(對指令中添加描述信息,名稱,別名,以及設置指令對應的運行函數)

// registerOsExpCommands
func (ec *baseExpCommandService) registerOsExpCommands() []*modelCommand {
   // 讀取當前blade程序同級別yaml目錄下的chaosblade-os-spec-%s.yaml文件
   file := path.Join(util.GetYamlHome(), fmt.Sprintf("chaosblade-os-spec-%s.yaml", version.Ver))
   // 轉換成指令model,並分配OS執行器客戶端
   models, err := specutil.ParseSpecsToModel(file, os.NewExecutor())
   if err != nil {
      return nil
   }
   osCommands := make([]*modelCommand, 0)
   for idx := range models.Models {
      model := &models.Models[idx]
      command := ec.registerExpCommand(model, "")
      osCommands = append(osCommands, command)
   }
   return osCommands
}


// registerActionCommand 對指令中添加描述信息,名稱,別名,以及設置指令對應的運行函數
func (ec *baseExpCommandService) registerActionCommand(target, scope string, actionCommandSpec spec.ExpActionCommandSpec) *actionCommand {
   // 省略......
   command.command = &cobra.Command{
      Use:      actionCommandSpec.Name(),
      Aliases:  actionCommandSpec.Aliases(),
      Short:    actionCommandSpec.ShortDesc(),
      Long:     actionCommandSpec.LongDesc(),
      Example:  actionCommandSpec.Example(),
      // 設置指令對應的運行函數actionRunEFunc
      RunE:     ec.actionRunEFunc(target, scope, command, actionCommandSpec),
      PostRunE: ec.actionPostRunEFunc(command),
   }
   // 省略......
  return command
}


通過圖來看CLI的啓動流程比較清晰,感興趣的同學可以根據圖和上面的代碼進行源碼閱讀:

指令執行

在指令生成時我們已經對Cobar Command設置了對應的運行函數actionRunEFunc(),也就是說當我們在控制檯執行blade故障命令後,Cobar會將請求路由到我們指定的actionRunEFunc函數中。

那麼在actionRunEFunc都做了哪些事情呢?由於actionRunEFunc中代碼比較長,這裏就不粘貼了,流程如下:

執行器

在CLI中支持的執行器客戶端類型有多種,執行器則是代表支持哪些種類的故障場景。在文章的開篇已經介紹過了,每個執行器的客戶端運行思路基本差不多。例如:

  • OS 執行器客戶端是調用ChaosBlade程序中的bin目錄下的chaos_os可執行文件,在chaos_os可執行文件中會真正的進行基礎資源類故障。
chaosOsBin := path.Join(util.GetProgramPath(), "bin", spec.ChaosOsBin)
command := os_exec.CommandContext(ctx, chaosOsBin, argsArray...)
log.Debugf(ctx, "run command, %s %v", chaosOsBin, argsArray)




  • Java 執行器客戶端要相對複雜一些,由於java的故障場景底層都是利用JVM-Sandbox開源項目完成的agent掛載和字節碼增強,所以需要調用JVM-SandBox接口來完成故障注入/銷燬的指令。
// createUrl中會拼接SandBox的地址和端口
url, body, resp := e.createUrl(ctx, port, model)
if err != nil {
   return resp
}
result, err, code = util.PostCurl(url, body, "")


  • K8S 執行器客戶端是通過調用K8S接口,對ChaosBlade自定義資源完成的資源變更,然後ChaosBlade Operator利用list/watch機制實現的故障注入/清除
func create(cli client.Client, chaosblade *v1alpha1.ChaosBlade) (result *v1alpha1.ChaosBlade, err error) {
   err = cli.Create(context.TODO(), chaosblade)
   if err != nil {
      return nil, err
   }
   return get(cli, chaosblade.Name)
}


其他故障執行器的客戶端實現也基本相同,在這裏就不一一贅述了。

總結

ChaosBlade Tool是一個的故障注入工具箱,有CLI項目以及多個故障執行器組件構成,它開箱即用,並提供了多種場景的故障注入能力,其中包括基礎資源、Java應用、K8S、Docker、CRI、中間件、雲平臺、C++等。

ChaosBlade Cli是故障工具箱中的觸發者,它提供故障指令的生成,接收用戶的command指令,分發指令到不同的執行器中去運行,它的源碼相對簡單,並不涉及真正的故障注入能力。

作者介紹

Github 賬號:binbin0325,公衆號:檸檬汁CodeSentinel-Golang Committer 、ChaosBlade Committer 、 Nacos PMC 、Apache Dubbo-Go Committer。目前主要關注於混沌工程、中間件以及雲原生方向。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章