在微服务的架构中,随着业务迭代的加速和服务数量的增加,每一次服务的更新发布都存在潜在的故障风险,对系统的可靠性带来挑战。如何降低新版本发布对业务的影响,让更新更加可控成为微服务治理的重要工作之一。
通过灰度发布,通过对流量的细粒度控制,可以控制新版本发布影响的范围。比如可以控制新版本只作用于少部分流量,或者只在某些用户中试用,然后边扩大范围边测试,逐步地将新版本覆盖所有流量。
灰度发布的实现,底层实际上是对流量进行拆分。就像负载均衡一样,将流量均衡的拆分到上游实例中。灰度发布则是将这种拆分控制得更加精细,今天就来介绍如何使用服务网格来实现精细的流量拆分。
相比于在微服务 SDK 实现灰度发布,服务网格将该能力卸载到了 sidecar 中,解耦带来的显著优势这里不在过多赘述。
演示
说明
在今天的演示中,我们使用两种应用 curl 以及 Pipy 实现的 httpbin 分别作为客户端和服务端。服务有两个版本 v1 和 v2,通过部署 httpbin-v1 和 httpbin-v2 来模拟版本发布中的新旧两个版本。
可能细心的看客有留意到在演示中经常使用 Pipy 来实现 httpbin 的功能,得益于使用 Pipy 实现的 web 服务可以很容易地定制响应内容,方便观察测试结果。
前置条件
安装服务网格
下载 osm-edge CLI。
system=$(uname -s | tr [:upper:] [:lower:])
arch=$(dpkg --print-architecture)
release=v1.3.4
curl -L https://github.com/flomesh-io/osm-edge/releases/download/${release}/osm-edge-${release}-${system}-${arch}.tar.gz | tar -vxzf -
./${system}-${arch}/osm version
cp ./${system}-${arch}/osm /usr/local/bin/
复制代码
安装服务网格,并等待所有组件成功运行。
osm install --timeout 120s
复制代码
部署示例应用
应用 curl 和 httpbin 运行在各自的命名空间下,且命名空间通过命令 osm namespace add xxx
交由服务网格纳管。
kubectl create ns httpbin
kubectl create ns curl
osm namespace add httpbin curl
复制代码
部署 v1 版本的 httpbin,这个版本对所有 HTTP 请求返回 Hi, I am v1!
。其他应用访问通过 Service httpbin
来访问 httpbin。
kubectl apply -n httpbin -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: httpbin
spec:
ports:
- name: pipy
port: 8080
targetPort: 8080
protocol: TCP
selector:
app: pipy
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin-v1
labels:
app: pipy
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: pipy
version: v1
template:
metadata:
labels:
app: pipy
version: v1
spec:
containers:
- name: pipy
image: flomesh/pipy:latest
ports:
- name: pipy
containerPort: 8080
command:
- pipy
- -e
- |
pipy()
.listen(8080)
.serveHTTP(new Message('Hi, I am v1!\n'))
EOF
复制代码
部署 curl 应用。
kubectl apply -n curl -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: curl
labels:
app: curl
service: curl
spec:
ports:
- name: http
port: 80
selector:
app: curl
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: curl
spec:
replicas: 1
selector:
matchLabels:
app: curl
template:
metadata:
labels:
app: curl
spec:
containers:
- image: curlimages/curl
imagePullPolicy: IfNotPresent
name: curl
command: ["sleep", "365d"]
EOF
复制代码
等待所有应用成功运行。
kubectl wait --for=condition=ready pod --all -A
复制代码
测试应用访问。
curl_client="$(kubectl get pod -n curl -l app=curl -o jsonpath='{.items[0].metadata.name}')"
# 这里发送 4 个请求
kubectl exec "$curl_client" -n curl -c curl -- curl -s httpbin.httpbin:8080 httpbin.httpbin:8080 httpbin.httpbin:8080 httpbin.httpbin:8080
复制代码
可以看到如下结果。
Hi, I am v1!
Hi, I am v1!
Hi, I am v1!
Hi, I am v1!
复制代码
接下来部署 v2 版本的 httpbin。
部署 v2 版本
v2 版本的 httpbin 对所有 HTTP 请求返回 Hi, I am v2!
。在部署之前,我们需要设置默认的流量拆分策略,否则通过 Service httpbin
可以访问到新版本的实例。
创建 Service httpbin-v1
,其标签选择器相比 Service httpbin
多了 version
标签。当前二者有相同的 endpoints。
kubectl apply -n httpbin -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: httpbin-v1
spec:
ports:
- name: pipy
port: 8080
targetPort: 8080
protocol: TCP
selector:
app: pipy
version: v1
EOF
复制代码
应用下面的 TrafficSplit
拆分策略,将所有流量都拆分到 httpbin-v1
。
kubectl apply -n httpbin -f - <<EOF
apiVersion: split.smi-spec.io/v1alpha4
kind: TrafficSplit
metadata:
name: httpbin-split
spec:
service: httpbin
backends:
- service: httpbin-v1
weight: 100
EOF
复制代码
然后部署新版本。
kubectl apply -n httpbin -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: httpbin-v2
spec:
ports:
- name: pipy
port: 8080
targetPort: 8080
protocol: TCP
selector:
app: pipy
version: v2
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin-v2
labels:
app: pipy
version: v2
spec:
replicas: 1
selector:
matchLabels:
app: pipy
version: v2
template:
metadata:
labels:
app: pipy
version: v2
spec:
containers:
- name: pipy
image: flomesh/pipy:latest
ports:
- name: pipy
containerPort: 8080
command:
- pipy
- -e
- |
pipy()
.listen(8080)
.serveHTTP(new Message('Hi, I am v2!\n'))
EOF
复制代码
等待新版本成功运行。
kubectl wait --for=condition=ready pod -n httpbin -l version=v2
复制代码
此时再次发送请求,可以看到处理请求的仍然是 v1 版本的 httpbin。
kubectl exec "$curl_client" -n curl -c curl -- curl -s httpbin.httpbin:8080 httpbin.httpbin:8080 httpbin.httpbin:8080 httpbin.httpbin:8080
Hi, I am v1!
Hi, I am v1!
Hi, I am v1!
Hi, I am v1!
复制代码
灰度发布
接下来,修改拆分的策略将 25% 的流量拆分到 v2 版本。
kubectl apply -n httpbin -f - <<EOF
apiVersion: split.smi-spec.io/v1alpha4
kind: TrafficSplit
metadata:
name: httpbin-split
spec:
service: httpbin
backends:
- service: httpbin-v1
weight: 75
- service: httpbin-v2
weight: 25
EOF
复制代码
再次发送请求,可以看到有 1/4 的流量是有 v2 版本处理的。
kubectl exec "$curl_client" -n curl -c curl -- curl -s httpbin.httpbin:8080 httpbin.httpbin:8080 httpbin.httpbin:8080 httpbin.httpbin:8080
Hi, I am v1!
Hi, I am v2!
Hi, I am v1!
Hi, I am v1!
复制代码
灰度发布进阶
假如 v2 版本中,更新的是 /test
端点的功能。出于风险控制,我们希望只有部分流量访问 v2 版本的 /test
端点,其他端点如 /demo
只访问 v1
版本。
此时我们需要引入另一个资源来对访问 /test
的流量进行定义:定义路由。这里定义了两种路由:
kubectl apply -n httpbin -f - <<EOF
apiVersion: specs.smi-spec.io/v1alpha4
kind: HTTPRouteGroup
metadata:
name: httpbin-test
spec:
matches:
- name: test
pathRegex: "/test"
methods:
- GET
---
apiVersion: specs.smi-spec.io/v1alpha4
kind: HTTPRouteGroup
metadata:
name: httpbin-all
spec:
matches:
- name: test
pathRegex: ".*"
methods:
- GET
EOF
复制代码
然后更新流量拆分策略,将路由关联到拆分策略中。同时创建新的策略
kubectl apply -n httpbin -f - <<EOF
apiVersion: split.smi-spec.io/v1alpha4
kind: TrafficSplit
metadata:
name: httpbin-split
spec:
service: httpbin
matches:
- name: httpbin-test
kind: HTTPRouteGroup
backends:
- service: httpbin-v1
weight: 75
- service: httpbin-v2
weight: 25
---
apiVersion: split.smi-spec.io/v1alpha4
kind: TrafficSplit
metadata:
name: httpbin-all
spec:
service: httpbin
matches:
- name: httpbin-all
kind: HTTPRouteGroup
backends:
- service: httpbin-v1
weight: 100
EOF
复制代码
此时尝试访问 /test
端点,只有 25% 的流量由新版本处理。
kubectl exec "$curl_client" -n curl -c curl -- curl -s httpbin.httpbin:8080/test httpbin.httpbin:8080/test httpbin.httpbin:8080/test httpbin.httpbin:8080/test
Hi, I am v1!
Hi, I am v2!
Hi, I am v1!
Hi, I am v1!
复制代码
再请求下 /demo
端点,可以看到全部流量都去到了旧的 v1 版本。
kubectl exec "$curl_client" -n curl -c curl -- curl -s httpbin.httpbin:8080/demo httpbin.httpbin:8080/demo httpbin.httpbin:8080/demo httpbin.httpbin:8080/demo
Hi, I am v1!
Hi, I am v1!
Hi, I am v1!
Hi, I am v1!
复制代码
符合预期。
引用链接
[1]
Pipy: https://github.com/flomesh-io/pipy
评论