前言
上一章分析了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_ip和port,调用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_ip和port,调用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如果检测到任务超时,会终止任务,执行所有恢复节点;