目录

一、ChaosBlade 简介

二、部分 JVM 实验介绍

1、权限

2、JVM 实验准备

3、注入方法延时

4、修改方法返回值

         5、抛出自定义异常

6、使用自定义脚本替换指定类方法的内容

         7、结束 JVM 实验

         8、问题排查

三、多台机器同时实验的简单处理方法


一、ChaosBlade 简介

ChaosBlade 是阿里开源的混沌工程品牌,包含 chaosblade 工具和 chaosblade-box 平台等项目。ChaosBlade 按照混沌工程的思想,将故障抽象成了一个个实验,并按照科学实验的方法对其过程进行组织。chaosblade 工具支持了大量的故障场景和丰富的命令参数,能够很方便地对常见故障场景进行实验。

二、部分 JVM 实验介绍

相比于 CPU 使用率飙升等基础设施相关故障导致的系统不稳定,我们的关注点目前更加集中在:系统所依赖的两方或三方服务故障导致的不稳定情况。这些故障基本都可以使用修改服务代码逻辑的方式实现,也就是 chaosblade 中 JVM 实验部分所涵盖的内容。所以,以下主要介绍 JVM 实验场景。

(说明:以下代码块中 $ 开头的是需要输入的命令;# 开头的文字是注释;正常无开头标识的为命令返回)

1、权限

操作人员需要提前申请好与线上服务相同的权限,我们这里通常就是要演练的那台机器上的 work 权限。

环境配置

chaosblade 工具是开箱即用的,只需要下载并解压缩其 release 文件,就可以调用其中的可执行文件来进行故障注入。

# 1) 创建并切换到放置chaosblade的目录
$ mkdir -p /home/work/chaos/bin && cd /home/work/chaos/bin

# 2) 下载release文件
$ curl https://chaosblade.oss-cn-hangzhou.aliyuncs.com/agent/github/1.2.0/chaosblade-1.2.0-linux-amd64.tar.gz -o chaosblade-1.2.0-linux-amd64.tar.gz

# 3) 解压缩
$ tar -zxvf chaosblade-1.2.0-linux-amd64.tar.gz && rm chaosblade-1.2.0-linux-amd64.tar.gz

# 4) 给当前会话新增blade命令,下文都将使用该命令
$ alias blade="/home/work/chaos/bin/chaosblade-1.2.0/blade"

2、JVM 实验准备

JVM 场景的实验是通过 Java agent 技术来实现的,所以在实验前需要首先将 chaosblade 的 agent 挂载到实验对象的进程中(更多参数请参考 blade prepare jvm):

# 1) 以 test-api 服务为例,拿到进程号。当前例子中进程号为 32676
$ ps -aux | grep test-api
work      32676  4.6  6.7 24081412 8944200 ?    Sl   6月02 463:07 /home/work/bin/test-api/java/bin/java *** -server test-api start

# 2) 挂载 java agent (挂载过程需要等十几秒钟)
#    -j 参数为服务下的 Java 目录(一般为 /home/work/bin/service-name/java )
#    --pid 参数为上一步得到的进程号
$ blade prepare jvm -j /home/work/bin/test-api/java/ --pid 32676
{"code":200,"success":true,"result":"5ee14b7d18d895fd"}
# 返回code为200则说明挂载成功,请记录result对应的实验对象id:5ee14b7d18d895fd,之后会用于实验卸载

挂载成功后,就可以进行实验了。但是一定要注意,所有的实验都结束后,一定要将 agent 卸载掉,具体操作方式见第 7 节。agent 挂载成功后可以进行任意多次第 4, 5, 6 节中描述的实验,各个实验也可以同时进行,但要在卸载 agent 之前将每个创建成功的实验都销毁掉

3、注入方法延时

# 1) 对一个方法注入8秒的延时
#    --time 为延时时长,单位是毫秒
#    --classname 为该方法所在类的全限定名
#    --methodename 为该方法的方法名
#    --pid 为之前挂载好 agent 的进程号
$ blade create jvm delay --time 8000 --classname=com...ChargeController --methodname=payNotify --pid 32676
{"code":200,"success":true,"result":"d0e4be6ee34eab54"}
# 返回code为200则说明注入成功

# 2) 销毁该实验,撤销注入的延时,参数d0e4be6ee34eab54是步骤1)中返回的实验对象Id
$ blade destroy d0e4be6ee34eab54
{"code":200,"success":true,"result":{"target":"jvm","action":"delay","flags":{"classname":"com...ChargeController","methodname":"payNotify","pid":"32676","time":"8000"}}}
# 返回code为200则说明成功销毁了实验

除以上简单案例外,还可以设定影响的请求条数、请求百分比等,具体请参考 blade create jvm delay

4、修改方法返回值

chaosblade 可直接修改方法的返回为指定值,更多参数见 blade create jvm return

# 1) 修改方法的返回值,将该方法的所有调用返回值都修改为false
#    方法的返回值类型需要是基本类型,value参数值的类型需要和方法返回值类型相匹配
#    value可以为null,这种情况下不遵从上述约束
#    --value 指定方法的固定返回值
$ blade create jvm return --value false --classname com...NotifyManager --methodname verifyNotifyInfo --pid 32676
{"code":200,"success":true,"result":"b847f4d9f8f1cb1f"}

# 2) 销毁该实验
$ blade d b847f4d9f8f1cb1f
{"code":200,"success":true,"result":{"target":"jvm","action":"return","flags":{"classname":"com...NotifyManager","methodname":"verifyNotifyInfo","pid":"32676"}}}

5、抛出自定义异常

blade create jvm throwCustomException 可以让对特定方法的调用直接抛出一个异常

# 1) 让一个方法抛出自定义的异常
#    --exception 参数为要抛出异常的全限定名,必须继承 java.lang.Exception 或           #    java.lang.Exception 本身
#    --exception-mesage 参数为异常所带的自定义信息
$ blade create jvm throwCustomException --exception java.lang.Exception --exception-message this-is-a-mocked-exception --classname com...NotifyManager --methodname verifyNotifyInfo --pid 32676
{"code":200,"success":true,"result":"06fc1ce09c721f90"}

# 2) 销毁该实验
$ blade d 06fc1ce09c721f90
{"code":200,"success":true,"result":{"target":"jvm","action":"throwCustomException","flags":{"classname":"com...NotifyManager","exception":"java...Exception","exception-message":"this-is-a-mocked-exception","methodname":"verifyNotifyInfo","pid":"32676"}}}

6、使用自定义脚本替换指定类方法的内容

chaosblade 也支持用 Java 或 Groovy 脚本自行实现一个复杂的故障场景,如针对特定用户或商户的过滤等。脚本内容需要符合下文中的规范,更多内容参见 blade create jvm script

# 1) 用自定义脚本替换特定方法的实现内容
#    --script-file 自定义脚本文件所在的绝对路径,脚本书写规范见下文
#    --debug (无参数)将 chaosblade 的日志级别设定为 debug,打印更详细的日志
$ blade create jvm script --classname com...ChargeController --methodname Notify --script-file /home/work/niwanjia/Script.java --pid 32676 --debug
{"code":200,"success":true,"result":"0454edf1f266789d"}

# 2) 销毁该实验
$ blade d 0454edf1f266789d
{"code":200,"success":true,"result":{"target":"jvm","action":"script","flags":{"classname":"com...ChargeController","debug":"true","methodname":"Notify","pid":"32676","script-file":"/home/work/niwanjia/Script.java"}}}

自定义脚本的规范:

  • 必须创建一个类,对类名和包名没有要求,其中所依赖的类,必须是目标应用所具备的类。
  • 同包下的类引用,必须写全包名,比如故障脚本类是 com.example.controller.ExceptionScript,类中引入了同包下的 DubboController 类,则 DubboController 必须添加 com.example.controller.DubboController。引入非同包下的类,无需写全包名。
  • 必须添加 public Object run(Map params) 方法,其中 params 对象中包含目标方法参数,key 是参数索引下标,从 0 开始,比如目标方法是 public String call(Object obj1, Object obj2){},则 params.get("0") 则返回的是 obj1 对象,可以执行 params.put("0", ) 来修改目标方法参数(目标方法及 --classname 和 --methodname 所指定的类方法)(注意:"0"为字符串)。
  • 上述方法返回的对象如果不为空,则会根据脚本中返回的对象来修改目标方法返回值,注意类型必须和目标方法返回值一致。如果上述方法返回 null,则不会修改目标方法返回值。

上面例子中使用的脚本内容:

package com.anywhere.you.want;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Map;
import net.paoding.rose.web.Invocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Script {

//  该脚本将接收到的通知内容直接丢弃掉
    public Object run(Map<String, Object> params) {
        Logger log = LoggerFactory.getLogger(Script.class);
        Invocation inv = (Invocation) params.get("1");
        StringBuilder sb = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new InputStreamReader(inv.getRequest().getInputStream()))) {
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            log.info("drop notify. content: {}", sb.toString());
            return "@SUCCESS";
        } catch (Exception e) {
            log.error("mock notify error: {}", e.getMessage(), e);
            return "@error";
        }
    }
}

7、结束 JVM 实验

JVM 实验结束后,需要卸载之前加载到目标进程上的 agent。请在卸载 agent 前确认已将所有实验都撤销完毕,以免影响线上业务。

# 1) 如果忘了加载 agent 时返回的实验对象 id,可以通过 status 命令查看
#    --type 为 prepare 或者 create 
$ blade status --type prepare
{
        "code": 200,
        "success": true,
        "result": [
                {
                        "Uid": "5ee14b7d18d895fd",
                        "ProgramType": "jvm",
                        "Process": "",
                        "Port": "17758",
                        "Pid": "32676",
                        "Status": "Running",
                        "Error": "",
                        "CreateTime": "2021-06-09T15:31:32.449179887+08:00",
                        "UpdateTime": "2021-06-09T15:31:45.729185574+08:00"
                }
          ]
}

# 2) 卸载 agent,参数 5ee14b7d18d895fd 为之前 prepare 命令返回的实验对象 Id
$ blade r 5ee14b7d18d895fd
{"code":200,"success":true,"result":"success"}

8、问题排查

1) 参数等的错误通常会直接在命令的响应中体现,如:

flag needs an argument: --pid
# 或者
required flag(s) "methodname" not set

2) 如果响应中 code 不是 200,其后的 result 中就会有错误描述,基本上可定位问题,如:

{"code":406,"success":false,"error":"the experiment exists"}

3) 创建实验时响应Code 为 200,但程序表现与预期不符,就需要去 chaosblade 的日志中找原因了。日志的位置为 ~/logs/chaosblade/chaosblade.log ,如:

2021-06-09 17:01:49 INFO  Match rule: {"target":"jvm","matcher":{"matchers":{"classname":"com...ChargeController","methodname":"wxpayNotify"}},"action":{"name":"script"},"actionName":"script"}
2021-06-09 17:01:49 WARN  inject exception
com.alibaba.chaosblade.exec.plugin.jvm.script.base.ScriptException: Compilation failed.
Source: /com/taobao/csp/monkeyking/script/java/source/Script.java
Line 24: cannot find symbol
  symbol:   class ChargeController
  location: class com.taobao.csp.monkeyking.script.java.source.Script
1       package com.taobao.csp.monkeyking.script.java.source;
2       import com.xiaomi.upay.common.utils.ErrorCounter;
*** 略 ***
22                  return "@SUCCESS";
23              } catch (Exception e) {
24                  ErrorCounter.countError(ChargeController.class);
25                  log.error(e.getMessage(), e);
26                  return "@error";
27              }
28          }
29      }

三、多台机器同时实验的简单处理方法

chaosblade 的 server 命令可以启动其内置的服务器,从而将其混沌实验能力通过http接口暴露出来。我们就获得了通过脚本远程创建混沌实验的能力,同时也可以利用该接口同时控制多台机器展开实验。利用 server 命令,增加权限管理等功能后,也可以搭建简单混沌实验平台。

单机案例:

# 1) 在实验目标机器上启动 server
#    --port 参数为 http 服务监听的接口
[work@myrboot ~]$ blade server start --port 19526
success, listening on :19526

#————————————————  以下命令都在另一台机器执行  ——————————————————
# 2) 通过 http 接口,远程启动一个实验
#    cmd 参数的内容与本地命令行相同
work@myrboot-PC:~$ curl "http://myrboot:19526/chaosblade?cmd=prepare%20jvm%20--pid%2012984"
{"code":200,"success":true,"result":"cf0683a7be4a506e"}

# 3) 通过 http 接口,远程关闭实验
work@myrboot-PC:~$ curl "http://myrboot:19526/chaosblade?cmd=revoke%20cf0683a7be4a506e"
{"code":200,"success":true,"result":"success"}

# 4) 停止 server
work@myrboot-PC:~$ curl "http://myrboot:19526/chaosblade?cmd=server%20stop"
curl: (52) Empty reply from server

以上代码块中的多个 http 请求可以通过预先编辑好的脚本来执行,这就达到了半自动执行混沌实验的目标。同时在多台机器中启动 chaosblade server,就可以通过脚本操控多台机器同时执行混沌实验。

调用多台机器的简单 shell 脚本:

#!/bin/bash

domains[0]="myrboot"
domains[1]="myrboot1"

set -x
for domain in ${domains[@]};
do
   curl "http://${domain}:19526/chaosblade?cmd=prepare%20jvm%20--pid%2012984"
done
set +x

实验完成后请务必停止所有的 chaosblade server,因为该接口没有任何安全认证,并且提供了远程代码注入的能力,非常危险!

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐