隨著小步快跑、快速迭代的開發模式被越來越多的網路企業認同和採用,應用的變更、升級頻率變得越來越頻繁。為了因應不同的升級需求,確保升級過程順利順利進行,並誕生了一系列的部署發布模式。
停機發布 - 把舊版的應用實例完全停止,然後再發布新的版本。這種發布模式主要為了解決新舊版本互不相容、無法共存的問題,缺點是一段時間內服務完全不可用。
藍綠發布 - 在線上同時部署相同數量的新舊版應用程式實例。待新版本測試通過後,將流量一次地切到新的服務實例上來。這種發布模式解決了停機發布中存在的服務完全不可用問題,但會造成比較大的資源消耗。
滾動發布 - 分批次逐步取代應用實例。這種發布模式不會中斷服務,同時也不會消耗過多額外的資源,但由於新舊版本實例同時在線,可能導致來自相同客戶端的請求在新舊版中切換而產生相容性問題。
金絲雀發布 - 逐漸將流量從舊版切換到新版本。如果觀察一段時間後沒有發現問題,就進一步擴大新版本流量,同時減少舊版上流量。
A/B 測試 - 同時上線兩個或多個版本,收集使用者對這些版本的回饋,分析評估出最佳版本正式採用。
在 k8s 中,pod 是部署和升級的基本單位。一般來說,一個 pod 代表一個應用實例,而 pod 又會以 Deployment、StatefulSet、DaemonSet、Job 等形式部署運行,以下依序介紹在這些部署形式下 pod 的升級方法。
Deployment 是 pod 最常見的部署形式,這裡將以基於 spring boot 的 java 應用程式為例進行介紹。該應用程式是基於真實應用抽象化的簡單版本,非常具有代表性,它有以下特點:
#應用程式啟動後,需要花費一定的時間加載配置,在這段時間內,無法對外提供服務。
應用程式能夠啟動並不表示它能夠正常提供服務。
應用程式如果無法提供服務不一定能自動退出。
在升級過程中需要保證即將下線的應用程式實例不會接收到新的請求且有足夠時間處理完當前請求。
為了讓具有上述特點的應用實現零點子機時間和無生產中斷的升級,需要精心配置 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 可以設定 pod 的取代策略,主要參數如下。
.spec.strategy.type
- 用於指定替換 pod 的策略類型。此參數可取值 Recreate 或 RollingUpdate,預設為 RollingUpdate。
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 秒後就不會重新啟動。
預設情況下,一旦新建立的pod 變成就緒狀態k8s 就會認為該pod 是可用的,從而將舊的pod 刪除掉。但有時問題可能會在新 pod 真正處理用戶請求時才暴露,因此一個更穩健的做法是當某個新 pod 就緒後對其觀察一段時間再刪掉舊的 pod。
參數 minReadySeconds 可以控制 pod 處於就緒狀態的觀察時間。如果 pod 中的容器在這段時間內都能正常運作,k8s 才會認為新 pod 可用,從而將舊的 pod 刪除掉。在配置該參數時,需要仔細權衡,如果設定得太小,可能造成觀察不充分,如果設定得太大,又會拖慢升級進度。範例將 minReadySeconds 設定成了 120 秒,這樣能保證處於就緒狀態的 pod 能經歷一個完整的活性偵測週期。
當 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 的刪除流程。
重複步驟 3、4、5 直到所有舊 pod 被刪除,並且可用的新 pod 達到目標副本數。
应用的升级并不总会一帆风顺,在升级过程中或升级完成后都有可能遇到新版本行为不符合预期需要回滚到稳定版本的情况。K8s 会将 PodTemplateSpec 的每一次变更(如果更新模板标签或容器镜像)都记录下来。这样,如果新版本出现问题,就可以根据版本号方便地回滚到稳定版本。回滚 Deployment 的详细操作步骤可参考文档 Rolling Back a Deployment。
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 开始单调递增的序号,而在滚动更新时,它们会按逆序依次被替换。
可以通过参数.spec.updateStrategy.rollingUpdate.partition
实现只升级部分 pod 的目的。在配置了 partition 后,只有序号大于或等于 partition 的 pod 才会进行滚动升级,其余 pod 将保持不变。
Partition 的另一个应用是可以通过不断减少 partition 的取值实现金丝雀升级。具体操作方法可参考文档 Rolling Out a Canary。
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。
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: 8080 protocol: TCP readinessProbe: failureThreshold: 1 httpGet: path: /user/service/test port: 8080 scheme: HTTP initialDelaySeconds: 30 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1
经测试 , 再对sprintboot 应用进行更新时, 访问不再出现502的报错。
以上是k8s服務springboot專案應用程式升級時出現502錯誤怎麼解決的詳細內容。更多資訊請關注PHP中文網其他相關文章!