Kubernetesにおけるフォレンジックコンテナチェックポイント処理

Authors: Adrian Reber (Red Hat)

フォレンジックコンテナチェックポイント処理はCheckpoint/Restore In Userspace (CRIU)に基づいており、コンテナがチェックポイントされていることを認識することなく、実行中のコンテナのステートフルコピーを作成することができます。 コンテナのコピーは、元のコンテナに気づかれることなく、サンドボックス環境で複数回の分析やリストアが可能です。 フォレンジックコンテナチェックポイント処理はKubernetes v1.25でalpha機能として導入されました。

どのように機能しますか?

CRIUを使用してコンテナのチェックポイントやリストアを行うことが可能です。 CRIUはruncやcrun、CRI-O、containerdと統合されており、Kubernetesで実装されているフォレンジックコンテナチェックポイント処理は、既存のCRIU統合を使用します。

なぜ重要なのか?

CRIUと対応する統合機能を使用することで、後でフォレンジック分析を行うために、ディスク上で実行中のコンテナに関する全ての情報と状態を取得することが可能です。 フォレンジック分析は、疑わしいコンテナを停止したり影響を与えることなく検査するために重要となる場合があります。 コンテナが本当に攻撃を受けている場合、攻撃者はコンテナを検査する処理を検知するかもしれません。 チェックポイントを取得しサンドボックス環境でコンテナを分析することは、元のコンテナや、おそらく攻撃者にも検査を認識されることなく、コンテナを検査することができる可能性があります。

フォレンジックコンテナチェックポイント処理のユースケースに加えて、内部状態を失うことなく、あるノードから他のノードにコンテナを移行することも可能です。 特に初期化時間の長いステートフルコンテナの場合、チェックポイントからリストアすることは再起動後の時間が節約されるか、起動時間がより早くなる可能性があります。

コンテナチェックポイント処理を利用するには?

機能はフィーチャーゲートで制限されているため、新しい機能を使用する前にContainerCheckpointを有効にしてください。

ランタイムがコンテナチェックポイント処理をサポートしている必要もあります。

  • containerd: サポートは現在検討中です。詳細はcontainerdプルリクエスト#6965を見てください。
  • CRI-O: v1.25はフォレンジックコンテナチェックポイント処理をサポートしています。

CRI-Oでの使用例

CRI-Oとの組み合わせでフォレンジックコンテナチェックポイント処理を使用するためには、ランタイムをコマンドラインオプション--enable-criu-support=trueで起動する必要があります。 Kubernetesでは、ContainerCheckpointフィーチャーゲートを有効にしたクラスターを実行する必要があります。 チェックポイント処理の機能はCRIUによって提供されているため、CRIUをインストールすることも必要となります。 通常、runcやcrunはCRIUに依存しているため、自動的にインストールされます。

執筆時点ではチェックポイント機能はCRI-OやKubernetesにおいてalpha機能としてみなされており、セキュリティ影響がまだ検討中であることに言及することも重要です。

コンテナとPodが実行されると、チェックポイントを作成することが可能になります。 チェックポイント処理kubeletレベルでのみ公開されています。 コンテナをチェックポイントするためには、コンテナが実行されているノード上でcurlを実行し、チェックポイントをトリガーします。

curl -X POST "https://localhost:10250/checkpoint/namespace/podId/container"

default名前空間内のcountersと呼ばれるPod内のcounterと呼ばれるコンテナに対し、kubelet APIエンドポイントが次の場所で到達可能です。

curl -X POST "https://localhost:10250/checkpoint/default/counters/counter"

厳密には、kubeletの自己署名証明書を許容し、kubeletチェックポイントAPIの使用を認可するために、下記のcurlコマンドのオプションが必要です。

--insecure --cert /var/run/kubernetes/client-admin.crt --key /var/run/kubernetes/client-admin.key

このkubelet APIが実行されると、CRI-Oからチェックポイントの作成をリクエストします。 CRI-Oは低レベルランタイム(例えばrunc)からチェックポイントをリクエストします。 そのリクエストを確認すると、runcは実際のチェックポイントを行うためにcriuツールを呼び出します。

チェックポイント処理が終了すると、チェックポイントは/var/lib/kubelet/checkpoints/checkpoint-<pod-name>_<namespace-name>-<container-name>-<timestamp>.tarで利用可能になります。

その後、そのtarアーカイブを使用してコンテナを別の場所にリストアできます。

Kubernetesの外部でチェックポイントしたコンテナをリストアする(CRI-Oを使用)

チェックポイントtarアーカイブを使用すると、CRI-Oのサンドボックスインスタンス内のKubernetesの外部にコンテナをリストア可能です。 リストア中のより良いユーザエクスペリエンスのために、main CRI-O GitHubブランチからCRI-Oのlatestバージョンを使用することを推奨します。 CRI-O v1.25を使用している場合、コンテナを開始する前にKubernetesが作成する特定のディレクトリを手動で作成する必要があります。

Kubernetesの外部にコンテナをリストアするための最初のステップは、crictlを使用してPodサンドボックスを作成することです。

crictl runp pod-config.json

次に、さきほどチェックポイントしたコンテナを新しく作成したPodサンドボックスにリストアします。

crictl create <POD_ID> container-config.json pod-config.json

container-config.jsonのレジストリでコンテナイメージを指定する代わりに、前に作成したチェックポイントアーカイブへのパスを指定する必要があります。

{
  "metadata": {
      "name": "counter"
  },
  "image":{
      "image": "/var/lib/kubelet/checkpoints/<checkpoint-archive>.tar"
  }
}

次に、そのコンテナを開始するためにcrictl start <CONTAINER_ID>を実行すると、さきほどチェックポイントしたコンテナのコピーが実行されているはずです。

Kubernetes内でチェックポイントしたコンテナをリストアする

先ほどチェックポイントしたコンテナをKubernetes内で直接リストアするためには、レジストリにプッシュできるイメージにチェックポイントアーカイブを変換する必要があります。

ローカルのチェックポイントアーカイブを変換するための方法として、buildahを使用した下記のステップが考えられます。

newcontainer=$(buildah from scratch)
buildah add $newcontainer /var/lib/kubelet/checkpoints/checkpoint-<pod-name>_<namespace-name>-<container-name>-<timestamp>.tar /
buildah config --annotation=io.kubernetes.cri-o.annotations.checkpoint.name=<container-name> $newcontainer
buildah commit $newcontainer checkpoint-image:latest
buildah rm $newcontainer

出来上がったイメージは標準化されておらず、CRI-Oとの組み合わせでのみ動作します。 このイメージはalphaにも満たないフォーマットであると考えてください。 このようなチェックポイントイメージのフォーマットを標準化するための議論が進行中です。 これはまだ標準化されたイメージフォーマットではなく、CRI-Oを--enable-criu-support=trueで起動した場合のみ動作することを忘れないでください。 CRIUサポートでCRI-Oを起動することのセキュリティ影響はまだ明確ではなく、そのため、イメージフォーマットだけでなく機能も気を付けて使用するべきです。

さて、そのイメージをコンテナイメージレジストリにプッシュする必要があります。 例えば以下のような感じです。

buildah push localhost/checkpoint-image:latest container-image-registry.example/user/checkpoint-image:latest

このチェックポイントイメージ(container-image-registry.example/user/checkpoint-image:latest)をリストアするために、イメージはPodの仕様(Specification)に記載する必要があります。 以下はマニフェストの例です。

apiVersion: v1
kind: Pod
metadata:
  namePrefix: example-
spec:
  containers:
  - name: <container-name>
    image: container-image-registry.example/user/checkpoint-image:latest
  nodeName: <destination-node>

Kubernetesは新しいPodをノード上にスケジュールします。 そのノード上のKubeletは、registry/user/checkpoint-image:latestとして指定されたイメージをもとに、コンテナを作成し開始するようにコンテナランタイム(この例ではCRI-O)に指示をします。 CRI-Oはregistry/user/checkpoint-image:latestがコンテナイメージでなく、チェックポイントデータへの参照であることを検知します。 その時、コンテナを作成し開始する通常のステップの代わりに、CRI-Oはチェックポイントデータをフェッチし、指定されたチェックポイントからコンテナをリストアします。

Pod内のアプリケーションはチェックポイントを取得しなかったかのように実行し続けます。 コンテナ内では、アプリケーションはチェックポイントからリストアされず通常起動したコンテナのような見た目や動作をします。

これらのステップで、あるノードで動作しているPodを、別のノードで動作している新しい同等のPodに置き換えることができ、そのPod内のコンテナの状態を失うことはないです。

どのように参加すればよいですか?

SIG Nodeにはいくつかの手段でアクセスすることができます。

さらなる読み物

コンテナチェックポイントの分析方法に関する詳細は後続のブログForensic container analysisを参照してください。

更新: dockershimの削除に関するFAQ

この記事は2020年の後半に投稿されたオリジナルの記事Dockershim Deprecation FAQの更新版です。 この記事にはv1.24のリリースに関する更新を含みます。


この文書では、Kubernetesからの dockershim の削除に関するよくある質問について説明します。 この削除はKubernetes v1.20リリースの一部としてはじめて発表されたものです。 Kubernetes v1.24のリリースにおいてdockershimは実際にKubernetesから削除されました。

これが何を意味するかについては、ブログ記事Don't Panic: Kubernetes and Dockerをご覧ください。

dockershim削除の影響範囲を確認するをお読みいただくことで、 dockershimの削除があなたやあなたの組織に与える影響をご判断いただけます。

Kubernetes 1.24リリースに至るまでの間、Kubernetesコントリビューターはこの移行を円滑に行えるようにするために尽力してきました。

dockershimはなぜKubernetesから削除されたのですか?

Kubernetesの初期のバージョンは、特定のコンテナランタイム上でのみ動作しました。 Docker Engineです。その後、Kubernetesは他のコンテナランタイムと連携するためのサポートを追加しました。 オーケストレーター(Kubernetesなど)と多くの異なるコンテナランタイムの間の相互運用を可能にするため、 CRI標準が作成されました。 Docker Engineはそのインターフェイス(CRI)を実装していないため、Kubernetesプロジェクトは移行を支援する特別なコードを作成し、 その dockershim コードをKubernetes自身の一部としました。

dockershimコードは常に一時的な解決策であることを意図されていました(このためshimと名付けられています)。 コミュニティでの議論や計画については、dockershimの削除によるKubernetes改良の提案にてお読みいただけます。

実際、dockershimのメンテナンスはKubernetesメンテナーにとって大きな負担になっていました。

さらに、dockershimとほとんど互換性のなかった機能、たとえばcgroups v2やユーザーネームスペースなどが、 これらの新しいCRIランタイムに実装されています。Kubernetesからdockershimを削除することで、これらの分野でのさらなる開発が可能になります。

Dockerとコンテナは同じものですか?

DockerはLinuxのコンテナパターンを普及させ、その基盤技術の発展に寄与してきましたが、 Linuxのコンテナ技術そのものはかなり以前から存在しています。 また、コンテナエコシステムはDockerを超えてより広範に発展してきました。 OCIやCRIのような標準は、Dockerの機能の一部を置き換えたり、既存の機能を強化したりすることで、 私達のエコシステムの多くのツールの成長と繁栄を助けてきました。

既存のコンテナイメージは引き続き使えるのですか?

はい、docker buildから生成されるイメージは、全てのCRI実装で動作します。 既存のイメージも全く同じように動作します。

プライベートイメージについてはどうでしょうか?

はい、すべてのCRIランタイムはKubernetesで使われているものと同一のpull secretsをサポートしており、 PodSpecまたはService Accountを通して利用できます。

Kubernetes 1.23でDocker Engineを引き続き使用できますか?

はい、1.20で変更されたのは、Docker Engineランタイムを使用している場合に警告ログがkubelet起動時に出るようになったことだけです。 この警告は、1.23までのすべてのバージョンで表示されます。 dockershimの削除はKubernetes 1.24で行われました。

Kubernetes v1.24以降を実行している場合は、Docker Engineを引き続きコンテナランタイムとして利用できますか?をご覧ください。 (CRIがサポートされているKubernetesリリースを使用している場合、dockershimから切り替えることができることを忘れないでください。 リリースv1.24からはKubernetesにdockershimが含まれなくなったため、必ず切り替えなければなりません)。

どのCRIの実装を使うべきでしょうか?

これは難しい質問で、様々な要素に依存します。 もしDocker Engineがうまく動いているのであれば、containerdに移行するのは比較的簡単で、 性能もオーバーヘッドも確実に改善されるでしょう。 しかし、他の選択のほうがあなたの環境により適合する場合もありますので、 CNCF landscapeにあるすべての選択肢を検討されることをおすすめします。

Docker Engineを引き続きコンテナランタイムとして利用できますか?

第一に、ご自身のPCで開発やテスト用途でDockerを使用している場合、何も変わることはありません。 Kubernetesでどのコンテナランタイムを使っていても、Dockerをローカルで使い続けることができます。 コンテナではこのような相互運用性を実現できます。

MirantisとDockerは、Kubernetesから内蔵のdockershimが削除された後も、 Docker Engineの代替アダプターを維持することにコミットしています。 代替アダプターの名前はcri-dockerdです。

cri-dockerdをインストールして、kubeletをDocker Engineに接続するために使用することができます。 詳細については、Migrate Docker Engine nodes from dockershim to cri-dockerdを読んでください。

今現在でプロダクション環境に他のランタイムを使用している例はあるのでしょうか?

Kubernetesプロジェクトが生み出したすべての成果物(Kubernetesバイナリ)は、リリースごとに検証されています。

また、kindプロジェクトは以前からcontainerdを使っており、プロジェクトのユースケースにおいて安定性が向上してきています。 kindとcontainerdは、Kubernetesコードベースの変更を検証するために毎日何回も利用されています。 他の関連プロジェクトも同様のパターンを追っており、他のコンテナランタイムの安定性と使いやすさが示されています。 例として、OpenShift 4.xは2019年6月以降、CRI-Oランタイムをプロダクション環境で使っています。

他の事例や参考資料はについては、 containerdとCRI-O(Cloud Native Computing Foundation (CNCF)の2つのコンテナランタイム)の採用例をご覧ください。

OCIという単語をよく見るのですが、これは何ですか?

OCIはOpen Container Initiativeの略で、コンテナツールとテクノロジー間の数多くのインターフェースの標準化を行った団体です。 彼らはコンテナイメージをパッケージするための標準仕様(OCI image-spec)と、 コンテナを実行するための標準仕様(OCI runtime-spec)をメンテナンスしています。 また、runcという形でruntime-specの実装もメンテナンスしており、 これはcontainerdCRI-Oの両方でデフォルトの下位ランタイムとなっています。 CRIはこれらの低レベル仕様に基づいて、コンテナを管理するためのエンドツーエンドの標準を提供します。

CRI実装を変更する際に注意すべきことは何ですか?

DockerとほとんどのCRI(containerdを含む)において、下位で使用されるコンテナ化コードは同じものですが、 いくつかの細かい違いが存在します。移行する際に考慮すべき一般的な事項は次のとおりです。

  • ログ設定
  • ランタイムリソースの制限
  • ノード構成スクリプトでdockerコマンドやコントロールソケット経由でDocker Engineを使用しているもの
  • kubectlのプラグインでdocker CLIまたはDocker Engineコントロールソケットが必要なもの
  • KubernetesプロジェクトのツールでDocker Engineへの直接アクセスが必要なもの(例:廃止されたkube-imagepullerツール)
  • registry-mirrorsやinsecureレジストリなどの機能の設定
  • その他の支援スクリプトやデーモンでDocker Engineが利用可能であることを想定していてKubernetes外で実行されるもの(モニタリング・セキュリティエージェントなど)
  • GPUまたは特別なハードウェア、そしてランタイムおよびKubernetesとそれらハードウェアの統合方法

あなたがKubernetesのリソース要求/制限やファイルベースのログ収集DaemonSetを使用しているのであれば、それらは問題なく動作し続けますが、 dockerdの設定をカスタマイズしていた場合は、それを新しいコンテナランタイムに適合させる必要があるでしょう。

他に注意することとしては、システムメンテナンスを実行するようなものや、コンテナ内でイメージをビルドするようなものが動作しなくなります。 前者の場合は、crictlツールをdrop-inの置き換えとして使用できます(docker cliからcrictlへのマッピングを参照)。 後者の場合は、imgbuildahkanikobuildkit-cli-for-kubectlのようなDockerを必要としない新しいコンテナビルドの選択肢を使用できます。

containerdを使っているのであれば、ドキュメントを参照して、移行するのにどのような構成が利用可能かを確認するところから始めるといいでしょう。

containerdとCRI-OをKubernetesで使用する方法に関しては、コンテナランタイムに関するKubernetesのドキュメントを参照してください。

さらに質問がある場合どうすればいいでしょうか?

ベンダーサポートのKubernetesディストリビューションを使用している場合、彼らの製品に対するアップグレード計画について尋ねることができます。 エンドユーザーの質問に関しては、エンドユーザーコミュニティフォーラムに投稿してください。

dockershimの削除に関する決定については、専用のGitHub issueで議論することができます。

変更点に関するより詳細な技術的な議論は、待ってください、DockerはKubernetesで非推奨になったのですか?という素晴らしいブログ記事も参照してください。

dockershimを使っているかどうかを検出できるツールはありますか?

はい!Detector for Docker Socket (DDS)というkubectlプラグインをインストールすることであなたのクラスターを確認していただけます。 DDSは、アクティブなKubernetesワークロードがDocker Engineソケット(docker.sock)をボリュームとしてマウントしているかを検出できます。 さらなる詳細と使用パターンについては、DDSプロジェクトのREADMEを参照してください。

ハグしていただけますか?

はい、私達は引き続きいつでもハグに応じています。🤗🤗🤗

Don't Panic: Kubernetes and Docker

著者: Jorge Castro, Duffie Cooley, Kat Cosgrove, Justin Garrison, Noah Kantrowitz, Bob Killen, Rey Lejano, Dan “POP” Papandrea, Jeffrey Sica, Davanum “Dims” Srinivas

Kubernetesはv1.20より新しいバージョンで、コンテナランタイムとしてDockerをサポートしません

パニックを起こす必要はありません。これはそれほど抜本的なものではないのです。

概要: ランタイムとしてのDockerは、Kubernetesのために開発されたContainer Runtime Interface(CRI)を利用しているランタイムを選んだ結果としてサポートされなくなります。しかし、Dockerによって生成されたイメージはこれからも、今までもそうだったように、みなさんのクラスターで使用可能です。

もし、あなたがKubernetesのエンドユーザーであるならば、多くの変化はないでしょう。これはDockerの死を意味するものではありませんし、開発ツールとして今後Dockerを使用するべきでない、使用することは出来ないと言っているのでもありません。Dockerはコンテナを作成するのに便利なツールですし、docker buildコマンドで作成されたイメージはKubernetesクラスター上でこれからも動作可能なのです。

もし、GKE、EKS、AKSといったマネージドKubernetesサービス(それらはデフォルトでcontainerdを使用しています)を使っているのなら、ワーカーノードがサポート対象のランタイムを使用しているか、Dockerのサポートが将来のK8sバージョンで切れる前に確認しておく必要があるでしょう。 もし、ノードをカスタマイズしているのなら、環境やRuntimeの仕様に合わせて更新する必要があるでしょう。サービスプロバイダーと確認し、アップグレードのための適切なテストと計画を立ててください。

もし、ご自身でClusterを管理しているのなら、やはり問題が発生する前に必要な対応を行う必要があります。v1.20の時点で、Dockerの使用についての警告メッセージが表示されるようになります。将来のKubernetesリリース(現在の計画では2021年下旬のv1.22)でDockerのRuntimeとしての使用がサポートされなくなれば、containerdやCRI-Oといった他のサポート対象のRuntimeに切り替える必要があります。切り替える際、そのRuntimeが現在使用しているDocker Daemonの設定をサポートすることを確認してください。(Loggingなど)

では、なぜ混乱が生じ、誰もが恐怖に駆られているのか。

ここで議論になっているのは2つの異なる場面についてであり、それが混乱の原因になっています。Kubernetesクラスターの内部では、Container runtimeと呼ばれるものがあり、それはImageをPullし起動する役目を持っています。Dockerはその選択肢として人気があります(他にはcontainerdやCRI-Oが挙げられます)が、しかしDockerはそれ自体がKubernetesの一部として設計されているわけではありません。これが問題の原因となっています。

お分かりかと思いますが、ここで”Docker”と呼んでいるものは、ある1つのものではなく、その技術的な体系の全体であり、その一部には"containerd"と呼ばれるものもあり、これはそれ自体がハイレベルなContainer runtimeとなっています。Dockerは素晴らしいもので、便利です。なぜなら、多くのUXの改善がされており、それは人間が開発を行うための操作を簡単にしているのです。しかし、それらはKubernetesに必要なものではありません。Kubernetesは人間ではないからです。 このhuman-friendlyな抽象化レイヤが作られてために、結果としてはKubernetesクラスターはDockershimと呼ばれるほかのツールを使い、本当に必要な機能つまりcontainerdを利用してきました。これは素晴らしいとは言えません。なぜなら、我々がメンテする必要のあるものが増えますし、それは問題が発生する要因ともなります。今回の変更で実際に行われることというのは、Dockershimを最も早い場合でv1.23のリリースでkubeletから除外することです。その結果として、Dockerのサポートがなくなるということなのです。 ここで、containerdがDockerに含まれているなら、なぜDockershimが必要なのかと疑問に思われる方もいるでしょう。

DockerはCRI(Container Runtime Interface)に準拠していません。もしそうであればshimは必要ないのですが、現実はそうでありません。 しかし、これは世界の終わりでありません、心配しないでください。みなさんはContainer runtimeをDockerから他のサポート対象であるContainer runtimeに切り替えるだけでよいのです。

1つ注意すべきことは、クラスターで行われる処理のなかでDocker socket(/var/run/docker.sock)に依存する部分がある場合、他のRuntimeへ切り替えるとこの部分が働かなくなるでしょう。このパターンはしばしばDocker in Dockerと呼ばれます。このような場合の対応方法はたくさんあります。kanikoimgbuildahなどです。

では開発者にとって、この変更は何を意味するのか。これからもDockerfileを使ってよいのか。これからもDockerでビルドを行ってよいのか。

この変更は、Dockerを直接操作している多くのみなさんとは別の場面に影響を与えるでしょう。 みなさんが開発を行う際に使用しているDockerと、Kubernetesクラスターの内部で使われているDocker runtimeは関係ありません。これがわかりにくいことは理解しています。開発者にとって、Dockerはこれからも便利なものであり、このアナウンスがあった前と変わらないでしょう。DockerでビルドされたImageは、決してDockerでだけ動作するというわけではありません。それはOCI(Open Container Initiative) Imageと呼ばれるものです。あらゆるOCI準拠のImageは、それを何のツールでビルドしたかによらず、Kubernetesから見れば同じものなのです。containerdCRI-Oも、そのようなImageをPullし、起動することが出来ます。 これがコンテナの仕様について、共通の仕様を策定している理由なのです。

さて、この変更は決定しています。いくつかの問題は発生するかもしてませんが、決して壊滅的なものではなく、ほとんどの場合は良い変化となるでしょう。Kubernetesをどのように使用しているかによりますが、この変更が特に何の影響も及ぼさない人もいるでしょうし、影響がとても少ない場合もあります。長期的に見れば、物事を簡単にするのに役立つものです。 もし、この問題がまだわかりにくいとしても、心配しないでください。Kubernetesでは多くのものが変化しており、その全てに完璧に精通している人など存在しません。 経験の多寡や難易度にかかわらず、どんなことでも質問してください。我々の目標は、全ての人が将来の変化について、可能な限りの知識と理解を得られることです。 このブログが多くの質問の答えとなり、不安を和らげることができればと願っています。

別の情報をお探しであれば、dockershimの削除に関するFAQを参照してください。