Kubernetes 部署策略
在Kubernetes中有幾種不同的方式發(fā)布應(yīng)用蛋欣,所以為了讓應(yīng)用在升級(jí)期間依然平穩(wěn)提供服務(wù)航徙,選擇一個(gè)正確的發(fā)布策略就非常重要了。
選擇正確的部署策略是要依賴于我們的業(yè)務(wù)需求的陷虎,下面我們列出了一些可能會(huì)使用到的策略:
- 重建(recreate):停止舊版本部署新版本
- 滾動(dòng)更新(rolling-update):一個(gè)接一個(gè)地以滾動(dòng)更新方式發(fā)布新版本
- 藍(lán)綠(blue/green):新版本與舊版本一起存在到踏,然后切換流量
- 金絲雀(canary):將新版本面向一部分用戶發(fā)布,然后繼續(xù)全量發(fā)布
- A/B測(cè)(a/b testing):以精確的方式(HTTP 頭尚猿、cookie窝稿、權(quán)重等)向部分用戶發(fā)布新版本。A/B測(cè)實(shí)際上是一種基于數(shù)據(jù)統(tǒng)計(jì)做出業(yè)務(wù)決策的技術(shù)谊路。在 Kubernetes 中并不原生支持讹躯,需要額外的一些高級(jí)組件來(lái)完成改設(shè)置(比如Istio、Linkerd、Traefik潮梯、或者自定義 Nginx/Haproxy 等)骗灶。
重建(Recreate) - 最好在開發(fā)環(huán)境
策略定義為Recreate
的Deployment
,會(huì)終止所有正在運(yùn)行的實(shí)例秉馏,然后用較新的版本來(lái)重新創(chuàng)建它們耙旦。
spec:
replicas: 3
strategy:
type: Recreate
重新創(chuàng)建策略是一個(gè)虛擬部署,包括關(guān)閉版本A萝究,然后在關(guān)閉版本A后部署版本B. 此技術(shù)意味著服務(wù)的停機(jī)時(shí)間取決于應(yīng)用程序的關(guān)閉和啟動(dòng)持續(xù)時(shí)間免都。
我們這里創(chuàng)建兩個(gè)相關(guān)的資源清單文件,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
app-v2.yaml 文件內(nèi)容如下:
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
上面兩個(gè)資源清單文件中的 Deployment 定義幾乎是一致的帆竹,唯一不同的是定義的環(huán)境變量VERSION值不同绕娘,接下來(lái)按照下面的步驟來(lái)驗(yàn)證Recreate策略:
- 版本1提供服務(wù)
- 刪除版本1
- 部署版本2
- 等待所有副本準(zhǔn)備就緒
首先部署第一個(gè)應(yīng)用:
$ kubectl apply -f app-v1.yaml
service "my-app" created
deployment.apps "my-app" created
測(cè)試版本1是否部署成功:
$ kubectl get pods -l app=my-app
NAME READY STATUS RESTARTS AGE
my-app-7b4874cd75-m5kct 1/1 Running 0 19m
my-app-7b4874cd75-pc444 1/1 Running 0 19m
my-app-7b4874cd75-tlctl 1/1 Running 0 19m
$ kubectl get svc my-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-app NodePort 10.108.238.76 <none> 80:32532/TCP 5m
$ curl http://127.0.0.1:32532
Host: my-app-7b4874cd75-pc444, Version: v1.0.0
可以看到版本1的應(yīng)用正常運(yùn)行了。為了查看部署的運(yùn)行情況栽连,打開一個(gè)新終端并運(yùn)行以下命令:
$ watch kubectl get po -l app=my-app
然后部署版本2的應(yīng)用:
$ kubectl apply -f app-v2.yaml
這個(gè)時(shí)候可以觀察上面新開的終端中的 Pod 列表的變化险领,可以看到之前的3個(gè) Pod 都會(huì)先處于Terminating狀態(tài),并且3個(gè) Pod 都被刪除后才開始創(chuàng)建新的 Pod秒紧。
然后測(cè)試第二個(gè)版本應(yīng)用的部署進(jìn)度:
$ while sleep 0.1; do curl http://127.0.0.1:32532; 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-f885c8d45-sp44p, Version: v2.0.0
Host: my-app-f885c8d45-t8g7g, Version: v2.0.0
Host: my-app-f885c8d45-sp44p, Version: v2.0.0
......
可以看到最開始的階段服務(wù)都是處于不可訪問的狀態(tài)绢陌,然后到第二個(gè)版本的應(yīng)用部署成功后才正常訪問,可以看到現(xiàn)在訪問的數(shù)據(jù)是版本2了熔恢。
最后脐湾,可以執(zhí)行下面的命令來(lái)清空上面的資源對(duì)象:
$ kubectl delete all -l app=my-app
結(jié)論:
1.應(yīng)用狀態(tài)全部更新
2.停機(jī)時(shí)間取決于應(yīng)用程序的關(guān)閉和啟動(dòng)消耗的時(shí)間
滾動(dòng)更新(rolling-update)
滾動(dòng)更新通過(guò)逐個(gè)替換實(shí)例來(lái)逐步部署新版本的應(yīng)用,直到所有實(shí)例都被替換完成為止叙淌。它通常遵循以下過(guò)程:在負(fù)載均衡器后面使用版本 A 的實(shí)例池秤掌,然后部署版本 B 的一個(gè)實(shí)例,當(dāng)服務(wù)準(zhǔn)備好接收流量時(shí)(Readiness Probe 正常)凿菩,將該實(shí)例添加到實(shí)例池中机杜,然后從實(shí)例池中刪除一個(gè)版本 A 的實(shí)例并關(guān)閉,如下圖所示:
下圖是滾動(dòng)更新過(guò)程應(yīng)用接收流量的示意圖:
下面是 Kubernetes 中通過(guò) Deployment 來(lái)進(jìn)行滾動(dòng)更新的關(guān)鍵參數(shù):
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2 # 一次可以添加多少個(gè)Pod
maxUnavailable: 1 # 滾動(dòng)更新期間最大多少個(gè)Pod不可用
maxSurge
和maxUnavailable
可以設(shè)置為0衅谷,但是不能同時(shí)為0
現(xiàn)在仍然使用上面的 app-v1.yaml 這個(gè)資源清單文件椒拗,新建一個(gè)定義滾動(dòng)更新的資源清單文件 app-v2-rolling-update.yaml,文件內(nèi)容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
app: my-app
spec:
replicas: 10
# maxUnavailable設(shè)置為0可以完全確保在滾動(dòng)更新期間服務(wù)不受影響获黔,還可以使用百分比的值來(lái)進(jìn)行設(shè)置蚀苛。
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
# 初始延遲設(shè)置高點(diǎn)可以更好地觀察滾動(dòng)更新過(guò)程
initialDelaySeconds: 15
periodSeconds: 5
上面的資源清單中我們?cè)诃h(huán)境變量中定義了版本2,然后通過(guò)設(shè)置strategy.type=RollingUpdate來(lái)定義該 Deployment 使用滾動(dòng)更新的策略來(lái)更新應(yīng)用玷氏,接下來(lái)我們按下面的步驟來(lái)驗(yàn)證滾動(dòng)更新策略:
- 版本1提供服務(wù)
- 部署版本2
- 等待直到所有副本都被版本2替換完成
同樣堵未,首先部署版本1應(yīng)用:
$ kubectl apply -f app-v1.yaml
service "my-app" created
deployment.apps "my-app" created
測(cè)試版本1是否部署成功:
$ kubectl get pods -l app=my-app
NAME READY STATUS RESTARTS AGE
my-app-7b4874cd75-h8c4d 1/1 Running 0 47s
my-app-7b4874cd75-p4l8f 1/1 Running 0 47s
my-app-7b4874cd75-qnt7p 1/1 Running 0 47s
$ kubectl get svc my-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-app NodePort 10.109.99.184 <none> 80:30486/TCP 1m
$ curl http://127.0.0.1:30486
Host: my-app-7b4874cd75-qnt7p, Version: v1.0.0
同樣,在一個(gè)新終端中執(zhí)行下面命令觀察 Pod 變化:
$ watch kubectl get pod -l app=my-app
然后部署滾動(dòng)更新版本2應(yīng)用:
$ kubectl apply -f app-v2-rolling-update.yaml
deployment.apps "my-app" configured
這個(gè)時(shí)候在上面的 watch 終端中可以看到多了很多 Pod盏触,還在創(chuàng)建當(dāng)中渗蟹,并沒有一開始就刪除之前的 Pod块饺,同樣,這個(gè)時(shí)候執(zhí)行下面命令雌芽,測(cè)試應(yīng)用狀態(tài):
$ while sleep 0.1; do curl http://127.0.0.1:30486; done
Host: my-app-7b4874cd75-vrlj7, Version: v1.0.0
......
Host: my-app-7b4874cd75-vrlj7, Version: v1.0.0
Host: my-app-6b5479d97f-2fk24, Version: v2.0.0
Host: my-app-7b4874cd75-p4l8f, Version: v1.0.0
......
Host: my-app-6b5479d97f-s5ctz, Version: v2.0.0
Host: my-app-7b4874cd75-5ldqx, Version: v1.0.0
......
Host: my-app-6b5479d97f-5z6ww, Version: v2.0.0
們可以看到上面的應(yīng)用并沒有出現(xiàn)不可用的情況授艰,最開始訪問到的都是版本1的應(yīng)用,然后偶爾會(huì)出現(xiàn)版本2的應(yīng)用世落,直到最后全都變成了版本2的應(yīng)用淮腾,而這個(gè)時(shí)候看上面 watch 終端中 Pod 已經(jīng)全部變成10個(gè)版本2的應(yīng)用了,我們可以看到這就是一個(gè)逐步替換的過(guò)程屉佳。
如果在滾動(dòng)更新過(guò)程中發(fā)現(xiàn)新版本應(yīng)用有問題谷朝,我們可以通過(guò)下面的命令來(lái)進(jìn)行一鍵回滾:
$ kubectl rollout undo deploy my-app
deployment.apps "my-app"
如果你想保持兩個(gè)版本的應(yīng)用都存在,那么我們也可以執(zhí)行 pause 命令來(lái)暫停更新:
$ kubectl rollout pause deploy my-app
deployment.apps "my-app" paused
這個(gè)時(shí)候我們?cè)偃パh(huán)訪問我們的應(yīng)用就可以看到偶爾會(huì)出現(xiàn)版本1的應(yīng)用信息了武花。
如果新版本應(yīng)用程序沒問題了圆凰,也可以繼續(xù)恢復(fù)更新:
$ kubectl rollout resume deploy my-app
deployment.apps "my-app" resumed
最后,可以執(zhí)行下面的命令來(lái)清空上面的資源對(duì)象:
$ kubectl delete all -l app=my-app
結(jié)論:
1.版本在實(shí)例之間緩慢替換
2.rollout/rollback 可能需要一定時(shí)間
3.無(wú)法控制流量
藍(lán)/綠(blue/green) - 最好用來(lái)驗(yàn)證 API 版本問題
藍(lán)/綠發(fā)布是版本2 與版本1 一起發(fā)布体箕,然后流量切換到版本2送朱,也稱為紅/黑部署。藍(lán)/綠發(fā)布與滾動(dòng)更新不同干旁,版本2(綠) 與版本1(藍(lán))一起部署,在測(cè)試新版本滿足要求后炮沐,然后更新更新 Kubernetes 中扮演負(fù)載均衡器角色的 Service 對(duì)象争群,通過(guò)替換 label selector 中的版本標(biāo)簽來(lái)將流量發(fā)送到新版本,如下圖所示:
下面是藍(lán)綠發(fā)布策略下應(yīng)用方法的示例圖:
在 Kubernetes 中大年,我們可以用兩種方法來(lái)實(shí)現(xiàn)藍(lán)綠發(fā)布换薄,通過(guò)單個(gè) Service
對(duì)象或者 Ingress
控制器來(lái)實(shí)現(xiàn)藍(lán)綠發(fā)布,實(shí)際操作都是類似的翔试,都是通過(guò) label
標(biāo)簽去控制轻要。
實(shí)現(xiàn)藍(lán)綠發(fā)布的關(guān)鍵點(diǎn)就在于 Service 對(duì)象中 label selector 標(biāo)簽的匹配方法,比如我們重新定義版本1 的資源清單文件 app-v1-single-svc.yaml垦缅,文件內(nèi)容如下:
apiVersion: v1
kind: Service
metadata:
name: my-app
labels:
app: my-app
spec:
type: NodePort
ports:
- name: http
port: 80
targetPort: http
# 注意這里我們匹配 app 和 version 標(biāo)簽冲泥,當(dāng)要切換流量的時(shí)候,我們更新 version 標(biāo)簽的值壁涎,比如: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
上面定義的資源對(duì)象中凡恍,最重要的就是 Service 中 label
selector
的定義:
selector:
app: my-app
version: v1.0.0
版本2 的應(yīng)用定義和以前一樣,新建文件 app-v2-single-svc.yaml怔球,文件內(nèi)容如下:
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
然后按照下面的步驟來(lái)驗(yàn)證使用單個(gè) Service 對(duì)象實(shí)現(xiàn)藍(lán)/綠部署的策略:
- 版本1 應(yīng)用提供服務(wù)
- 部署版本2 應(yīng)用
- 等到版本2 應(yīng)用全部部署完成
- 切換入口流量從版本1 到版本2
- 關(guān)閉版本1 應(yīng)用
首先嚼酝,部署版本1 應(yīng)用:
$ kubectl apply -f app-v1-single-svc.yaml
service "my-app" created
deployment.apps "my-app-v1" created
測(cè)試版本1 應(yīng)用是否部署成功:
$ 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
同樣,新開一個(gè)終端竟坛,執(zhí)行如下命令觀察 Pod 變化:
$ watch kubectl get pod -l app=my-app
然后部署版本2 應(yīng)用:
$ kubectl apply -f app-v2-single-svc.yaml
deployment.apps "my-app-v2" created
然后在上面 watch 終端中可以看到會(huì)多3個(gè)my-app-v2開頭的 Pod闽巩,待這些 Pod 部署成功后钧舌,我們?cè)偃ピL問當(dāng)前的應(yīng)用:
$ 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
......
我們會(huì)發(fā)現(xiàn)訪問到的都是版本1 的應(yīng)用,和我們剛剛部署的版本2 沒有任何關(guān)系涎跨,這是因?yàn)槲覀?Service 對(duì)象中通過(guò) label selector 匹配的是version=v1.0.0這個(gè)標(biāo)簽洼冻,我們可以通過(guò)修改 Service 對(duì)象的匹配標(biāo)簽,將流量路由到標(biāo)簽version=v2.0.0的 Pod 去:
$ kubectl patch service my-app -p '{"spec":{"selector":{"version":"v2.0.0"}}}'
service "my-app" patched
然后再去訪問應(yīng)用六敬,可以發(fā)現(xiàn)現(xiàn)在都是版本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 的匹配標(biāo)簽即可:
$ kubectl patch service my-app -p '{"spec":{"selector":{"version":"v1.0.0"}}}'
如果新版本已經(jīng)完全符合我們的需求了,就可以刪除版本1 的應(yīng)用了:
$ kubectl delete deploy my-app-v1
最后外构,同樣普泡,執(zhí)行如下命令清理上述資源對(duì)象:
$ kubectl delete all -l app=my-app
結(jié)論:
1.實(shí)時(shí)部署/回滾
2.避免版本問題,因?yàn)橐淮胃氖钦麄€(gè)應(yīng)用的改變
3.需要兩倍的資源
4.在發(fā)布到生產(chǎn)之前审编,應(yīng)該對(duì)整個(gè)應(yīng)用進(jìn)行適當(dāng)?shù)臏y(cè)試
金絲雀(Canary) - 讓部分用戶參與測(cè)試
金絲雀部署是讓部分用戶訪問到新版本應(yīng)用撼班,在 Kubernetes 中,可以使用兩個(gè)具有相同 Pod 標(biāo)簽的 Deployment 來(lái)實(shí)現(xiàn)金絲雀部署垒酬。新版本的副本和舊版本的一起發(fā)布砰嘁。在一段時(shí)間后如果沒有檢測(cè)到錯(cuò)誤,則可以擴(kuò)展新版本的副本數(shù)量并刪除舊版本的應(yīng)用勘究。
如果需要按照具體的百分比來(lái)進(jìn)行金絲雀發(fā)布矮湘,需要盡可能的啟動(dòng)多的 Pod 副本,這樣計(jì)算流量百分比的時(shí)候才方便口糕,比如缅阳,如果你想將 1% 的流量發(fā)送到版本 B,那么我們就需要有一個(gè)運(yùn)行版本 B 的 Pod 和 99 個(gè)運(yùn)行版本 A 的 Pod景描,當(dāng)然如果你對(duì)具體的控制策略不在意的話也就無(wú)所謂了十办,如果你需要更精確的控制策略,建議使用服務(wù)網(wǎng)格(如 Istio)超棺,它們可以更好地控制流量向族。
在下面的例子中,我們使用 Kubernetes 原生特性來(lái)實(shí)現(xiàn)一個(gè)窮人版的金絲雀發(fā)布棠绘,如果你想要對(duì)流量進(jìn)行更加細(xì)粒度的控制件相,請(qǐng)使用豪華版本的 Istio。下面是金絲雀發(fā)布的應(yīng)用請(qǐng)求示意圖:
接下來(lái)我們按照下面的步驟來(lái)驗(yàn)證金絲雀策略:
- 10個(gè)副本的版本1 應(yīng)用提供服務(wù)
- 版本2 應(yīng)用部署1個(gè)副本(意味著小于10%的流量)
- 等待足夠的時(shí)間來(lái)確認(rèn)版本2 應(yīng)用足夠穩(wěn)定沒有任何錯(cuò)誤信息
- 將版本2 應(yīng)用擴(kuò)容到10個(gè)副本
- 等待所有實(shí)例完成
- 關(guān)閉版本1 應(yīng)用
首先弄唧,創(chuàng)建版本1 的應(yīng)用資源清單适肠,app-v1-canary.yaml,內(nèi)容如下:
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 對(duì)象中的 label selector 標(biāo)簽候引,不在具有版本相關(guān)的標(biāo)簽了侯养,然后定義版本2 的資源清單文件,app-v2-canary.yaml澄干,文件內(nèi)容如下:
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 都具有一個(gè)共同的標(biāo)簽app=my-app逛揩,所以對(duì)應(yīng)的 Service 會(huì)匹配兩個(gè)版本的 Pod柠傍。
首先,部署版本1 應(yīng)用:
$ kubectl apply -f app-v1-canary.yaml
service "my-app" created
deployment.apps "my-app-v1" created
然后測(cè)試版本1 應(yīng)用是否正確部署了:
$ 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
同樣辩稽,新開一個(gè)終端惧笛,查看 Pod 的變化:
$ watch kubectl get po
然后部署版本2 應(yīng)用:
$ kubectl apply -f app-v2-canary.yaml
deployment.apps "my-app-v2" created
然后在 watch 終端頁(yè)面可以看到多了一個(gè) Pod,現(xiàn)在一共 11 個(gè) Pod逞泄,其中只有1 個(gè) Pod 運(yùn)行新版本應(yīng)用患整,然后同樣可以循環(huán)訪問該應(yīng)用,查看是否會(huì)有版本2 的應(yīng)用信息:
$ 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 的應(yīng)用信息喷众,偶爾會(huì)出現(xiàn)版本2 的應(yīng)用信息各谚,這就證明我們的金絲雀發(fā)布成功了,待確認(rèn)了版本2 的這個(gè)應(yīng)用沒有任何問題后到千,可以將版本2 應(yīng)用擴(kuò)容到10 個(gè)副本:
$ kubectl scale --replicas=10 deploy my-app-v2
deployment.extensions "my-app-v2" scaled
其實(shí)這個(gè)時(shí)候訪問應(yīng)用的話新版本和舊版本的流量分配是1:1了昌渤,確認(rèn)了版本2 正常后,就可以刪除版本1 的應(yīng)用了:
$ kubectl delete deploy my-app-v1
deployment.extensions "my-app-v1" deleted
最終留下的是 10 個(gè)新版本的 Pod 了憔四,到這里我們的整個(gè)金絲雀發(fā)布就完成了膀息。
同樣,最后了赵,執(zhí)行下面的命令刪除上面的資源對(duì)象:
$ kubectl delete all -l app=my-app
結(jié)論:
1.部分用戶獲取新版本
2.方便錯(cuò)誤和性能監(jiān)控
3.快速回滾
4.發(fā)布較慢
5.流量精準(zhǔn)控制很浪費(fèi)(99%A / 1%B = 99 Pod A潜支,1 Pod B)
如果你對(duì)新功能的發(fā)布沒有信心,建議使用金絲雀發(fā)布的策略柿汛。
A/B測(cè)試(A/B testing) - 最適合部分用戶的功能測(cè)試
A/B 測(cè)試實(shí)際上是一種基于統(tǒng)計(jì)信息而非部署策略來(lái)制定業(yè)務(wù)決策的技術(shù)毁腿,與業(yè)務(wù)結(jié)合非常緊密。但是它們也是相關(guān)的苛茂,也可以使用金絲雀發(fā)布來(lái)實(shí)現(xiàn)。
除了基于權(quán)重在版本之間進(jìn)行流量控制之外鸠窗,A/B 測(cè)試還可以基于一些其他參數(shù)(比如 Cookie妓羊、User Agent、地區(qū)等等)來(lái)精確定位給定的用戶群稍计,該技術(shù)廣泛用于測(cè)試一些功能特性的效果躁绸,然后按照效果來(lái)進(jìn)行確定。
我們經(jīng)吵枷可以在今日頭條的客戶端中就會(huì)發(fā)現(xiàn)有大量的 A/B 測(cè)試净刮,同一個(gè)地區(qū)的用戶看到的客戶端有很大不同。
要使用這些細(xì)粒度的控制硅则,仍然還是建議使用 Istio淹父,可以根據(jù)權(quán)重或 HTTP 頭等來(lái)動(dòng)態(tài)請(qǐng)求路由控制流量轉(zhuǎn)發(fā)。
下面是使用 Istio 進(jìn)行規(guī)則設(shè)置的示例怎虫,因?yàn)?Istio 還不太穩(wěn)定暑认,以下示例規(guī)則將來(lái)可能會(huì)更改:
route:
- tags:
version: v1.0.0
weight: 90
- tags:
version: v2.0.0
weight: 10
關(guān)于在 Istio 中具體如何做 A/B 測(cè)試困介,我們這里就不再詳細(xì)介紹了,我們?cè)趇stio-book文檔中有相關(guān)的介紹蘸际。
結(jié)論:
1.幾個(gè)版本并行運(yùn)行
2.完全控制流量分配
3.特定的一個(gè)訪問錯(cuò)誤難以排查座哩,需要分布式跟蹤
4.Kubernetes 沒有直接的支持,需要其他額外的工具
總結(jié)
發(fā)布應(yīng)用有許多種方法粮彤,當(dāng)發(fā)布到開發(fā)/測(cè)試環(huán)境的時(shí)候根穷,重建或者滾動(dòng)更新通常是一個(gè)不錯(cuò)的選擇。
- 在生產(chǎn)環(huán)境导坟,滾動(dòng)更新或者藍(lán)綠發(fā)布比較合適屿良,但是新版本的提前測(cè)試是非常有必要的。
- 如果你對(duì)新版本的應(yīng)用不是很有信心的話乍迄,那應(yīng)該使用金絲雀發(fā)布管引,將用戶的影響降到最低。
- 最后闯两,如果你的公司需要在特定的用戶群體中進(jìn)行新功能的測(cè)試褥伴,例如,移動(dòng)端用戶請(qǐng)求路由到版本 A漾狼,桌面端用戶請(qǐng)求路由到版本 B重慢,那么你就看使用A/B 測(cè)試,通過(guò)使用 Kubernetes 服務(wù)網(wǎng)關(guān)的配置逊躁,可以根據(jù)某些請(qǐng)求參數(shù)來(lái)確定用戶應(yīng)路由的服務(wù)似踱。
————————————————
版權(quán)聲明:本文為CSDN博主「看,未來(lái)」的原創(chuàng)文章稽煤,遵循CC 4.0 BY-SA版權(quán)協(xié)議核芽,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_43762191/article/details/125922745