阿里雲k8s服務springboot項目應用升級時出現502錯誤

背景
隨着小步快跑、快速迭代的開發模式被愈來愈多的互聯網企業認同和採用,應用的變動、升級頻率變得愈來愈頻繁。爲了應對不一樣的升級需求,保證升級過程平穩順利地進行,誕生了一系列的部署發佈模式。java

停機發布 - 把老版的應用實例徹底中止,再發布新的版本。這種發佈模式主要爲了解決新老版本互不兼容、沒法共存的問題,缺點是一段時間內服務徹底不可用。
藍綠髮布 - 在線上同時部署相同數量的新老版本應用實例。待新版本測試經過後,將流量一次性地切到新的服務實例上來。這種發佈模式解決了停機發布中存在的服務徹底不可用問題,但會形成比較大的資源消耗。
滾動發佈 - 分批次逐步替換應用實例。這種發佈模式不會中斷服務,同時也不會消耗過多額外的資源,但因爲新老版本實例同時在線,可能致使來自相同客戶端的請求在新老版中切換而產生兼容性問題。
金絲雀發佈 - 逐漸將流量從老版本切換到新版本上。若是觀察一段時間後沒有發現問題,就進一步擴大新版本流量,同時減小老版本上流量。
A/B 測試 - 同時上線兩個或多個版本,收集用戶對這些版本的反饋,分析評估出最好版本正式採用。
隨着愈來愈多的應用被容器化,如何方便地讓容器應用平穩順利升級受到了普遍關注。本文將介紹 k8s 中不一樣部署形式下應用的升級方法,並重點介紹如何對 Deployment 中的應用實施滾動發佈(本文所做的調研基於k8s 1.13)。spring

K8s 應用升級
在 k8s 中,pod 是部署和升級的基本單位。通常來講,一個 pod 表明一個應用實例,而 pod 又會以 Deployment、StatefulSet、DaemonSet、Job 等形式部署運行,下面依次介紹在這些部署形式下 pod 的升級方法。ide

Deployment
Deployment 是 pod 最多見的部署形式,這裏將以基於 spring boot 的 java 應用爲例進行介紹。該應用是基於真實應用抽象出來的簡單版本,很是具備表明性,它有以下特色:spring-boot

應用啓動後,須要花費必定的時間加載配置,在這段時間內,沒法對外提供服務。
應用可以啓動並不意味着它可以正常提供服務。
應用若是沒法提供服務不必定能自動退出。
在升級過程當中須要保證即將下線的應用實例不會接收到新的請求且有足夠時間處理完當前請求。
參數配置
爲了讓具備上述特色的應用實現零宕機時間和無生產中斷的升級,須要精心地配置 Deployment 中的相關參數。這裏和升級有關的配置以下(完整配置參見 spring-boot-probes-v1.yaml)。工具

kind: Deployment
...
spec:
replicas: 8
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 3
maxUnavailable: 2
minReadySeconds: 120
...
template:
...
spec:
containers:測試

  • name: spring-boot-probes
    image: registry.cn-hangzhou.aliyuncs.com/log-service/spring-boot-probes:1.0.0
    ports:
    • containerPort: 8080
      terminationGracePeriodSeconds: 60
      readinessProbe:
      httpGet:
      path: /actuator/health
      port: 8080
      initialDelaySeconds: 30
      periodSeconds: 10
      successThreshold: 1
      failureThreshold: 1
      livenessProbe:
      httpGet:
      path: /actuator/health
      port: 8080
      initialDelaySeconds: 40
      periodSeconds: 20
      successThreshold: 1
      failureThreshold: 3
      ...
        

配置 strategy
經過 strategy 能夠配置 pod 的替換策略,主要參數以下。日誌

.spec.strategy.type - 用於指定替換 pod 的策略類型。該參數可取值 Recreate 或 RollingUpdate,默認爲 RollingUpdate。orm

Recreate - K8s 會先刪掉所有原有 pod 再建立新的 pod。該方式適用於新老版本互不兼容、沒法共存的場景。但因爲該方式會形成一段時間內服務徹底不可用,在上述場景以外須慎用。
RollingUpdate - K8s 會將 pod 分批次逐步替換掉,可用來實現服務熱升級。
.spec.strategy.rollingUpdate.maxSurge - 指定在滾動更新過程當中最多可建立多少個額外的 pod,能夠是數字或百分比。該值設置得越大、升級速度越快,但會消耗更多的系統資源。
.spec.strategy.rollingUpdate.maxUnavailable - 指定在滾動更新過程當中最多容許多少個 pod 不可用, 能夠是數字或百分比。該值設置得越大、升級速度越快,但服務會越不穩定。
經過調節 maxSurge 和 maxUnavailable,能夠知足不一樣場景下的升級需求。進程

若是您但願在保證系統可用性和穩定性的前提下儘量快地進行升級,能夠將 maxUnavailable 設置爲 0,同時爲 maxSurge 賦予一個較大值。
若是系統資源比較緊張,pod 負載又比較低,爲了加快升級速度,能夠將 maxSurge 設置爲 0,同時爲 maxUnavailable 賦予一個較大值。須要注意的是,若是 maxSurge 爲 0,maxUnavailable 爲 DESIRED,可能形成整個服務的不可用,此時 RollingUpdate 將退化成停機發布。
樣例選擇了一個折中方案,將 maxSurge 設置爲 3,將 maxUnavailable 設置爲 2,平衡了穩定性、資源消耗和升級速度。資源

配置探針
K8s 提供如下兩類探針:

ReadinessProbe - 默認狀況下,一旦某個 pod 中的全部容器所有啓動,k8s 就會認爲該 pod 處於就緒狀態,從而將流量發往該 pod。但某些應用啓動後,還須要完成數據或配置文件的加載工做才能對外提供服務,所以經過容器是否啓動來判斷其是否就緒並不嚴謹。經過爲容器配置就緒探針,能讓 k8s 更準確地判斷容器是否就緒,從而構建出更健壯的應用。K8s 保證只有 pod 中的全部容器所有經過了就緒探測,才容許 service 將流量發往該 pod。一旦就緒探測失敗,k8s 會中止將流量發往該 pod。
LivenessProbe - 默認狀況下,k8s 會認爲處於運行狀態下的容器是可用的。但若是應用在出現問題或不健康時沒法自動退出(例如發生嚴重死鎖),這種判斷就會出現問題。經過爲容器配置活性探針,能讓 k8s 更準確地判斷容器是否正常運行。若是容器沒有經過活性探測,kubelet 會將其中止,並根據重啓策略決定下一步的動做。
探針的配置很是靈活,用戶能夠指定探針的探測頻率、探測成功閾值、探測失敗閾值等。各參數的含義和配置方法可參考文檔 Configure Liveness and Readiness Probes。

樣例爲目標容器配置了就緒探針和活性探針:

就緒探針的 initialDelaySeconds 設置成 30,這是由於應用平均須要 30 秒時間完成初始化工做。
在配置活性探針時,須要保證容器有足夠時間到達就緒狀態。若是參數 initialDelaySeconds、periodSeconds、failureThreshold 設置得太小,可能形成容器還未就緒就被重啓,以致於永遠沒法達到就緒狀態。樣例中的配置保證若是容器能在啓動後的 80 秒內就緒就不會被重啓,相對 30 秒的平均初始化時間有足夠的緩衝。
就緒探針的 periodSeconds 設置成 10,failureThreshold 設置成 1。這樣當容器異常時,大約 10 秒後就不會有流量發往它。
活性探針的 periodSeconds 設置成 20,failureThreshold 設置成 3。這樣當容器異常時,大約 60 秒後就不會被重啓。
配置 minReadySeconds
默認狀況下,一旦新建立的 pod 變成就緒狀態 k8s 就會認爲該 pod 是可用的,從而將老的 pod 刪除掉。但有時問題可能會在新 pod 真正處理用戶請求時才暴露,所以一個更穩健的作法是當某個新 pod 就緒後對其觀察一段時間再刪掉老的 pod。

參數 minReadySeconds 能夠控制 pod 處於就緒狀態的觀察時間。若是 pod 中的容器在這段時間內都能正常運行,k8s 纔會認爲新 pod 可用,從而將老的 pod 刪除掉。在配置該參數時,須要仔細權衡,若是設置得太小,可能形成觀察不充分,若是設置得過大,又會拖慢升級進度。樣例將 minReadySeconds 設置成了 120 秒,這樣能保證處於就緒狀態的 pod 能經歷一個完整的活性探測週期。

配置 terminationGracePeriodSeconds
當 k8s 準備刪除一個 pod 時,會向該 pod 中的容器發送 TERM 信號並同時將 pod 從 service 的 endpoint 列表中移除。若是容器沒法在規定時間(默認 30 秒)內終止,k8s 會向容器發送 SIGKILL 信號強制終止進程。Pod 終止的詳細流程可參考文檔 Termination of Pods。

因爲應用處理請求最長耗時 40 秒,爲了讓其在關閉前可以處理完已到達服務端的請求,樣例設置了 60 秒的優雅關閉時間。針對不一樣的應用,您能夠根據實際狀況調整 terminationGracePeriodSeconds 的取值。

觀察升級行爲
上述配置可以保證目標應用的平滑升級。咱們能夠經過更改 Deployment 中 PodTemplateSpec 的任意字段觸發 pod 升級,並經過運行命令kubectl get rs -w觀察升級行爲。這裏觀察到的新老版本的 pod 副本數的變化狀況以下:

建立 maxSurge 個新 pod。這時 pod 總數達到了容許的上限,即 DESIRED + maxSurge。
不等新 pod 就緒或可用,馬上啓動 maxUnavailable 個老 pod 的刪除流程。這時可用 pod 數爲 DESIRED - maxUnavailable。
某個老 pod 被徹底刪除,這時會馬上補充一個新 pod。
某個新 pod 經過了就緒探測變成了就緒態,k8s 會將流量發往該 pod。但因爲未達到規定的觀察時間,該 pod 並不會被視做可用。
某個就緒 pod 在觀察期內運行正常被視做可用,這時能夠再次啓動某個老 pod 的刪除流程。
重複步驟 三、四、5 直到全部老 pod 被刪除,而且可用的新 pod 達到目標副本數。
失敗回滾
應用的升級並不總會一路順風,在升級過程當中或升級完成後都有可能遇到新版本行爲不符合預期須要回滾到穩定版本的狀況。K8s 會將 PodTemplateSpec 的每一次變動(若是更新模板標籤或容器鏡像)都記錄下來。這樣,若是新版本出現問題,就能夠根據版本號方便地回滾到穩定版本。回滾 Deployment 的詳細操做步驟可參考文檔 Rolling Back a Deployment。

StatefulSet
StatefulSet 是針對有狀態 pod 經常使用的部署形式。針對這類 pod,k8s 一樣提供了許多參數用於靈活地控制它們的升級行爲。好消息是這些參數大部分都和升級 Deployment 中的 pod 相同。這裏重點介紹二者存在差別的地方。

策略類型
在 k8s 1.7 及以後的版本中,StatefulSet 支持 OnDelete 和 RollingUpdate 兩種策略類型。

OnDelete - 當更新了 StatefulSet 中的 PodTemplateSpec 後,只有手動刪除舊的 pod 後纔會建立新版本 pod。這是默認的更新策略,一方面是爲了兼容 k8s 1.6 及以前的版本,另外一方面也是爲了支持升級過程當中新老版本 pod 互不兼容、沒法共存的場景。
RollingUpdate - K8s 會將 StatefulSet 管理的 pod 分批次逐步替換掉。它與 Deployment 中 RollingUpdate 的區別在於 pod 的替換是有序的。例如一個 StatefulSet 中包含 N 個 pod,在部署的時候這些 pod 被分配了從 0 開始單調遞增的序號,而在滾動更新時,它們會按逆序依次被替換。
Partition
能夠經過參數.spec.updateStrategy.rollingUpdate.partition實現只升級部分 pod 的目的。在配置了 partition 後,只有序號大於或等於 partition 的 pod 纔會進行滾動升級,其他 pod 將保持不變。

Partition 的另外一個應用是能夠經過不斷減小 partition 的取值實現金絲雀升級。具體操做方法可參考文檔 Rolling Out a Canary。

DaemonSet
DaemonSet 保證在所有(或者一些)k8s 工做節點上運行一個 pod 的副本,經常使用來運行監控或日誌收集程序。對於 DaemonSet 中的 pod,用於控制它們升級行爲的參數與 Deployment 幾乎一致,只是在策略類型方面略有差別。DaemonSet 支持 OnDelete 和 RollingUpdate 兩種策略類型。

OnDelete - 當更新了 DaemonSet 中的 PodTemplateSpec 後,只有手動刪除舊的 pod 後纔會建立新版本 pod。這是默認的更新策略,一方面是爲了兼容 k8s 1.5 及以前的版本,另外一方面也是爲了支持升級過程當中新老版本 pod 互不兼容、沒法共存的場景。
RollingUpdate - 其含義和可配參數與 Deployment 的 RollingUpdate 一致。
滾動更新 DaemonSet 的具體操做步驟可參考文檔 Perform a Rolling Update on a DaemonSet。

Job
Deployment、StatefulSet、DaemonSet 通常用於部署運行常駐進程,而 Job 中的 pod 在執行完特定任務後就會退出,所以不存在滾動更新的概念。當您更改了一個 Job 中的 PodTemplateSpec 後,須要手動刪掉老的 Job 和 pod,並以新的配置從新運行該 job。

總結
K8s 提供的功能可讓大部分應用實現零宕機時間和無生產中斷的升級,但也存在一些沒有解決的問題,主要包括如下幾點:

目前 k8s 原生僅支持停機發布、滾動發佈兩類部署升級策略。若是應用有藍綠髮布、金絲雀發佈、A/B 測試等需求,須要進行二次開發或使用一些第三方工具。
K8s 雖然提供了回滾功能,但回滾操做必須手動完成,沒法根據條件自動回滾。
有些應用在擴容或縮容時一樣須要分批逐步執行,k8s 還未提供相似的功能。
livenessProbe:
failureThreshold: 3
httpGet:
path: /user/service/test
port: 8080
scheme: HTTP
initialDelaySeconds: 40
periodSeconds: 20
successThreshold: 1
timeoutSeconds: 1
name: dataline-dev
ports:

  • containerPort: 8080protocol: TCPreadinessProbe:failureThreshold: 1httpGet:path: /user/service/testport: 8080scheme: HTTPinitialDelaySeconds: 30periodSeconds: 10successThreshold: 1timeoutSeconds: 1
相關文章
相關標籤/搜索