作者:Alex Leong
应用程序故障注入(failure injection)是混沌工程(chaos engineering)的形式之一,我们在其中人为地增加微服务应用程序中某些服务的错误率,以查看这对整个系统有什么影响。传统上,你需要在服务代码中添加某种类型的故障注入库,以便进行应用程序故障注入。值得庆幸的是,服务网格为我们提供了一种注入应用程序故障的方法,而无需修改或重新构建我们的服务。
结构良好的微服务应用程序的一个特点,是它能够优雅地容忍单个服务的失败。当这些故障以服务崩溃的形式出现时,Kubernetes通过创建新的pod来替换已经崩溃的pod,在治愈这些故障方面做得非常好。然而,失败也可能更加微妙,导致服务返回更高的错误率。这些类型的故障不能由Kubernetes自动修复,但仍然会导致功能损失。
使用流量分割SMI API注入故障
通过使用服务网格接口(Service Mesh Interface)的流量分割API(Traffic Split API),我们可以很容易地注入应用程序故障。这允许我们以一种与实现无关、跨服务网格工作的方式进行故障注入。
为此,我们首先部署一个只返回错误的新服务。这可以像配置为返回HTTP 500响应的NGINX服务一样简单,也可以是更复杂的服务,返回专门设计的错误,以执行你希望测试的某些条件。然后,我们创建一个流量分割资源,该资源指示服务网格将目标服务流量的百分比发送到错误服务。例如,通过将服务流量的10%发送给错误服务,我们向该服务注入了一个人工的10%故障率。
让我们来看一个使用Linkerd作为服务网格实现的实例。
例子
我们将首先安装Linkerd CLI,并将其部署到我们的Kubernetes集群:
> curl https://run.linkerd.io/install | sh
> export PATH=$PATH:$HOME/.linkerd2/bin
> linkerd install | kubectl apply -f -
> linkerd check
现在我们将安装booksapp示例应用程序:
> linkerd inject https://run.linkerd.io/booksapp.yml | kubectl apply -f -
此应用程序中的一个服务配置了错误率。这个演示的重点,是表明我们可以在应用程序中不需要任何支持就可以注入故障,所以让我们删除配置的故障率:
> kubectl edit deploy/authors
# Find and remove these lines:
# - name: FAILURE_RATE
# value: "0.5"
我们现在应该看到应用程序是健康的:
> linkerd stat deploy
NAME MESHED SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99 TCP_CONN
authors 1/1 100.00% 6.6rps 3ms 58ms 92ms 6
books 1/1 100.00% 8.0rps 4ms 81ms 119ms 6
traffic 1/1 - - - - - -
webapp 3/3 100.00% 7.7rps 24ms 91ms 117ms 9
现在我们可以创建错误服务了。在这里,我将使用NGINX配置为只响应HTTP状态码500。创建一个名为error-injector.yaml的文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: error-injector
labels:
app: error-injector
spec:
selector:
matchLabels:
app: error-injector
replicas: 1
template:
metadata:
labels:
app: error-injector
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
name: nginx
protocol: TCP
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: nginx-config
configMap:
name: error-injector-config
---
apiVersion: v1
kind: Service
metadata:
labels:
app: error-injector
name: error-injector
spec:
clusterIP: None
ports:
- name: service
port: 7002
protocol: TCP
targetPort: nginx
selector:
app: error-injector
type: ClusterIP
---
apiVersion: v1
data:
nginx.conf: |2
events {
worker_connections 1024;
}
http {
server {
location / {
return 500;
}
}
}
kind: ConfigMap
metadata:
name: error-injector-config
和部署它:
> kubectl apply -f error-injector.yaml
现在我们可以创建一个流量分割资源,它将把10%的图书服务定向到错误服务。创建一个名为error-split.yaml的文件:
apiVersion: split.smi-spec.io/v1alpha1
kind: TrafficSplit
metadata:
name: error-split
spec:
service: books
backends:
- service: books
weight: 900m
- service: error-injector
weight: 100m
和部署它:
> kubectl apply -f error-split.yaml
我们现在可以看到从webapp调用到书籍10%的故障率:
> linkerd routes deploy/webapp --to service/books
ROUTE SERVICE SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99
[DEFAULT] books 90.66% 6.6rps 5ms 80ms 96ms
我们还可以看到应用程序如何优雅地处理这些故障:
> kubectl port-forward deploy/webapp 7000 &
> open http://localhost:7000
看起来其实不太好!如果刷新页面几次,有时会看到内部服务器错误页面。
我们学习了一些有价值的东西,关于我们的应用程序如何面对服务错误。让我们恢复我们的应用程序,只需删除流量分割资源:
> kubectl delete trafficsplit/error-split
总结
在本文中,通过使用SMI API(由Linkerd提供)将一部分流量动态重定向到一个简单的“总是失败”目的地,我们演示了在服务级别进行故障注入的快速而简单的方法。这种方法的美妙之处在于,我们能够完全通过SMI API来完成它,而不需要更改任何应用程序代码。
当然,故障注入是一个广泛的主题,还有许多更复杂的方法来注入故障,包括某些路由故障、只匹配特定条件的请求故障或在整个应用程序拓扑中传播单个“毒丸”请求。这些类型的故障注入将需要比这篇文章所涵盖的更多的设定。
Linkerd是一个社区项目,由CNCF(Cloud Native Computing Foundation,云原生计算基金会)托管。如果你有功能需求、问题或评论,我们欢迎你加入我们快速增长的社区!Linkerd代码托管在GitHub上,我们在Slack、Twitter和邮件列表上都有一个蓬勃发展的社区。来一起玩吧!