上一篇文章咱們圍繞如何合理利用資源的主題作了一些最佳實踐的分享,這一次咱們就如何提升服務可用性的主題來展開探討。html
怎樣提升咱們部署服務的可用性呢?K8S 設計自己就考慮到了各類故障的可能性,並提供了一些自愈機制以提升系統的容錯性,但有些狀況仍是可能致使較長時間不可用,拉低服務可用性的指標。本文將結合生產實踐經驗,爲你們提供一些最佳實踐來最大化的提升服務可用性。java
K8S 的設計就是假設節點是不可靠的。節點越多,發生軟硬件故障致使節點不可用的概率就越高,因此咱們一般須要給服務部署多個副本,根據實際狀況調整 replicas 的值,若是值爲 1 就必然存在單點故障,若是大於 1 但全部副本都調度到同一個節點了,那仍是有單點故障,有時候還要考慮到災難,好比整個機房不可用。node
因此咱們不只要有合理的副本數量,還須要讓這些不一樣副本調度到不一樣的拓撲域(節點、可用區),打散調度以免單點故障,這個能夠利用 Pod 反親和性來作到,反親和主要分強反親和與弱反親和兩種。更多親和與反親和信息可參考官方文檔Affinity and anti-affinity。nginx
先來看個強反親和的示例,將 DNS 服務強制打散調度到不一樣節點上:api
affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: k8s-app operator: In values: - kube-dns topologyKey: kubernetes.io/hostname
labelSelector.matchExpressions
寫該服務對應 pod 中 labels 的 key 與 value,由於 Pod 反親和性是經過判斷 replicas 的 pod label 來實現的。topologyKey
指定反親和的拓撲域,即節點 label 的 key。這裏用的 kubernetes.io/hostname
表示避免 pod 調度到同一節點,若是你有更高的要求,好比避免調度到同一個可用區,實現異地多活,能夠用 failure-domain.beta.kubernetes.io/zone
。一般不會去避免調度到同一個地域,由於通常同一個集羣的節點都在一個地域,若是跨地域,即便用專線時延也會很大,因此 topologyKey
通常不至於用 failure-domain.beta.kubernetes.io/region
。requiredDuringSchedulingIgnoredDuringExecution
調度時必須知足該反親和性條件,若是沒有節點知足條件就不調度到任何節點 (Pending)。若是不用這種硬性條件可使用 preferredDuringSchedulingIgnoredDuringExecution
來指示調度器儘可能知足反親和性條件,即弱反親和性,若是實在沒有知足條件的,只要節點有足夠資源,仍是可讓其調度到某個節點,至少不會 Pending。bash
咱們再來看個弱反親和的示例:app
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: k8s-app operator: In values: - kube-dns topologyKey: kubernetes.io/hostname
注意到了嗎?相比強反親和有些不一樣哦,多了一個 weight
,表示此匹配條件的權重,而匹配條件被挪到了 podAffinityTerm
下面。dom
有時候咱們須要對節點進行維護或進行版本升級等操做,操做以前須要對節點執行驅逐 (kubectl drain),驅逐時會將節點上的 Pod 進行刪除,以便它們漂移到其它節點上,當驅逐完畢以後,節點上的 Pod 都漂移到其它節點了,這時咱們就能夠放心的對節點進行操做了。分佈式
有一個問題就是,驅逐節點是一種有損操做,驅逐的原理:post
這個過程是先刪除,再建立,並不是是滾動更新,所以更新過程當中,若是一個服務的全部副本都在被驅逐的節點上,則可能致使該服務不可用。
咱們再來下什麼狀況下驅逐會致使服務不可用:
針對第一點,咱們可使用前面講的反親和性來避免單點故障。
針對第二和第三點,咱們能夠經過配置 PDB (PodDisruptionBudget) 來避免全部副本同時被刪除,驅逐時 K8S 會 "觀察" nginx 的當前可用與指望的副本數,根據定義的 PDB 來控制 Pod 刪除速率,達到閥值時會等待 Pod 在其它節點上啓動並就緒後再繼續刪除,以免同時刪除太多的 Pod 致使服務不可用或可用性下降,下面給出兩個示例。
示例一 (保證驅逐時 nginx 至少有 90% 的副本可用):
apiVersion: policy/v1beta1kind: PodDisruptionBudgetmetadata: name: zk-pdbspec: minAvailable: 90% selector: matchLabels: app: zookeeper
示例二 (保證驅逐時 zookeeper 最多有一個副本不可用,至關於逐個刪除並等待在其它節點完成重建):
apiVersion: policy/v1beta1kind: PodDisruptionBudgetmetadata: name: zk-pdbspec: maxUnavailable: 1 selector: matchLabels: app: zookeeper
解決了服務單點故障和驅逐節點時致使的可用性下降問題後,咱們還須要考慮一種可能致使可用性下降的場景,那就是滾動更新。爲何服務正常滾動更新也可能影響服務的可用性呢?別急,下面我來解釋下緣由。
假如集羣內存在服務間調用:
當 server 端發生滾動更新時:
發生兩種尷尬的狀況:
針對第一種狀況,能夠給 container 加 preStop,讓 Pod 真正銷燬前先 sleep 等待一段時間,等待 client 所在節點 kube-proxy 更新轉發規則,而後再真正去銷燬容器。這樣能保證在 Pod Terminating 後還能繼續正常運行一段時間,這段時間若是由於 client 側的轉發規則更新不及時致使還有新請求轉發過來,Pod 仍是能夠正常處理請求,避免了鏈接異常的發生。聽起來感受有點不優雅,但實際效果仍是比較好的,分佈式的世界沒有銀彈,咱們只能儘可能在當前設計現狀下找到並實踐可以解決問題的最優解。
針對第二種狀況,能夠給 container 加 ReadinessProbe (就緒檢查),讓容器內進程真正啓動完成後才更新 Service 的 Endpoint,而後 client 所在節點 kube-proxy 再更新轉發規則,讓流量進來。這樣可以保證等 Pod 徹底就緒了纔會被轉發流量,也就避免了連接異常的發生。
最佳實踐 yaml 示例:
readinessProbe: httpGet: path: /healthz port: 80 httpHeaders: - name: X-Custom-Header value: Awesome initialDelaySeconds: 10 timeoutSeconds: 1 lifecycle: preStop: exec: command: ["/bin/bash", "-c", "sleep 10"]
更多信息請參考 Specifying a Disruption Budget for your Application 。
咱們都知道,給 Pod 配置健康檢查也是提升服務可用性的一種手段,配置 ReadinessProbe (就緒檢查) 能夠避免將流量轉發給還沒啓動徹底或出現異常的 Pod;配置 LivenessProbe (存活檢查) 可讓存在 bug 致使死鎖或 hang 住的應用重啓來恢復。可是,若是配置配置很差,也可能引起其它問題,這裏根據一些踩坑經驗總結了一些指導性的建議:
【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公衆號,及時獲取更多幹貨!!