本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

使用 Jenkins 在 Kubernetes 中實現零停機部署

自從我們將 Kubernetes 持續部署Azure 容器服務外掛新增到 Jenkins 更新中心以來,“如何建立零停機部署”是我們最常被問到的問題之一。我們在 Azure 上建立了一個快速入門模板,以演示零停機部署的外觀。儘管我們的示例使用 Azure,但該概念很容易應用於所有 Kubernetes 安裝。

滾動更新

Kubernetes 支援 RollingUpdate(滾動更新)策略,以在不造成停機的情況下逐步替換舊 Pod,同時繼續為客戶端提供服務。要執行滾動更新部署:

  • .spec.strategy.type 設定為 RollingUpdate(預設值)。
  • .spec.strategy.rollingUpdate.maxUnavailable.spec.strategy.rollingUpdate.maxSurge 設定為合理的數值。
    • maxUnavailable:更新過程中可以不可用的最大 Pod 數量。這可以是副本計數的絕對數字或百分比;預設值為 25%。
    • maxSurge:可以建立的超出所需 Pod 數量的最大 Pod 數量。同樣,這可以是副本計數的絕對數字或百分比;預設值為 25%。
  • 為您的服務容器配置 readinessProbe(就緒探測),以幫助 Kubernetes 確定 Pod 的狀態。Kubernetes 只會將客戶端流量路由到具有健康活躍探測的 Pod。

我們將使用官方 Tomcat 映象的部署來演示這一點。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: tomcat-deployment-rolling-update
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: tomcat
        role: rolling-update
    spec:
      containers:
      - name: tomcat-container
        image: tomcat:${TOMCAT_VERSION}
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /
            port: 8080
  strategy:
    type: RollingUpdate
    rollingUp      maxSurge: 50%

如果當前部署中執行的 Tomcat 是版本 7,我們可以將 ${TOMCAT_VERSION} 替換為 8 並將其應用於 Kubernetes 叢集。透過 Kubernetes 持續部署Azure 容器服務外掛,可以從環境變數中獲取該值,從而簡化部署過程。

在幕後,Kubernetes 像這樣管理更新:

Deployment Process

  • 最初,所有 Pod 都在執行 Tomcat 7,前端服務將流量路由到這些 Pod。
  • 在滾動更新期間,Kubernetes 會關閉一些 Tomcat 7 Pod 並建立相應的新 Tomcat 8 Pod。它確保:
    • 所需 Pod 中最多 maxUnavailable 個 Pod 不可用,也就是說,至少 (replicas - maxUnavailable) 個 Pod 應該服務客戶端流量,在我們的例子中是 2-1=1。
    • 在更新過程中最多可以建立 maxSurge 個 Pod,在我們的例子中是 2*50%=1。
  • 一個 Tomcat 7 Pod 被關閉,一個 Tomcat 8 Pod 被建立。Kubernetes 不會將流量路由到其中任何一個,因為它們的就緒探測尚未成功。
  • 當新的 Tomcat 8 Pod 經就緒探測確定為就緒時,Kubernetes 將開始將流量路由到它。這意味著在更新過程中,使用者可能會看到舊服務和新服務。
  • 滾動更新透過關閉 Tomcat 7 Pod 並建立 Tomcat 8 Pod,然後將流量路由到就緒的 Pod 來繼續。
  • 最後,所有 Pod 都執行 Tomcat 8。

滾動更新策略確保我們始終有一些就緒的後端 Pod 服務客戶端請求,因此不會出現服務停機。但是,需要格外注意:

  • 在更新期間,舊 Pod 和新 Pod 都可能服務請求。如果服務層中沒有明確定義的會話親和性,使用者可能會被路由到新 Pod,然後又回到舊 Pod。
  • 這還要求您為資料和 API 維護明確定義的前向和後向相容性,這可能具有挑戰性。
  • Pod 啟動後,可能需要很長時間才能準備好處理流量。可能會有一段較長的時間視窗,在此期間流量由比平時更少的後端 Pod 提供服務。通常,這應該不是問題,因為我們傾向於在服務不那麼繁忙時進行生產升級。但這也會延長問題 1 的時間視窗。
  • 我們無法對正在建立的新 Pod 進行全面的測試。將應用程式更改從開發/QA 環境轉移到生產環境可能會帶來破壞現有功能的持續風險。就緒探測可以進行一些工作來檢查就緒狀態,但是,它應該是一個可以定期執行的輕量級任務,不適合用作啟動完整測試的入口點。

藍綠部署

引自 TechTarget 的藍綠部署:

藍綠部署是一種用於釋出軟體程式碼的變更管理策略。藍綠部署,也可能被稱為 A/B 部署,需要兩個配置完全相同的相同硬體環境。當一個環境處於活動狀態併為終端使用者提供服務時,另一個環境保持空閒。

容器技術提供了一個獨立的執行所需服務的環境,這使得建立藍綠部署所需的相同環境變得非常容易。Kubernetes 中松耦合的服務 - ReplicaSets,以及基於標籤/選擇器的服務路由使得在不同後端環境之間切換變得容易。透過這些技術,Kubernetes 中的藍綠部署可以按如下方式進行:

  • 部署之前,基礎設施按如下方式準備:
    • 使用 TOMCAT_VERSION=7TARGET_ROLE 分別設定為 blue 或 green 準備藍色部署和綠色部署。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: tomcat-deployment-${TARGET_ROLE}
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: tomcat
        role: ${TARGET_ROLE}
    spec:
      containers:
      - name: tomcat-container
        image: tomcat:${TOMCAT_VERSION}
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /
            port: 8080
  • 準備公共服務端點,該端點最初路由到其中一個後端環境,例如 TARGET_ROLE=blue
kind: Service
apiVersion: v1
metadata:
  name: tomcat-service
  labels:
    app: tomcat
    role: ${TARGET_ROLE}
    env: prod
spec:
  type: LoadBalancer
  selector:
    app: tomcat
    role: ${TARGET_ROLE}
  ports:
    - port: 80
      targetPort: 8080
  • (可選)準備一個測試端點,以便我們可以訪問後端環境進行測試。它們類似於公共服務端點,但僅供開發/運維團隊內部訪問。
kind: Service
apiVersion: v1
metadata:
  name: tomcat-test-${TARGET_ROLE}
  labels:
    app: tomcat
    role: test-${TARGET_ROLE}
spec:
  type: LoadBalancer
  selector:
    app: tomcat
    role: ${TARGET_ROLE}
  ports:
    - port: 80
      targetPort: 8080
  • 在非活動環境中更新應用程式,例如綠色環境。在部署配置中將 TARGET_ROLE=greenTOMCAT_VERSION=8 設定為更新綠色環境。
  • 透過 tomcat-test-green 測試端點測試部署,以確保綠色環境已準備好服務客戶端流量。
  • 透過使用 TARGET_ROLE=green 更新服務配置,將前端服務路由切換到綠色環境。
  • 在公共端點上執行額外的測試,以確保其正常工作。
  • 現在藍色環境處於空閒狀態,我們可以:
    • 保留舊應用程式,以便在新應用程式出現問題時可以回滾。
    • 更新它以使其成為活動環境的熱備份。
    • 減少其副本數量以節省佔用資源。

Resources

與滾動更新相比,藍綠部署 *公共服務要麼路由到舊應用程式,要麼路由到新應用程式,但絕不會同時路由到兩者。

  • 新 Pod 準備就緒所需的時間不影響公共服務質量,因為只有當所有新 Pod 都經過測試並準備就緒後,流量才會路由到它們。
  • 我們可以在新環境投入公共流量服務之前對其進行全面測試。請記住,這是在生產環境中,測試不應汙染即時應用程式資料。

Jenkins 自動化

Jenkins 提供易於設定的工作流程來自動化您的部署。透過 Pipeline 支援,可以靈活地構建零停機部署工作流程並可視化部署步驟。為了方便 Kubernetes 資源的部署過程,我們釋出了基於 kubernetes-client 構建的 Kubernetes 持續部署Azure 容器服務外掛。您可以將資源部署到 Azure Kubernetes Service (AKS) 或通用 Kubernetes 叢集,而無需 kubectl,並且它支援資源配置中的變數替換,因此您可以將特定於環境的資源部署到叢集,而無需更新資源配置。我們建立了一個 Jenkins Pipeline 來演示 AKS 的藍綠部署。流程如下:

Jenkins Pipeline

  • 預清理:清理工作區。
  • SCM:從原始碼管理系統拉取程式碼。
  • 準備映象:準備應用程式 Docker 映象並將其上傳到某個 Docker 倉庫。
  • 檢查環境:確定活動和非活動環境,這將驅動後續部署。
  • 部署:將新的應用程式資源配置部署到非活動環境。使用 Azure 容器服務外掛,這可以透過以下方式完成:
acsDeploy azureCredentialsId: 'stored-azure-credentials-id',
          configFilePaths: "glob/path/to/*/resource-config-*.yml",
          containerService: "aks-name | AKS",
          resourceGroupName: "resource-group-name",
          enableConfigSubstitution: true
  • 驗證暫存:驗證部署到非活動環境,以確保其正常工作。再次強調,請注意這是在生產環境中,因此在測試期間務必小心,不要汙染即時應用程式資料。
  • 確認:可選地,傳送電子郵件通知以進行手動使用者批准,以繼續進行實際環境切換。
  • 切換:將前端服務端點路由切換到非活動環境。這只是對 AKS Kubernetes 叢集的另一個服務部署。
  • 驗證生產:驗證前端服務端點在新環境中是否正常工作。
  • 後清理:對臨時檔案進行一些後清理。

對於滾動更新策略,只需將部署配置部署到 Kubernetes 叢集,這是一個簡單、單一的步驟。

整合所有內容

我們構建了一個 Azure 快速入門模板,以演示如何使用 Jenkins 在 AKS (Kubernetes) 上進行零停機部署。訪問 Jenkins Kubernetes 藍綠部署並點選“部署到 Azure”按鈕以獲取工作演示。此模板將提供:

  • 一個 AKS 叢集,包含以下資源:
    • 兩個相似的部署,分別代表“藍色”和“綠色”環境。兩者最初都設定為使用 tomcat:7 映象。
    • 兩個測試端點服務(tomcat-test-bluetomcat-test-green),它們連線到相應的部署,可用於測試部署是否已準備好投入生產使用。
    • 一個生產服務端點(tomcat-service),代表使用者將訪問的公共端點。最初,它路由到“藍色”環境。
  • 一個執行在 Ubuntu 16.04 VM 上的 Jenkins Master,並配置了 Azure 服務主體憑據。Jenkins 例項有兩個示例作業:
    • AKS Kubernetes 滾動更新部署流水線,用於演示 AKS 的滾動更新部署。
    • AKS Kubernetes 藍綠部署流水線,用於演示 AKS 的藍綠部署。
    • 我們沒有在快速入門模板中包含電子郵件確認步驟。要新增此步驟,您需要在 Jenkins 系統配置中配置電子郵件 SMTP 伺服器詳細資訊,然後在“切換”之前新增一個流水線階段。
stage('Confirm') {
    mail (to: 'to@example.com',
        subject: "Job '${env.JOB_NAME}' (${env.BUILD_NUMBER}) is waiting for input",
        body: "Please go to ${env.BUILD_URL}.")
    input 'Ready to go?'
}

按照步驟設定資源,然後啟動 Jenkins 構建作業即可進行嘗試。