RuntimeClass を使って Kubernetes クラスタで gVisor ランタイムを使う

はじめに

本記事では、Google が開発した低レベルコンテナランタイムである gVisor を利用して、特定の Kubernetes ノードでセキュリティを強化する方法を解説する。

今回使用した環境は以下の通りである。

  • OS: Linux (Ubuntu 22.04 LTS) x 3
    • 2 vCPU
    • 4GB Memory
    • 60GB Disk
  • Kubernetes: v1.31.3
  • Containerd v1.7.24

gVisor とは

gVisor は、Google によって開発された低レベルのコンテナランタイムで、セキュリティを重視した設計が特徴となっている。runsc とも言われる。

従来のコンテナランタイムと異なり、gVisor はシステムコールを直接ホストカーネルに渡すのではなく、独自にエミュレートすることにより、セキュリティが強化され、ホストカーネルへの攻撃や意図しない操作から保護する効果がある。

The Container Security Platform - gVisor
The Container Security Platform - gVisor favicon gvisor.dev
The Container Security Platform - gVisor

Kubernetes クラスタでは、通常 Containerd や CRI-O などの CRI ランタイムが利用されており、それらがさらに低レベルの OCI ランタイムを呼び出してコンテナを実行している。

今回試すこと

デフォルトだと、Containerd では runc が使われているが、gVisor と併用することもできる。Kubernetes クラスタでは RuntimeClass というリソースを使用することで、特定の Pod に対して異なる OCI ランタイムを柔軟に指定することができる。

事前準備

現在の Containerd の設定を確認

# Containerd の設定ファイルを確認
$ grep -A 3 runtimes /etc/containerd/config.toml
      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]

        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
          base_runtime_spec = ""
          cni_conf_dir = ""
          cni_max_conf_num = 0
--
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
            BinaryName = ""
            CriuImagePath = ""
            CriuPath = ""

上記のコマンドで、Containerd にランタイムがどのように定義されているかを確認した。runc がデフォルトとして登録されており、それ以外は設定されていないのが分かる。

ノードに gVisor を導入

次に、任意のワーカーノードに gVisor を導入する。

$ ssh k8s-study2

下記の公式ドキュメントを参考にした。

Installation
 Installation favicon gvisor.dev
 Installation
# インストール
$ (
  set -e
  ARCH=$(uname -m)
  URL=https://storage.googleapis.com/gvisor/releases/release/latest/${ARCH}
  wget ${URL}/runsc ${URL}/runsc.sha512 \
    ${URL}/containerd-shim-runsc-v1 ${URL}/containerd-shim-runsc-v1.sha512
  sha512sum -c runsc.sha512 \
    -c containerd-shim-runsc-v1.sha512
  rm -f *.sha512
  chmod a+rx runsc containerd-shim-runsc-v1
  sudo mv runsc containerd-shim-runsc-v1 /usr/local/bin
)

$ runsc --version
runsc version release-20241118.0
spec: 1.1.0-rc.1

$ runsc install

# containerd の設定ファイルを書き換える
$ vim /etc/containerd/config.toml

下記を [plugins."io.containerd.grpc.v1.cri".containerd.runtimes] の箇所にネストして記載する。

/etc/contained/config.toml
...
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
          runtime_type = "io.containerd.runsc.v1"
...
# Containerd を再起動
$ sudo systemctl restart containerd

# ワーカーノードでの作業終わり
$ exit

検証

RuntimeClass の作成

Kubernetes クラスタへ RuntimeClass リソースを作成する。

rtc.yaml
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: gvisor
handler: runsc

YAML はこちらを参考にした。

ランタイムクラス(Runtime Class)
FEATURE STATE: Kubernetes v1.20 [stable] このページではRuntimeClassリソースと、runtimeセクションのメカニズムについて説明します。 RuntimeClassはコンテナランタイムの設定を選択するための機能です。そのコンテナランタイム設定はPodのコンテナを稼働させるために使われます。 RuntimeClassを使う動機 異なるPodに異なるRuntimeClassを設定することで、パフォーマンスとセキュリティのバランスをとることができます。例えば、ワークロードの一部に高レベルの情報セキュリティ保証が必要な場合、ハードウェア仮想化を使用するコンテナランタイムで実行されるようにそれらのPodをスケジュールすることを選択できます。その後、追加のオーバーヘッドを犠牲にして、代替ランタイムをさらに分離することでメリットが得られます。 RuntimeClassを使用して、コンテナランタイムは同じで設定が異なるPodを実行することもできます。 セットアップ ノード上でCRI実装を設定する。(ランタイムに依存) 対応するRuntimeClassリソースを作成する。 1. ノード上でCRI実装を設定する RuntimeClassを通じて利用可能な設定はContainer Runtime Interface (CRI)の実装依存となります。 ユーザーの環境のCRI実装の設定方法は、対応するドキュメント(下記)を参照ください。 備考:RuntimeClassは、クラスター全体で同じ種類のノード設定であることを仮定しています。(これは全てのノードがコンテナランタイムに関して同じ方法で構成されていることを意味します)。 設定が異なるノードをサポートするには、スケジューリングを参照してください。 RuntimeClassの設定は、RuntimeClassによって参照されるハンドラー名を持ちます。そのハンドラーは有効なDNSラベル名でなくてはなりません。 2. 対応するRuntimeClassリソースを作成する ステップ1にて設定する各項目は、関連するハンドラー 名を持ちます。それはどの設定かを指定するものです。各ハンドラーにおいて、対応するRuntimeClassオブジェクトが作成されます。 そのRuntimeClassリソースは現時点で2つの重要なフィールドを持ちます。それはRuntimeClassの名前(metadata.name)とハンドラー(handler)です。そのオブジェクトの定義は下記のようになります。 # RuntimeClassはnode.k8s.ioというAPIグループで定義されます。 apiVersion: node.k8s.io/v1 kind: RuntimeClass metadata: # RuntimeClass名 # RuntimeClassはネームスペースなしのリソースです。 name: myclass # 対応するCRI設定 handler: myconfiguration RuntimeClassオブジェクトの名前はDNSサブドメイン名に従う必要があります。 備考:RuntimeClassの書き込み操作(create/update/patch/delete)はクラスター管理者のみに制限されることを推奨します。 これはたいていデフォルトで有効となっています。さらなる詳細に関してはAuthorization Overviewを参照してください。 使用例 RuntimeClassがクラスターに対して設定されると、PodSpecでruntimeClassNameを指定して使用できます。 例えば apiVersion: v1 kind: Pod metadata: name: mypod spec: runtimeClassName: myclass # ... これは、kubeletに対してPodを稼働させるためのRuntimeClassを使うように指示します。もし設定されたRuntimeClassが存在しない場合や、CRIが対応するハンドラーを実行できない場合、そのPodはFailedというフェーズになります。 エラーメッセージに関しては対応するイベントを参照して下さい。 もしruntimeClassNameが指定されていない場合、デフォルトのRuntimeHandlerが使用され、これはRuntimeClassの機能が無効であるときのふるまいと同じものとなります。 CRIの設定 CRIランタイムのセットアップに関するさらなる詳細は、コンテナランタイムを参照してください。
ランタイムクラス(Runtime Class) favicon kubernetes.io
ランタイムクラス(Runtime Class)
$ kubectl apply -f rtc.yaml

$ kubectl get runtimeclass
NAME     HANDLER   AGE
gvisor   runsc     5s

$ kubectl describe runtimeclass gvisor
Name:         gvisor
Namespace:
Labels:       <none>
Annotations:  <none>
API Version:  node.k8s.io/v1
Handler:      runsc
Kind:         RuntimeClass
Metadata:
  Creation Timestamp:  2024-12-02T02:37:41Z
  Resource Version:    1121670
  UID:                 b54b5073-d9f8-4c63-9b60-7761b4c4549f
Events:                <none>

Pod の作成

これで準備が整ったので、実際に Pod を作成して試してみる。

Pod は以下のように 2 つ作成する。

  1. RuntimeClass の設定なし
  2. RuntimeClass の設定あり(gVisor を指定)
nginx-gvisor.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx-gvisor
  name: nginx-gvisor
spec:
  nodeName: k8s-study2      # gVisor を導入したノードを指定
  runtimeClassName: gvisor  # RuntimeClass 名を指定
  containers:
  - image: nginx
    name: nginx-gvisor
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
# 1 つ目の Pod を作成
$ kubectl run nginx --image nginx

# 2 つ目の Pod を作成
$ kubectl apply -f nginx-gvisor.yaml

# Pod の状態を確認
$ kubectl get po -o wide
NAME           READY   STATUS    RESTARTS   AGE     IP               NODE            NOMINATED NODE   READINESS GATES
nginx          1/1     Running   0          2m25s   192.168.44.144   k8s-study2      <none>           <none>
nginx-gvisor   1/1     Running   0          8s      192.168.44.147   k8s-study2      <none>           <none>

動作確認

それぞれの Pod 内で dmesg コマンドを使って、カーネルのログを確認する。

$ kubectl exec nginx -- dmesg
dmesg: read kernel buffer failed: Operation not permitted
command terminated with exit code 1

$ kubectl exec nginx-gvisor -- dmesg
[    0.000000] Starting gVisor...
[    0.454514] Letting the watchdogs out...
[    0.707171] Conjuring /dev/null black hole...
[    1.192689] Checking naughty and nice process list...
[    1.464322] Waiting for children...
[    1.654970] Segmenting fault lines...
[    1.752302] Creating bureaucratic processes...
[    2.165221] Reading process obituaries...
[    2.399834] Granting licence to kill(2)...
[    2.444516] Checking naughty and nice process list...
[    2.611171] Forking spaghetti code...
[    2.711935] Setting up VFS...
[    3.137197] Setting up FUSE...
[    3.567744] Ready!

結果としては、1 つ目の nginx Pod の方では何も表示されないが、2 つ目の nginx-gvisor Pod では gVisor に関するログを確認できた。

ワーカーノードに入って crictl コマンドを使う方法でもランタイムの情報を確認することができる。

$ ssh k8s-study2
$ crictl ps | grep nginx
CONTAINER           IMAGE               CREATED              STATE               NAME                        ATTEMPT             POD ID              POD
d68725fd30aab       1ee494ebb83f2       2 minutes ago        Running             nginx-gvisor                0                   568b0c5f94069       nginx-gvisor
1f4c575e24a61       1ee494ebb83f2       4 minutes ago        Running             nginx                       0                   4627faa7cc3ed       nginx

# nginx コンテナの場合
$ crictl inspect --output go-template --template='{{ .info.runtimeType }}' 1f4c575e24a61
io.containerd.runc.v2

# nginx-gvisor コンテナの場合
$ crictl inspect --output go-template --template='{{ .info.runtimeType }}' d68725fd30aab
io.containerd.runsc.v1

まとめ

本記事では、Kubernetes クラスタの任意のノードに gVisor を導入する手順を紹介した。gVisor は、特にセキュリティ要件の高い環境で有効な低レベルコンテナランタイムであり、Kubernetes ではそれを自由に選択できる RuntimeClass というリソースの使い方を確認した。

使い方についてはまだ深掘りが必要だが、従来のランタイムである runc と使い分けることで、セキュリティの高いユースケースにも対応できるのではないかと思う。

参考

The Container Security Platform - gVisor
The Container Security Platform - gVisor favicon gvisor.dev
The Container Security Platform - gVisor
ランタイムクラス(Runtime Class)
FEATURE STATE: Kubernetes v1.20 [stable] このページではRuntimeClassリソースと、runtimeセクションのメカニズムについて説明します。 RuntimeClassはコンテナランタイムの設定を選択するための機能です。そのコンテナランタイム設定はPodのコンテナを稼働させるために使われます。 RuntimeClassを使う動機 異なるPodに異なるRuntimeClassを設定することで、パフォーマンスとセキュリティのバランスをとることができます。例えば、ワークロードの一部に高レベルの情報セキュリティ保証が必要な場合、ハードウェア仮想化を使用するコンテナランタイムで実行されるようにそれらのPodをスケジュールすることを選択できます。その後、追加のオーバーヘッドを犠牲にして、代替ランタイムをさらに分離することでメリットが得られます。 RuntimeClassを使用して、コンテナランタイムは同じで設定が異なるPodを実行することもできます。 セットアップ ノード上でCRI実装を設定する。(ランタイムに依存) 対応するRuntimeClassリソースを作成する。 1. ノード上でCRI実装を設定する RuntimeClassを通じて利用可能な設定はContainer Runtime Interface (CRI)の実装依存となります。 ユーザーの環境のCRI実装の設定方法は、対応するドキュメント(下記)を参照ください。 備考:RuntimeClassは、クラスター全体で同じ種類のノード設定であることを仮定しています。(これは全てのノードがコンテナランタイムに関して同じ方法で構成されていることを意味します)。 設定が異なるノードをサポートするには、スケジューリングを参照してください。 RuntimeClassの設定は、RuntimeClassによって参照されるハンドラー名を持ちます。そのハンドラーは有効なDNSラベル名でなくてはなりません。 2. 対応するRuntimeClassリソースを作成する ステップ1にて設定する各項目は、関連するハンドラー 名を持ちます。それはどの設定かを指定するものです。各ハンドラーにおいて、対応するRuntimeClassオブジェクトが作成されます。 そのRuntimeClassリソースは現時点で2つの重要なフィールドを持ちます。それはRuntimeClassの名前(metadata.name)とハンドラー(handler)です。そのオブジェクトの定義は下記のようになります。 # RuntimeClassはnode.k8s.ioというAPIグループで定義されます。 apiVersion: node.k8s.io/v1 kind: RuntimeClass metadata: # RuntimeClass名 # RuntimeClassはネームスペースなしのリソースです。 name: myclass # 対応するCRI設定 handler: myconfiguration RuntimeClassオブジェクトの名前はDNSサブドメイン名に従う必要があります。 備考:RuntimeClassの書き込み操作(create/update/patch/delete)はクラスター管理者のみに制限されることを推奨します。 これはたいていデフォルトで有効となっています。さらなる詳細に関してはAuthorization Overviewを参照してください。 使用例 RuntimeClassがクラスターに対して設定されると、PodSpecでruntimeClassNameを指定して使用できます。 例えば apiVersion: v1 kind: Pod metadata: name: mypod spec: runtimeClassName: myclass # ... これは、kubeletに対してPodを稼働させるためのRuntimeClassを使うように指示します。もし設定されたRuntimeClassが存在しない場合や、CRIが対応するハンドラーを実行できない場合、そのPodはFailedというフェーズになります。 エラーメッセージに関しては対応するイベントを参照して下さい。 もしruntimeClassNameが指定されていない場合、デフォルトのRuntimeHandlerが使用され、これはRuntimeClassの機能が無効であるときのふるまいと同じものとなります。 CRIの設定 CRIランタイムのセットアップに関するさらなる詳細は、コンテナランタイムを参照してください。
ランタイムクラス(Runtime Class) favicon kubernetes.io
ランタイムクラス(Runtime Class)
kubeadmで作ったKubernetesクラスターでcontainerd + gVisorのRuntimeClassを動かすぞ2021年エディション~~~~ - inductor's blog
containerd + gVisorでKubernetesのワークロードが動かせたのでうれしいよって話です。
kubeadmで作ったKubernetesクラスターでcontainerd + gVisorのRuntimeClassを動かすぞ2021年エディション~~~~ - inductor's blog favicon blog.inductor.me
kubeadmで作ったKubernetesクラスターでcontainerd + gVisorのRuntimeClassを動かすぞ2021年エディション~~~~ - inductor's blog
Kubernetes の Runtime Class について知ろう
CloudNative Days Spring 2021 ONLINE の発表資料です 「Kubernetes の Runtime Class について知ろう」
Kubernetes の Runtime Class について知ろう favicon speakerdeck.com
Kubernetes の Runtime Class について知ろう
オンプレKubernetes(K8s)のランタイムをgVisorに変えてみる
はじめに 今回は、仮想環境上に構築したKubernetesクラスタのWorker …
オンプレKubernetes(K8s)のランタイムをgVisorに変えてみる favicon c.itdo.jp
オンプレKubernetes(K8s)のランタイムをgVisorに変えてみる