做者 | 郝樹偉(流生)阿里雲高級研發工程師前端
軟件技術更新換代很快,但咱們追求的目標是一直不變的,那就是在安全穩定的前提下,增長應用的部署頻率,縮短產品功能的迭代週期,這樣的好處就是企業能夠在更短的時間內得到產品的價值、更快地得到客戶反饋和響應客戶需求,從而進一步提高產品的競爭力;除此以外,企業還能夠釋放更多的資源投入到創新業務的研發上,創造更多的價值,這是一個良性循環的過程。node
應用產品的快速迭代誠然能給咱們帶來各類各樣的好處,但挑戰也與其並存。更高頻率的應用發佈,意味着線上業務有不可預期故障的風險更大,除了產品上線以前在預發測試環境中充分測試驗證迭代功能以外,制定最合適的應用發佈策略就是另一個很是重要的話題,由於它能夠最大限度的下降業務故障的風險以及帶來的損失。nginx
咱們說頻繁地進行產品迭代意味着更大的故障風險,傳統應用如此,雲原生應用更是如此。由於雲原生應用一般都是基於雲的分佈式部署模式,且每一個應用多是由多個功能組件互相調用來一塊兒提供完整的服務的,每一個組件都有本身獨立的迭代流程和計劃。在這種狀況下,功能組件越多,意味着出錯的機率越大。那麼如何在應用交付層面對上述這些痛點作出改進,咱們總結出如下幾個雲原生應用交付的關鍵點。後端
Kubernetes 是一個可移植的,可擴展的開源平臺,用於管理容器化的工做負載和服務,可促進聲明式配置和自動化。它自身的平臺能力已經知足了咱們前面提到的大部分需求。Kubernetes 使用容器技術部署應用,這樣的好處包括但不限於:api
Kubernetes 還提供了應用管理、調度、監控和運維的強大能力:安全
但 Kubernetes 它也有不少功能是不提供但容許擴展的部分,好比日誌採集、監控報警等能力。下面這張圖就是阿里雲容器服務是在支持標準 Kubernetes 的基礎上,對與用戶息息相關的能力作了加強和提高後的架構大圖,包括提供最大的彈性化與低廉成本的全球化接入能力,強大的安全架構支撐能力,深度整合阿里雲基礎資源服務的能力,並通過 雙11 驗證和沉澱了海量用戶經驗,同時支持專有、託管、無服務化、邊緣和神龍裸金屬等多種產品形態,咱們今天后面的全部演示就是在此平臺上作的。網絡
在 Kubernetes 中應用交付的邊界是什麼?架構
從簡單處入手,咱們能夠認爲應用的交付就是它的網絡服務模式,服務的的後端資源以及業務數據的持久化存儲,這些資源被分別抽象成 service、deployment/pod,volume 資源等。app
以一個 wordpress 應用爲例,它包括兩個功能組件:前端組件處理用戶請求,後端組件存儲數據。前端組件包括一個 frontend service 和 3 個 pod,後端組件包括一個 backend service 和一個 pod 組件,因此這個 wordpress 應用交付的資源就是 2 個 service 和總共 4 個後端 pod。這個後端的 pod 資源咱們在 Kubernetes 中經過 deployment 來統一管理,service 資源至關於一個負載均衡器,把請求路由到後端 pod 上,它涉及集羣內各個組件之間調用以及外部用戶訪問集羣內服務,因此有不一樣的種類劃分。負載均衡
根據服務暴露的方式不一樣,能夠分爲如下幾種:
經過爲 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 是把 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 的方式雖然能夠把服務暴露給集羣外訪問,可是也有不少缺點:
因此在生產中通常不推薦這種方式,但若是你的應用對成本比較敏感又能容忍服務有不可用窗口期的話,是可使用這種方式的。
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
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
$ kubectl apply -f go-demo-v1.yaml
$ 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 ...
$ kubectl apply -f go-demo-v2.yaml
$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
$ 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
$ kubectl apply -f go-demo-v1.yaml -f go-demo-v2.yaml -f service.yaml
$ 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
$ [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 倍資源,在新版本有缺陷的狀況下隻影響少部分用戶,把損失降到最低。
對於灰度發佈的概念來講,有人認爲它跟金絲雀發佈講的是一個東西,有人認爲它們不一樣。它跟金絲雀發佈的過程是相同的,但目的有所不一樣:
示例應用 1 以下, 這個示例中咱們經過 pod 的數量來控制流量比例。
go-demo-v1.yaml
設定副本數爲 9apiVersion: 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
設定副本數爲 1apiVersion: 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
$ 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
$ kubectl apply -f go-demo-v1.yaml -f go-demo-v2.yaml -f service-v1.yaml -f service-v2.yaml -f nginx.yaml
$ 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:50apiVersion: 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
$ 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:100apiVersion: 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
$ 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 當中經常使用的幾種發佈策略的介紹。
「阿里巴巴雲原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,作最懂雲原生開發者的技術圈。」