在管理集群的時候我們常常會遇到資源不足的情況,在這種情況下我們要保證整個集群可用,並且盡可能減少應用的損失。根據該問題提出以下兩種方案:一種為優化kubelet參數,另一種為腳本化診斷處理。
1. 概念解釋
- CPU 的使用時間是可壓縮的,換句話說它本身無狀態,申請資源很快,也能快速正常回收。
- 內存(memory)大小是不可壓縮的,因為它是有狀態的(內存里面保存的數據),申請資源很慢(需要計算和分配內存塊的空間),並且回收可能失敗(被占用的內存一般不可回收)。
2. 優化kubelet參數
優化kubelet參數通過k8s資源表示、節點資源配置及kubelet參數設置、應用優先級和資源動態調整這幾個方面來介紹。k8s資源表示為yaml文件中如何添加requests和limites參數。節點資源配置及kubelet參數設置描述為一個node上面資源配置情況,從而來優化kubelet參數。應用優先級描述為當資源不足時,優先保留那些pod不被驅逐。資源動態調整描述為運算能力的增減,如:HPA 、VPA和Cluster Auto Scaler。
2.1.1 k8s資源表示
- 在k8s中,資源表示配置字段是 spec.containers[].resource.limits/request.cpu/memory。yaml格式如下:
spec: template: ... spec: containers: ... resources: limits: cpu: "1" memory: 1000Mi requests: cpu: 20m memory: 100Mi
2.1.2 資源動態調整
動態調整的思路:應用的實際流量會不斷變化,因此使用率也是不斷變化的,為了應對應用流量的變化,我們應用能夠自動調整應用的資源。比如在線商品應用在促銷的時候訪問量會增加,我們應該自動增加 pod 運算能力來應對;當促銷結束后,有需要自動降低 pod 的運算能力防止浪費。
運算能力的增減有兩種方式:改變單個 pod 的資源,已經增減 pod 的數量。這兩種方式對應了 kubernetes 的 HPA 和 VPA和Cluster Auto Scaler。
- HPA: 橫向 pod 自動擴展的思路是這樣的:kubernetes 會運行一個 controller,周期性地監聽 pod 的資源使用情況,當高於設定的閾值時,會自動增加 pod 的數量;當低於某個閾值時,會自動減少 pod 的數量。自然,這里的閾值以及 pod 的上限和下限的數量都是需要用戶配置的。
- VPA: VPA 調整的是單個 pod 的 request 值(包括 CPU 和 memory)VPA 包括三個組件:
(1)Recommander:消費 metrics server 或者其他監控組件的數據,然后計算 pod 的資源推薦值
(2)Updater:找到被 vpa 接管的 pod 中和計算出來的推薦值差距過大的,對其做 update 操作(目前是 evict,新建的 pod 在下面 admission controller 中會使用推薦的資源值作為 request)
(3)Admission Controller:新建的 pod 會經過該 Admission Controller,如果 pod 是被 vpa 接管的,會使用 recommander 計算出來的推薦值 - CLuster Auto Scaler:能夠根據整個集群的資源使用情況來增減節點。Cluster Auto Scaler 就是監控這個集群因為資源不足而 pending 的 pod,根據用戶配置的閾值調用公有雲的接口來申請創建機器或者銷毀機器。
2.1.3 節點資源配置及kubelet參數設置
節點資源的配置一般分為 2 種:
- 資源預留:為系統進程和 k8s 進程預留資源
- pod 驅逐:節點資源到達一定使用量,開始驅逐 pod
包含如下:
(1) Node Capacity:Node的所有硬件資源
(2)kube-reserved:給kube組件預留的資源:kubelet,kube-proxy以及docker等
(3) system-reserved:給system進程預留的資源
(4) eviction-threshold:kubelet eviction的閾值設定
(5)Allocatable:真正scheduler調度Pod時的參考值(保證Node上所有Pods的request resource不超過Allocatable)
allocatable的值即對應 describe node 時看到的allocatable容量,pod 調度的上限
計算公式:節點上可配置值 = 總量 - 預留值 - 驅逐閾值
Allocatable = Capacity - Reserved(kube+system) - Eviction Threshold
以上配置均在kubelet 中添加,涉及的參數有:
--kube-reserved=cpu=200m,memory=250Mi \ --system-reserved=cpu=200m,memory=250Mi \ --eviction-hard=memory.available<5%,nodefs.available<10%,imagefs.available<10% \ --eviction-soft=memory.available<10%,nodefs.available<15%,imagefs.available<15% \ --eviction-soft-grace-period=memory.available=2m,nodefs.available=2m,imagefs.available=2m \ --eviction-max-pod-grace-period=120 \ --eviction-pressure-transition-period=30s \ --eviction-minimum-reclaim=memory.available=0Mi,nodefs.available=500Mi,imagefs.available=500Mi
以上配置均為百分比,舉例:
以2核4GB內存40GB磁盤空間的配置為例,Allocatable是1.6 CPU,3.3Gi 內存,25Gi磁盤。當pod的總內存消耗大於3.3Gi或者磁盤消耗大於25Gi時,會根據相應策略驅逐pod。
Allocatable = 4Gi - 250Mi -250Mi - 4Gi*5% = 3.3Gi
(1) 配置 k8s組件預留資源的大小,CPU、Mem
指定為k8s系統組件(kubelet、kube-proxy、dockerd等)預留的資源量,
如:--kube-reserved=cpu=1,memory=2Gi,ephemeral-storage=1Gi。 這里的kube-reserved只為非pod形式啟動的kube組件預留資源,假如組件要是以static pod(kubeadm)形式啟動的,那並不在這個kube-reserved管理並限制的cgroup中,而是在kubepod這個cgroup中。 (ephemeral storage需要kubelet開啟feature-gates,預留的是臨時存儲空間(log,EmptyDir),生產環境建議先不使用) ephemeral-storage是kubernetes1.8開始引入的一個資源限制的對象,kubernetes 1.10版本中kubelet默認已經打開的了,到目前1.11還是beta階段,主要是用於對本地臨時存儲使用空間大小的限制,如對pod的empty dir、/var/lib/kubelet、日志、容器可讀寫層的使用大小的限制。
(2)配置 系統守護進程預留資源的大小(預留的值需要根據機器上容器的密度做一個合理的值)
含義:為系統守護進程(sshd, udev等)預留的資源量,
如:--system-reserved=cpu=500m,memory=1Gi,ephemeral-storage=1Gi。 注意,除了考慮為系統進程預留的量之外,還應該為kernel和用戶登錄會話預留一些內存。
(3)配置 驅逐pod的硬閾值
含義:設置進行pod驅逐的閾值,這個參數只支持內存和磁盤。
通過--eviction-hard標志預留一些內存后,當節點上的可用內存降至保留值以下時, kubelet 將會對pod進行驅逐。 配置:--eviction-hard=memory.available<5%,nodefs.available<10%,imagefs.available<10%
(4)配置 驅逐pod的軟閾值
--eviction-soft=memory.available<10%,nodefs.available<15%,imagefs.available<15%
(5)定義達到軟閾值之后,持續時間超過多久才進行驅逐
--eviction-soft-grace-period=memory.available=2m,nodefs.available=2m,imagefs.available=2m
(6)驅逐pod前最大等待時間=min(pod.Spec.TerminationGracePeriodSeconds, eviction-max-pod-grace-period),單位為秒
--eviction-max-pod-grace-period=120
(7)至少回收的資源量
--eviction-minimum-reclaim=memory.available=0Mi,nodefs.available=500Mi,imagefs.available=500Mi
(8)防止波動,kubelet 多久才上報節點的狀態
--eviction-pressure-transition-period=30s
(9)centos7配置示例:
查看kubelet Service文件
[root@cwztapp001 kubelet.service.d]# cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf # Note: This dropin only works with kubeadm and kubelet v1.11+ [Service] Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf" Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml" # This is a file that "kubeadm init" and "kubeadm join" generate at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env # This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use # the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file. EnvironmentFile=-/etc/default/kubelet Environment="KUBELET_EXTRA_ARGS=--node-ip=192.168.30.100 --hostname-override=cwztapp001" ExecStart= ExecStart=/usr/local/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS You have mail in /var/spool/mail/root
根據kubelet Service配置文件可以看出kubelet參數配置文件為/var/lib/kubelet/config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1 authentication: anonymous: enabled: false webhook: cacheTTL: 0s enabled: true x509: clientCAFile: /etc/kubernetes/pki/ca.crt authorization: mode: Webhook webhook: cacheAuthorizedTTL: 0s cacheUnauthorizedTTL: 0s clusterDNS: - 169.254.25.10 clusterDomain: cluster.local cpuManagerReconcilePeriod: 0s evictionHard: memory.available: 5% evictionMaxPodGracePeriod: 120 evictionPressureTransitionPeriod: 30s evictionSoft: memory.available: 10% evictionSoftGracePeriod: memory.available: 2m featureGates: CSINodeInfo: true ExpandCSIVolumes: true RotateKubeletClientCertificate: true VolumeSnapshotDataSource: true fileCheckFrequency: 0s healthzBindAddress: 127.0.0.1 healthzPort: 10248 httpCheckFrequency: 0s imageMinimumGCAge: 0s kind: KubeletConfiguration kubeReserved: cpu: 200m memory: 250Mi maxPods: 110 nodeStatusReportFrequency: 0s nodeStatusUpdateFrequency: 0s rotateCertificates: true runtimeRequestTimeout: 0s staticPodPath: /etc/kubernetes/manifests streamingConnectionIdleTimeout: 0s syncFrequency: 0s systemReserved: cpu: 1000m memory: 250Mi volumeStatsAggPeriod: 0s
2.1.4 應用優先級
當資源不足時,配置了如上驅逐參數,pod之間的驅逐順序是怎樣的呢?以下描述設置不同優先級來確保集群中核心的組件不被驅逐還正常運行,OOM 的優先級如下,pod oom 值越低,也就越不容易被系統殺死。:
BestEffort Pod > Burstable Pod > 其它進程(內核init進程等) > Guaranteed Pod > kubelet/docker 等 > sshd 等進程
kubernetes 把 pod 分成了三個 QoS 等級,而其中和limits和requests參數有關:
- Guaranteed:oom優先級最低,可以考慮數據庫應用或者一些重要的業務應用。除非 pods 使用超過了它們的 limits,或者節點的內存壓力很大而且沒有 QoS 更低的 pod,否則不會被殺死。
- Burstable:這種類型的 pod 可以多於自己請求的資源(上限有 limit 指定,如果 limit 沒有配置,則可以使用主機的任意可用資源),但是重要性認為比較低,可以是一般性的應用或者批處理任務。
- Best Effort:oom優先級最高,集群不知道 pod 的資源請求情況,調度不考慮資源,可以運行到任意節點上(從資源角度來說),可以是一些臨時性的不重要應用。pod 可以使用節點上任何可用資源,但在資源不足時也會被優先殺死。
Pod 的 requests 和 limits 是如何對應到這三個 QoS 等級上的,可以用下面一張表格概括:
request是否配置 | limits是否配置 | 兩者的關系 | Qos | 說明 |
---|---|---|---|---|
是 | 是 | requests=limits | Guaranteed | 所有容器的cpu和memory都必須配置相同的requests和limits |
是 | 是 | request<limit | Burstable | 只要有容器配置了cpu或者memory的request和limits就行 |
是 | 否 | Burstable | 只要有容器配置了cpu或者memory的request就行 | |
否 | 是 | Guaranteed/Burstable | 如果配置了limits,k8s會自動把對應資源的request設置和limits一樣。如果所有容器所有資源都配置limits,那就是Guaranteed;如果只有部分配置了limits,就是Burstable | |
否 | 否 | Best Effort | 所有的容器都沒有配置資源requests或limits |
說明:
- request和limits相同,可以參考資源動態調整中的VPA設置合理值。
- 如果只配置了limits,沒有配置request,k8s會把request值和limits值一樣。
- 如果只配置了request,沒有配置limits,該pod共享node上可用的資源,實際上很反對這樣設置。
總結
動態地資源調整通過 kubelet 驅逐程序進行的,但需要和應用優先級配合使用才能達到很好的效果,否則可能驅逐集群中核心組件。
3. 腳本化診斷處理
什么叫腳本化診斷處理呢?它的含義為:當集群中的某台機器資源(一般指memory)用到85%-90%時,腳本自動檢查到且該節點為不可調度。缺點為:背離了資源動態調整中CLuster Auto Scaler特點。
- 集群中每台機器都可以執行kubectl指令:
如果沒有設置,可將master機器上的$HOME/.kube/config文件拷貝到node機器上。 - 可以通過shell-operator自動診斷機器資源且做cordon操作處理
- 腳本中關鍵說明
(1)獲取本地IP:ip a | grep ‘state UP’ -A2| grep inet | grep -v inet6 | grep -v 127 | sed ‘s/[ \t]*//g’ | cut -d ‘ ’ -f2 | cut -d ‘/’ -f1
(2)獲取本地ip對應的node名:kubectl get nodes -o wide | grep “本地ip” | awk ‘{print $1}’
(3)不可調度:kubectl cordon node <node名>
(4)獲取總內存: free -m | awk ‘NR==2{print $2}’
(5)獲取使用內存: free -m | awk ‘NR==2{print $3}’