[Kubernetes]Dockerからcontainerdへ変更後node-exporterのファイル関連のメトリクスが不正確になった事例紹介


はじめに

Kubernetes v1.24ではdockershimが削除されます。そのため、このタイミングでContainer RuntimeをDockerから別のものへ切り替える人もいるのではないでしょうか。
Container RuntimeをDockerからcontainerdへ変更した際に、私達のチームではnode-exporterで取得するPVC/PVのファイルシステムのサイズが正しく取得できない事象が発生しました。
そこで、本ドキュメントでは、本事象の原因調査し対応した事例をベースに、Mount Propagationの挙動を紹介します。

なお、本事象はprometheus/node_exporterのIssue #474で修正済みのため、本IssueがFixされる前のnode-exporterのManifestを利用している場合や設定値のアップデートで漏れていた場合などで発生します。prometheus-community/helm-chartsではこの修正コミット、prometheus-operatorではPR #1806にて修正済みです。

発生事象

Dockerからcontainerdへ切り替えた際、ルートディレクトリ(/)のMount Propagationの挙動で注意が必要です。
例えば、Podにてnode-exporterを実行しており、ルートディレクトリ(/)をマウントする際、以下のManifest(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でマウントした外部ストレージのボリューム上のファイルシステムのメトリクス(e.g. node_filesystem_size_bytesなど)の値が間違った値となります。

(例: 間違ったメトリクスの値)

  • ノードのルートディレクトリに30Giのディスクを割り当て
  • 同じノード上のPodにPVC/PVで50Giのボリュームを割り当て

上記環境において、PVC/PVのボリュームのサイズについて、上述のManifestのnode-exporterが返すnode_filesystem_size_bytesを確認すると以下の値がそれぞれ返ってきます。

  • Docker: 50Gi
  • containerd: 30Gi

Dockerは正しくPVC/PVのボリュームの値が取得できますが、containerdは間違った値(ルートディレクトリの値)となります。

※ 説明のため簡略化しサイズを示していますが、メトリクスの正確な値はGiをBytesに変換且つFileSystemの管理情報サイズなどが引かれた値となります

対象方法

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 Propargationについては、以下に簡単な説明を行います。

(前提知識) Mount Propagation とは

KubernetesにおけるMount propergationとは、コンテナによってマウントされたボリュームを、同じPod内の他コンテナまたは同じノード上の他Podにマウント情報を共有できる機能です。
Kubernetesでは、Manifestのcontainers.volumeMountsmountPropagationフィールドにて設定できます。
Linux KernelではShared Subtreesとも呼ばれています。
ここでは、Kubernetesにて指定できるオプションのうち、良く利用するであろうデフォルトのmountPropagation: None (Linuxではprivateオプション)とmountPropagation: HostToContainer (Linuxではrslaveオプション)について、その挙動の違いをUbuntu20.04上で動作させつつ解説します。

まず、事前に/mntディレクトリ以下に、以下のディレクトリ/ファイルを準備します。

/mnt# tree
.
├── bar
│   ├── d
│   ├── e
│   └── f
├── baz
├── foo
│   ├── a
│   ├── b
│   └── c
└── qux

次に、/mnt/foo/mnt/baz,/mnt/quxにbindマウントします。

/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マウントの基本的な動作になります。
この時のPropagationを見てみます。

/mnt# findmnt -o TARGET,PROPAGATION |grep /mnt
├─/mnt/baz                            shared
├─/mnt/qux                            shared

sharedとなっています。このモードは、全てのマウント情報が伝播されるモードです。

shared/rsharedとKubernetesでの注意点

Kubernetesでは、mountPropagation: Bidirectionalを指定した場合、上記で説明したshared時のマウント情報の伝播をサブツリーまで再帰的に伝播させるオプションであるrsharedの動きとなります。
ただし、このモードはbindマウントしたディレクトリ(コンテナからアクセスできるディレクトリ)からホストの元のディレクトリへ情報が双方向に伝播(Propagtion)されるため、ホスト(ノード)を破壊する恐れがあります。そのため、利用する際は十分に気をつける必要があります。
Kubernetesの公式ドキュメントでも以下の注意書きが記載されています。

Warning: Bidirectional mount propagation can be dangerous. It can 
damage the host operating system and therefore it is allowed only in 
privileged containers. Familiarity with Linux kernel behavior is strongly 
recommended. In addition, any volume mounts created by containers in pods 
must be destroyed (unmounted) by the containers on termination.

次に、privaterslaveの動作を確認します。
/mnt/bazprivateに、/mnt/quxrslave(slaveを再帰的にサブツリーに伝播するオプション)に設定し、マウント情報の伝播について確認します。

/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

このようにprivateオプションの/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オプションとしてしまった場合、node-exporterでファイルシステムの情報を取得しているSystem Callのstatfsからは、新たにbindマウントされたマウントの情報(PVでマウントされるボリュームの情報)が伝播されずルートディレクト自身の情報が返ってしまうためです。
一方、Dockerでは後方互換の維持のためルートディレクトリをマウントする際、自動的にrslaveオプションの指定となるスペシャルな実装が入っています。
そのため、DockerをContainer Runtimeとして利用している場合は、node-exporterでルートディレクトリをマウントする際、PodにてmountPropagationを省略してもDocker内ではrslaveオプションでマウントされるため、問題が発生しません。

感想

今回、mountPropagationの設定の有無で、node-exporterのようにルートディレクトリをマウントする場合においては、Dockerとcontainerdで挙動に違いが出る点を紹介しました。
私達のチームではcontainerdしか試していませんが、本現象はDocker以外のContainer Runtimeでも発生する問題と推察します。
また、node-exporter以外でもルートディレクトリをマウントしているようなPodの場合でも発生する可能性があります。
もし、bindマウントを行なっているKubernetesのPVC/PVのような環境で、ファイルシステムの情報が正しくないと思った際に疑ってみるポイントの一つですので、Mount Propagationというものがあるという知識はあると良いかもしれません。
また、本ドキュメントでは、必要な箇所に限定しMount Propagationの説明を簡単に紹介していますが、このMount Propagationは他にもオプションがあり奥が深いものの一つです。
興味のある方はLinux KernelのShared Subtreesを読むと良いでしょう。