[Kubernetes] 在从Docker切换到containerd后,介绍了一个文件相关度量不准确的node-exporter案例
首先
在Kubernetes v1.24中,dockershim将被删除。因此,在这个时机可能会有人切换Container Runtime从Docker到其他替代品。
在将Container Runtime从Docker切换到containerd时,我们的团队遇到了无法正确获取PVC / PV文件系统大小的问题,该问题是由node-exporter导致的。
因此,本文档将根据处理该问题的案例来介绍Mount Propagation的行为。
顺便提一下,该问题已在prometheus/node_exporter的Issue#474中修复,因此在此问题被修复之前,如果使用了旧版本的node-exporter Manifest文件或在配置更新中遗漏了该问题,就会发生此问题。在prometheus-community/helm-charts中已修正此问题的提交,在prometheus-operator中已通过PR#1806进行修正。
事件发生
切り替えは、Dockerからcontainerdへの移行時において、ルートディレクトリ(/)のマウントプロパゲーションの挙動に注意が必要です。例えば、node-exporterを含むPodを実行している場合、ルートディレクトリ(/)をマウントする際に、以下のManifestの一部抜粋にあるように、containers.volumeMounts.mountPropagationの指定を省略すると注意が必要です。
...
containers:
- name: node-exporter
image: prom/node-exporter:v1.3.1
...
args:
...
- --path.rootfs
- "/rootfs"
...
volumeMounts:
- mountPath: /rootfs
name: root-volume
readOnly: true
...
volumes:
- name: root-volume
hostPath:
path: /
...
以PVC/PV挂载的外部存储卷上的文件系统指标(例如node_filesystem_size_bytes)的值出现错误。
(例:错误的度量值)
-
- ノードのルートディレクトリに30Giのディスクを割り当て
- 同じノード上のPodにPVC/PVで50Giのボリュームを割り当て
在上述环境中,关于PVC/PV的容量大小,通过上述清单中的node-exporter返回的node_filesystem_size_bytes进行确认,将得到以下值。
-
- Docker: 50Gi
- containerd: 30Gi
Docker能够正确获取PVC/PV卷的值,而containerd会返回错误的值(即根目录的值)。
为了说明进行了简化并显示大小,但是指标的精确值是将Gi转换为Bytes,并减去文件系统管理信息大小等的值。
对应的方法
如果使用node-exporter,可以在根目录(/)的挂载选项中添加mountPropagation: HostToContainer,可以获取正确的指标。
@@ -156,6 +156,7 @@
name: sys-volume
readOnly: true
- mountPath: /rootfs
+ mountPropagation: HostToContainer
name: root-volume
readOnly: true
- mountPath: /var/run/dbus/system_bus_socket
为什么会发生这样的现象?
这种现象不仅限于containerd,在除了Docker以外的Container Runtime也有可能发生。
要理解并解决这种现象,需要事先了解如何在使用PVC/PV将卷挂载到容器时,挂载的配置和bind挂载的Mount Propagation的理解。
关于PVC/PV卷在容器中的挂载配置,可以参考「Kubernetes中与存储相关的指标列表:PV的挂载和监控范围」。
关于Mount Propagation,以下是简单的说明。
(前提知识) Mount Propagation 是什么?
在Kubernetes中,Mount Propagation是指将由容器挂载的卷的挂载信息共享给同一Pod内的其他容器或同一节点上的其他Pod的功能。
在Kubernetes中,可以通过Manifest的containers.volumeMounts的mountPropagation字段进行设置。
在Linux内核中,它也被称为Shared Subtrees。
在这里,我们将在Ubuntu20.04上运行并解释两个常用的Kubernetes可指定选项的默认mountPropagation行为:mountPropagation: None(在Linux中为private选项)和mountPropagation: HostToContainer(在Linux中为rslave选项)。
首先,在/mnt目录下,预先准备以下目录/文件。
/mnt# tree
.
├── bar
│ ├── d
│ ├── e
│ └── f
├── baz
├── foo
│ ├── a
│ ├── b
│ └── c
└── qux
下一步,将/mnt/foo绑定挂载到/mnt/baz和/mnt/qux。
/mnt# mount --bind /mnt/foo /mnt/baz
/mnt# mount --bind /mnt/foo /mnt/qux
/mnt# tree
.
├── bar
│ ├── d
│ ├── e
│ └── f
├── baz
│ ├── a
│ ├── b
│ └── c
├── foo
│ ├── a
│ ├── b
│ └── c
└── qux
├── a
├── b
└── c
到目前为止,这是关于bind挂载的基本操作。我们来看看这时的传播情况。
/mnt# findmnt -o TARGET,PROPAGATION |grep /mnt
├─/mnt/baz shared
├─/mnt/qux shared
共享状态。这种模式是传播所有装载信息的模式。
警告:双向挂载传播可能很危险。它可能会损坏主机操作系统,因此只允许在特权容器中使用。强烈推荐了解Linux内核行为。此外,容器中的pod所创建的任何卷挂载必须在容器终止时被销毁(卸载)。
接下来,我们将确认 private 和 rslave 的操作。
我们会将 /mnt/baz 设置为 private,将 /mnt/qux 设置为 rslave(在子树中递归传播从属设置),并验证相关的挂载信息传播。
/mnt# mount --make-private /mnt/baz
/mnt# mount --make-rslave /mnt/qux
/mnt# findmnt -o TARGET,PROPAGATION |grep /mnt
├─/mnt/baz private
├─/mnt/qux private,slave
# /mnt/fooの配下にbarディレクトリを作成しbindマウントします
/mnt# mkdir /mnt/foo/bar
/mnt# tree
.
├── bar
│ ├── d
│ ├── e
│ └── f
├── baz
│ ├── a
│ ├── b
│ ├── bar
│ └── c
├── foo
│ ├── a
│ ├── b
│ ├── bar
│ └── c
└── qux
├── a
├── b
├── bar
└── c
# /mnt/bar を /mnt/foo/bar へbindマウントし /mnt/baz (private), /mnt/qux (rslave) へのマウント情報の伝播を確認します
/mnt# mount --bind /mnt/bar /mnt/foo/bar
/mnt# tree
.
├── bar
│ ├── d
│ ├── e
│ └── f
├── baz
│ ├── a
│ ├── b
│ ├── bar
│ └── c
├── foo
│ ├── a
│ ├── b
│ ├── bar
│ │ ├── d
│ │ ├── e
│ │ └── f
│ └── c
└── qux
├── a
├── b
├── bar
│ ├── d
│ ├── e
│ └── f
└── r
在私有选项/mnt/baz下,bar目录的挂载信息完全不会传递,而在rslave选项/mnt/qux下,bar目录的挂载信息会传播。
在Docker和containerd中,node-exporter的行为差异有哪些?
如果在Kubernetes中省略了mountPropagation,则默认值为mountPropagation: None(即private选项)。
从上述的“Mount Propagation”说明中可以看出,当使用containerd在像node-exporter这样的Pod中挂载根目录时,需要使用rslave选项(mountPropagation: HostToContainer)。
如果像本例一样错误地将其设置为private选项,则无法将新绑定的挂载(PV挂载的卷信息)传播到从statfs系统调用中获取文件系统信息的node-exporter,从而返回了根目录本身的信息。
另一方面,为了保持向后兼容性,在Docker中自动选择rslave选项来挂载根目录的特殊实现。
因此,如果将Docker用作容器运行时时,当在node-exporter中挂载根目录时,即使在Pod中省略了mountPropagation,在Docker内部也会使用rslave选项进行挂载,因此不会出现问题。
印象
在这次讨论中,我们介绍了在设置mountPropagation时,对于像node-exporter这样在根目录挂载的情况下,Docker和containerd会有不同的行为差异。虽然我们的团队只尝试过containerd,但我们推测这个现象在除了Docker以外的其他容器运行时中也会出现。此外,即使在像node-exporter之外的其他挂载了根目录的Pod中,也有可能出现这个问题。如果您在使用类似Kubernetes的PVC/PV进行bind挂载的环境中怀疑文件系统信息不正确时,Mount Propagation可以作为一个怀疑点,因此了解有关Mount Propagation的知识可能会有所帮助。此外,尽管本文档只对必要的部分进行了简要介绍,但Mount Propagation是一个拥有多个选项和深层次内容的概念。如果您对此感兴趣,可以阅读Linux内核的Shared Subtrees。