混沌工程: 系统级故障模拟

1,667 阅读6分钟

背景

随着容器技术的普及以及借助着K8s容器编排能力,业务开发的灵活性和部署速度显著提高,但随之带来的是系统的复杂性以及可靠性测试的难度逐渐上升。随着业务的发展以及分布式系统的规模逐渐壮大,我们需要在系统中的弱点通过生产环境暴露给用户之前,主动发现。这时候混沌工程的引入就十分必要。

混沌工程是在分布式系统上进行实验的学科, 目的是建立对系统抵御生产环境中失控条件的能力以及信心。(有时即使分布式系统中的所有单个服务都正常运行, 这些服务之间的交互也会导致不可预知的结果。这些不可预知的结果, 由影响生产环境的罕见且具有破坏性的事件复合而成,令这些分布式系统存在内在的混沌。)

在验证过程中遇到的痛点、难点

1.多数场景下,测试的链路覆盖只能覆盖到常规或正常的业务逻辑链路。对于某些异常场景,例如上下游服务的异常等。构造起来十分困难、需要其他团队协作甚至在测试环境中无法模拟,对于这种无法覆盖到的场景一旦在线上发生就有可能出现未知的行为甚至导致系统的异常;缺少一种有效且简单的故障模拟手段去发现问题

2.系统可靠性测试难以进行,对于对可靠性要求较高的系统,测试过程中出了要掌握各个业务流程的行为,也需要对服务在某些恶劣场景下的表现作出评估

3.系统告警。多数场景下配置的告警策略等在测试环境难以触发,导致了监控告警本身不可信,也就难以完全信赖告警的结果去判断系统是否正常。

方案选型

ChaosBlade,阿里开源的混沌模型注入工具。目前支持的场景有:基础资源、Java 应用、C++ 应用、Docker 容器以及 Kubernetes 平台。

云原生场景下的方案实施

这里只介绍下ChaosBlade在K8S平台下的使用,其他场景与应用下的使用可以参考官方文档

1.概念引入

1.Kubernetes(k8s)

Kubernetes(k8s)是自动化容器操作的开源平台,这些操作包括部署,调度和节点集群间扩展。

2.POD

Pod(上图绿色方框)安排在节点上,包含一组容器和卷。同一个Pod里的容器共享同一个网络命名空间,可以使用localhost互相通信。Pod是短暂的,不是持续性实体。

3.Replication Controller

Replication Controller确保任意时间都有指定数量的Pod“副本”在运行。如果为某个Pod创建了Replication Controller并且指定3个副本,它会创建3个Pod,并且持续监控它们。如果某个Pod不响应,那么Replication Controller会替换它

replication controller与deployment的区别: Replication Controller(RC)为Kubernetes的一个核心内容,应用托管到Kubernetes之后,需要保证应用能够持续的运行,Replication Controller就是这个保证的key;Deployment同样为Kubernetes的一个核心内容,主要职责同样是为了保证pod的数量和健康,90%的功能与Replication Controller完全一样,可以看做新一代的Replication Controller,具备一些新的特性。

4.Node

Node(节点)是物理或者虚拟机器,

5.ChaosBlade-Operator

ChaosBlade-Operator是 ChaosBlade 的 Kubernetes 平台实验场景实现。将混沌实验通过 Kubernetes 标准的 CRD 方式定义,用户可以像定义 Deployment 或 StatefulSet 那样定义 ChaosBlade 实验,只要对 kubectl 和 Kubernetes 对象有所了解,就可以轻松的创建、更新和删除实验场景;同时也可以通过 chaosblade cli 工具来操作实验场景。

2.环境安装

ChaosBlade-Operator 需要使用 Helm 安装

# 安装Helm
$ curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash

# 下载安装包
$ wget -qO chaosblade-operator-0.6.0.tgz https://chaosblade.oss-cn-hangzhou.aliyuncs.com/agent/github/0.6.0/chaosblade-operator-0.6.0-v3.tgz
# 为 chaosblade 创建一个 namespace
$ kubectl create namespace chaosblade
# 安装 ChaosBlade-Operator
$ helm install chaos chaosblade-operator-0.6.0.tgz --set webhook.enable=true --namespace=chaosblade
# 查看安装结果
$ kubectl get pod -n chaosblade | grep chaosblade
chaosblade-operator-6b6b484599-gdgq8   1/1     Running   0          4d23h
chaosblade-tool-7wtph                  1/1     Running   0          4d20h
chaosblade-tool-r4zdk                  1/1     Running   0          4d23h

ChaosBlade-Operator 启动后将会在每个节点部署一个 chaosblade-tool Pod 和一个 chaosblade-operator Pod,如果都运行正常,则安装成功。上面设置 --set webhook.enable=true 是为了 Pod 文件系统 I/O 故障实验,如果不需要进行该实验,则无需添加该设置。

3.常用实验

1.Container实验场景

通用执行命令

container-id获取方式

kubectl -n xxx-xxx-xx-xxxx get pod xxxx-xxxx-xxxx-xxxx -o custom-columns=CONTAINER:.status.containerStatuses[0].name,ID:.status.containerStatuses[0].containerID

执行命令,开始试验

$ kubectl apply -f remove_container_by_id.yaml

查看试验状态

执行 kubectl get blade remove-container-by-id -o json 命令,查看实验状态。

停止实验

执行命令:kubectl delete -f remove_container_by_id.yaml

或者直接删除 blade 资源:kubectl delete blade remove-container-by-id
1.1 删除 container

实验目的: 在container层面模拟服务异常重启情况下的服务、数据流处理情况(模拟故障发生时或服务再次回复)

remove_container_by_id.yaml 内容:

apiVersion: chaosblade.io/v1alpha1
kind: ChaosBlade
metadata:
  name: remove-container-by-id
spec:
  experiments:
  - scope: container
    target: container
    action: remove
    desc: "remove container by id"
    matchers:
    - name: container-ids
      value: ["c6cdcf60b82b854bc4bab64308b466102245259d23e14e449590a8ed816403ed"]
      # pod name
    - name: names
      value: ["guestbook-7b87b7459f-cqkq2"]
    - name: namespace
      value: ["chaosblade"]
1.2 CPU负载

实验目的: 在container层面模拟服务高负载场景下业务处理能力的表现(高负载下易高频触发某些代码执行异常)

increase_container_cpu_load_by_id.yaml 内容:

apiVersion: chaosblade.io/v1alpha1
kind: ChaosBlade
metadata:
  name: increase-container-cpu-load-by-id
spec:
  experiments:
  - scope: container
    target: cpu
    action: fullload
    desc: "increase container cpu load by id"
    matchers:
    - name: container-ids
      value:
      - "5ad91eb49c1c6f8357e8d455fd27dad5d0c01c5cc3df36a3acdb1abc75f68a11"
    - name: cpu-percent
      value: ["100"]
      # pod names
    - name: names
      value: ["redis-slave-55d8c8ffbd-jd8sm"]
    - name: namespace
      value: ["chaosblade"]
1.3 网络异常

实验目的: 在container层面模拟服务网络异常(高时延、网络数据包异常等场景下的服务表现)

delay_container_network_by_id.yaml 内容:

apiVersion: chaosblade.io/v1alpha1
kind: ChaosBlade
metadata:
  name: delay-container-network-by-id
spec:
  experiments:
  - scope: container
    target: network
    action: delay
    desc: "delay container network by container id"
    matchers:
    - name: container-ids
      value:
      - "02655dfdd9f0f712a10d63fdc6721f4dcee0a390e37717fff068bf3f85abf85e"
    - name: names
      value:
      - "redis-master-68857cd57c-hknb6"
    - name: namespace
      value:
      - "chaosblade"
    - name: local-port
      value: ["6379"]
    - name: interface
      value: ["eth0"]
    - name: time
      value: ["3000"]
    - name: offset
      value: ["1000"]

观测实验结果

# 获取实验 pod ip
$ kubectl get pod -l app=redis,role=master -o jsonpath={.status..podIP}
10.42.69.44

# 测试时间
$ time echo "" | telnet 10.42.69.44 6379
Trying 10.42.69.44...
Connected to 10.42.69.44.
Escape character is '^]'.
Connection closed by foreign host.
real    0m3.790s
user    0m0.007s
sys     0m0.001s

一些其他实验的模拟可以参考: "Container实验模拟"

2.POD实验场景

通用执行命令

执行命令,开始试验

$ kubectl apply -f remove_container_by_id.yaml

查看试验状态

执行 kubectl get blade remove-container-by-id -o json 命令,查看实验状态。

停止实验

执行命令:kubectl delete -f remove_container_by_id.yaml

或者直接删除 blade 资源:kubectl delete blade remove-container-by-id
2.1 POD网络异常

实验目的: 在pod层面模拟服务网络异常(高时延、网络数据包异常等场景下的服务表现)

delay_pod_network_by_names.yaml 内容:

apiVersion: chaosblade.io/v1alpha1
kind: ChaosBlade
metadata:
  name: delay-pod-network-by-names
spec:
  experiments:
  - scope: pod
    target: network
    action: delay
    desc: "delay pod network by names"
    matchers:
    - name: names
      value:
      - "redis-master-68857cd57c-dzbs9"
    - name: namespace
      value:
      - "chaosblade"
    - name: local-port
      value: ["6379"]
    - name: interface
      value: ["eth0"]
    - name: time
      value: ["3000"]
    - name: offset
      value: ["1000"]

观测实验结果

# 获取实验 pod ip
$ kubectl get pod -l app=redis,role=master -o jsonpath={.status..podIP}
10.42.69.44

# 测试时间
$ time echo "" | telnet 10.42.69.44 6379
Trying 10.42.69.44...
Connected to 10.42.69.44.
Escape character is '^]'.
Connection closed by foreign host.
real    0m3.790s
user    0m0.007s
sys     0m0.001s
2.2 删除pod

delete_pod_by_labels.yaml 内容:

apiVersion: chaosblade.io/v1alpha1
kind: ChaosBlade
metadata:
  name: delete-two-pod-by-labels
spec:
  experiments:
  - scope: pod
    target: pod
    action: delete
    desc: "delete pod by labels"
    matchers:
    - name: labels
      value:
      - "role=master"
    - name: namespace
      value:
      - "chaosblade"
    - name: evict-count
      value:
      - "2"

一些其他实验的模拟可以参考: "POD实验模拟"

3.Node实验场景

Node级别的实验影响面会比较广,因为某个团队的某些pod会分布在目标node上,不是十分可控,此方式建议用在灾难演练(集群中部分Node瘫痪)时使用,这里就不做具体展开介绍了

Node的实验可以参考 "Node实验模拟"

系列文章

"混沌工程: 系统级故障模拟"

"故障注入: 代码级故障模拟"

相关链接

"互动教程"