目前,本人正在中科院软件所的微服务研究组从事部分研究工作。由于本人所在科研小组的研究内容( 微服务自动扩缩容相关 ),需要经常使微服务应用处于"高 CPU 利用率" 和 "高内存使用"的状态。因此,为了方便导师和实验室的各位师兄进行实验,本人特地开发了一个可以注入进 Pod 中的错误注入容器,来模拟上述的高负载状态。
导师和师兄们使用后对我的工作给予了肯定,因此我准备将开发过程和简单使用方法写成文章做个记录( 也就是本文 ),一来方便自己日后工作学习,二来也方便有类似实验需求的其他同仁们使用这个小项目,为大家的研究节省时间。更具体的安装和使用方法,可以移步本项目 Github 的代码仓库,其中有非常详细的说明。
什么是微服务中的"Sidecar 运行模式?"
(以 Sidecar 模式部署并运行的微服务单元)
Sidecar 运行模式是最近两年比较火的一种微服务部署和运行方法,它由目前流行的 ServiceMesh(服务网格) 架构推广而来。
具体而言,Sidecar 运行模式是一种"将不属于业务应用的功能以独立的容器运行于业务容器旁边",在 K8s 中表现出的样子就是将具有不同功能的模块封装成不同的镜像,并以不同的容器运行在同一个 Pod 中。这种说法非常形象,因为 Sidecar 这个单词的本意就是三轮摩托侧面的"跨斗",这里形容独立于业务应用但又与业务应用部署在一起非常合适。
(Sidecar ,中文意思为摩托车的跨斗,不由赞叹命名的非常生动)
架构设计
本项目的错误注入模块也采用了 Sidecar 这种设计思想,将用于模拟 CPU、内存等故障的模块独立封装成一个镜像,并在 Pod 启动时以 Sidecar 的形式运行在主业务容器旁边。这样,不用它时他就会安安静静地当个美男子,完全不用担心它会影响到正常业务的运行;一旦需要它模拟错误产生,由于与业务容器同处于一个 Pod 之中(而 K8s 又以 Pod 为基本单元),因此他模拟出的错误亦被 K8s 集群视为业务应用所在 Pod 产生而被监测到。
(Pod 中的每个容器都有自己的端口映射到外部主机,因此不会相互影响)
注入方式设计
本项目在设计之初是采用“在容器内修改环境变量”的方式对容器注入故障的,但事实证明这种方法太low,而且非常麻烦。因此在后续设计和实现中采用了目前较为流行的通过 REST API 传递 POST 请求的方式使容器模拟错误,这样就极大地方便了师兄们展开实验,而且也可以模拟出“微服务间调用而产生错误”的场景( 上游服务调用错误注入的 API 而模拟下游服务产生错误 )。
多进程模拟故障产生
此外,原本该项目的实现是单进程的,故每注入一个错误都要等待错误结束后才能获得应答并注入下一个错误,这十分不利于实验的进行。因此在后面我们改为了多进程运行,即每当一个错误产生时,程序都会建立一个子进程用于运行错误故障,而原来的进程则立刻产生注入是否成功的应答并继续监听相应的服务端口,从而满足应答实时汇报和多种错误同时注入的功能需求。
(该程序的主要运行流程-以 “A 进程监听服务端口”的状态为起点)
运行
本应用以 Web 服务的方式运行,并封装为镜像保存于 DockerHub 上。因此使用之前需要先以容器的形式运行镜像。以 docker 命令运行本应用的参考命令如下所示:
# 使用 docker 命令简单测试该应用( 测试版本为 docker 18.03-ce )
docker run --rm -it -d --name fault-injection-server -p 5000:5000 xinyaotian/micro-fault-injection:1.0.0
待容器就绪后,访问您启动该容器的主机 IP 的 5000 号端口,如果出现了使用指引界面,就表明您的服务启动成功,可以参考使用说明进行错误注入了。
(为了方便师兄们和大家的使用, 特地在服务首页制作了简易的使用方法指引, 为大家节省查 Github 文档的时间)
错误注入
本项目主要支持四种故障类型: cpu、内存、磁盘和网络,均通过 POST 请求向 /fault-inject 搭配相应参数进行注入。具体的注入方法如下所示( 注意更改 IP 和端口号 ):
1.注入 CPU 故障
# fault_type=cpu 指定错误故障类型(此处为 cpu 类型)
# thread_num=4 触发该错误的线程数(此处为 4 个线程)
# duration=15 故障持续时间,单位为秒(此处为 15 秒)
curl -X POST -d 'fault_type=cpu&thread_num=4&duration=15' http://localhost:5000/fault-inject
2. 注入内存故障
# fault_type=mem 指定错误故障类型(此处为 mem 类型)
# mem_size=120M 指定内存泄露的数值(此处为 120M ,注意 M 不能省略)
# thread_num=4 触发该错误的线程数(此处为 4 个线程)
# duration=15 故障持续时间,单位为秒(此处为 15 秒)
curl -X POST -d 'fault_type=mem&mem_size=120M&thread_num=4&duration=15' http://localhost:5000/fault-inject
3.注入磁盘故障
# fault_type=disk 指定错误故障类型(此处为 disk 类型)
# io_times=4
# duration=15 故障持续时间,单位为秒(此处为 15 秒)
curl -X POST -d 'fault_type=disk&io_times=4&duration=15' http://localhost:5000/fault-inject
4.注入网络故障
# fault_type=net 指定错误故障类型(此处为 net 类型)
# net_port=100
curl -X POST -d 'fault_type=net&net_port=100' http://localhost:5000/fault-inject
本项目的镜像将作为原本微服务应用的 Sidecar 独立部署运行, 因此在 K8s 环境中其应该与业务应用部署于同一个 Pod 之中。
您可以利用 yaml 启动一个单独的 Pod 来初步体验一下这个应用。快读启动的 yaml 如下所示( Istio 同样可以这样写, K8s 版本为 1.13.1, Istio 版本为 1.0.6 ):
---
# 为 fault injection 创建 service 分配端口 #
---
apiVersion: v1
kind: Service
metadata:
name: single-fault-injection
namespace: default
spec:
selector:
# deployment identifier
# 这个标签要与 deployment 中相对应
app: fault-injection
ports:
- protocol: TCP
port: 5000 # 容器内端口为 5000
nodePort: 30050 # 映射至主机的 30050 端口
type: NodePort # 映射方式为 NodePort
---
# 将该模块作为一个独立的 Pod 在 K8s 上进行部署 #
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: single-fault-injection
namespace: default
spec:
replicas: 1
template:
metadata:
labels:
# svc identifier
# 这个标签要与 service 中相对应
app: fault-injection
spec:
containers:
# 错误注入模块的 container
- name: fault-injection-container
image: xinyaotian/micro-fault-injection:1.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5000
# 该容器的资源限制, 错误注入可使其达到峰值 #
resources:
limits:
cpu: "0.4"
memory: 128Mi
如果您希望以 Sidecar 的形式将本应用部署在其他微服务的 Pod 之中同样可行,这也是本项目的设计初衷。在 K8s 环境下部署和启动的 yaml 如下所示意( Istio 同样可以这样写, K8s 版本为 1.13.1, Istio 版本为 1.0.6 ):
---
# 为 fault injection 创建 service 分配端口 #
---
apiVersion: v1
kind: Service
metadata:
name: your-microapp-with-faultinjection
namespace: default
spec:
selector:
# deployment identifier
# 这个标签要与 deployment 中相对应
app: sidecar-fault-injection
ports:
- protocol: TCP
port: 5000
nodePort: 30050
type: NodePort
---
# 相应的 deployment 配置( 与原应用配置在一起 )
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: your-microapp-with-faultinjection
namespace: default
spec:
replicas: 1
template:
metadata:
labels:
# svc identifier
# 这个标签要与 service 中相对应
app: sidecar-fault-injection
spec:
containers:
# 你原来应用的 container 信息
- name: your-micro-app
image: your-docker-name/project:1.0
imagePullPolicy: IfNotPresent
env:
- name: PATH_VALUE
value: "example"
ports:
- containerPort: 80
# ------------------- #
# Sidecar 错误注入模块的 container
- name: fault-injection-sidecar
image: xinyaotian/micro-fault-injection:1.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5000
# ------------------- #
---
在我自己的实验集群上,利用 Istio 启动该服务,并对其注入内存故障后,可以在 Grafana 监控面板上清晰地看到微服务 Pod 的内存忽高忽低,非常有趣。 错误注入的结果如下图所示。
(注入内存故障后, 我自己的 Istio 集群监测微服务资源面板的可视化展现)
项目到这里已经满足了实验室内师兄们绝大部分的科研需求。在之后的迭代中预计还会加入故障的产生速率( 例如 CPU 使用率会呈线性或指数上涨等 ),相应的 API 也会采用向前兼容的形式进行扩充( 添加更多的可选参数,不填则设置为默认值 ),以确保这个版本或之前版本使用者的代码无需改变而可继续使用后续版本。
此外,简单的用户控制面板也会在后续版本中开发。通过提供的用户界面,使用者可以在界面上输入参数并通过按钮进行错误注入,而不再仅仅通过发送 POST 请求( 虽然底层原理还是本地请求 )。相信用户界面会在汇报演示时为导师和师兄们带来诸多便利。
本项目主要以 Sidecar 的方式开发了一个用于错误注入的实验镜像,并通过在 docker 和 k8s 上运行从而达到对微服务单元注入故障的目标,为研究集群自动扩缩容、微服务自动扩缩容等课题提供了前提保障和研究条件。
感谢您的阅读,如果您对这个项目有什么更好的建议,或指出哪里设计有问题,我都会非常欢迎,洗耳恭听。非常希望于读完本文的您进行交流,欢迎您在下方留言。
如果您的科研团队也有类似需求,也非常欢迎与我们进行合作,并对针对本项目提出宝贵的改进意见。