前言
軟體技術更新換代很快,但我們追求的目标是一直不變的,那就是在安全穩定的前提下,增加應用的部署頻率,縮短産品功能的疊代周期,這樣的好處就是企業可以在更短的時間内獲得産品的價值、更快地獲得客戶回報和響應客戶需求,進而進一步提升産品的競争力;除此之外,企業還可以釋放更多的資源投入到創新業務的研發上,創造更多的價值,這是一個良性循環的過程。
應用産品的快速疊代誠然能給我們帶來各種各樣的好處,但挑戰也與其并存。更高頻率的應用釋出,意味着線上業務有不可預期故障的風險更大,除了産品上線之前在預發測試環境中充分測試驗證疊代功能之外,制定最合适的應用釋出政策就是另外一個非常重要的話題,因為它可以最大限度的降低業務故障的風險以及帶來的損失。
雲原生應用傳遞的關鍵點
我們說更頻繁地産品疊代意味着更大的故障風險,傳統應用如此,雲原生應用更是如此,因為雲原生應用通常都是基于雲的分布式部署模式,且每個應用可能是由多個功能元件互相調用來一起提供完整的服務的,每個元件都有自己獨立的疊代流程和計劃。在這種情況下,功能元件越多,意味着出錯的機率越大。那麼如何在應用傳遞層面對上述這些痛點做出改進,我們總結出以下幾個雲原生應用傳遞的關鍵點。

- 如何充分利用雲原生架構基礎設施的優勢。這個優勢我們可以簡單總結為兩點:彈性和高可用
- 如何具有跨平台移植和傳遞的能力。基礎設施底層的計算、存儲、網絡資源有很大的差異化,在以前,基礎架構的不同是由上層應用決定的,而雲原生應用的傳遞需要具有跨平台移植和傳遞的能力
- 如何實作應用運維自治化。自治化不等于自動化,自動化是指觸發一個流程,流程結束後能自動達到想要的一個預期結果,而自治化是指應用再高可用的運作态時,如果其中某個功能元件的某個副本出現故障,應用能自動移除故障副本并補充新的應用副本
- 如何讓應用變得更具有可預測性。應用的傳遞終态,在我們編寫應用編排模闆的時候就是可預測到的,如果應用的傳遞變得更有可預測性,那麼風險也會最大程度地降低
- 如何提高應用更快的平均恢複時間。如果應用有超出了應用自治的能力範疇之外的故障發生需要人工介入,那更快的平均恢複時間就意味着更低的業務損失。
Kubernetes是一個可移植的,可擴充的開源平台,用于管理容器化的工作負載和服務,可促進聲明式配置和自動化。它自身的平台能力已經滿足了我們前面提到的大部分需求。Kubernetes使用容器技術部署應用,這樣的好處包括但不限于:
- 應用程式建立和部署更靈活
- 可移植性
- 環境一緻性
- 松耦合和分布式
- 資源隔離
- 高效率和高密度的資源利用
快速傳遞雲原生應用的多種釋出政策詳解
Kubernetes又提供了應用管理、排程、監控和運維的強大能力:
- 服務發現和負載均衡能力
- 應用的自動部署和復原能力
- 應用的自治修複能力
- 存儲編排能力
- 密鑰和配置管理能力
但Kubernetes它也有很多功能是不提供但允許擴充的部分,比如日志采集、監控報警等能力,下面這張圖就是阿裡雲容器服務是在支援标準Kubernetes的基礎上對與使用者息息相關的能力做了增強和提升後的架構大圖,包括提供最大的彈性化與低廉成本的全球化接入能力,強大的安全架構支撐能力,深度整合阿裡雲基礎資源服務的能力并經過雙十一驗證和沉澱了海量使用者經驗,同時支援專有、托管、無服務化、邊緣和神龍裸金屬等多種産品形态,我們今天後面的所有示範就是在此平台上做的。
應用傳遞的邊界
在Kubernetes中應用傳遞的邊界是什麼?從簡單處入手,我們可以認為應用的傳遞就是它的網絡服務模式,服務的的後端資源以及業務資料的持久化存儲,這些資源被分别抽象成service、deployment/pod,volume資源等。
以一個wordpress應用為例,它包括兩個功能元件,前端元件處理使用者請求,後端元件存儲資料,前端元件包括一個frontend service和3個pod,後端元件包括一個backend service和一個pod元件,是以這個wordpress應用傳遞的資源就是2個service和總共4個後端pod。這個後端的pod資源我們在kubernetes中通過deployment來統一管理,service資源相當于一個負載均衡器,把請求路由到後端pod上,它涉及叢集内各個元件之間調用以及外部使用者通路叢集内服務,是以有不同的種類劃分。
根據服務暴露的方式不同,可以分為以下幾種:
ClusterIP
通過為Kubernetes的Service配置設定一個叢集内部可通路的固定虛拟IP(Cluster IP),實作叢集内的通路。為最常見的方式。
apiVersion: v1
kind: Service
metadata:
name: wordpress
spec:
type: ClusterIP # 預設的service類型,服務僅暴露為叢集内部可通路
ports:
- port: 80 # 暴露給叢集内部的服務端口
targetPort: 80 # 容器監聽的服務端口
protocol: TCP
selector:
app: wordpress # 轉發請求到有相同标簽的後端pod
NodePort
NodePort是把service的port映射到叢集節點的一個端口上,如果你不指定這個端口,系統将選擇一個随機端口。大多數時候我們應該讓 Kubernetes 來選擇端口,使用者自己來選擇可用端口代價太大。
apiVersion: v1
kind: Service
metadata:
name: wordpress
spec:
type: NodePort # NodePort service類型,服務暴露一個固定的靜态端口用于叢集外部通路
ports:
- port: 80 # 暴露給叢集内部的服務端口
targetPort: 80 # 容器監聽的服務端口
protocol: TCP
nodePort: 31570 # 叢集外部可以通過此端口通路服務
selector:
app: wordpress # 轉發請求到有相同标簽的後端pod
NodePort的方式雖然可以把服務暴露給叢集外通路,但是也有很多缺點:
- 每個端口隻能是一種服務
- 端口範圍有限制,一般是30000-32767
-
如果節點的IP位址變化了的話,你需要做一些變更操作去适配
是以在生産中一般不推薦這種方式,但如果你的應用對成本比較敏感又能容忍服務有不可用視窗期的話,是可以使用這種方式的
LoadBalancer
是服務暴露到叢集外或者公網上的标準方式,但它依賴cloud provider提供的一個負載均衡器的能力,負載均衡器會單獨配置設定一個ip位址并監聽後端服務的指定端口,請求的流量會通過指定的端口轉發到後端對應的服務。
apiVersion: v1
kind: Service
metadata:
name: wordpress
spec:
type: LoadBalancer # LoadBalancer service類型,一般依賴于公共雲廠商供的負載均衡能力
ports:
- port: 80 # 暴露給叢集内部的服務端口
targetPort: 80 # 容器監聽的服務端口
protocol: TCP
selector:
app: wordpress # 轉發請求到有相同标簽的後端pod
Ingress
ClusterIP
服務類型僅限叢集内通信,
NodePort
可以實作暴露服務通路入口,但每個節點都會占用一個端口,會增加端口管理的複雜性,LoadBalancer通常需要第三方雲提供商支援,有一定的限制性。而Ingress這個服務類型跟我們前面的三種服務類型不一樣,它實際上不是一種服務類型,而是類似一種叢集服務入口的存在,它可以基于你配置的不同路徑或者子域名把流量路由到對應的後端服務,更像是一個“智能路由”服務
前面是介紹了一些應用釋出涉及到的資源類型,以及service資源類型的幾種模式,那service是如何找到對應的後端pod的呢,這個就是标簽的作用,我們可以每個應用的pod和service都被打了同樣的标簽,這個标簽的機制就是我們後面要講的幾種應用釋出政策的關鍵點了。
應用的釋出政策
在kubernetes叢集中除了根據業務需求標明服務暴露方式外,為了讓應用在更新期間依然平穩提供服務,選擇一個正确的釋出政策就非常重要了。
滾動釋出
第一種應用釋出政策呢就是滾動釋出,這也是比較常見的政策,它是通過逐個替換執行個體來逐漸部署新版本的應用,直到所有執行個體都被替換完成為止。 我們看這個示意圖,目前我的應用提供的服務版本是v1, 這個服務的後端有3個副本, 但我更新版本v2的時候,它是一個副本一個副本地開始替換,直到最終服務的後端全部替換成v2版本。
一個應用示例的編排檔案如下:
go-demo-v1.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-demo
spec:
replicas: 3
selector:
matchLabels:
app: go-demo
template:
metadata:
labels:
app: go-demo
spec:
containers:
- name: go-demo
image: registry.cn-hangzhou.aliyuncs.com/haoshuwei24/go-demo:v1
imagePullPolicy: Always
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: go-demo
spec:
ports:
- port: 80
targetPort: 8080
name: go-demo
selector:
app: go-demo
type: ClusterIP
部署版本v1:
$ kubectl apply -f go-demo-v1.yaml
檢視pod運作狀态:
$ kubectl get po
NAME READY STATUS RESTARTS AGE
go-demo-78bc65c564-2rhxp 1/1 Running 0 19s
go-demo-78bc65c564-574z6 1/1 Running 0 19s
go-demo-78bc65c564-sgl2s 1/1 Running 0 19s
通路應用服務:
$ while sleep 0.1; do curl http://172.19.15.25; done
Version: v1
Version: v1
Version: v1
Version: v1
Version: v1
Version: v1
更新
go-demo-v1.yaml
為
go-demo-v2.yaml
并更新鏡像tag:
...
registry.cn-hangzhou.aliyuncs.com/haoshuwei24/go-demo:v2
...
部署版本v2:
$ kubectl apply -f go-demo-v2.yaml
可以檢視pod會被新版本pod逐個替換:
$kubectl get po -w
NAME READY STATUS RESTARTS AGE
application-demo-8594ff4967-85jsg 1/1 Running 0 3m24s
application-demo-8594ff4967-d4sv8 1/1 Terminating 0 3m22s
application-demo-8594ff4967-w6lpz 0/1 Terminating 0 3m20s
application-demo-b98d94554-4mwqd 1/1 Running 0 3s
application-demo-b98d94554-ng9wx 0/1 ContainerCreating 0 1s
application-demo-b98d94554-pmc5g 1/1 Running 0 4s
通路服務會發現在應用滾動更新過程中,版本v1和v2都會被通路到,這個時間的長短取決于應用的啟動速度:
$ while sleep 0.1; do curl http://172.19.15.25; done
Version: v1
Version: v2
Version: v1
Version: v1
Version: v2
Version: v1
Version: v1
Version: v2
滾動釋出優點就是它比較簡單,而且不會占用太多的計算資源;缺點是
- 版本在執行個體之間緩慢替換
- 這個滾動釋出可能需要一定時間
-
無法控制流量
從應用再叢集中的終态上來說,叢集中要麼隻有版本1的應用後端,要麼隻有版本2的後端;如果版本2有缺陷,那麼線上服務應用到的就是整體使用者, 雖然我們有機制可以快速復原,但涉及到整體使用者使用故障的代價還是太大。
藍綠釋出
第二種就是藍綠釋出,藍/綠釋出是應用版本1與版本2的後端pod都部署在環境中,通過控制流量切換來決定釋出哪個版本。 與滾動釋出相比,藍綠釋出政策下的應用終态,是可以同時存在版本1和版本2兩種pod的,我們可以通過service流量的切換來決定目前服務使用哪個版本的後端。
go-demo-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-demo-v1
spec:
replicas: 4
selector:
matchLabels:
app: go-demo
version: v1
template:
metadata:
labels:
app: go-demo
version: v1
spec:
containers:
- name: go-demo
image: registry.cn-hangzhou.aliyuncs.com/haoshuwei24/go-demo:v1
imagePullPolicy: Always
ports:
- containerPort: 8080
go-demo-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-demo-v2
spec:
replicas: 4
selector:
matchLabels:
app: go-demo
version: v2
template:
metadata:
labels:
app: go-demo
version: v2
spec:
containers:
- name: go-demo
image: registry.cn-hangzhou.aliyuncs.com/haoshuwei24/go-demo:v2
imagePullPolicy: Always
ports:
- containerPort: 8080
service.yaml
apiVersion: v1
kind: Service
metadata:
name: go-demo
spec:
ports:
- port: 80
targetPort: 8080
name: go-demo
selector:
app: go-demo
version: v1
type: ClusterIP
部署以上3個資源:
$ kubectl apply -f go-demo-v1.yaml -f go-demo-v2.yaml -f service.yaml
通路服務可以看到目前隻通路到版本1的服務:
$ while sleep 0.1; do curl http://172.19.8.137; done
Version: v1
Version: v1
Version: v1
Version: v1
Version: v1
修改
service.yaml
的spec.selector下version=v2為:
apiVersion: v1
kind: Service
metadata:
name: go-demo
spec:
ports:
- port: 80
targetPort: 8080
name: go-demo
selector:
app: go-demo
version: v2
type: ClusterIP
重新部署:
$ kubectl apply -f service.yaml
重新通路服務可以看到很快切換到了版本2上:
$ [root@iZbp13u3z7d2tqx0cs6ovqZ blue-green]# while sleep 0.1; do curl http://172.19.8.137; done
Version: v2
Version: v2
Version: v2
我們剛才說道滾動更新有一個過程需要時間,即使復原,它也需要一定的時間才能復原完畢,在新版本應用有缺陷的情況下,藍綠釋出的政策可以快速在v1和v2兩個版本之前切流量,是以這個切換流量的時間跟滾動更新相比就縮短了很多了,但藍綠釋出的缺點跟滾動釋出相同的就是這個缺陷會影響到整體使用者,服務要麼百分百切換到版本2上,要麼百分百切換到版本1上,這是個非0即100的操作,即使藍綠釋出政策可以大大縮短故障恢複時間,但在某些場景下也是不可接受的。 而且叢集環境中同時存在兩個版本的pod副本,資源占用的話相比滾動釋出是2倍的
金絲雀釋出(灰階釋出)
第三種要介紹的釋出政策是金絲雀釋出,金絲雀部署是應用版本1和版本2同時部署在環境中,并且使用者請求有可能會路由到版本1的後端,也可能會路由到版本2的後端,進而達到讓一部分新使用者通路到版本2的應用。 這種釋出政策下呢我們可以通過調整流量百分比來逐漸控制應用向新的版本切換,它與藍綠部署相比,它不僅繼承了藍綠部署的優點,而且占用資源優于藍綠部署所需要的2倍資源,在新版本有缺陷的情況下隻影響少部分使用者,把損失降到最低。
對于灰階釋出的概念來說,有人認為它跟金絲雀釋出講的是一個東西,有人認為它們不同,它跟金絲雀釋出的過程是相同的,但目的有所不同,金絲雀釋出它更傾向于能快速擷取使用者的一些回報,比如我可能不确定我的這個新版本功能的使用者體驗是否能被大衆很好的接受,我期望能得到線上使用者的一些及時回報,在産品側做功能體驗做調整之後再疊代v3版本;而灰階釋出則是我的産品功能已經設計并開發的很完善了,現在就是要逐漸替換線上的舊版本,但是要控制釋出可能帶來的風險,是以要灰階釋出。
示例應用1如下, 這個示例中我們通過pod的數量來控制流量比例:
go-demo-v1.yaml
設定副本數為9
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-demo-v1
spec:
replicas: 9
selector:
matchLabels:
app: go-demo
version: v1
template:
metadata:
labels:
app: go-demo
version: v1
spec:
containers:
- name: go-demo
image: registry.cn-hangzhou.aliyuncs.com/haoshuwei24/go-demo:v1
imagePullPolicy: Always
ports:
- containerPort: 8080
go-demo-v2.yaml
設定副本數為1
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-demo-v2
spec:
replicas: 1
selector:
matchLabels:
app: go-demo
version: v2
template:
metadata:
labels:
app: go-demo
version: v2
spec:
containers:
- name: go-demo
image: registry.cn-hangzhou.aliyuncs.com/haoshuwei24/go-demo:v2
imagePullPolicy: Always
ports:
- containerPort: 8080
service.yaml
apiVersion: v1
kind: Service
metadata:
name: go-demo
spec:
ports:
- port: 80
targetPort: 8080
name: go-demo
selector:
app: go-demo
type: ClusterIP
$ kubectl apply -f go-demo-v1.yaml -f go-demo-v2.yaml -f service.yaml
通路服務可以看到基本上是10%的流量切換到版本2上:
$ while sleep 0.1; do curl http://172.19.8.248; done
Version: v1
Version: v2
Version: v1
Version: v1
Version: v1
Version: v1
Version: v1
Version: v1
Version: v1
Version: v1
Version: v1
另外我們可以使用nginx ingress controller來控制流量切換,這個方式要更精準。
go-demo-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-demo-v1
spec:
replicas: 3
selector:
matchLabels:
app: go-demo
version: v1
template:
metadata:
labels:
app: go-demo
version: v1
spec:
containers:
- name: go-demo
image: registry.cn-hangzhou.aliyuncs.com/haoshuwei24/go-demo:v1
imagePullPolicy: Always
ports:
- containerPort: 8080
go-demo-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-demo-v2
spec:
replicas: 1
selector:
matchLabels:
app: go-demo
version: v2
template:
metadata:
labels:
app: go-demo
version: v2
spec:
containers:
- name: go-demo
image: registry.cn-hangzhou.aliyuncs.com/haoshuwei24/go-demo:v2
imagePullPolicy: Always
ports:
- containerPort: 8080
service-v1.yaml
apiVersion: v1
kind: Service
metadata:
name: go-demo-v1
spec:
ports:
- port: 80
targetPort: 8080
name: go-demo
selector:
app: go-demo
version: v1
type: ClusterIP
service-v2.yaml
apiVersion: v1
kind: Service
metadata:
name: go-demo-v2
spec:
ports:
- port: 80
targetPort: 8080
name: go-demo
selector:
app: go-demo
version: v2
type: ClusterIP
ingress.yaml
, 設定
nginx.ingress.kubernetes.io/service-weight: | go-demo-v1: 100, go-demo-v2: 0
, 版本1 100%流量, 版本2 0%流量:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/service-weight: |
go-demo-v1: 100, go-demo-v2: 0
name: go-demo
labels:
app: go-demo
spec:
rules:
- host: go-demo.example.com
http:
paths:
- path: /
backend:
serviceName: go-demo-v1
servicePort: 80
- path: /
backend:
serviceName: go-demo-v2
servicePort: 80
部署以上4個資源:
$ kubectl apply -f go-demo-v1.yaml -f go-demo-v2.yaml -f service-v1.yaml -f service-v2.yaml -f nginx.yaml
通路服務可以看到流量100%到版本1上:
$ while sleep 0.1; do curl http://go-demo.example.com; done
Version: v1
Version: v1
Version: v1
Version: v1
Version: v1
Version: v1
ingress.yaml
, 設定流量比為50:50
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/service-weight: |
go-demo-v1: 50, go-demo-v2: 50
name: go-demo
labels:
app: go-demo
spec:
rules:
- host: go-demo.example.com
http:
paths:
- path: /
backend:
serviceName: go-demo-v1
servicePort: 80
- path: /
backend:
serviceName: go-demo-v2
servicePort: 80
通路服務可以看到流量50%到版本1上, 50%到版本2上:
$ while sleep 0.1; do curl http://go-demo.example.com; done
Version: v2
Version: v1
Version: v1
Version: v1
Version: v2
Version: v2
Version: v1
Version: v1
Version: v2
Version: v2
ingress.yaml
, 設定流量比為0:100
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/service-weight: |
go-demo-v1: 0, go-demo-v2: 100
name: go-demo
labels:
app: go-demo
spec:
rules:
- host: go-demo.example.com
http:
paths:
- path: /
backend:
serviceName: go-demo-v1
servicePort: 80
- path: /
backend:
serviceName: go-demo-v2
servicePort: 80
通路服務可以看到流量100%到版本2上:
$ while sleep 0.1; do curl http://go-demo.example.com; done
Version: v2
Version: v2
Version: v2
Version: v2
Version: v2
Version: v2
Version: v2
Version: v2
Version: v2
Version: v2
不管是金絲雀釋出還是灰階釋出,缺點就是釋出周期相對來說要慢很多。
在這些釋出政策當中,當你在開發測試環境中對用用做更新釋出的話,用滾動釋出。在生産環境,滾動更新或者藍綠釋出在新版本已經提前測試充分的情況下可以用。如果對新版本的應用的更新需要最大限度地控制風險,降低故障對使用者的影響的話,那就使用金絲雀釋出或灰階釋出。以上就是我們在kubernetes當中常用的幾種釋出政策的介紹。