「宣告式部署」模式的核心,是 Kubernetes 的 Deployment 資源。這個抽象把一組容器的升級與回滾流程封裝起來,讓它的執行成為可重複、可自動化的活動。

問題#

我們已經能以自助方式把隔離環境切成命名空間,並透過排程器以最少的人工介入把服務放進這些環境。但隨著微服務數量成長,持續更新它們、換上新版本,本身就成了越來越沉重的負擔

把服務升級到下一版涉及一連串活動:啟動新版 Pod、優雅地停止舊版 Pod、等待並驗證它啟動成功、失敗時還得整個回滾到前一版。執行這些活動時,你只能二選一:

  • 容許一段停機時間,但不會有兩個版本同時運行;
  • 不停機,但因為更新過程中兩個版本並存,資源用量會上升。

手動執行這些步驟容易出現人為錯誤,寫成腳本又需要相當可觀的工夫——兩者都會很快讓發布流程變成瓶頸。

解法#

Kubernetes 也把這件事自動化了。透過 Deployment 的概念,我們可以描述應用該如何被更新、採用何種策略,並調校更新流程的各種面向。想想看:每個發布週期、每個微服務實例都要做多次部署(依團隊與專案不同,週期可能從幾分鐘到數個月),這又是 Kubernetes 省下的一大筆工夫。

如同排程器需要主機有足夠資源、適當的配置政策與定義良好的容器資源輪廓才能有效運作,Deployment 也期待容器是稱職的雲原生公民。Deployment 的核心能力是「可預期地啟動與停止一組 Pod」,要讓它如預期運作,容器本身通常必須:

  • 監聽並尊重生命週期事件(例如 SIGTERM,見「受管生命週期」);
  • 提供健康檢查端點(見「健康探針」),用以指出自己是否成功啟動。

若容器把這兩件事做到位,平台就能乾淨地關閉舊容器、啟動更新後的實例來取代它們。接著,更新流程的其餘面向都可以用宣告的方式定義,並作為一個具備預設步驟與預期結果的原子動作來執行。

為什麼 kubectl 的命令式滾動更新已被棄用

Kubernetes 從一開始就支援滾動更新,但第一版實作本質上是命令式的:由客戶端 kubectl 告訴伺服器每個更新步驟該做什麼。

kubectl rolling-update 指令雖然還在,卻已高度棄用,因為命令式取徑有以下缺點:

  • 它下的是「把系統弄成某狀態」的指令,而不是描述期望的最終狀態
  • 替換容器與 ReplicationController 的整套編排邏輯都由 kubectl 執行——更新過程中它在幕後監控並與 API Server 互動,等於把本應屬於伺服器端的責任搬到了客戶端。
  • 你可能需要不只一道指令才能讓系統達到期望狀態,而這些指令必須在不同環境下自動化且可重複。
  • 你的變更可能隨時間被別人覆寫。
  • 更新流程必須文件化,並隨服務演進持續維護。
  • 想知道自己部署了什麼,唯一的辦法是檢查系統現況;而現況有時並非期望狀態,這時還得拿它跟部署文件對照。

正因如此,Kubernetes 引入了 Deployment 資源物件來支援由後端完全託管的宣告式更新。由於宣告式更新優點眾多,且命令式支援終將消失,本模式只聚焦於宣告式更新。

滾動部署(Rolling Deployment)#

Kubernetes 中宣告式更新應用的方式就是 Deployment。在幕後,Deployment 會建立一個支援集合式標籤選擇器的 ReplicaSet;同時 Deployment 抽象讓你能用 RollingUpdate(預設)與 Recreate 等策略來塑造更新行為。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: random-generator
spec:
  replicas: 3 # 宣告三個複本:滾動更新要有意義,複本數必須大於 1
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1 # 更新期間可額外暫時執行的 Pod 數,本例最多共四個複本
      maxUnavailable: 1 # 更新期間可不可用的 Pod 數,本例同時最少有兩個 Pod 可用
    selector:
      matchLabels:
        app: random-generator
    template:
      metadata:
        labels:
          app: random-generator
      spec:
        containers:
          - image: k8spatterns/random-generator:1.0
            name: random-generator
            readinessProbe: # 就緒探針是滾動部署達成零停機的關鍵,別忘了它
              exec:
                command: ["stat", "/random-generator-ready"]

RollingUpdate 策略確保更新過程沒有停機時間。幕後 Deployment 的實作做的事類似:建立新的 ReplicaSet,用新容器取代舊容器。它的強化之處在於你可以控制新容器推出的速率——透過 maxSurgemaxUnavailable 欄位控制可用 Pod 與超額 Pod 的範圍。

圖 3-1:滾動部署

要觸發一次宣告式更新,有三種做法:

  • kubectl replace 以新版的 Deployment 整份取代舊的。
  • kubectl patch 修補、或用 kubectl edit 互動式編輯 Deployment,設定新版容器映像檔。
  • kubectl set image 在 Deployment 中設定新映像檔。

除了解決前述命令式部署的缺點,Deployment 還帶來以下好處:

  • Deployment 是 Kubernetes 資源物件,其狀態完全由 Kubernetes 內部管理;整個更新流程在伺服器端執行,不需客戶端介入。
  • 它的宣告式本質,讓你看到的是「部署後的狀態應該長什麼樣」,而不是「要如何一步步達成」。
  • Deployment 定義是可執行的物件,在抵達正式環境前已在多個環境試過、測過。
  • 更新流程被完整記錄並版本化,可暫停、繼續,也可回滾到先前版本。

固定式部署(Fixed Deployment)#

RollingUpdate 適合確保更新期間零停機,但副作用是更新過程中會有兩個版本的容器同時執行。當更新引入了不向後相容的服務 API 變更、而客戶端無法應付時,這會對服務使用者造成問題。這種情境就該用 Recreate 策略。

圖 3-2:使用 Recreate 策略的固定式部署

Recreate 的效果等同於把 maxUnavailable 設成宣告的複本數:它先殺掉當前版本的所有容器,等舊容器被逐出後,再同時啟動所有新容器。

代價好處
Recreate舊容器全停、新容器尚未就緒的期間會有停機時間不會有兩個版本並存,服務使用者一次只需應付一個版本

藍綠發布(Blue-Green Release)#

藍綠部署是一種在正式環境部署軟體的發布策略,目的是把停機時間降到最低並降低風險。Kubernetes 的 Deployment 抽象定義了「不可變容器如何從一個版本轉換到另一個版本」,我們可以把它當作建構單元,搭配其他 Kubernetes 原語來實作這個更進階的發布策略。

若不使用 Service Mesh 或 Knative 這類擴充,藍綠部署必須手動進行

技術上的做法是:

  1. 建立第二個 Deployment,裝載最新版的容器(稱為),此時它還不服務任何請求。原本 Deployment 的舊 Pod 複本(稱為)仍在執行並服務實際流量。
  2. 當我們確信新版 Pod 健康、可以處理實際請求時,把流量從舊複本切到新複本。在 Kubernetes 中,這是透過更新 Service 的選擇器去比對新容器(標記為綠)來達成。
  3. 一旦綠容器接手所有流量,藍容器就可以刪除,資源釋出供未來的藍綠部署使用。

圖 3-3:藍綠發布

藍綠的好處是同一時間只有一個版本在服務請求,降低了服務使用者處理多版本並存的複雜度。缺點是藍綠容器同時運行期間需要兩倍的應用容量;此外,長時間執行的行程與轉換期間的資料庫狀態漂移,都可能造成相當棘手的問題。

金絲雀發布(Canary Release)#

金絲雀發布是一種柔性的正式環境上線方式:只用新實例取代一小部分舊實例。這個技巧讓只有部分使用者接觸到更新版本,藉此降低新版本進入正式環境的風險。當我們對新版服務、以及它在小樣本使用者上的表現感到滿意後,再把所有舊實例換成新版本。

圖 3-4:金絲雀發布

在 Kubernetes 中的實作方式是:為新容器版本建立一個新的 ReplicaSet(最好透過 Deployment),複本數設得很小,作為金絲雀實例。此時 Service 會把一部分使用者導向更新後的 Pod 實例。當我們確信新 ReplicaSet 一切如預期後,就把新 ReplicaSet 擴大、舊 ReplicaSet 縮到零。某種意義上,這是一次受控且經使用者測試的漸進式推出

討論#

Deployment 原語是個好例子,展示 Kubernetes 如何把「手動更新應用」這種繁瑣流程,轉化為可重複、可自動化的宣告式活動:

  • 開箱即用的部署策略(rolling、recreate)控制舊容器如何被新容器取代。
  • 發布策略(blue-green、canary)控制新版本如何對服務使用者變得可用。

後兩種發布策略的轉換觸發點仰賴人的決策,因此並非全自動,需要人為介入。

圖 3-5:部署與發布策略總覽,含轉換期間的實例數量

每套軟體都不一樣,部署複雜系統通常需要額外的步驟與檢查。本章討論的技巧涵蓋 Pod 的更新流程,但不包含更新與回滾 Pod 的其他依賴,例如 ConfigMap、Secret 或其他相依服務。

撰寫本書時,Kubernetes 有一份提案要在部署流程中加入掛鉤(hook):Pre 與 Post hook 讓你能在 Kubernetes 執行部署策略的前後執行自訂指令,這些指令可在部署進行中執行額外動作,並能中止、重試或繼續部署。這是邁向新一代自動化部署與發布策略的好起點。

在提案落地之前,可行的做法是在更高層級把更新流程腳本化:用 Deployment 與本書討論的其他原語,來管理服務及其依賴的更新流程。

無論你採用哪種部署策略,Kubernetes 都必須知道你的應用 Pod 何時已啟動並運行,才能執行抵達目標部署狀態所需的步驟序列。下一個模式「健康探針」就會說明應用如何把自己的健康狀態傳達給 Kubernetes。

更多資訊#

  • Declarative Deployment Example
  • Rolling Update
  • Deployments
  • Run a Stateless Application Using a Deployment
  • Blue-Green Deployment
  • Canary Release
  • DevOps with OpenShift