在微服务的架构中,随着业务迭代的加速和服务数量的增加,每一次服务的更新发布都存在潜在的故障风险,对系统的可靠性带来挑战。如何降低新版本发布对业务的影响,让更新更加可控成为微服务治理的重要工作之一。
通过灰度发布,通过对流量的细粒度控制,可以控制新版本发布影响的范围。比如可以控制新版本只作用于少部分流量,或者只在某些用户中试用,然后边扩大范围边测试,逐步地将新版本覆盖所有流量。
灰度发布的实现,底层实际上是对流量进行拆分。就像负载均衡一样,将流量均衡的拆分到上游实例中。灰度发布则是将这种拆分控制得更加精细,今天就来介绍如何使用服务网格来实现精细的流量拆分。
相比于在微服务 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.4curl -L https://github.com/flomesh-io/osm-edge/releases/download/${release}/osm-edge-${release}-${system}-${arch}.tar.gz | tar -vxzf -./${system}-${arch}/osm versioncp ./${system}-${arch}/osm /usr/local/bin/
复制代码
安装服务网格,并等待所有组件成功运行。
osm install --timeout 120s
复制代码
部署示例应用
应用 curl 和 httpbin 运行在各自的命名空间下,且命名空间通过命令 osm namespace add xxx 交由服务网格纳管。
kubectl create ns httpbinkubectl create ns curlosm namespace add httpbin curl
复制代码
部署 v1 版本的 httpbin,这个版本对所有 HTTP 请求返回 Hi, I am v1!。其他应用访问通过 Service httpbin 来访问 httpbin。
kubectl apply -n httpbin -f - <<EOFapiVersion: v1kind: Servicemetadata: name: httpbinspec: ports: - name: pipy port: 8080 targetPort: 8080 protocol: TCP selector: app: pipy---apiVersion: apps/v1kind: Deploymentmetadata: name: httpbin-v1 labels: app: pipy version: v1spec: 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 - <<EOFapiVersion: v1kind: Servicemetadata: name: curl labels: app: curl service: curlspec: ports: - name: http port: 80 selector: app: curl---apiVersion: apps/v1kind: Deploymentmetadata: name: curlspec: 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 - <<EOFapiVersion: v1kind: Servicemetadata: name: httpbin-v1spec: ports: - name: pipy port: 8080 targetPort: 8080 protocol: TCP selector: app: pipy version: v1EOF
复制代码
应用下面的 TrafficSplit 拆分策略,将所有流量都拆分到 httpbin-v1。
kubectl apply -n httpbin -f - <<EOFapiVersion: split.smi-spec.io/v1alpha4kind: TrafficSplitmetadata: name: httpbin-splitspec: service: httpbin backends: - service: httpbin-v1 weight: 100EOF
复制代码
然后部署新版本。
kubectl apply -n httpbin -f - <<EOFapiVersion: v1kind: Servicemetadata: name: httpbin-v2spec: ports: - name: pipy port: 8080 targetPort: 8080 protocol: TCP selector: app: pipy version: v2---apiVersion: apps/v1kind: Deploymentmetadata: name: httpbin-v2 labels: app: pipy version: v2spec: 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:8080Hi, I am v1!Hi, I am v1!Hi, I am v1!Hi, I am v1!
复制代码
灰度发布
接下来,修改拆分的策略将 25% 的流量拆分到 v2 版本。
kubectl apply -n httpbin -f - <<EOFapiVersion: split.smi-spec.io/v1alpha4kind: TrafficSplitmetadata: name: httpbin-splitspec: service: httpbin backends: - service: httpbin-v1 weight: 75 - service: httpbin-v2 weight: 25EOF
复制代码
再次发送请求,可以看到有 1/4 的流量是有 v2 版本处理的。
kubectl exec "$curl_client" -n curl -c curl -- curl -s httpbin.httpbin:8080 httpbin.httpbin:8080 httpbin.httpbin:8080 httpbin.httpbin:8080Hi, 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 - <<EOFapiVersion: specs.smi-spec.io/v1alpha4kind: HTTPRouteGroupmetadata: name: httpbin-testspec: matches: - name: test pathRegex: "/test" methods: - GET---apiVersion: specs.smi-spec.io/v1alpha4kind: HTTPRouteGroupmetadata: name: httpbin-allspec: matches: - name: test pathRegex: ".*" methods: - GETEOF
复制代码
然后更新流量拆分策略,将路由关联到拆分策略中。同时创建新的策略
kubectl apply -n httpbin -f - <<EOFapiVersion: split.smi-spec.io/v1alpha4kind: TrafficSplitmetadata: name: httpbin-splitspec: service: httpbin matches: - name: httpbin-test kind: HTTPRouteGroup backends: - service: httpbin-v1 weight: 75 - service: httpbin-v2 weight: 25---apiVersion: split.smi-spec.io/v1alpha4kind: TrafficSplitmetadata: name: httpbin-allspec: service: httpbin matches: - name: httpbin-all kind: HTTPRouteGroup backends: - service: httpbin-v1 weight: 100EOF
复制代码
此时尝试访问 /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/testHi, 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/demoHi, I am v1!Hi, I am v1!Hi, I am v1!Hi, I am v1!
复制代码
符合预期。
引用链接
[1] Pipy: https://github.com/flomesh-io/pipy
评论