ChaosBlade源码(二)box控制台

627 阅读20分钟

前言

上一章分析了blade黑屏命令行的实现原理,本章分析chaosblade-box和chaosblade-box-agent如何通过白屏控制blade命令。

主要包含:

1)用户/license/namespace之间的关系;

2)探针注册;

3)心跳与探活;

4)k8s探针上报pod;

5)创建演练;

6)发起演练;

注:

1)box(1.0.5):白屏控制台,一个springboot应用,下发演练指令给agent;

2)box-agent(1.0.3):探针,一个go应用,接收box指令,执行blade命令行;

一、使用案例

1、box安装

直接启动java进程,依赖mysql。(数据初始化将在启动阶段自动完成)

注:官方有helm安装方式,这里为了方便演示直接启动。

chaosblade-box用户可以自己随意注册。

用户在社区版本里比较重要,每个用户有自己的license,且用户之间数据隔离。

chaosblade-box-agent与chaosblade-box通讯,需要用到这个license做身份验证。

演练分为两种:

1)主机:除k8s实验外的所有实验,比如blade create jvm/cpu/cri等,仅依赖blade命令行;

2)Kubernetes:k8s实验,依赖chaosblade-operator、chaosblade-tool、chaosblade crd(见上一章);

探针也同样分为两种,区别主要是chaosblade-box-agent安装在主机内,还是安装在k8s内。

2、主机实验

安装agent探针

下载探针。

agent包含四个组件:

1)agent:可执行程序,与box通讯;

2)chaosblade:blade命令行工具;(注意版本,这里下载下来版本比较低,根据实际情况自己调整)

3)chaosctl.sh:agent启动脚本;

4)chaosrcv.sh:后台crontab任务脚本;

启动agent:

1)-k:指定license,由box提供;

2)-p:应用名;

3)-g:应用分组;

4)-P:agent暴露http端口;

5)-t:box连接地址;

在box侧发现agent上线。

发起演练

创建jvm 自定义异常实验。

开始演练。

目标机器agent收到请求。

目标机器看到实验确实创建成功。

继续节点,进入恢复流程。

恢复完成,目标主机实验状态变更为Destroyed,增强class被恢复。

小总结

用户在box白屏发起演练,box与宿主机上的agent通讯,执行blade黑屏命令。

3、k8s实验

安装agent探针

下载chart包。

helm安装。

观察chaos-agent正常启动。

chaos-agent会用到自己的serviceaccount,后续需要与apiserver通讯。

在box侧发现agent上线。

agent会上报应用到box。

agent会根据pod的label上报应用信息,如果未设置以下label,则无法上报到box。

发起演练

选择资源类型=Kubernetes发起演练。

与主机不同的是,需要额外指定容器属性,比如labels选择器找pod,container-name容器名找container。

开始演练,agent侧可以看到日志,调用blade create k8s。

kubectl describe chaosblade看到chaosblade资源创建成功。

blade status --type c,目标container查看blade实验状态也创建成功。

同主机,继续演练销毁实验。

小总结

与主机模式的区别并不大,由agent执行blade命令行,在k8s中创建chaosblade资源,后续流程都与blade create k8s一致了。

二、用户/license/namespace

在box中 不同用户间的数据是隔离的,每个用户只能看到自己的数据(除公共的演练经验)。

每个agent需要获取用户的license,才能注册到box,通过license可以将agent与用户关联。

UserController#userRegister:用户注册逻辑能说明白用户、license、namespace之间的关系。

用户注册时会分配license,和用户是一对一关系。(t_chaos_user)

CloudNamespaceImpl#initDefaultNamespace:用户注册会关联一个默认namespace,agent注册时也需要指定注册到哪个namespace。(t_chaos_namespace)

每个用户下能创建多个namespace做数据隔离。

三、Agent探针注册

1、agent发起注册请求

conn/connect/connect.go:40,agent启动会向box发起一次http注册请求,如果失败,agent将启动失败。

注册请求会包含以下核心参数:

1)license(ak):启动参数指定,用于标识这是哪个box用户的agent;

2)namespace:启动参数指定,默认default,标识注册到用户的哪个namespace下;

3)ip:当前机器ip,后续box需要通过这个ip与agent通讯;(http)

4)agentMode:agent模式,如主机模式为host,k8s模式为k8s_helm;

5)clusterId/clusterName:k8s模式启动时,指定k8s集群id和name;

6)appName(appInstance):应用名,启动参数指定,默认chaos-default-app;

7)appGroup:应用分组,启动参数指定,默认chaos-default-app-group;

8)deviceType:设备类型,agent固定值0,代表host;

9)deviceId:设备id,取自hostname;

2、box处理注册请求

RegisteredRequestCommand#execute:box侧先做基础校验

1)根据license查用户;

2)根据用户和namespace查namespace;

RegisteredRequestCommand#execute:box接着是一波upsert,涉及设备应用两部分。

注册后设备和应用的关系如下。

RegisteredRequestCommand#execute:box将license匹配的用户(uid/sk)和设备配置id(configurationId) 返回。

3、agent接收box响应

conn/connect/connect.go:107,如果与box通讯异常或box返回业务异常,则agent退出,否则,agent将box返回的uid(用户id)和cid(设备配置id)放入全局缓存。

四、心跳与探活

1、agent发送心跳

conn/heartbeat/heartbeat.go:47,agent每5s向box发送心跳,调用box的 /chaos/AgentHeartBeat端点。

调用box请求参数如下,注意Headers和Params都在http请求体中,Headers不是http请求头。cid为设备配置id。

2、box处理心跳

PrivateScope#updateHeartBeatTime:box侧根据设备配置id(cid)更新device和application_device上线,记录最近心跳时间。

3、box发送ping请求

AgentPingScheduleJob#execute:box侧每10s查询device中所有在线的agent,使用device的private_ipport,调用agent的ping端点。

AgentPingScheduleJob#pingAgent:如果ping失败,更新设备下线。注意和应用设备无关。

4、agent处理ping请求

web/handler/ping.go:32,agent侧记录日志,返回成功。

box侧,观测到agent探针(device)上线;agent侧,观测到日志Receive server ping request,代表双边通讯正常。

五、pod上报

对于主机实验,演练应用为agent注册的应用,由agent直接下发blade指令。

对于k8s实验:

1)k8s节点级演练(如k8s节点cpu满载),同主机实验,指定应用为agent注册的应用;

2)其余场景需要agent上报pod作为演练应用;

如果用官方helm chart安装agent,会在agent启动参数中设置kubernetes.pod.report=true,开启pod上报,每10s上报集群中的pod

collector/kubernetes/pod.go:59:

1)初始化indexer;

2)通过indexer查询当前k8s集群中全量pod列表pod过滤逻辑都在box端

3)调用box的/chaos/k8sPod端点上报pod,reportK8sMetric上报存在的pod,reportNotExistResource上报被删除的pod。agent会缓存所有pod的md5:

如果pod发生变更,会发送pod全量数据;

如果pod未变更,仅发送摘要数据,如pod的uid;

如果pod被删除,仅发送uid和cid(device配置id,上报pod信息给box后,box会返回);

ChaosAgentCallbackServiceImpl#handlerPodMetric:box侧根据用户和探针cid查询得到探针设备。

PodReportHandler#handler:根据agent上报的pod信息,对pod分为三组

1)如果pod发生变更,需要走探针注册中的应用注册逻辑,每个pod会成为一个应用,返回pod的uid和cid(box分配的应用设备配置id)的映射关系(一个map);

2)如果pod未变更,只需要更新心跳的pod,lease方法仅更新application_device心跳时间;

3)如果pod被删除,delete方法仅标记application_device状态为下线;

忽略application_device心跳更新或下线逻辑,仅关注应用注册。

ApplicationDeviceController#handlerPodAppInfo:前面说到,agent会上报集群中所有pod,过滤由box处理。如果无法拿到pod的label中的应用名,则不会注册为一个应用

ApplicationDeviceController#defaultParseAppName:应用名label的获取优先级如下:

chaos/app-instance>app-group-name>app.kubernetes.io/name>k8s-app>app;

注:应用组名取label=chaos/app-group,否则组名=应用名-group。

ApplicationDeviceController#persistencePodAppInfo:pod应用注册,逻辑通探针注册中的应用注册部分,upsert三张应用表(application、application_group、application_device),关键点在于cid的生成。

注:hostConfigurationId是agent探针的cid。

PrivateScope#generatorKubernetesConfigurationId:pod应用设备id(application_device.configuration_id)=用户+namepsace+探针的vpcId+设备类型(2代表pod)+设备id(pod的uid)

六、创建演练

1、演练场景

1)Scope:枚举,0-主机,2-k8s;

2)类目:包括二级类目;

3)方法:具体实验方法,如jvm自定义异常;

类目数据通过spring.datasource.data指定脚本导入。

方法数据通过chaosblade.spec-cloud.yaml指定。

ChaosBladeFunctionLoader#load:每次启动时读取yaml,解析后落库(支持更新)。

注:通过配置chaos.function.sync.type=None(默认ALL),在首次启动后,可加快启动速度。

2、编辑演练对象

创建演练:

1)每个演练可以定义多个分组;

2)每个分组指定n个机器,即应用设备(在线);

3)每个分组可配置多个演练内容,根据方法function定义配置演练参数;

每个方法依赖n个方法(dependencies)故障注入(phase=2) 方法会至少对应一个故障恢复(phase=8) 方法。

每个方法对应n个方法参数,大多和blade命令行参数一一对应。

3、编辑全局配置

1、顺序执行or阶段执行:

1)顺序执行,按function顺序执行,如异常注入1->异常恢复1>异常注入2->异常恢复2;

2)阶段执行,按phase顺序执行,如异常注入1>异常注入2->异常恢复1->异常恢复2;

2、可选择定时执行(注:box内置quartz实现定时调度);

3、必选自动恢复时间,当到达时间后自动终止实验任务中所有实验;

4、演练模型

演练(experiment) :包含全局配置和当前状态,一个演练包含多个分组。

演练分组(experiment_mini_flow_group) :每个演练可以分为n组,每组定义不同的演练机器(hosts)和演练流程。

演练流程(experiment_mini_flow) :一个分组下有n个流程,流程按照顺序编排,每个流程对应一个演练活动。

演练活动(experiment_activity) :每个活动对应一个方法(function)执行的模板(activity_definition),比如jvm异常注入,class=x,method=y,比如jvm异常恢复。

七、发起演练

针对一个演练配置可以发起多次演练。

1、演练任务模型

演练任务(experiment_task) :针对一个演练,可以创建多次演练任务

活动任务(activity_task) :一个演练任务下会有多个活动任务活动任务是一个运行时的演练活动(experiment_activity) ,包含注入恢复阶段,活动任务按顺序串联。

执行结果(app_execute_result) :每个活动任务会对应一组应用设备,比如jvm自定义异常需要注入n个pod,活动任务+应用设备维度对应一个执行结果,里面包含调用agent的详细情况。

守护实例(experiment_guard_instance) :一个演练任务对应多个守护实例,最主要的守护实例是自动恢复,15分钟后演练任务自动终止。

blade实验id(blade_exp_id) :持久化agent返回的blade实验id。

演练任务反馈(experiment_task_feedback) :每个演练任务结束,填写反馈。

2、发起演练

ExperimentExecutionCommand#execute:

Step1,创建演练任务落库,返回第一个需要执行的活动任务;

Step2,自动执行第一个活动任务;

ExperimentTaskCreateCommand#execute:Step1创建演练任务落库包含:1)一个演练任务;2)n个活动任务;3)机器数量*n个执行结果。

ExperimentExecutionCommand#runTask:Step2,活动任务是异步执行,线程池core=50,max=100,linked阻塞队列无界。

ActivityTaskExecutionCommand#execute:执行活动任务,流程较长。

3、注入

活动任务处理分为三个部分:

1)BaseActivityInvokeInterceptor:活动维度的前置和后置处理,调用机器维度处理时会启动n(机器数量)个线程并行跑;

2)BaseMiniAppInvokeInterceptor:机器维度的前置和后置处理;

3)ChaosBladeAppInvoker:远程调用agent;

活动任务维度-前置拦截

ActivityLogInvokeInterceptor,打印日志,如:ActivityTask=taskId,Display=true,开始运行。

ActivityPauseInvokeInterceptor,如果配置before参数,这里会睡眠x毫秒。

机器维度-前置拦截

MiniAppTaskStatusChangedInvokeInterceptor,更新执行结果状态为RUNNING。

ApplicationHostMiniAppInvokeInterceptor,根据应用设备找到探针设备,设置为实际调用host。

box调用agent创建实验

ChaosBladeAppInvoker#invoke:最终执行远程调用。

调用端点http://xxxx:19527/chaosblade,入参中cmd是blade命令参数,waiting-time是k8s场景下特有写死0.5秒,不会等待实验创建的最终结果。

注:chaosblade-operator处理chaosblade资源是异步的,blade命令创建k8s实验会每隔1s查询chaosblade资源,最多等待waiting-time(默认20s)得到实际结果。

agent创建实验

ChaosbladeHandler负责管理chaosblade实验。

chaosblade.go:67,将box入参cmd直接透传至blade命令行,解析返回json返回至客户端。

如k8s实验返回参数:

机器维度-后置拦截

MiniAppChaosBladeInvokeInterceptor#createChaosBladeExpRecord:

Step1,saveRecord,将agent返回的实验uid持久化到t_chaos_blade_exp_uid

Step2,afterCreate,如果是k8s探针,KubernetesChaosBladeMiniAppInterceptor调用agent根据uid反查实验状态(cmd=query k8s create uid),将内层uid(实际底层operator执行的blade实验id)更新到t_chaos_blade_exp_uid.sub_exp_uid

MiniAppTaskStatusChangedInvokeInterceptor#saveTask:

根据blade执行情况,更新app_execute_result执行结果,运行状态为FINISHED,结果状态根据实际调用情况决策为成功或失败。

活动任务维度-后置拦截

ActivityPauseInvokeInterceptor,如果配置after参数,允许活动执行后睡眠x毫秒。

ActivityLogInvokeInterceptor,打印活动执行结束日志,如ActivityTask=taskId,Display=true,运行结束。

ActivityTaskStatusChangedInvokeInterceptor,推进当前活动任务状态,更新为运行状态FINISHED。结果状态可根据失败容忍度调整,默认只要一个机器失败,就直接失败。

接下来是否要推进后续任务,根据情况决定。

ExperimentTaskPusher#push:

case1,ignorePush,如默认演练需要用户手动推进,则这里直接结束。

case2,自动推进情况下,如果后续无任务,执行实验任务结束。

case3,自动推进情况下,如果后续还有任务,再次提交任务执行请求不过还在当前线程同步执行

4、手动推进

重试

如果活动任务失败,允许发起重试。

例如k8s探针情况下,chaosblade-operator异步处理chaosblade资源,首次做注入如果目标container中没有blade命令,还需要复制blade命令行工具到目标container,容易遇到unexpected status错误。(上面MiniAppChaosBladeInvokeInterceptor反查blade uid无法拿到注入成功状态)

ActivityTaskRetryCommand#execute:重试会查询活动任务下需要重试的机器,设置上下文Retrying,异步执行注入。(还是ActivityTaskExecutionCommand

ActivityTaskExecutionCommand底层还是需要执行一次新的blade实验,幂等逻辑依赖于底层blade工具。

CreateHandler#handleInjection:如chaosblade-exec-jvm会判断增强点是否已经存在,如果存在则返回406。

在box侧,如果是主机探针,直接根据重试结果执行状态更新即可。

如果是k8s探针,MiniAppChaosBladeInvokeInterceptor会查询当前活动任务执行机器的最近一次blade实验id(即首次blade实验id),调用agent反查实验状态。

KubernetesChaosBladeMiniAppInterceptor#confirmChaosBladeTaskStatus:

ActivityTaskResultConfirmCommand#execute:更新活动任务的用户check状态=继续或终止。

继续

ActivityTaskResultConfirmCommand#asyncPushTask:推进task状态,底层还是找下一个task提交执行。

ExperimentTaskPusher#push:如果后续没有任务,执行实验任务结束。

ExperimentTaskFinishedCommand#execute:实验任务结束

1)将所有剩余blade_exp_uid中未恢复的blade实验直接调用agent恢复;(为保护策略服务)

2)未运行完成的活动任务result=REJECT;

3)实验任务状态FINISHED;

4)实验状态READY;

终止

ExperimentTaskStopCommand#execute:

1)实验任务STOPPING;

2)停止线程池内正在运行的活动任务;

3)向后执行所有恢复任务;

ExperimentTaskStopCommand#recoverTasks:找到第一个recover活动任务,还是提交ActivityTaskExecutionCommand异步执行。

ExperimentTaskPusher#acquireNextTask:在当前recover任务执行完成后,如果实验任务在STOPPING状态,后续只会执行recover任务。

5、恢复

恢复流程和注入基本一致,都是经过多个拦截器后,调用agent。

MiniAppChaosBladeInvokeInterceptor,机器维度前置拦截,根据活动任务id和机器找blade的实验id,用于后续blade命令拼接。

box调用agent销毁cmd=destroy uid,agent执行destroy。

MiniAppChaosBladeInvokeInterceptor,机器维度后置拦截,blade的实验uid记录更新为expired。如果是k8s实验,这里仍然需要反查agent,query k8s destroy uid

6、自动恢复

配置实验默认会设置15分钟自动恢复的保护策略,当到达时间后自动终止实验任务。

底层控制blade实验自动终止的是timeout参数(秒)。

以k8s探针为例。

blade命令如果包含timeout参数,会后台执行shell睡眠x秒后执行destroy命令删除实验。

cli/cmd/create.go:250:blade命令后置处理。

box侧需要主动检测实验超时,通过用户页面轮询实验状态触发

即用户不打开演练中的任务页面,虽然底层blade已经超时结束,但box侧仍然处于Running状态。

ExperimentTaskGuardInstanceCreatedListener#addDurationGuardInstance:实验任务创建完成(发起演练) 后,会创建一个实验守护实例experiment_guard_instance。

ExperimentTaskGuardInfoQueryCommand#execute:box控制台轮询演练执行情况,会触发守护实例执行。

ExperimentAutoRecoveryLoadCommand#internalExecute:超时自动恢复守护实例,如果发现实验任务超时,会触发stop指令,直接终止演练任务。(见手动推进-终止)

总结

chaosblade-box通过白屏方式管理混沌实验。

在主机模式下,agent探针直接安装在目标主机中,box下发指令,agent直接在本机执行blade命令。

在k8s模式下,agent探针部署在k8s集群中,box下发指令,agent在本机执行blade create k8s命令。

用户/license/namespace

在box中,大部分数据是按照用户隔离的(除公共的演练经验),每个用户可管理多个namespace。

每个用户注册会颁发一个license,agent启动需要指定注册到哪个用户(license)的哪个namespace下。

探针注册

agent启动后向box发送一次注册请求:

1)license(ak):启动参数指定,用于标识这是哪个box用户的agent;

2)namespace:启动参数指定,默认default,标识注册到用户的哪个namespace下;

3)ip:当前机器ip,后续box需要通过这个ip与agent通讯;(http)

4)agentMode:agent模式,如主机模式为host,k8s模式为k8s_helm;

5)clusterId/clusterName:k8s模式启动时,指定k8s集群id和name;

6)appName(appInstance):应用名,启动参数指定,默认chaos-default-app;

7)appGroup:应用分组,启动参数指定,默认chaos-default-app-group;

8)deviceType:设备类型,agent固定值0,代表host;

9)deviceId:设备id,取自hostname;

box根据license找到用户,upsert四份数据:

1)Device设备:一个设备代表一个探针;

2)Application应用:应用包含应用组、应用、应用设备;

box处理后会返回agent两个数据:uid(用户id)cid(设备配置id)

agent将两个数据缓存下来,后续需要用到。

心跳与探活

心跳:

1)agent:每5s向box发送心跳( /chaos/AgentHeartBeat),请求参数包含注册返回的cid(设备配置id);

2)box:根据cid更新device和application_device上线,记录最近心跳时间;

探活:

1)box:每10s查询device中所有在线的agent,使用device的private_ipport,调用agent的ping端点;

2)agent:直接返回即可;

3)box:如果ping失败,更新设备下线;

要确认双边通讯正常:

1)box侧,观测到agent探针(device)上线;

2)agent侧,观测到日志Receive server ping request;

pod上报

对于k8s探针,需要上报pod注册成为Application应用,才能发起应用级别演练。

如果用官方helm chart安装agent,会在agent启动参数中设置kubernetes.pod.report=true,开启pod上报。

agent每10s上报集群中的pod(所有) ,调用box的 /chaos/k8sPod端点。

box根据pod的label获取应用和应用组:

应用label:chaos/app-instance>app-group-name>app.kubernetes.io/name>k8s-app>app;

应用组label:chaos/app-group>应用名-group。

无法拿到pod的label中的应用名,则不会注册为一个应用

注册应用就是upsert application的三份数据,同探针注册。

创建演练

用户根据场景选择执行方法。

类目数据通过spring.datasource.data指定dml脚本启动时刷入。

方法数据通过chaosblade.spec-cloud.yaml指定,程序启动阶段通过代码upsert写入db。

每个方法有n个参数,如process代表进程名。

创建演练生成4份数据:

演练(experiment) :包含全局配置(执行模式/超时时间等)和当前状态,一个演练包含多个分组。

演练分组(experiment_mini_flow_group) :每个演练可以分为n组,每组定义不同的演练机器(hosts)和演练流程。

演练流程(experiment_mini_flow) :一个分组下有n个流程,流程按照顺序编排,每个流程对应一个演练活动。

演练活动(experiment_activity) :每个活动对应一个方法(function)的运行模板,包含运行时的参数,如process=app.jar。

发起演练

演练任务(experiment_task) :针对一个演练,可以创建多次演练任务

活动任务(activity_task) :一个演练任务下会有多个活动任务活动任务是一个运行时的演练活动(experiment_activity) ,包含注入恢复阶段,活动任务按顺序串联。

执行结果(app_execute_result) :每个活动任务会对应一组应用设备,比如jvm自定义异常需要注入n个pod,活动任务+应用设备维度对应一个执行结果,里面包含调用agent的详细情况。

守护实例(experiment_guard_instance) :一个演练任务对应多个守护实例,最主要的守护实例是自动恢复,15分钟后演练任务自动终止。

blade实验id(blade_exp_id) :持久化agent返回的blade实验id。

演练任务反馈(experiment_task_feedback) :每个演练任务结束,填写反馈。

当用户发起演练后,自动执行第一个活动任务。

活动任务完成,默认需要需要用户手动确认(可配置自动执行)。

用户可选择继续or终止or重试:

1)如果终止,则向后执行所有恢复节点;

2)如果继续,则向后推进;

3)如果重试,则重新执行当前活动任务,幂等需要blade工具自己支持;

每个活动任务下的应用是并行执行的,对于每个应用:

1)box:调用agent的chaosblade端点传入cmd命令,如create jvm;

2)agent:拼接blade执行/opt/chaosblade/blade create jvm,返回json结果;

3)box:接收结果,如果是注入则记录blade_exp_id,后续恢复需要用到,如果是恢复则更新blade_exp_id失效;对于k8s探针,还需要反查agent,传入cmd如blade query k8s uid;

演练任务默认15分钟自动恢复

1)在创建实验时,box按照用户配置的自动恢复时间,拼接cmd携带timeout参数,blade会后台运行sleep && blade destroy ,超时后blade会自己销毁实验;

2)box侧,如果用户停留在演练任务页面,会主动轮询任务状态。box如果检测到任务超时,会终止任务,执行所有恢复节点;