萬字長文|在 Kubernetes 中部署高可用應用程式的最佳實踐!

萬字長文|在 Kubernetes 中部署高可用應用程式的最佳實踐!

新鈦雲服已為您服務

1303

萬字長文|在 Kubernetes 中部署高可用應用程式的最佳實踐!

真正的生產型應用會涉及多個容器。這些容器必須跨多個伺服器主機進行部署。容器安全性

https://www。redhat。com/zh/topics/security/container-security

需要多層部署,因此可能會比較複雜。但 Kubernetes 有助於解決這一問題。

Kubernetes 可以提供所需的編排和管理功能,以便您針對這些工作負載大規模部署容器

。藉助 Kubernetes 編排功能,您可以構建跨多個容器的應用服務、跨叢集排程、擴充套件這些容器,並長期持續管理這些容器的健康狀況。有了 Kubernetes,您便可切實採取一些措施來提高 IT 安全性。

高可用性(High Availability,HA)是指應用系統無中斷執行的能力,通常可透過提高該系統的容錯能力來實現。一般情況下,透過設定

replicas

給應用建立多個副本,可以適當提高應用容錯能力,但這並不意味著應用就此實現高可用性。

萬字長文|在 Kubernetes 中部署高可用應用程式的最佳實踐!

眾所周知,在Kubernetes環境中部署一個可用的應用程式是一件輕而易舉的事。但另外一方面,如果要部署一個可容錯,高可靠且易用的應用程式則不可避免地會遇到很多問題。在本文中,

我們將討論在Kubernetes中部署高可用應用程式並以簡潔的方式給大家展示

,本文也會重點介紹在Kubernetes部署高可用應用所需要注意的故則以及相應的方案。

請注意,我們不會使用任何現有的部署方案。我們也不會固定特定的CD解決方案,我們將省略模板生成 Kubernetes 清單的問題。在本文中,我們只討論部署到叢集時 Kubernetes 清單的最終內容,其它的部分本文不作過多的討論。

一、副本數量

通常情況下,至少需要兩個副本才能保證應用程式的最低高可用。但是,您可能會問,為什麼單個副本無法做到高可用?問題在於 Kubernetes 中的許多實體(Node、Pod、ReplicaSet ,Deployment等)都是非持久化的,即在某些條件下,它們可能會被自動刪除或者重新被建立。因此,Kubernetes 叢集本身以及在其中執行的應用服務都必須要考慮到這一點。

例如,當使用autoscaler服務縮減您的節點數量時,其中一些節點將會被刪除,包括在該節點上執行的 Pod。如果您的應用只有一個例項且在執行刪除的節點上,此時,您可能會發現您的應用會變的不可用,儘管這時間通常是比較短的,因為對應的

Pod

會在新的節點上重新被建立。

一般來說,如果你只有一個應用程式副本,它的任何異常停服都會導致停機。換句話說,您必須

為正在執行的應用程式保持至少兩個副本,從而防止單點故障。

副本越多,在某些副本發生故障的情況下,您的應用程式的計算能力下降的幅度也就越小。例如,假設您有兩個副本,其中一個由於節點上的網路問題而失敗。應用程式可以處理的負載將減半(只有兩個副本中的一個可用)。當然,新的副本會被排程到新的節點上,應用的負載能力會完全恢復。但在那之前,增加負載可能會導致服務中斷,這就是為什麼您

必須保留一些副本。

上述建議與未使用 HorizontalPodAutoscaler 的情況相關。對於有多個副本的應用程式,最好的替代方法是配置 HorizontalPodAutoscaler 並讓它管理副本的數量。本文的最後會詳細描述HorizontalPodAutoscaler。

二、更新策略

Deployment 的預設更新策略需要減少舊+新的 ReplicaSet Pod 的數量,其Ready狀態為更新前數量的 75%。因此,在更新過程中,應用程式的計算能力可能會下降到正常水平的 75%,這可能會導致部分故障(應用程式效能下降)。

strategy。RollingUpdate。maxUnavailable

引數允許您配置更新期間可能變得不可用的Pod的最大百分比。因此,要麼確保您的應用程式在 25% 的Pod不可用的情況下也能順利執行,要麼降低該

maxUnavailable

引數。請注意,該

maxUnavailable

引數已四捨五入。

預設更新策略 ( RollingUpdate)有一個小技巧:應用程式在滾動更新過程中,多副本策略依然有效,新舊版本會同時存在,一直到所有的應用都更新完畢。但是,如果由於某種原因無法並行執行不同的副本和不同版本的應用程式,那麼您可以使用

strategy。type: Recreate

引數。 在Recreate策略下,所有現有Pod在建立新Pod之前都會被殺死。這會導致短暫的停機時間。

其他部署策略(藍綠、金絲雀等)通常可以為 RollingUpdate 策略提供更好的替代方案。但是,我們沒有在本文中考慮它們,因為它們的實現取決於用於部署應用程式的軟體。

三、跨節點的統一副本分佈

Kubernetes 的設計理念為假設節點不可靠,節點越多,發生軟硬體故障導致節點不可用的機率就越高。所以我們通常需要給應用部署多個副本,並根據實際情況調整

replicas

的值。該值如果為1 ,就必然存在單點故障。該值如果大於1但所有副本都排程到同一個節點,仍將無法避免單點故障。

為了避免單點故障,我們需要有合理的副本數量,還需要讓不同副本排程到不同的節點。為此,

您可以指示排程程式避免在同一節點上啟動同一 Deployment 的多個 Pod:

affinity:

PodAntiAffinity:

preferredDuringSchedulingIgnoredDuringExecution:

- PodAffinityTerm:

labelSelector:

matchLabels:

app: testapp

topologyKey: kubernetes。io/hostname

最好使用

preferredDuringSchedulingaffinity

而不是

requiredDuringScheduling

。 如果新Pod所需的節點數大於可用節點數,後者可能會導致無法啟動新 Pod。儘管如此,

requiredDuringScheduling

當提前知道節點和應用程式副本的數量並且您需要確保兩個Pod不會在同一個節點上結束時,親和性也就會派上用場。

四、優先順序

priorityClassName

代表您的Pod優先順序。排程器使用它來決定首先排程哪些 Pod,如果節點上沒有剩餘Pod空間,應該首先驅逐哪些 Pod。

您將需要新增多個PriorityClass(

https://kubernetes。io/docs/concepts/configuration/Pod-priority-preemption/#priorityclass

)型別資源並使用

priorityClassName

。 以下是如何

PriorityClasses

變化的示例:

Cluster

。 Priority > 10000:叢集關鍵元件,例如 kube-apiserver。

Daemonsets

。 Priority: 10000:通常不建議將 DaemonSet Pods 從叢集節點中驅逐,並替換為普通應用程式。

Production-high

。 Priority: 9000:有狀態的應用程式。

Production-medium。

Priority: 8000:無狀態應用程式。

Production-low

。 Priority: 7000:不太重要的應用程式。

Default

。 Priority: 0:非生產應用程式。

設定優先順序將幫助您避免關鍵元件的突然被驅逐。此外,如果缺乏節點資源,關鍵應用程式將驅逐不太重要的應用程式。

五、停止容器中的程序

STOPSIGNAL

中指定的訊號(通常為

TERM

訊號)被髮送到程序以停止它。但是,某些應用程式無法正確處理它並且無法正常停服。對於在 Kubernetes 中執行的應用程式也是如此。

例如下面描述,為了正確關閉 nginx,你需要一個

preStop

這樣的鉤子:

lifecycle:

preStop:

exec:

command:

- /bin/sh

- -ec

- |

sleep 3

nginx -s quit

上述的簡要說明:

sleep 3

可防止因刪除端點而導致的競爭狀況。

nginx -s quit

正確關閉nginx。映象中不需要配置此行,因為

STOPSIGNAL: SIGQUIT

引數預設設定在映象中。

STOPSIGNAL

的處理方式依賴於應用程式本身。實際上,對於大多數應用程式,您必須透過谷歌搜尋或者其它途徑來獲取處理

STOPSIGNAL

的方式。如果訊號處理不當,

preStop

鉤子可以幫助您解決問題。另一種選擇是用應用程式能夠正確處理的訊號(並允許它正常關閉)從而來替換

STOPSIGNAL

terminationGracePeriodSeconds

是關閉應用程式的另一個重要引數。它指定應用程式正常關閉的時間段。如果應用程式未在此時間範圍內終止(預設為 30 秒),它將收到一個

KILL

訊號。因此,如果您認為執行

preStop

鉤子和/或關閉應用程式

STOPSIGNAL

可能需要超過 30 秒,您將需要增加

terminateGracePeriodSeconds

引數。例如,如果來自 Web 服務客戶端的某些請求需要很長時間才能完成(比如涉及下載大檔案的請求),您可能需要增加它。

值得注意的是,

preStop hook

有一個鎖定機制,即只有在

preStop hook

執行完畢後才能傳送

STOPSIGNAL

訊號。同時,在

preStop

鉤子執行期間,

terminationGracePeriodSeconds

倒計時繼續進行。所有由鉤子引起的程序以及容器中執行的程序都將在

TerminationSeconds

結束後被終止。

此外,某些應用程式具有特定設定,用於設定應用程式必須終止的截止日期(例如,在Sidekiq 中的

——timeout

選項)。因此,您必須確保如果應用程式有此配置,則它的值略應該低於

terminationGracePeriodSeconds

六、預留資源

排程器使用 Pod的

resources。requests

來決定將Pod排程在哪個節點上。例如,無法在沒有足夠空閒(即

非請求

)資源來滿足Pod資源請求的節點上排程Pod。另一方面,

resources。limits

允許您限制嚴重超過其各自請求的Pod的資源消耗。

一個很好的方式是設定limits等於 requests。將limits設定為遠高於requests可能會導致某些節點的Pod無法獲取請求的資源的情況。這可能會導致節點(甚至節點本身)上的其他應用程式出現故障。。Kubernetes 根據其資源方案為每個Pod分配一個 QoS 類。然後,K8s 使用 QoS 類來決定應該從節點中驅逐哪些

Pod

因此,您必須同時為 CPU 和記憶體設定

requests 和 limits

。如果Linux 核心版本早於 5。4(

https://engineering。indeedblog。com/blog/2019/12/cpu-throttling-regression-fix/

)。在某些情況下,您唯一可以省略的是

CPU

限制(在 EL7/CentOS7 的情況下,如果要支援

limits

,則核心版本必須大於 3。10。0-1062。8。1。el7)。

此外,某些應用程式的記憶體消耗往往以無限的方式增長。一個很好的例子是用於快取的 Redis 或基本上“獨立”執行的應用程式。為了限制它們對節點上其他應用程式的影響,您可以(並且應該)為要消耗的記憶體量設定限制。

唯一的問題是,當應用程式達到此限制時應用程式將會被KILL。應用程式無法預測/處理此訊號,這可能會阻止它們正常關閉。這就是為什麼除了 Kubernetes 限制之外,我們

強烈建議使用專門針對於應用程式本身的機制來限制記憶體消耗

,使其不會超過(或接近)在 Pod的

limits。memory

引數中設定的數值。

這是一個Redis配置案例,可以幫助您解決這個問題:

maxmemory 500mb # if the amount of data exceeds 500 MB。。。

maxmemory-policy allkeys-lru # 。。。Redis would delete rarely used keys

至於 Sidekiq,你可以使用

Sidekiq worker killer

https://github。com/klaxit/sidekiq-worker-killer

):

require ‘sidekiq/worker_killer’

Sidekiq。configure_server do |config|

config。server_middleware do |chain|

# Terminate Sidekiq correctly when it consumes 500 MB

chain。add Sidekiq::WorkerKiller, max_rss: 500

end

end

很明顯,在所有這些情況下,

limits。memory

需要高於觸發上述機制的閾值。

七、探針

在 Kubernetes 中,探針(健康檢查)用於確定是否可以將流量切換到應用程式(

readiness

probes)以及應用程式是否需要重新啟動(

liveness

probes)。它們在更新部署和啟動新Pod方面發揮著重要作用。

首先,我們想為所有探頭型別提供一個建議:為

timeoutSeconds

引數設定一個較高的值 。一秒的預設值太低了,它將對 readiness Probe 和 liveness probes 產生嚴重影響。

如果

timeoutSeconds

太低,應用程式響應時間的增加(由於服務負載均衡,所有Pod通常同時發生)可能會導致這些Pod從負載均衡中刪除(在就緒探測失敗的情況下),或者,更糟糕的是,在級聯容器重新啟動時(在活動探測失敗的情況下)。

7.1. 活性探針(liveness probes)

在實踐中,活性探針的使用並不像您想象的那樣廣泛。它的目的是在應用程式被凍結時重新啟動容器。然而,在現實生活中,應用程式出現死鎖是一個意外情況,而不是常規的現象。如果應用程式出於某種原因導致這種異常的現象(例如,它在資料庫斷開後無法恢復與資料庫的連線),您必須在應用程式中修復它,而不是“增加”基於 liveness probes 的解決方法。

雖然您可以使用 liveness probes 來檢查這些狀態,但我們建議

預設情況下不使用 liveness Probe

或僅執行一些

基本的存活的探測,例如探測 TCP 連線(記住設定大一點的超時值)

。這樣,應用程式將重新啟動以響應明顯的死鎖,而不會進入不停的重新啟動的死迴圈(即重新啟動它也無濟於事)。

不合理的 liveness probes 配置引起的風險是非常嚴重的。在最常見的情況下, liveness probes 失敗是由於應用程式負載增加(它根本無法在

timeout

引數指定的時間內完成)或由於當前正在檢查(直接或間接)的外部依賴項的狀態。

在後一種情況下,所有容器都將重新啟動。

在最好的情況下,這不會導致任何結果,但在最壞的情況下,這將使應用程式完全不可用,也可能是長期不可用。如果大多數Pod的容器在短時間內重新啟動,可能會導致應用程式長期完全不可用(如果它有大量副本)。一些容器可能比其他容器更快地變為

READY

,並且整個負載將分佈在這個有限數量的執行容器上。這最終會導致 liveness probes 超時,也將觸發更多的重啟。

另外,如果應用程式對已建立的連線數有限制並且已達到該限制,請確保liveness probes不會停止響應。通常,您必須為liveness probes指定一個單獨的應用程式執行緒/程序來避免此類問題。例如,如果應用程式有11個執行緒(每個客戶端一個執行緒),則可以將客戶端數量限制為10個,以確保liveness probes有一個空閒執行緒可用。

另外,當然,不要向 liveness Probe 新增任何外部依賴項檢查。

7.2.

就緒探針(Readiness probe)

事實證明,readinessProbe 的設計並不是很成功。readinessProbe 結合了兩個不同的功能:

它會在容器啟動期間找出應用程式是否可用;

它檢查容器成功啟動後應用程式是否仍然可用。

在實踐中,絕大多數情況下都需要第一個功能,而第二個功能的使用頻率僅與 liveness Probe 一樣。配置不當的 readiness Probe 可能會導致類似於 liveness Probe 的問題。在最壞的情況下,它們最終還可能導致應用程式長期不可用。

當 readiness Probe 失敗時,Pod 停止接收流量。在大多數情況下,這種行為沒什麼用,因為流量通常或多或少地在 Pod 之間均勻分佈。因此,一般來說,readiness Probe 要麼在任何地方都有效,要麼不能同時在大量 Pod 上工作。在某些情況下,此類行為可能有用。但是,根據我的經驗,這也主要是在某些特殊情況下才有用。

儘管如此,readiness Probe 還具有另一個關鍵功能:它有助於確定新建立的容器何時可以處理流量,以免將負載轉發到尚未準備好的應用程式。這個 readiness Probe 功能在任何時候都是必要的。

換句話說,readiness Probe的一個功能需求量很大,而另一個功能根本不需要。startup Probe的引入解決了這一難題。它最早出現在Kubernetes 1。16中,在v1。18中成為beta版,在v1。20中保持穩定。因此,最好使用readiness Probe檢查應用程式在Kubernetes 1。18以下版本中是否已就緒,而在Kubernetes 1。18及以上版本中是否已就緒則推薦使用startup Probe。同樣,如果在應用程式啟動後需要停止單個Pod的流量,可以使用Kubernetes 1。18+中的readiness Probe。

7.3. 啟動探針

startup Probe 檢查容器中的應用程式是否準備就緒。然後它將當前 Pod 標記為準備好接收流量或繼續更新/重新啟動部署。與 readiness Probe 不同,startup Probe 在容器啟動後停止工作。

我們不建議使用 startup Probe 來檢查外部依賴:它的失敗會觸發容器重啟,這最終可能導致 Pod 處於

CrashLoopBackOff

狀態。在這種狀態下,嘗試重新啟動失敗的容器之間的延遲可能高達五分鐘。這可能會導致不必要的停機時間,因為儘管應用程式已

準備好重新啟動

,但容器會繼續等待,直到因

CrashLoopBackOff

而嘗試重新啟動的時間段結束。

如果您的應用程式接收流量並且您的 Kubernetes 版本為 1。18 或更高版本,則您應該使用 startup Probe。

另請注意,增加

failureThreshold

配置而不是設定

initialDelaySeconds

是配置探針的首選方法。這將使容器儘快可用。

八、檢查外部依賴

如您所知,readiness Probe 通常用於檢查外部依賴項(例如資料庫)。雖然這種方法理應存在,但建議您將檢查外部依賴項的方法與檢查 Pod 中的應用程式是否滿負荷執行(並切斷向其傳送流量)的方法分開也是個好主意)。

您可以使用

initContainers

在執行主容器的

startupProbe/readinessProbe

之前檢查外部依賴項。很明顯,在這種情況下,您將不再需要使用 readiness Probe 檢查外部依賴項。

initContainers

不需要更改應用程式程式碼。您不需要嵌入額外的工具來使用它們來檢查應用程式容器中的外部依賴項。通常,它們相當容易實現:

initContainers:

- name: wait-postgres

image: postgres:12。1-alpine

command:

- sh

- -ec

- |

until (pg_isready -h example。org -p 5432 -U postgres); do

sleep 1

done

resources:

requests:

cpu: 50m

memory: 50Mi

limits:

cpu: 50m

memory: 50Mi

- name: wait-redis

image: redis:6。0。10-alpine3。13

command:

- sh

- -ec

- |

until (redis-cli -u redis://redis:6379/0 ping); do

sleep 1

done

resources:

requests:

cpu: 50m

memory: 50Mi

limits:

cpu: 50m

memory: 50Mi

九、完整示例

下面是無狀態應用程式的生產級部署的完整示例,其中包含上面提供的所有建議。可以作為大家生成的參考。

您將需要 Kubernetes 1。18 或更高版本以及核心版本為 5。4 或更高版本的基於 Ubuntu/Debian 的Kubernetes節點。

apiVersion: apps/v1

kind: Deployment

metadata:

name: testapp

spec:

replicas: 10

selector:

matchLabels:

app: testapp

template:

metadata:

labels:

app: testapp

spec:

affinity:

PodAntiAffinity:

preferredDuringSchedulingIgnoredDuringExecution:

- PodAffinityTerm:

labelSelector:

matchLabels:

app: testapp

topologyKey: kubernetes。io/hostname

priorityClassName: production-medium

terminationGracePeriodSeconds: 40

initContainers:

- name: wait-postgres

image: postgres:12。1-alpine

command:

- sh

- -ec

- |

until (pg_isready -h example。org -p 5432 -U postgres); do

sleep 1

done

resources:

requests:

cpu: 50m

memory: 50Mi

limits:

cpu: 50m

memory: 50Mi

containers:

- name: backend

image: my-app-image:1。11。1

command:

- run

- app

- ——trigger-graceful-shutdown-if-memory-usage-is-higher-than

- 450Mi

- ——timeout-seconds-for-graceful-shutdown

- 35s

startupProbe:

httpGet:

path: /simple-startup-check-no-external-dependencies

port: 80

timeoutSeconds: 7

failureThreshold: 12

lifecycle:

preStop:

exec:

[“sh”, “-ec”, “#command to shutdown gracefully if needed”]

resources:

requests:

cpu: 200m

memory: 500Mi

limits:

cpu: 200m

memory: 500Mi

十、PodDisruptionBudget

PodDisruptionBudget(PDB:

https://kubernetes。io/docs/concepts/workloads/Pods/disruptions/#Pod-disruption-budgets

)機制是在生產環境中執行的應用程式的必備工具。它為您提供了一種方法,可以指定同時不可用的應用程式Pod數量的最大限制。在上文中,我們討論了一些有助於避免潛在風險情況的方法:執行多個應用程式副本,指定PodAntiAffinity(以防止多個Pod被分配到同一節點),等等。

但是,您可能會遇到多個 K8s 節點同時不可用的情況。例如,假設您決定將例項節點切換升級到更高配置的的例項節點。除此之外可能還有其他原因,本文不做更詳細的描述。最終的問題都是多個節點同時被刪除。你可能會認為,Kubernetes裡的一切都是曇花一現的!哪怕節點異常或被刪除,節點上面的Pod 將會被自動重建到其他節點上,因此,這又會有什麼關係呢?好吧,讓我們來繼續往下看看。

假設應用程式有三個副本。負載在它們之間均勻分佈,而 Pod 則分佈在節點之間。在這種情況下,即使其中一個副本出現故障,應用程式也將繼續執行。然而,兩個副本的故障則會導致服務整體降級:一個單獨的 Pod 根本無法單獨處理整個負載。客戶端將開始收到 5XX 錯誤。(當然,您可以在 nginx 容器中設定速率限制;在這種情況下,錯誤將是

429 Too Many Requests

。不過,服務仍然會降級)。

這就是 PodDisruptionBudget 可以提供幫助的地方。我們來看看它的配置清單:

apiVersion: policy/v1beta1

kind: PodDisruptionBudget

metadata:

name: app-pdb

spec:

maxUnavailable: 1

selector:

matchLabels:

app: app

上述的配置清單非常簡單;你可能熟悉它的大部分配置,

maxUnavailable

是其中最有趣的。此欄位描述可同時不可用的最大 Pod 數。這可以是數字也可以是百分比。

假設為應用程式配置了 PDB。如果出於某種原因,兩個或多個節點開始驅逐應用程式 Pod,會發生什麼?上述 PDB 一次只允許驅逐一個 Pod。因此,第二個節點會等到副本數量恢復到驅逐前的水平,然後才會驅逐第二個副本。

作為替代方案,您還可以設定

minAvailable

引數。例如:

apiVersion: policy/v1beta1

kind: PodDisruptionBudget

metadata:

name: app-pdb

spec:

minAvailable: 80%

selector:

matchLabels:

app: app

此引數可確保叢集中至少有 80% 的副本始終可用。因此,如有必要,只能驅逐 20% 的副本。

minAvailable

可以是絕對數或百分比。

但有一個問題:叢集中必須有足夠的節點滿足

PodAntiAffinity

條件。否則,您可能會遇到副本被逐出的情況,但由於缺少合適的節點,排程程式無法重新部署它。因此,排空一個節點將需要很長時間才能完成,並且會為您提供兩個應用程式副本而不是三個。當然,你可以使用用命令

kubectl describe

來檢視一個一致在等待中的Pod,看看發生了什麼事情,並解決問題。但是,最好還是儘量去避免這種情況發生。

總而言之,請

始終為系統的關鍵元件配置 PDB

十一、HorizontalPodAutoscaler

讓我們考慮另一種情況:如果應用程式的意外負載明顯高於平時,會發生什麼情況?是的,您可以手動擴充套件叢集,但這不是我們推薦使用的方法。

這就是HorizontalPodAutoscaler(HPA,

https://kubernetes。io/docs/tasks/run-application/horizontal-Pod-autoscale/

)的用武之地。藉助 HPA,您可以選擇一個指標並將其用作觸發器,根據指標的值自動向上/向下擴充套件叢集。想象一下,在一個安靜的夜晚,您的叢集突然因流量大幅上升而爆炸,例如,Reddit 使用者發現了您的服務,CPU 負載(或其他一些 Pod 指標)增加,達到閾值,然後 HPA 開始發揮作用。它擴充套件了叢集,從而在大量 Pod 之間均勻分配負載。

也正是多虧了這一點,所有傳入的請求都被成功處理。同樣重要的是,在負載恢復到平均水平後,

HPA 會縮小叢集以降低基礎設施成本

。這聽起來很不錯,不是嗎?

讓我們看看 HPA 是如何計算要新增的副本數量的。這是官方文件中提供的公式:

desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]

現在假設:

當前副本數為3;

當前度量值為 100;

度量閾值為 60;

在這種情況下,結果則為

3 * ( 100 / 60 )

,即“大約”5 個副本(HPA 會將結果向上舍入)。因此,應用程式將獲得額外的另外兩個副本。當然,HPA操作還會繼續:如果負載減少,HPA 將繼續計算所需的副本數(使用上面的公式)來縮小叢集。

另外,還有一個是我們最關心的部分。你應該使用什麼指標?首先想到的是主要指標,例如 CPU 或記憶體利用率。如果您的 CPU 和記憶體消耗與負載成正比,這將起作用。但是如果 Pod 處理不同的請求呢?有些請求需要較大的 CPU 週期,有些可能會消耗大量記憶體,還有一些只需要最少的資源。

例如,讓我們看一看RabbitMQ佇列和處理它的例項。假設佇列中有十條訊息。監控顯示訊息正在穩定且定期地退出佇列(按照RabbitMQ的術語)。也就是說,我們覺得佇列中平均有10條訊息是可以的。但是負載突然增加,佇列增加到100條訊息。然而,worker的CPU和記憶體消耗保持不變:他們穩定地處理佇列,在佇列中留下大約80-90條訊息。

但是如果我們使用一個自定義指標來描述佇列中的訊息數量呢?讓我們按如下方式配置我們的自定義指標:

當前副本數為3;

當前度量值為 80;

度量閾值為 15。

因此,

3 * ( 80 / 15 ) = 16

。在這種情況下,HPA 可以將 worker 的數量增加到 16,並且它們會快速處理佇列中的所有訊息(此時 HPA 將再次減少它們的數量)。但是,必須準備好所有必需的基礎架構以容納此數量的 Pod。也就是說,它們必須適合現有節點,或者在使用Cluster Autoscaler(

https://github。com/kubernetes/autoscaler/tree/master/cluster-autoscaler

)的情況下,必須由基礎設施供應商(雲提供商)提供新節點。換句話說,我們又回到規劃叢集資源了。

現在讓我們來看看一些清單:

apiVersion: autoscaling/v1

kind: HorizontalPodAutoscaler

metadata:

name: php-apache

spec:

scaleTargetRef:

apiVersion: apps/v1

kind: Deployment

name: php-apache

minReplicas: 1

maxReplicas: 10

targetCPUUtilizationPercentage: 50

這個很簡單。一旦 CPU 負載達到 50%,HPA 就會開始將副本數量擴充套件到最多 10 個。

下面是一個比較有趣的案例:

apiVersion: autoscaling/v1

kind: HorizontalPodAutoscaler

metadata:

name: worker

spec:

scaleTargetRef:

apiVersion: apps/v1

kind: Deployment

name: worker

minReplicas: 1

maxReplicas: 10

metrics:

- type: External

external:

metric:

name: queue_messages

target:

type: AverageValue

averageValue: 15

請注意,在此示例中,HPA 使用

自定義指標

https://kubernetes。io/docs/tasks/run-application/horizontal-Pod-autoscale/#support-for-custom-metrics

)。它將根據佇列的大小(

queue_messages

指標)做出擴充套件決策。鑑於佇列中的平均訊息數為 10,我們將閾值設定為 15。這樣可以更準確地管理副本數。如您所見,與基於 CPU 的指標相比,自定義指標可實現更準確的叢集自動縮放。

附加的功能:

HPA 配置選項是多樣化。例如,您可以組合不同的指標。在下面的清單中,同時使用CPU 利用率和佇列大小來觸發擴充套件決策。

apiVersion: autoscaling/v1

kind: HorizontalPodAutoscaler

metadata:

name: worker

spec:

scaleTargetRef:

apiVersion: apps/v1

kind: Deployment

name: worker

minReplicas: 1

maxReplicas: 10

metrics:

- type: Resource

resource:

name: cpu

target:

type: Utilization

averageUtilization: 50

- type: External

external:

metric:

name: queue_messages

target:

type: AverageValue

averageValue: 15

這種情況下,HPA又該採用什麼計算演算法?好吧,它使用計算出的最高副本數,而不考慮所利用的指標如何。例如,如果基於 CPU 指標的值顯示需要新增 5 個副本,而基於佇列大小的指標值僅給出 3 個 Pod,則 HPA 將使用較大的值並新增 5 個 Pod。

隨著Kubernetes 1。18的釋出,你現在有能力來定義

scaleUp

scaleDown

方案(

https://kubernetes。io/docs/tasks/run-application/horizontal-Pod-autoscale/#support-for-configurable-scaling-behavior

)。例如:

behavior:

scaleDown:

stabilizationWindowSeconds: 60

policies:

- type: Percent

value: 5

periodSeconds: 20

- type: Pods

value: 5

periodSeconds: 60

selectPolicy: Min

scaleUp:

stabilizationWindowSeconds: 0

policies:

- type: Percent

value: 100

periodSeconds: 10

正如您在上面的清單中看到的,它有兩個部分。第一個 (

scaleDown

) 定義縮小引數,而第二個 (

scaleUp

) 用於放大。每個部分都有

stabilizationWindowSeconds

。 這有助於防止在副本數量持續波動時出現所謂的“抖動”(或不必要的縮放)。這個引數本質上是作為副本數量改變後的超時時間。

現在讓我們談談這兩者的策略。

scaleDown

策略允許您指定在

type: Percent

特定時間段內縮減的 Pod 百分比 。如果負載具有周期性現象,您必須做的是降低百分比並增加持續時間。在這種情況下,隨著負載的減少,HPA 不會立即殺死大量 Pod(根據其公式),而是會逐漸殺死對應的Pod。此外,您可以設定

type: Pods

在指定時間段內允許 HPA 殺死的最大 Pod 數量 。

注意

selectPolicy: Min

引數。這意味著 HPA 使用影響最小 Pod 數量的策略。因此,如果百分比值(上例中的 5%)小於數字替代值(上例中的 5 個 Pod),HPA 將選擇百分比值。相反,

selectPolicy: Max

策略會產生相反的效果。

scaleUp

部分中使用了類似的引數。請注意,在大多數情況下,叢集必須(幾乎)立即擴容,因為即使是輕微的延遲也會影響使用者及其體驗。因此,在本節中

StabilizationWindowsSeconds

設定為0。如果負載具有迴圈模式,HPA可以在必要時將副本計數增加到

maxReplicas

(如HPA清單中定義的)。我們的策略允許HPA每10秒(

periodSeconds:10

)向當前執行的副本新增多達100%的副本。

最後,您可以將

selectPolicy

引數設定

Disabled

為關閉給定方向的縮放:

behavior:

scaleDown:

selectPolicy: Disabled

大多數情況下,當 HPA 未按預期工作時,才會使用策略。

策略帶來了靈活性的同時,也使配置清單更難掌握。

最近,HPA能夠跟蹤一組 Pod 中單個容器的資源使用情況(在 Kubernetes 1。20 中作為 alpha 功能引入)(

https://kubernetes。io/docs/tasks/run-application/horizontal-Pod-autoscale/#container-resource-metrics

)。

HPA:總結

讓我們以完整的 HPA 清單示例結束本段:

apiVersion: autoscaling/v1

kind: HorizontalPodAutoscaler

metadata:

name: worker

spec:

scaleTargetRef:

apiVersion: apps/v1

kind: Deployment

name: worker

minReplicas: 1

maxReplicas: 10

metrics:

- type: External

external:

metric:

name: queue_messages

target:

type: AverageValue

averageValue: 15

behavior:

scaleDown:

stabilizationWindowSeconds: 60

policies:

- type: Percent

value: 5

periodSeconds: 20

- type: Pods

value: 5

periodSeconds: 60

selectPolicy: Min

scaleUp:

stabilizationWindowSeconds: 0

policies:

- type: Percent

value: 100

periodSeconds: 10

請注意,此示例僅供參考。您將需要對其進行調整以適應您自己的操作的具體情況。

Horizontal Pod Autoscaler 簡介:

HPA 非常適合生產環境。但是,在為 HPA 選擇指標時,您必須謹慎並儘量多的考慮現狀。

錯誤的度量標準或錯誤的閾值將導致資源浪費(來自不必要的副本)或服務降級(如果副本數量不夠)。密切監視應用程式的行為並對其進行測試,直到達到正確的平衡。

十二、VerticalPodAutoscaler

VPA(

https://github。com/kubernetes/autoscaler/tree/master/vertical-Pod-autoscaler

)分析容器的資源需求並設定(如果啟用了相應的模式)它們的限制和請求。

假設您部署了一個新的應用程式版本,其中包含一些新功能,結果發現,比如說,匯入的庫是一個巨大的資源消耗者,或者程式碼沒有得到很好的最佳化。換句話說,應用程式資源需求增加了。您在測試期間沒有注意到這一點(因為很難像在生產中那樣載入應用程式)。

當然,在更新開始之前,相關的請求和限制已經為應用程式設定好了。現在,應用程式達到記憶體限制,其Pod由於OOM而被殺死。VPA可以防止這種情況!乍一看,VPA看起來是一個很好的工具,應該是被廣泛的使用的。但在現實生活中,情況並非是如此,下面會簡單說明一下。

主要問題(尚未解決)是Pod需要重新啟動才能使資源更改生效。在未來,VPA將在不重啟Pod的情況下對其進行修改,但目前,它根本無法做到這一點。但是不用擔心。如果您有一個“編寫良好”的應用程式,並且隨時準備重新部署(例如,它有大量副本;它的PodAntiAffinity、PodDistributionBudget、HorizontalPodAutoscaler都經過仔細配置,等等),那麼這並不是什麼大問題。在這種情況下,您(可能)甚至不會注意到VPA活動。

遺憾的是,可能會出現其他不太令人愉快的情況,如:應用程式重新部署得不太好,由於缺少節點,副本的數量受到限制,應用程式作為StatefulSet執行,等等。在最壞的情況下,Pod的資源消耗會因負載增加而增加,HPA開始擴充套件叢集,然後突然,VPA開始修改資源引數並重新啟動Pod。因此,高負載分佈在其餘的Pod中。其中一些可能會崩潰,使事情變得更糟,並導致失敗的連鎖反應。

這就是為什麼深入瞭解各種 VPA 操作模式很重要。讓我們從最簡單的開始——“Off模式”。

Off模式:

模式所

做的只是計算Pod的資源消耗並提出建議。我們在大多數情況下都使用這種模式(

我們建議使用這種模式

)。但首先,讓我們看幾個例子。

一些基本清單如下:

apiVersion: autoscaling。k8s。io/v1

kind: VerticalPodAutoscaler

metadata:

name: my-app-vpa

spec:

targetRef:

apiVersion: “apps/v1”

kind: Deployment

name: my-app

updatePolicy:

updateMode: “Recreate”

containerPolicies:

- containerName: “*”

minAllowed:

cpu: 100m

memory: 250Mi

maxAllowed:

cpu: 1

memory: 500Mi

controlledResources: [“cpu”, “memory”]

controlledValues: RequestsAndLimits

我們不會詳細介紹此清單的引數:文章(

https://povilasv。me/vertical-Pod-autoscaling-the-definitive-guide/

)詳細描述了 VPA 的功能和細節。簡而言之,我們指定 VPA 目標 (

targetRef

) 並選擇更新策略。

此外,我們指定了 VPA 可以使用的資源的上限和下限。主要關注

updateMode

部分。在“重新建立”或“自動”模式下,VPA 將重新建立 Pod ,因此需要考慮由此帶來的短暫停服風險(直到上述用於 Pod的 資源引數更新的補丁可用)。由於我們不想要它,我們使用“off”模式:

apiVersion: autoscaling。k8s。io/v1

kind: VerticalPodAutoscaler

metadata:

name: my-app-vpa

spec:

targetRef:

apiVersion: “apps/v1”

kind: Deployment

name: my-app

updatePolicy:

updateMode: “Off” # !!!

resourcePolicy:

containerPolicies:

- containerName: “*”

controlledResources: [“cpu”, “memory”]

VPA 開始收集指標。您可以使用

kubectl describe vpa

命令檢視建議(只需讓 VPA 執行幾分鐘即可):

Recommendation:

Container Recommendations:

Container Name: nginx

Lower Bound:

Cpu: 25m

Memory: 52428800

Target:

Cpu: 25m

Memory: 52428800

Uncapped Target:

Cpu: 25m

Memory: 52428800

Upper Bound:

Cpu: 25m

Memory: 52428800

執行幾天(一週、一個月等)後,VPA 建議將更加準確。然後是在應用程式清單中調整限制的最佳時機。這樣,您可以避免由於缺乏資源而導致的 OOM 終止並節省基礎設施(如果初始請求/限制太高)。

現在,讓我們談談使用 VPA 的一些細節。

其他 VPA 模式:

請注意,在“Initial”模式下,VPA 在 Pod 啟動時分配資源,以後不再更改它們。因此,如果過去一週的負載相對較低,VPA 將為新建立的 Pod 設定較低的請求/限制。如果負載突然增加,可能會導致問題,因為請求/限制將遠低於此類負載所需的數量。如果您的負載均勻分佈並以線性方式增長,則此模式可能會派上用場。

在“Auto”模式下,VPA 重新建立 Pod。因此,應用程式必須正確處理重新啟動。如果它不能正常關閉(即透過正確關閉現有連線等),您很可能會捕獲一些可避免的 5XX 錯誤。很少建議使用帶有 StatefulSet 的 Auto 模式:想象一下 VPA 試圖將 PostgreSQL 資源新增到生產中……

至於開發環境,您可以自由試驗以找到您可以接受的(稍後)在生產中使用的資源級別。假設您想在“Initial”模式下使用 VPA,並且我們在Redis叢集中使用

maxmemory

引數 。您很可能需要更改它以根據您的需要進行調整。問題是 Redis 不關心 cgroups 級別的限制。

換句話說,如果您的 Pod 的記憶體上限為 1GB,那麼

maxmemory

設定的是2GB ,您將面臨很大的風險。但是你怎麼能設定

maxmemory

成和

limit

一樣呢?嗯,有辦法!您可以使用 VPA 推薦的值:

apiVersion: apps/v1

kind: Deployment

metadata:

name: redis

labels:

app: redis

spec:

replicas: 1

selector:

matchLabels:

app: redis

template:

metadata:

labels:

app: redis

spec:

containers:

- name: redis

image: redis:6。2。1

ports:

- containerPort: 6379

resources:

requests:

memory: “100Mi”

cpu: “256m”

limits:

memory: “100Mi”

cpu: “256m”

env:

- name: MY_MEM_REQUEST

valueFrom:

resourceFieldRef:

containerName: app

resource: requests。memory

- name: MY_MEM_LIMIT

valueFrom:

resourceFieldRef:

containerName: app

resource: limits。memory

您可以使用環境變數來獲取記憶體限制(並從應用程式需求中減去 10%)並將結果值設定為

maxmemory

。您可能需要對

sed

用於處理 Redis 配置的 init 容器做一些事情,因為預設的 Redis 容器映像不支援

maxmemory

使用環境變數進行傳遞。儘管如此,這個解決方案還是很實用的。

最後,我想將您的注意力轉移到 VPA 一次性驅逐所有 DaemonSet Pod 的事實。我們目前正在開發修復此問題的補丁(

https://github。com/kubernetes/kubernetes/pull/98307

)。

最終的 VPA 建議:

“OFF”模式適用於大多數情況。

您可以在開發環境中嘗試“Auto”和“Initial”模式。

只有在您已經積累了大量的經驗並對其進行了徹底測試的情況下,才能在生產中使用VPA。此外,你必須清楚地瞭解你在做什麼以及為什麼要這樣做。

與此同時,我們熱切期待 Pod 資源的熱(免重啟)更新功能。

請注意,同時使用 HPA 和 VPA 存在一些限制。例如,如果基於 CPU 或基於記憶體的指標用作觸發器,則 VPA 不應與 HPA 一起使用。原因是當達到閾值時,VPA 會增加資源請求/限制,而 HPA 會新增新副本。

因此,負載將急劇下降,並且該過程將反向進行,從而導致“抖動”。官方檔案(

https://github。com/kubernetes/autoscaler/tree/master/vertical-Pod-autoscaler#known-limitations

)更清楚地說明了現有的限制。。

結論:

我們分享了一些 Kubernetes 高可用部署應用的的一些建議以及相關的案例,這些機制有助於部署高可用性應用程式。我們討論了排程器操作、更新策略、優先順序、探針等方面。最後一部分我們深入討論了剩下的三個關鍵主題:PodDisruptionBudget、HorizontalPodAutoscaler 和 VerticalPodAutoscaler。

問題中提到的大量案例都是基於我們生成的真實場景,如使用,請根據自生的環境進行調整。

透過本文,我們也希望讀者能夠根據自有的環境進行驗證,能夠一起分享各自的生成經驗,能偶一起推進Kubernetes相關技術的發展與進步。再次也感謝讀者能夠花費大量的時間認真讀完本文。

參考文章:

https://blog。flant。com/best-practices-for-deploying-highly-available-apps-in-kubernetes-part-1/

https://blog。flant。com/best-practices-for-deploying-highly-available-apps-in-kubernetes-part-2/

Migrating your app to Kubernetes: what to do with files?

ConfigMaps in Kubernetes: how they work and what you should remember

Best practices for deploying highly available apps in Kubernetes。 Part 1

Comparing Ingress controllers for Kubernetes

How we enjoyed upgrading a bunch of Kubernetes clusters from v1。16 to v1。19

Best practices for deploying highly available apps in Kubernetes。 Part 2

瞭解新鈦雲服

新鈦雲服榮膺第四屆FMCG零售消費品行業CIO年會「年度數字化服務最值得信賴品牌獎」

新鈦雲服三週歲,公司月營收超600萬元,定下百年新鈦的發展目標

當IPFS遇見雲服務|新鈦雲服與冰河分散式實驗室達成戰略協議

新鈦雲服正式獲批工信部ISP/IDC(含網際網路資源協作)牌照

深耕專業,矗立鰲頭,新鈦雲服獲千萬Pre-A輪融資

新鈦雲服,打造最專業的Cloud MSP+,做企業業務和雲之間的橋樑

新鈦雲服一週年,完成兩輪融資,服務五十多家客戶

上海某倉儲物流電子商務公司混合雲解決方案