本文发布已超过一年。较旧的文章可能包含过时内容。请检查页面中的信息自发布以来是否已不再准确。
镜像文件系统:配置 Kubernetes 将容器存储在单独的文件系统上
在运行/操作 Kubernetes 集群时,一个常见问题是磁盘空间不足。配置节点时,应为容器镜像和正在运行的容器预留足够的存储空间。容器运行时通常写入 /var
。这可以是一个单独的分区,也可以在根文件系统上。CRI-O 默认将其容器和镜像写入 /var/lib/containers
,而 containerd 则写入 /var/lib/containerd
。
在这篇博文中,我们将介绍如何配置容器运行时,使其内容存储在与默认分区不同的位置。
这样做可以为 Kubernetes 配置提供更大的灵活性,并支持为容器存储添加更大的磁盘,同时保持默认文件系统不受影响。
需要进一步解释的一个方面是 Kubernetes 写入磁盘的位置/内容。
理解 Kubernetes 磁盘使用情况
Kubernetes 具有持久数据和临时数据。kubelet 和本地 Kubernetes 特定存储的基础路径是可配置的,但通常假定为 /var/lib/kubelet
。在 Kubernetes 文档中,这有时被称为根文件系统或节点文件系统。这些数据主要可分为以下几类:
- 临时存储
- 日志
- 和容器运行时
这与大多数 POSIX 系统不同,因为根/节点文件系统不是 /
,而是 /var/lib/kubelet
所在的分区。
临时存储
Pods 和容器在运行时可能需要临时或瞬态的本地存储。临时存储的生命周期不会超过单个 Pod 的生命周期,并且不能在 Pod 之间共享。
日志
默认情况下,Kubernetes 将每个正在运行的容器的日志存储为 /var/log
中的文件。这些日志是临时性的,并由 kubelet 监控,以确保它们在 Pod 运行时不会变得太大。
您可以自定义每个节点的日志轮换设置来管理日志大小,并配置日志转发(使用第三方解决方案),以避免依赖节点本地存储。
容器运行时
容器运行时有两个不同的区域用于存储容器和镜像。
只读层:镜像通常被认为是只读层,因为它们在容器运行时不会被修改。只读层可以由多个层组合而成,形成一个单一的只读层。在容器顶部有一个薄层,如果容器写入文件系统,该层会为容器提供临时存储。
可写层:根据您的容器运行时,本地写入可能实现为分层写入机制(例如,Linux 上的
overlayfs
或 Windows 上的 CimFS)。这被称为可写层。本地写入也可以使用一个可写文件系统,该文件系统使用容器镜像的完整克隆进行初始化;这用于一些基于 hypervisor 虚拟化的运行时。
容器运行时文件系统包含只读层和可写层。在 Kubernetes 文档中,这被认为是 imagefs
。
容器运行时配置
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 标签,root 也需要
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
< 100MiBnodefs.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
所代表的文件系统报告临时存储容量和分配情况;当实际临时写入发生到镜像文件系统时,这可能会引起混淆。
未来工作
为了解决临时存储报告的局限性并为容器运行时提供更多配置选项,SIG Node 正在研究 KEP-4191。在 KEP-4191 中,Kubernetes 将检测可写层是否与只读层(镜像)分离。这将使我们能够将所有临时存储(包括可写层)放在同一个磁盘上,并允许为镜像使用单独的磁盘。
参与其中
如果您想参与其中,可以加入Kubernetes 节点特别兴趣小组 (SIG)。
如果您想分享反馈,可以在我们的 #sig-node Slack 频道进行。如果您尚未加入该 Slack 工作区,可以访问 https://slack.k8s.io/ 获取邀请。
特别感谢所有做出出色评审、分享宝贵见解或提出主题建议的贡献者。
- Peter Hunt
- Mrunal Patel
- Ryan Phillips
- Gaurav Singh