在Kubernetes
中有几种不同的方式发布应用,所以为了让应用在升级期间依然平稳提供服务,选择一个正确的发布策略就非常重要了。
选择正确的部署策略是要依赖于我们的业务需求的,下面我们列出了一些可能会使用到的策略:
A/B测
实际上是一种基于数据统计做出业务决策的技术。在 Kubernetes 中并不原生支持,需要额外的一些高级组件来完成改设置(比如Istio、Linkerd、Traefik、或者自定义 Nginx/Haproxy 等)。接下来我们来介绍下每种策略,看看在什么场景下面适合哪种策略。
重新创建策略是一个虚拟部署,包括关闭版本A,然后在关闭版本A后部署版本B. 此技术意味着服务的停机时间取决于应用程序的关闭和启动持续时间。
Recreate
的Deployment
,会终止所有正在运行的实例,然后用较新的版本来重新创建它们。 spec:
replicas: 3
strategy:
type: Recreate
[root@yygh-de test]# vim app-v1.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
labels:
app: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
strategy:
type: Recreate
selector:
matchLabels:
app: my-app
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
[root@yygh-de test]# vim app-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
app: my-app
spec:
replicas: 3
strategy:
type: Recreate
selector:
matchLabels:
app: my-app
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
VERSION
值不同,接下来按照下面的步骤来验证Recreate
策略:版本1提供服务
[root@yygh-de test]# kubectl apply -f app-v1.yaml
service/my-app created
deployment.apps/my-app created
[root@yygh-de test]# kubectl get pods -l app=my-app
NAME READY STATUS RESTARTS AGE
my-app-5b4755fccf-2k8kj 1/1 Running 0 5m8s
my-app-5b4755fccf-mwpvb 1/1 Running 0 5m8s
my-app-5b4755fccf-qck84 1/1 Running 0 5m8s
[root@yygh-de test]# kubectl get svc my-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-app NodePort 10.96.247.241 <none> 80:32077/TCP 6m19s
[root@yygh-de test]# curl http://127.0.0.1:32077
Host: my-app-5b4755fccf-mwpvb, Version: v1.0.0
[root@yygh-de test]# watch kubectl get pod -l app=my-app
[root@yygh-de test]# kubectl apply -f app-v2.yaml
这个时候可以观察上面新开的终端中的 Pod 列表的变化,可以看到之前的3个 Pod 都会先处于Terminating
状态,并且3个 Pod 都被删除后才开始创建新的 Pod。
[root@yygh-de test]# while sleep 0.1; do curl http://127.0.0.1:32077; done
curl: (7) Failed connect to 127.0.0.1:32532; Connection refused
curl: (7) Failed connect to 127.0.0.1:32532; Connection refused
......
Host: my-app-759bffd84f-wzx2q, Version: v2.0.0
Host: my-app-759bffd84f-ckdlm, Version: v2.0.0
Host: my-app-759bffd84f-c7vnp, Version: v2.0.0
Host: my-app-759bffd84f-ckdlm, Version: v2.0.0
......
可以看到最开始的阶段服务都是处于不可访问的状态,然后到第二个版本的应用部署成功后才正常访问,可以看到现在访问的数据是版本2了。
[root@yygh-de test]# kubectl delete all -l app=my-app
pod "my-app-759bffd84f-c7vnp"deleted
pod "my-app-759bffd84f-ckdlm"deleted
pod "my-app-759bffd84f-wzx2q"deleted
service"my-app"deleted
deployment.apps "my-app"deleted
replicaset.apps "my-app-5b4755fccf"deleted
replicaset.apps "my-app-759bffd84f"deleted
滚动更新通过逐个替换实例来逐步部署新版本的应用,直到所有实例都被替换完成为止。它通常遵循以下过程:在负载均衡器后面使用版本 A 的实例池,然后部署版本 B 的一个实例,当服务准备好接收流量时(Readiness Probe 正常),将该实例添加到实例池中,然后从实例池中删除一个版本 A 的实例并关闭,如下图所示:
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2 # 一次可以添加多少个Pod
maxUnavailable: 1 # 滚动更新期间最大多少个Pod不可用
[root@yygh-de test]# vim app-v2-rolling-update.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
app: my-app
spec:
replicas: 10
# maxUnavailable设置为0可以完全确保在滚动更新期间服务不受影响,还可以使用百分比的值来进行设置。
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: my-app
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
# 初始延迟设置高点可以更好地观察滚动更新过程
initialDelaySeconds: 15
periodSeconds: 5
strategy.type=RollingUpdate
来定义该 Deployment 使用滚动更新的策略来更新应用,接下来我们按下面的步骤来验证滚动更新策略: [root@yygh-de test]# kubectl apply -f app-v1.yaml
service/my-app created
deployment.apps/my-app created
[root@yygh-de test]# kubectl get pods -l app=my-app
NAME READY STATUS RESTARTS AGE
my-app-5b4755fccf-bmwm4 1/1 Running 0 41s
my-app-5b4755fccf-pbg9g 1/1 Running 0 41s
my-app-5b4755fccf-x6f5h 1/1 Running 0 41s
[root@yygh-de test]# kubectl get svc my-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-app NodePort 10.96.250.164 <none> 80:30896/TCP 66s
[root@yygh-de test]# curl http://127.0.0.1:30896
Host: my-app-5b4755fccf-bmwm4, Version: v1.0.0
[root@yygh-de test]# watch kubectl get pod -l app=my-app
[root@yygh-de test]# kubectl apply -f app-v2-rolling-update.yaml
deployment.apps/my-app configured
[root@yygh-de test]# while sleep 0.1; do curl http://127.0.0.1:30896; done
Host: my-app-5b4755fccf-qw4p6, Version: v1.0.0
Host: my-app-5b4755fccf-x6f5h, Version: v1.0.0
Host: my-app-5b4755fccf-qw4p6, Version: v1.0.0
Host: my-app-5b4755fccf-zkwg2, Version: v1.0.0
Host: my-app-7555568f55-qd4sb, Version: v2.0.0
Host: my-app-5b4755fccf-qw4p6, Version: v1.0.0
Host: my-app-5b4755fccf-zkwg2, Version: v1.0.0
Host: my-app-5b4755fccf-m7lxv, Version: v1.0.0
Host: my-app-5b4755fccf-zkwg2, Version: v1.0.0
Host: my-app-7555568f55-hkzfc, Version: v2.0.0
Host: my-app-7555568f55-qd4sb, Version: v2.0.0
Host: my-app-5b4755fccf-x6f5h, Version: v1.0.0
Host: my-app-5b4755fccf-fxmpz, Version: v1.0.0
我们可以看到上面的应用并没有出现不可用的情况,最开始访问到的都是版本1的应用,然后偶尔会出现版本2的应用,直到最后全都变成了版本2的应用,而这个时候看上面 watch 终端中 Pod 已经全部变成10个版本2的应用了,我们可以看到这就是一个逐步替换的过程。
[root@yygh-de test]# kubectl rollout undo deploy my-app
deployment.apps/my-app rolled back
[root@yygh-de test]# kubectl rollout pause deploy my-app
deployment.apps/my-app paused
这个时候我们再去循环访问我们的应用就可以看到偶尔会出现版本1的应用信息了。
[root@yygh-de test]# kubectl rollout resume deploy my-app
deployment.apps/my-app resumed
[root@yygh-de test]# kubectl delete all -l app=my-app
pod "my-app-5b4755fccf-2zqjp"deleted
pod "my-app-5b4755fccf-5r5kx"deleted
pod "my-app-5b4755fccf-bmwm4"deleted
pod "my-app-5b4755fccf-fxmpz"deleted
pod "my-app-5b4755fccf-pbg9g"deleted
pod "my-app-5b4755fccf-qzh6c"deleted
pod "my-app-5b4755fccf-sw8xd"deleted
pod "my-app-5b4755fccf-x6f5h"deleted
pod "my-app-5b4755fccf-zkwg2"deleted
pod "my-app-7555568f55-98zhs"deleted
pod "my-app-7555568f55-hkzfc"deleted
pod "my-app-7555568f55-qd4sb"deleted
service"my-app"deleted
deployment.apps "my-app"deleted
replicaset.apps "my-app-5b4755fccf"deleted
replicaset.apps "my-app-7555568f55"deleted
蓝/绿发布是版本2 与版本1 一起发布,然后流量切换到版本2,也称为红/黑部署。蓝/绿发布与滚动更新不同,版本2(绿
) 与版本1(蓝
)一起部署,在测试新版本满足要求后,然后更新更新 Kubernetes 中扮演负载均衡器角色的 Service 对象,通过替换 label selector 中的版本标签来将流量发送到新版本,如下图所示:
在资源对象中,最重要的就是 Service 中 label selector 的定义
selector:
app: my-app
version: v1.0.0
在 Kubernetes 中,我们可以用两种方法来实现蓝绿发布,通过单个 Service 对象或者 Ingress 控制器来实现蓝绿发布,实际操作都是类似的,都是通过 label 标签去控制。
[root@yygh-de test]# vim 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
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
[root@yygh-de test]# kubectl apply -f app-v1-single-svc.yaml
service/my-app created
deployment.apps/my-app-v1 created
[root@yygh-de test]# kubectl get pods -l app=my-app
NAME READY STATUS RESTARTS AGE
my-app-v1-5b4755fccf-6nvg4 1/1 Running 0 25s
my-app-v1-5b4755fccf-8qtjj 1/1 Running 0 25s
my-app-v1-5b4755fccf-z8kzg 1/1 Running 0 25s
[root@yygh-de test]# kubectl get svc -l app=my-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-app NodePort 10.96.226.55 <none> 80:31905/TCP 43s
[root@yygh-de test]# curl http://127.0.0.1:31905
Host: my-app-v1-5b4755fccf-z8kzg, Version: v1.0.0
[root@yygh-de ~]# watch kubectl get pod -l app=my-app
[root@yygh-de test]# kubectl apply -f app-v2-single-svc.yaml
deployment.apps/my-app-v2 created
my-app-v2
开头的 Pod,待这些 Pod 部署成功后,我们再去访问当前的应用 [root@yygh-de test]# while sleep 0.1; do curl http://127.0.0.1:31905; done
Host: my-app-v1-5b4755fccf-6nvg4, Version: v1.0.0
Host: my-app-v1-5b4755fccf-6nvg4, Version: v1.0.0
Host: my-app-v1-5b4755fccf-z8kzg, Version: v1.0.0
Host: my-app-v1-5b4755fccf-8qtjj, Version: v1.0.0
Host: my-app-v1-5b4755fccf-z8kzg, Version: v1.0.0
我们会发现访问到的都是版本1 的应用,和我们刚刚部署的版本2 没有任何关系,这是因为我们 Service 对象中通过 label selector 匹配的是version=v1.0.0
这个标签,我们可以通过修改 Service 对象的匹配标签,将流量路由到标签version=v2.0.0
的 Pod 去:
[root@yygh-de test]# kubectl patch service my-app -p '{"spec":{"selector":{"version":"v2.0.0"}}}'
service/my-app patched
[root@yygh-de test]# while sleep 0.1; do curl http://127.0.0.1:31905; done
Host: my-app-v2-759bffd84f-zxkf2, Version: v2.0.0
Host: my-app-v2-759bffd84f-zxkf2, Version: v2.0.0
Host: my-app-v2-759bffd84f-lm45n, Version: v2.0.0
Host: my-app-v2-759bffd84f-zxkf2, Version: v2.0.0
Host: my-app-v2-759bffd84f-lm45n, Version: v2.0.0
Host: my-app-v2-759bffd84f-zxkf2, Version: v2.0.0
Host: my-app-v2-759bffd84f-zxkf2, Version: v2.0.0
Host: my-app-v2-759bffd84f-lm45n, Version: v2.0.0
Host: my-app-v2-759bffd84f-zxkf2, Version: v2.0.0
Host: my-app-v2-759bffd84f-lm45n, Version: v2.0.0
[root@yygh-de test]# kubectl patch service my-app -p '{"spec":{"selector":{"version":"v1.0.0"}}}'
service/my-app patched
[root@yygh-de test]# kubectl delete deploy my-app-v1
deployment.apps "my-app-v1"deleted
[root@yygh-de test]# kubectl delete all -l app=my-app
pod "my-app-v2-759bffd84f-lm45n"deleted
pod "my-app-v2-759bffd84f-wjtvk"deleted
pod "my-app-v2-759bffd84f-zxkf2"deleted
service"my-app"deleted
deployment.apps "my-app-v2"deleted
replicaset.apps "my-app-v2-759bffd84f"deleted
金丝雀部署是让部分用户访问到新版本应用,在 Kubernetes 中,可以使用两个具有相同 Pod 标签的 Deployment 来实现金丝雀部署。新版本的副本和旧版本的一起发布。在一段时间后如果没有检测到错误,则可以扩展新版本的副本数量并删除旧版本的应用。
如果需要按照具体的百分比来进行金丝雀发布,需要尽可能的启动多的 Pod 副本,这样计算流量百分比的时候才方便,比如,如果你想将 1% 的流量发送到版本 B,那么我们就需要有一个运行版本 B 的 Pod 和 99 个运行版本 A 的 Pod,当然如果你对具体的控制策略不在意的话也就无所谓了,如果你需要更精确的控制策略,建议使用服务网格(如 Istio),它们可以更好地控制流量。
在下面的例子中,我们使用 Kubernetes 原生特性来实现一个穷人版的金丝雀发布,如果你想要对流量进行更加细粒度的控制,请使用豪华版本的 Istio。
[root@yygh-de test]# vim 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
[root@yygh-de test]# vim 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。
[root@yygh-de test]# kubectl apply -f app-v1-canary.yaml
service/my-app created
deployment.apps/my-app-v1 created
[root@yygh-de test]# kubectl get svc -l app=my-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-app NodePort 10.96.39.92 <none> 80:30927/TCP 25s
[root@yygh-de test]# curl http://127.0.0.1:30927
Host: my-app-v1-5b4755fccf-dfzwx, Version: v1.0.0
[root@yygh-de ~]# watch kubectl get pod
[root@yygh-de test]# kubectl apply -f app-v2-canary.yaml
deployment.apps/my-app-v2 created
[root@yygh-de test]# while sleep 0.1; do curl http://127.0.0.1:30927; done
Host: my-app-v1-5b4755fccf-n84kt, Version: v1.0.0
Host: my-app-v1-5b4755fccf-dfzwx, Version: v1.0.0
Host: my-app-v1-5b4755fccf-p2mh2, Version: v1.0.0
Host: my-app-v1-5b4755fccf-cvx2f, Version: v1.0.0
Host: my-app-v1-5b4755fccf-ntbf8, Version: v1.0.0
Host: my-app-v1-5b4755fccf-ntbf8, Version: v1.0.0
Host: my-app-v1-5b4755fccf-cvx2f, Version: v1.0.0
Host: my-app-v1-5b4755fccf-qjqbg, Version: v1.0.0
Host: my-app-v1-5b4755fccf-6qvb6, Version: v1.0.0
Host: my-app-v2-759bffd84f-lv8wc, Version: v2.0.0
Host: my-app-v1-5b4755fccf-dfzwx, Version: v1.0.0
Host: my-app-v1-5b4755fccf-n84kt, Version: v1.0.0
Host: my-app-v2-759bffd84f-lv8wc, Version: v2.0.0
Host: my-app-v1-5b4755fccf-p2mh2, Version: v1.0.0
Host: my-app-v1-5b4755fccf-x9csb, Version: v1.0.0
Host: my-app-v2-759bffd84f-lv8wc, Version: v2.0.0
[root@yygh-de test]# kubectl scale --replicas=10 deploy my-app-v2
deployment.apps/my-app-v2 scaled
[root@yygh-de test]# kubectl delete deploy my-app-v1
deployment.apps "my-app-v1"deleted
# 最终留下的是 10 个新版本的 Pod 了,到这里我们的整个金丝雀发布就完成了。
[root@yygh-de test]# kubectl delete all -l app=my-app
pod "my-app-v2-759bffd84f-4vr6p"deleted
pod "my-app-v2-759bffd84f-69fwj"deleted
pod "my-app-v2-759bffd84f-7w24k"deleted
pod "my-app-v2-759bffd84f-d8wwz"deleted
pod "my-app-v2-759bffd84f-flptr"deleted
pod "my-app-v2-759bffd84f-gznbk"deleted
pod "my-app-v2-759bffd84f-lv8wc"deleted
pod "my-app-v2-759bffd84f-q8rcn"deleted
pod "my-app-v2-759bffd84f-smbk5"deleted
pod "my-app-v2-759bffd84f-t6q9q"deleted
service"my-app"deleted
deployment.apps "my-app-v2"deleted
replicaset.apps "my-app-v2-759bffd84f"deleted
如果你对新功能的发布没有信心,建议使用金丝雀发布的策略。
A/B 测试实际上是一种基于统计信息而非部署策略来制定业务决策的技术,与业务结合非常紧密。但是它们也是相关的,也可以使用金丝雀发布来实现。
除了基于权重在版本之间进行流量控制之外,A/B 测试还可以基于一些其他参数(比如 Cookie、User Agent、地区等等)来精确定位给定的用户群,该技术广泛用于测试一些功能特性的效果,然后按照效果来进行确定。
我们经常可以在
今日头条
的客户端中就会发现有大量的 A/B 测试,同一个地区的用户看到的客户端有很大不同。
要使用这些细粒度的控制,仍然还是建议使用 Istio,可以根据权重或 HTTP 头等来动态请求路由控制流量转发。
route:
-tags:
version: v1.0.0
weight: 90
-tags:
version: v2.0.0
weight: 10
在本示例中,使用Istio 1.0.0。要安装Istio,请遵循Istio网站上的 说明。
默认情况下应启用自动边车注入。然后注释默认名称空间以启用它。
[root@yygh-de AB]# kubectl label namespace default istio-injection=enabled
namespace/default labeled
[root@yygh-de AB]# vim app-v1.yaml
apiVersion: v1
kind: Service
metadata:
name: my-app-v1
labels:
app: my-app
spec:
ports:
-name: http
port: 80
targetPort: http
selector:
app: my-app
version: v1.0.0
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-v1
labels:
app: my-app
spec:
replicas: 1
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
# 创建应用资源清单app-v2.yaml
[root@yygh-de AB]# vim app-v2.yaml
apiVersion: v1
kind: Service
metadata:
name: my-app-v2
labels:
app: my-app
spec:
ports:
-name: http
port: 80
targetPort: http
selector:
app: my-app
version: v2.0.0
---
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
返回此仓库中的a / b测试目录,使用istioctl命令部署两个应用程序,以注入用于代理请求的Istio sidecar容器:
[root@yygh-de AB]# kubectl apply -f app-v1.yaml -f app-v2.yaml
service/my-app-v1 created
deployment.apps/my-app-v1 created
service/my-app-v2 created
deployment.apps/my-app-v2 created
[root@yygh-de AB]# vim gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: my-app
labels:
app: my-app
spec:
servers:
-port:
number: 80
name: http
protocol: HTTP
hosts:
-my-app.local
----------------------------------------------------------------------------------------
[root@yygh-de AB]# vim virtualservice.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: my-app
labels:
app: my-app
spec:
hosts:
-my-app.local
gateways:
-my-app
http:
-route:
-destination:
host: my-app-v1
my-app.local
应该只看到版本1做出响应 [root@yygh-de AB]# curl $(minikube service istio-ingressgateway -n istio-system --url | head -n1) -H 'Host: my-app.local'
Host: my-app-v1-6d577d97b4-lxn22, Version: v1.0.0
根据权重应用Istio VirtualService规则:
[root@yygh-de AB]# vim virtualservice-weight.yaml # 您可以编辑每个目标的权重并将更新后的规则应用于Minikube
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: my-app
labels:
app: my-app
spec:
hosts:
-my-app.local
gateways:
-my-app
http:
-route:
-destination:
host: my-app-v1
weight: 90
-destination:
host: my-app-v2
weight: 10
[root@yygh-de AB]# service=$(minikube service istio-ingressgateway -n istio-system --url | head -n1)
# 您应该大约在10个版本2中看到1个请求
[root@yygh-de AB]# while sleep 0.1; do curl "$service" -H 'Host: my-app.local'; done
根据标题应用Istio VirtualService规则:
[root@yygh-de AB]# vim virtualservice-match.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: my-app
labels:
app: my-app
spec:
hosts:
-my-app.local
gateways:
-my-app
http:
-route:
-destination:
host: my-app-v1
match:
-headers:
x-api-version:
exact: v1.0.0
-route:
-destination:
host: my-app-v2
match:
-headers:
x-api-version:
exact: v2.0.0
[root@yygh-de AB]# service=$(minikube service istio-ingressgateway -n istio-system --url | head -n1)
[root@yygh-de AB]# curl $service -H 'Host: my-app.local' -H 'X-API-Version: v1.0.0'
Host: my-app-v1-6d577d97b4-s4h6k, Version: v1.0.0
[root@yygh-de AB]# curl $service -H 'Host: my-app.local' -H 'X-API-Version: v2.0.0'
Host: my-app-v2-65f9fdbb88-jtctt, Version: v2.0.0
[root@yygh-de AB]# kubectl delete gateway/my-app virtualservice/my-app
[root@yygh-de AB]# kubectl delete -f ./app-v1.yaml -f ./app-v2.yaml
[root@yygh-de AB]# kubectl delete -f <PATH-TO-ISTIO>/install/kubernetes/istio-demo.yaml
发布应用有许多种方法,当发布到开发/测试环境的时候,重建
或者滚动更新
通常是一个不错的选择。在生产环境,滚动更新
或者蓝绿发布
比较合适,但是新版本的提前测试是非常有必要的。如果你对新版本的应用不是很有信心的话,那应该使用金丝雀
发布,将用户的影响降到最低。最后,如果你的公司需要在特定的用户群体中进行新功能的测试,例如,移动端用户请求路由到版本 A,桌面端用户请求路由到版本 B,那么你就看使用A/B 测试
,通过使用 Kubernetes 服务网关的配置,可以根据某些请求参数来确定用户应路由的服务。