[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

共享状态。这种模式是传播所有装载信息的模式。

在Kubernetes中,如果指定mountPropagation: Bidirectional选项,则会递归地传播shared时的挂载信息到子目录中。然而,由于该模式会双向传播(bind)挂载的目录(容器可以访问的目录)到主机的原始目录,可能会破坏主机(节点)。因此,在使用时必须非常小心。Kubernetes官方文档中也提到了以下注意事项:
警告:双向挂载传播可能很危险。它可能会损坏主机操作系统,因此只允许在特权容器中使用。强烈推荐了解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。

广告
将在 10 秒后关闭
bannerAds