限制K8S Pod 磁盤容量使用的 3 種方法

Pod 如何使用磁盤
容器在運行期間會產生臨時文件、日誌。如果沒有任何配額機制,則某些容器可能很快將磁盤寫滿,影響宿主機內核和所有應用。容器的臨時存儲,例如 emptyDir,位於目錄/var/lib/kubelet/pods 下:

/var/lib/kubelet/pods/
└── ac0810f5-a1ce-11ea-9caf-00e04c687e45  # POD_ID
    ├── containers
    │   ├── istio-init
    │   │   └── 32390fd7
    │   ├── istio-proxy
    │   │   └── 70ed81da
    │   └── zookeeper
    │       └── e9e21e59
    ├── etc-hosts          # 命名空間的Host文件
    └── volumes            # Pod的卷
        ├── kubernetes.io~configmap  # ConfigMap類型的卷
        │   └── istiod-ca-cert
        │       └── root-cert.pem -> ..data/root-cert.pem
        ├── kubernetes.io~downward-api
        │   └── istio-podinfo
        │       ├── annotations -> ..data/annotations
        │       └── labels -> ..data/labels
        ├── kubernetes.io~empty-dir # Empty類型的卷
        │   ├── istio-data
        │   └── istio-envoy
        │       ├── envoy-rev0.json
        │       └── SDS
        ├── kubernetes.io~rbd       # RBD卷
        │   └── pvc-644a7e30-845e-11ea-a4e1-70e24c686d29 # /dev/rbd0掛載到這個掛載點
        ├── kubernetes.io~csi       # CSI卷
        └── kubernetes.io~secret    # Secret類型的卷
            └── default-token-jp4n8
                ├── ca.crt -> ..data/ca.crt
                ├── namespace -> ..data/namespace
                └── token -> ..data/token

持久卷的掛載點也位於/var/lib/kubelet/pods 下,但是不會導致存儲空間的消耗。容器的日誌,存放在/var/log/pods 目錄下。使用 Docker 時,容器的 rootfs 位於/var/lib/docker 下,具體位置取決於存儲驅動。

Pod 驅逐機制
磁盤容量不足觸發的驅逐
具體細節參考:/kubernetes-study-note#out-of-resource[1]。當不可壓縮資源(內存、磁盤)不足時,節點上的 Kubelet 會嘗試驅逐掉某些 Pod,以釋放資源,防止整個系統受到影響。其中,磁盤資源不足的信號來源有兩個:imagefs:容器運行時用作存儲鏡像、可寫層的文件系統 nodefs:Kubelet 用作卷、守護進程日誌的文件系統 當 imagefs 用量到達驅逐閾值,Kubelet 會刪除所有未使用的鏡像,釋放空間。當 nodefs 用量到達閾值,Kubelet 會選擇性的驅逐 Pod(及其容器)來釋放空間。

本地臨時存儲觸發的驅逐
較新版本的 K8S 支持設置每個 Pod 可以使用的臨時存儲的 request/limit,驅逐行爲可以更具有針對性。如果 Pod 使用了超過限制的本地臨時存儲,Kubelet 將設置驅逐信號,觸發 Pod 驅逐流程:對於容器級別的隔離,如果一個容器的可寫層、日誌佔用磁盤超過限制,則 Kubelet 標記 Pod 爲待驅逐 對於 Pod 級別的隔離,Pod 總用量限制,是每個容器限制之和。如果各容器用量之和+Pod 的 emptyDir 卷超過 Pod 總用量限制,標記 Pod 爲待驅逐

從編排層限制
從 K8S 1.8 開始,支持本地臨時存儲(local ephemeral storage),ephemeral 的意思是,數據的持久性(durability)不做保證。臨時存儲可能 Backed by 本地 Attach 的可寫設備,或者內存。Pod 可以使用本地臨時存儲來作爲暫存空間,或者存放緩存、日誌。Kubelet 可以利用本地臨時存儲,將 emptyDir 卷掛載給容器。Kubelet 也使用本地臨時存儲來保存節點級別的容器日誌、容器鏡像、容器的可寫層。Kubelet 會將日誌寫入到你配置好的日誌目錄,默認 /var/log。其它文件默認都寫入到 /var/lib/kubelet。在典型情況下,這兩個目錄可能都位於宿主機的 rootfs 之下。Kubernetes 支持跟蹤、保留/限制 Pod 能夠使用的本地臨時存儲的總量。

限制 Pod 用量
打開特性開關:LocalStorageCapacityIsolation,可以限制每個 Pod 能夠使用的臨時存儲的總量。注意:以內存爲媒介(tmpfs)的 emptyDir,其用量計入容器內存消耗,而非本地臨時存儲消耗。使用類似限制內存、CPU 用量的方式,限制本地臨時存儲用量:

spec.containers[].resources.limits.ephemeral-storage
spec.containers[].resources.requests.ephemeral-storage
單位可以是 E, P, T, G, M, K,或者 Ei, Pi, Ti, Gi, Mi, Ki(1024)。下面這個例子,Pod 具有兩個容器,每個容器最多使用 4GiB 的本地臨時存儲:

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: db
    image: mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: "password"
    resources:
      requests:
        ephemeral-storage: "2Gi"
      limits:
        ephemeral-storage: "4Gi"
  - name: wp
    image: wordpress
    resources:
      requests:
        ephemeral-storage: "2Gi"
      limits:
        ephemeral-storage: "4Gi"

對 Pod 用量的監控
不監控
如果禁用 Kubelet 對本地臨時存儲的監控,則 Pod 超過 limit 限制後不會被驅逐。但是,如果磁盤整體上容量太低,節點會被打上污點,所有不能容忍此污點的 Pod 都會被驅逐。

週期性掃描
Kubelet 可以執行週期性的掃描,檢查 emptyDir 卷、容器日誌目錄、可寫容器層,然後計算 Pod/容器使用了多少磁盤。這個模式下有個問題需要注意,Kubelet 不會跟蹤已刪除文件的描述符。也就是說,如果你創建一個文件,打開文件,寫入 1GB,然後刪除文件,這種情況下 inode 仍然存在(直到你關閉文件),空間仍然被佔用,但是 Kubelet 卻沒有算這 1GB.

Project Quotas
此特性在 1.15+處於 Alpha 狀態。Project quotas 是 Linux 操作系統級別的特性,用於在目錄級別限制磁盤用量。只有本地臨時存儲(例如 emptyDir)的後備(Backing)文件系統支持 Project quotas,纔可以使用該特性。XFS、ext4 都支持 Project quotas。K8S 將佔用從 1048576 開始的 Project ID,佔用中的 ID 註冊在/etc/projects、/etc/projid 文件中。如果系統中其它進程佔用 Project ID,則也必須在這兩個文件中註冊,這樣 K8S 纔會改用其它 ID。Quotas 比周期性掃描快,而且更加精準。當一個目錄被分配到一個 Project 中後,該目錄中創建的任何文件,都是在 Project 中創建的。爲了統計用量,內核只需要跟蹤 Project 中創建了多少 block 就可以了。如果文件被創建、然後刪除,但是它的文件描述符仍然處於打開狀態,這種情況下,它仍然消耗空間,不會出現週期性掃描的那種漏統計的問題。要啓用 Project Quotas,你需要:

開啓 Kubelet 特性開關:LocalStorageCapacityIsolationFSQuotaMonitoring

確保文件系統支持 Project quotas:

XFS 文件系統默認支持,不需要操作
ext4 文件系統,你需要在未掛載之前,啓用:
$ sudo tune2fs -O project -Q prjquota /dev/vda
確保文件系統掛載時,啓用了 Project quotas。使用掛載選項 prjquota

inode 耗盡問題
有的時候,我們會發現磁盤寫入時會報磁盤滿,但是 df 查看容量並沒有 100%使用,此時可能只是因爲 inode 耗盡造成的。當前 k8s 並不支持對 Pod 的臨時存儲設置 inode 的 limits/requests。但是,如果 node 進入了 inode 緊缺的狀態,kubelet 會將 node 設置爲 under pressure,不再接收新的 Pod 請求。

從容器引擎限制
Docker 提供了配置項 --storage-opt,可以限制容器佔用磁盤空間的大小,此大小影響鏡像和容器文件系統,默認 10G。你也可以在 /etc/docker/daemon.json 中修改此配置項:

{
    "storage-driver": "devicemapper",
    "storage-opts": [
        // devicemapper
        "dm.basesize=20G",
        // overlay2
        "overlay2.size=20G",
    ]
}

但是這種配置無法影響那些掛載的卷,例如 emptyDir。

從系統層限制
你可以使用 Linux 系統提供的任何能夠限制磁盤用量的機制,爲了和 K8S 對接,需要開發 Flexvolume 或 CSI 驅動。

磁盤配額
前文已經介紹過,K8S 目前支持基於 Project quotas 來統計 Pod 的磁盤用量。這裏簡單總結一下 Linux 磁盤配額機制。

配額目標
Linux 系統支持以下幾種角度的配額:

在文件系統級別,限制羣組能夠使用的最大磁盤額度
在文件系統級別,限制單個用戶能夠使用的最大磁盤額度
限制某個目錄(directory, project)能夠佔用的最大磁盤額度
前面 2 種配額,現代 Linux 都支持,不需要前提條件。你甚至可以在一個虛擬的文件系統上進行配額:

# 寫一個空白文件
$ dd if=/dev/zero of=/path/to/the/file bs=4096 count=4096
# 格式化
...
# 掛載爲虛擬文件系統
$ mount -o loop,rw,usrquota,grpquota /path/to/the/file /path/of/mount/point
# 進行配額設置...

第 3 種需要較新的文件系統,例如 XFS、ext4fs。

配額角度
配額可以針對 Block 用量進行,也可以針對 inode 用量進行。配額可以具有軟限制、硬限制。超過軟限制後,仍然可以正常使用,但是登陸後會收到警告,在 grace time 倒計時完畢之前,用量低於軟限制後,一切恢復正常。如果 grace time 到期仍然沒做清理,則無法創建新文件。

統計用量
啓用配額,內核自然需要統計用量。管理員要查詢用量,可以使用 xfs_quota 這樣的命令,比 du 這種遍歷文件計算的方式要快得多。

啓用配額
在保證底層文件系統支持之後,你需要修改掛載選項來啓用配額:

uquota/usrquota/quota:針對用戶設置配額
gquota/grpquota:針對羣組設置配額
pquota/prjquota:針對目錄設置配額

LVM
使用 LVM 你可以任意創建具有尺寸限制的邏輯卷,把這些邏輯卷掛載給 Pod 即可:

volumes:
- flexVolume:
    # 編寫的flexVolume驅動放到
    # /usr/libexec/kubernetes/kubelet-plugins/volume/exec/kubernetes.io~lvm/lvm
    driver: kubernetes.io/lvm
    fsType: ext4
    options:
      size: 30Gi
      volumegroup: docker
  name: mnt
volumeMounts:
  - mountPath: /mnt
    name: mnt

這需要修改編排方式,不使用 emptyDir 這種本地臨時存儲,還需要處理好邏輯卷清理工作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章