在运行和操作 Kubernetes 集群时,常见的一个问题就是磁盘空间不足。当节点被配置时,你应该确保为容器镜像和运行中的容器分配足够的存储空间。容器运行时通常会写入 /var
目录。这个目录可以作为一个单独的分区,也可以位于根文件系统上。默认情况下,CRI-O
会将其容器和镜像写入 /var/lib/containers
,而 containerd
会将其容器和镜像写入 /var/lib/containerd
。
Kubernetes 有持久数据和临时数据。kubelet
和本地 Kubernetes 特定存储的基本路径是可配置的,通常为 /var/lib/kubelet
。在 Kubernetes 文档中,这个路径有时被称为根文件系统或节点文件系统。这些数据的大部分可以分为以下几类:
- ephemeral storage(临时数据)
- logs
- container runtime(运行时数据)
这与大多数 POSIX 系统不同,因为在大多数 POSIX 系统中,根文件系统或节点文件系统是 /
,而在 Kubernetes 中,指的是/var/lib/kubelet
所在的磁盘。
ephemeral storage(临时数据)
Pods
和容器在其运行过程中可能需要临时或短暂的本地存储。临时存储的生命周期不会超过单个 Pod
的生命周期,并且不能在不同的 Pod
之间共享。
logs
默认情况下,Kubernetes 将每个运行中的容器的日志存储在 /var/log
目录下的文件中。这些日志是临时的,并且由 kubelet
监控,以确保它们在 Pod 运行期间不会变得过大。
您可以自定义每个节点的日志轮转设置来管理这些日志的大小,并配置日志传输(使用第三方解决方案)以避免依赖于节点本地存储。
container runtime(运行时数据)
容器运行时有两个不同的存储区域,用于存储容器和镜像。
- 只读层:镜像通常被视为只读层,因为它们在容器运行时不会被修改。只读层可以由多个层组合而成,形成一个单一的只读层。在容器上有一层薄薄的临时存储层,用于容器写入文件系统的情况。
- 可写层:根据您的容器运行时,本地写入可能实现为分层写机制(例如,在 Linux 上的
overlayfs
或在 Windows 上的CimFS
)。这被称为可写层。本地写入也可能使用一个初始化为容器镜像完整克隆的可写文件系统;这用于基于虚拟化的某些运行时。
容器运行时文件系统包含只读层和可写层。在 Kubernetes 文档中,这被称为 imagefs
。
Container runtime configurations
CRI-O
CRI-O
使用一个 TOML
格式的存储配置文件,允许您控制容器运行时如何存储持久数据和临时数据。CRI-O
利用了存储库(storage library)。
一些 Linux 发行版提供了一个关于存储的手册(man 5 containers-storage.conf
)。存储的主要配置文件位于 /etc/containers/storage.conf
,您可以在此控制临时数据和根目录的位置。
根目录是 CRI-O
存储持久数据的地方。
[storage]
# Default storage driver
driver = "overlay"
# Temporary storage location
runroot = "/var/run/containers/storage"
# Primary read/write location of container storage
graphroot = "/var/lib/containers/storage"
- graphroot
从容器运行时存储的持久数据(如果启用了SELinux
,这个路径必须匹配/var/lib/containers/storage
)。 - runroot
容器的临时读写访问(建议将此配置在临时文件系统上)。
这里有一个快速的方法来重新标记您的 graphroot
目录,以匹配 /var/lib/containers/storage:
semanage fcontext -a -e /var/lib/containers/storage <YOUR-STORAGE-PATH>
restorecon -R -v <YOUR-STORAGE-PATH>
containerd
containerd
运行时使用一个 TOML
配置文件来控制持久数据和临时数据的存储位置。配置文件的默认路径位于 /etc/containerd/config.toml
。
与 containerd 存储相关的字段是root
和 state
。
- root
containerd
元数据的根目录。默认值为/var/lib/containerd
。如果您的操作系统需要,根目录也需要 SELinux 标签。 - state
containerd
的临时数据。默认值为/run/containerd
。
Kubernetes 节点压力驱逐
Kubernetes 会自动检测容器文件系统是否与节点文件系统分离。当文件系统分离时,Kubernetes 负责监控节点文件系统和容器运行时文件系统。Kubernetes 文档中将节点文件系统和容器运行时文件系统分别称为 nodefs
和 imagefs
。如果 nodefs
或 imagefs
中的任何一个磁盘空间不足,那么整个节点将被认为存在磁盘压力。在这种情况下,Kubernetes 会首先通过删除未使用的容器和镜像来回收空间,然后才会考虑驱逐 Pod
。在一个具有 nodefs
和imagefs
的节点上,kubelet
会在 imagefs
上垃圾回收未使用的容器镜像,并从 nodefs
中移除已死亡的 Pod
及其容器。如果只有 nodefs
,那么 Kubernetes 的垃圾回收将包括已死亡的容器、已死亡的 Pod
和未使用的镜像。
Kubernetes 允许更多配置来确定磁盘是否已满
kubelet
内的驱逐管理器有一些配置设置,可以让您控制相关的阈值。对于文件系统,相关的测量指标包括 nodefs.available
、nodefs.inodesfree
、imagefs.available
和 imagefs.inodesfree
。如果容器运行时没有专用磁盘,则忽略 imagefs
。
用户可以使用现有的默认值:
-
memory.available < 100MiB
-
nodefs.available < 10%
-
imagefs.available < 15%
-
nodefs.inodesFree < 5%(仅限 Linux 节点)
Kubernetes 允许您在 kubelet 配置文件中设置用户定义的值,这些值可以在EvictionHard
和EvictionSoft
中定义。 -
EvictionHard
定义了限制;一旦这些限制被超过,Pod 将在没有任何宽限期的情况下被驱逐。 -
EvictionSoft
定义了限制;一旦这些限制被超过,Pod 将在设定的宽限期内被驱逐,宽限期可以按信号设置。
如果您指定了 EvictionHard 的值,它将替换默认值。这意味着在配置中设置所有信号非常重要。
例如,以下 kubelet
配置可用于设置驱逐信号和宽限期选项:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
address: "192.168.0.8"
port: 20250
serializeImagePulls: false
evictionHard:
memory.available: "100Mi"
nodefs.available: "10%"
nodefs.inodesFree: "5%"
imagefs.available: "15%"
imagefs.inodesFree: "5%"
evictionSoft:
memory.available: "100Mi"
nodefs.available: "10%"
nodefs.inodesFree: "5%"
imagefs.available: "15%"
imagefs.inodesFree: "5%"
evictionSoftGracePeriod:
memory.available: "1m30s"
nodefs.available: "2m"
nodefs.inodesFree: "2m"
imagefs.available: "2m"
imagefs.inodesFree: "2m"
evictionMaxPodGracePeriod: 60s
注意
Kubernetes 项目建议您要么使用默认的驱逐设置,要么设置所有的驱逐字段。您可以使用默认设置,也可以指定自己的 evictionHard
设置。如果您遗漏了某个信号,Kubernetes 将不会监控该资源。管理员或用户常见的一个错误配置是在 /var/lib/containers/storage
或 /var/lib/containerd
挂载一个新的文件系统。Kubernetes 会检测到这是一个独立的文件系统,因此如果您进行了这样的操作,需要确保 imagefs.inodesfree
和 imagefs.available
符合您的需求。
另一个容易混淆的领域是,即使您为节点定义了一个镜像文件系统,临时存储报告也不会改变。镜像文件系统(imagefs
)用于存储容器镜像层;如果容器写入其自身的根文件系统,这种本地写入不会计入容器镜像的大小。容器运行时存储这些本地修改的位置是运行时定义的,但通常也是镜像文件系统。如果Pod
中的容器写入一个基于文件系统的 emptyDir
卷,这将占用 nodefs
文件系统的空间。kubelet
始终基于 nodefs
文件系统报告临时存储容量和分配,这在临时写入实际上发生在镜像文件系统时可能会引起混淆。