$ kubectl delete all -l app=my-app
版本在实例之间缓慢替换
rollout/rollback 可能需要一定时间
无法控制流量
蓝/绿(blue/green) - 最好用来验证 API 版本问题
蓝/绿发布是版本 2 与版本 1 一起发布,然后流量切换到版本 2,也称为红/黑部署。蓝/绿发布与滚动更新不同,版本 2(绿
) 与版本 1(蓝
)一起部署,在测试新版本满足要求后,然后更新更新 Kubernetes 中扮演负载均衡器角色的 Service 对象,通过替换 label selector 中的版本标签来将流量发送到新版本,如下图所示:
下面是蓝绿发布策略下应用方法的示例图:
在 Kubernetes 中,我们可以用两种方法来实现蓝绿发布,通过单个 Service 对象或者 Ingress 控制器来实现蓝绿发布,实际操作都是类似的,都是通过 label 标签去控制。
实现蓝绿发布的关键点就在于 Service 对象中 label selector 标签的匹配方法,比如我们重新定义版本 1 的资源清单文件 app-v1-single-svc.yaml,文件内容如下:
apiVersion: v1
kind: Service
metadata:
name: my-app
labels:
app: my-app
spec:
type: NodePort
ports:
- name: http
port: 80
targetPort: http
# 注意这里我们匹配 app 和 version 标签,当要切换流量的时候,我们更新 version 标签的值,比如:v2.0.0
selector:
app: my-app
version: v1.0.0
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-v1
labels:
app: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
version: v1.0.0
template:
metadata:
labels:
app: my-app
version: v1.0.0
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9101"
spec:
containers:
- name: my-app
image: containersol/k8s-deployment-strategies
ports:
- name: http
containerPort: 8080
- name: probe
containerPort: 8086
env:
- name: VERSION
value: v1.0.0
livenessProbe:
httpGet:
path: /live
port: probe
initialDelaySeconds: 5
periodSeconds: 5
readinessProbe:
httpGet:
path: /ready
port: probe
periodSeconds: 5
上面定义的资源对象中,最重要的就是 Service 中 label selector 的定义:
selector:
app: my-app
version: v1.0.0
版本 2 的应用定义和以前一样,新建文件 app-v2-single-svc.yaml,文件内容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-v2
labels:
app: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
version: v2.0.0
template:
metadata:
labels:
app: my-app
version: v2.0.0
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9101"
spec:
containers:
- name: my-app
image: containersol/k8s-deployment-strategies
ports:
- name: http
containerPort: 8080
- name: probe
containerPort: 8086
env:
- name: VERSION
value: v2.0.0
livenessProbe:
httpGet:
path: /live
port: probe
initialDelaySeconds: 5
periodSeconds: 5
readinessProbe:
httpGet:
path: /ready
port: probe
periodSeconds: 5
然后按照下面的步骤来验证使用单个 Service 对象实现蓝/绿部署的策略:
版本 1 应用提供服务
部署版本 2 应用
等到版本 2 应用全部部署完成
切换入口流量从版本 1 到版本 2
关闭版本 1 应用
首先,部署版本 1 应用:
$ kubectl apply -f app-v1-single-svc.yaml
service "my-app" created
deployment.apps "my-app-v1" created
测试版本 1 应用是否部署成功:
$ kubectl get pods -l app=my-app
NAME READY STATUS RESTARTS AGE
my-app-v1-7b4874cd75-7xh6s 1/1 Running 0 41s
my-app-v1-7b4874cd75-dmq8f 1/1 Running 0 41s
my-app-v1-7b4874cd75-t64z7 1/1 Running 0 41s
$ kubectl get svc -l app=my-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-app NodePort 10.106.184.144 <none> 80:31539/TCP 50s
$ curl http://127.0.0.1:31539
Host: my-app-v1-7b4874cd75-7xh6s, Version: v1.0.0
同样,新开一个终端,执行如下命令观察 Pod 变化:
$ watch kubectl get pod -l app=my-app
然后部署版本 2 应用:
$ kubectl apply -f app-v2-single-svc.yaml
deployment.apps "my-app-v2" created
然后在上面 watch 终端中可以看到会多 3 个my-app-v2
开头的 Pod,待这些 Pod 部署成功后,我们再去访问当前的应用:
$ while sleep 0.1; do curl http://127.0.0.1:31539; done
Host: my-app-v1-7b4874cd75-dmq8f, Version: v1.0.0
Host: my-app-v1-7b4874cd75-dmq8f, Version: v1.0.0
......
我们会发现访问到的都是版本 1 的应用,和我们刚刚部署的版本 2 没有任何关系,这是因为我们 Service 对象中通过 label selector 匹配的是version=v1.0.0
这个标签,我们可以通过修改 Service 对象的匹配标签,将流量路由到标签version=v2.0.0
的 Pod 去:
$ kubectl patch service my-app -p '{"spec":{"selector":{"version":"v2.0.0"}}}'
service "my-app" patched
然后再去访问应用,可以发现现在都是版本 2 的信息了:
$ while sleep 0.1; do curl http://127.0.0.1:31539; done
Host: my-app-v2-f885c8d45-r5m6z, Version: v2.0.0
Host: my-app-v2-f885c8d45-r5m6z, Version: v2.0.0
......
如果你需要回滚到版本 1,同样只需要更改 Service 的匹配标签即可:
$ kubectl patch service my-app -p '{"spec":{"selector":{"version":"v1.0.0"}}}'
如果新版本已经完全符合我们的需求了,就可以删除版本 1 的应用了:
$ kubectl delete deploy my-app-v1
最后,同样,执行如下命令清理上述资源对象:
$ kubectl delete all -l app=my-app
实时部署/回滚
避免版本问题,因为一次更改是整个应用的改变
需要两倍的资源
在发布到生产之前,应该对整个应用进行适当的测试
金丝雀(Canary) - 让部分用户参与测试
金丝雀部署是让部分用户访问到新版本应用,在 Kubernetes 中,可以使用两个具有相同 Pod 标签的 Deployment 来实现金丝雀部署。新版本的副本和旧版本的一起发布。在一段时间后如果没有检测到错误,则可以扩展新版本的副本数量并删除旧版本的应用。
如果需要按照具体的百分比来进行金丝雀发布,需要尽可能的启动多的 Pod 副本,这样计算流量百分比的时候才方便,比如,如果你想将 1% 的流量发送到版本 B,那么我们就需要有一个运行版本 B 的 Pod 和 99 个运行版本 A 的 Pod,当然如果你对具体的控制策略不在意的话也就无所谓了,如果你需要更精确的控制策略,建议使用服务网格(如 Istio),它们可以更好地控制流量。
在下面的例子中,我们使用 Kubernetes 原生特性来实现一个穷人版的金丝雀发布,如果你想要对流量进行更加细粒度的控制,请使用豪华版本的 Istio。下面是金丝雀发布的应用请求示意图:
接下来我们按照下面的步骤来验证金丝雀策略:
10 个副本的版本 1 应用提供服务
版本 2 应用部署 1 个副本(意味着小于 10%的流量)
等待足够的时间来确认版本 2 应用足够稳定没有任何错误信息
将版本 2 应用扩容到 10 个副本
等待所有实例完成
关闭版本 1 应用
首先,创建版本 1 的应用资源清单,app-v1-canary.yaml,内容如下:
apiVersion: v1
kind: Service
metadata:
name: my-app
labels:
app: my-app
spec:
type: NodePort
ports:
- name: http
port: 80
targetPort: http
selector:
app: my-app
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-v1
labels:
app: my-app
spec:
replicas: 10
selector:
matchLabels:
app: my-app
version: v1.0.0
template:
metadata:
labels:
app: my-app
version: v1.0.0
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9101"
spec:
containers:
- name: my-app
image: containersol/k8s-deployment-strategies
ports:
- name: http
containerPort: 8080
- name: probe
containerPort: 8086
env:
- name: VERSION
value: v1.0.0
livenessProbe:
httpGet:
path: /live
port: probe
initialDelaySeconds: 5
periodSeconds: 5
readinessProbe:
httpGet:
path: /ready
port: probe
periodSeconds: 5
其中核心的部分也是 Service 对象中的 label selector 标签,不在具有版本相关的标签了,然后定义版本 2 的资源清单文件,app-v2-canary.yaml,文件内容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-v2
labels:
app: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
version: v2.0.0
template:
metadata:
labels:
app: my-app
version: v2.0.0
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9101"
spec:
containers:
- name: my-app
image: containersol/k8s-deployment-strategies
ports:
- name: http
containerPort: 8080
- name: probe
containerPort: 8086
env:
- name: VERSION
value: v2.0.0
livenessProbe:
httpGet:
path: /live
port: probe
initialDelaySeconds: 5
periodSeconds: 5
readinessProbe:
httpGet:
path: /ready
port: probe
periodSeconds: 5
版本 1 和版本 2 的 Pod 都具有一个共同的标签app=my-app
,所以对应的 Service 会匹配两个版本的 Pod。
首先,部署版本 1 应用:
$ kubectl apply -f app-v1-canary.yaml
service "my-app" created
deployment.apps "my-app-v1" created
然后测试版本 1 应用是否正确部署了:
$ kubectl get svc -l app=my-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-app NodePort 10.105.133.213 <none> 80:30760/TCP 47s
$ curl http://127.0.0.1:30760
Host: my-app-v1-7b4874cd75-tsh2s, Version: v1.0.0
同样,新开一个终端,查看 Pod 的变化:
然后部署版本 2 应用:
$ kubectl apply -f app-v2-canary.yaml
deployment.apps "my-app-v2" created
然后在 watch 终端页面可以看到多了一个 Pod,现在一共 11 个 Pod,其中只有 1 个 Pod 运行新版本应用,然后同样可以循环访问该应用,查看是否会有版本 2 的应用信息:
$ while sleep 0.1; do curl http://127.0.0.1:30760; done
Host: my-app-v1-7b4874cd75-bhxbp, Version: v1.0.0
Host: my-app-v1-7b4874cd75-wmcqc, Version: v1.0.0
Host: my-app-v1-7b4874cd75-tsh2s, Version: v1.0.0
Host: my-app-v1-7b4874cd75-ml58j, Version: v1.0.0
Host: my-app-v1-7b4874cd75-spsdv, Version: v1.0.0
Host: my-app-v2-f885c8d45-mc2fx, Version: v2.0.0
......
正常情况下可以看到大部分都是返回的版本 1 的应用信息,偶尔会出现版本 2 的应用信息,这就证明我们的金丝雀发布成功了,待确认了版本 2 的这个应用没有任何问题后,可以将版本 2 应用扩容到 10 个副本:
$ kubectl scale --replicas=10 deploy my-app-v2
deployment.extensions "my-app-v2" scaled
其实这个时候访问应用的话新版本和旧版本的流量分配是 1:1 了,确认了版本 2 正常后,就可以删除版本 1 的应用了:
$ kubectl delete deploy my-app-v1
deployment.extensions "my-app-v1" deleted
最终留下的是 10 个新版本的 Pod 了,到这里我们的整个金丝雀发布就完成了。
同样,最后,执行下面的命令删除上面的资源对象:
$ kubectl delete all -l app=my-app
部分用户获取新版本
方便错误和性能监控
流量精准控制很浪费(99%A / 1%B = 99 Pod A,1 Pod B)
如果你对新功能的发布没有信心,建议使用金丝雀发布的策略。
A/B 测试(A/B testing) - 最适合部分用户的功能测试
A/B 测试实际上是一种基于统计信息而非部署策略来制定业务决策的技术,与业务结合非常紧密。但是它们也是相关的,也可以使用金丝雀发布来实现。
除了基于权重在版本之间进行流量控制之外,A/B 测试还可以基于一些其他参数(比如 Cookie、User Agent、地区等等)来精确定位给定的用户群,该技术广泛用于测试一些功能特性的效果,然后按照效果来进行确定。
我们经常可以在今日头条
的客户端中就会发现有大量的 A/B 测试,同一个地区的用户看到的客户端有很大不同。
要使用这些细粒度的控制,仍然还是建议使用 Istio,可以根据权重或 HTTP 头等来动态请求路由控制流量转发。
下面是使用 Istio 进行规则设置的示例,因为 Istio 还不太稳定,以下示例规则将来可能会更改:
route:
- tags:
version: v1.0.0
weight: 90
- tags:
version: v2.0.0
weight: 10
关于在 Istio 中具体如何做 A/B 测试,我们这里就不再详细介绍了,我们在istio-book
文档中有相关的介绍。
几个版本并行运行
完全控制流量分配
特定的一个访问错误难以排查,需要分布式跟踪
Kubernetes 没有直接的支持,需要其他额外的工具
发布应用有许多种方法,当发布到开发/测试环境的时候,重建
或者滚动更新
通常是一个不错的选择。在生产环境,滚动更新
或者蓝绿发布
比较合适,但是新版本的提前测试是非常有必要的。如果你对新版本的应用不是很有信心的话,那应该使用金丝雀
发布,将用户的影响降到最低。最后,如果你的公司需要在特定的用户群体中进行新功能的测试,例如,移动端用户请求路由到版本 A,桌面端用户请求路由到版本 B,那么你就看使用A/B 测试
,通过使用 Kubernetes 服务网关的配置,可以根据某些请求参数来确定用户应路由的服务。
如果您有任何问题或反馈,请随时在下面留言。
本文主要参考链接:https://container-solutions.com/kubernetes-deployment-strategies/
阳明和他朋友们的一些项目