はじめに
#9ではPodmanというコンテナエンジンに入門しながら、rootlessコンテナの特徴について紹介しました。
Podmanはセキュリティの観点から非常に優れている一方、知名度やメンテナンスコストの観点から研究室サーバーのPodman移行には消極的でした。
しかし最近、研究室サーバーでDockerからPodmanへの移行を後押しするようなインシデントが発生しました。本記事では、DockerからPodmanへの段階的移行とそれに向けた両者の共存戦略について紹介します。
1. rootlessコンテナの必要性
1-1. rootlessコンテナとは
rootlessコンテナは、非特権ユーザーがコンテナを作成、実行、管理できることを指します。
本記事ではrootlessコンテナについて解説を行いません。詳しく知りたい方は、#9や『Podman in Action』を参考にしてください。また、手前味噌ですが、LTでの発表資料も合わせて共有させていただきます。
1-2. インシデントの概要と解決策
rootlessコンテナの概要が掴めたところで、今回研究室サーバーで発生してしまったインシデントを振り返ります。
端的に言うと、あるマシン上のイメージやコンテナがほとんど削除されてしまいました。事後調査によって、原因は非特権ユーザーがdocker system prune
コマンドを偶発的に実行してしまったことだと分かりました。
ここで強調したいのは、インシデントの責任が間違ってコマンドを実行してしまったユーザーではなく、最小権限の原則で設計できなかったインフラエンジニアにあるということです。
さらに今回は、誤って削除されたコンテナの中にバックアップの取れていないソースコード・データが含まれていました。研究室には、Dockerに初めて挑戦する学生や不慣れな学生も多いです。それにも関わらずバックアップに関するドキュメント整備を怠っていたのは私の責任でもあります。
以上のことから、2つの解決策を考えました。
1.についてはデフォルトでrootlessであるPodmanを導入することで、他のユーザーひいてはホストシステムに対するセキュリティレベルが大きく向上します。2.について、ソースコードはGitHubに*1、実験データはマウントしたホストのディレクトリやNASにそれぞれバックアップを取る方法を共有します。
これらにより、学生が研究のより本質的な部分に集中できるようになると考えています。
本記事では、1.のPodman移行にフォーカスして紹介します。
2. Podmanへの移行スケジュール
DockerからPodmanへの移行は段階的に行う必要があります。いくらCLI互換性があると言っても完全ではなく、既にDockerで進行中の研究も存在するためです。
移行スケジュールは下図の通りです。
執筆時点からちょうど1年後の2026年1月を目標に、Dockerユーザーの大部分をPodmanに移行させます。
移行期間中はPodmanの利用が推奨されますが、Dockerも非推奨ながら利用可能です。この初期段階で、Podmanの利用方法やDockerにおけるリスクをドキュメント化します。
また、事前調査から何人かはDocker Composeユーザーであることが分かっているので、Podman Composeの互換性を検証するとともに移行ガイドを作成します。
2026年の3月には私が卒業見込みなので、Podman含むインフラ周りの引き継ぎを行い、移行が完全に終了したユーザーについてはAnsibleでdockerグループから削除を行います。
なお、移行期間中にPodmanで何らかの不都合が生じた場合は切り戻しも検討しています。
Podmanは昨年CNCFに寄贈されたため、今後さらに注目が集まることが予想されます。しかし、研究室運営をより長い目で見た時にDockerやその他のコンテナエンジンがより良い選択肢になっているかもしれません。
学生が安心して研究に取り組める環境を提供するために、認知負荷を最小限にしながらも時代に合わせて変化をサポートしていくことが重要だと考えました。
3. DockerとPodmanの共存戦略
移行段階でDockerとPodmanを共存させるためのポイントをいくつか紹介します。
3-1. Podmanのインストール
Podmanのインストール方法については、公式ドキュメントや#9を参考にしてください。本記事で使用するバージョンは4.6.2
です。
インストールが完了するとpodman
コマンドを利用できるようになります。Podmanを中心に利用したいけどdocker
コマンドの方が馴染みがある場合はエイリアスを設定するのがオススメです。
$ echo "alias docker=podman" >> ~/.bashrc $ source ~/.bashrc $ docker Manage pods, containers and images Usage: podman [options] [command] # 省略
docker
コマンドとpodman
コマンドはほぼCLI互換なので、以上で基本的なセットアップは完了です。
3-2. Podmanのストレージの設定
#1のDockerの場合と同様に、Podmanでもイメージやコンテナの増加によってプライマリディスクの容量が不足する可能性が高いです。よって、Podmanのストレージディレクトリを4TBのSSD上に変更します。
podman info
コマンドでPodmanのシステム情報のうち、ストレージに関する部分を表示してみます。
$ podman info -f json | jq -r '.store' { "configFile": "/home/azuma/.config/containers/storage.conf", "containerStore": { "number": 4, "paused": 0, "running": 0, "stopped": 4 }, "graphDriverName": "overlay", "graphOptions": {}, "graphRoot": "/home/azuma/.local/share/containers/storage", "graphRootAllocated": 502484135936, "graphRootUsed": 224025227264, "graphStatus": { "Backing Filesystem": "xfs", "Native Overlay Diff": "true", "Supports d_type": "true", "Using metacopy": "false" }, "imageCopyTmpDir": "/var/tmp", "imageStore": { "number": 5 }, "runRoot": "/run/user/2401/containers", "volumePath": "/home/azuma/.local/share/containers/storage/volumes", "transientStore": false }
例えばrootlessユーザーのストレージパスは/home/$USER/.local/share/containers/storage
に設定されており、これらの設定を書き換えるには/home/$USER/.config/containers/storage.conf
に追記すれば良いことが分かります。
管理者がシステム全体に設定を適用したい場合はどうすれば良いでしょうか。答えはman 5 containers-storage.conf
コマンドあるいはmanページにあります。ストレージパスに関連する項目のみを簡単にまとめると以下の通りです。
graphroot
(デフォルト:/var/lib/containers/storage
)コンテナストレージのメインディレクトリ。
rootless_storage_path
(デフォルト:$XDG_DATA_HOME/containers/storage
もしくは$HOME/.local/share/containers/storage
)rootlessユーザーの(コンテナの)ストレージパス。管理者が全ユーザーのストレージの場所を変更したい時に利用される。
imagestore
(デフォルト:graphroot
と同じ)イメージのストレージパス。
runroot
(デフォルト:/run/containers/storage
)コンテナが生成する一時ファイルを保存するディレクトリ。rootlessユーザーはデフォルトで
/run/user/2401/containers
。
よって、管理者がrootless_storage_path
をSSD上の任意の(ただしDockerと重複しない)パスに変更すれば十分そうです。
ちなみにstorage.conf
の優先順位は低い方から/usr/containers/
→/etc/containers/
→$HOME/.config/containers/
→$XDG_CONFIG_HOME/containers/
(XDG_CONFIG_HOME
がセットされている場合)です。
今回は元々/etc/containers/storage.conf
が存在しなかったので、[storage]
の必須フィールドを指定しつつ以下のように作成しました。
[storage] driver = "overlay" runroot = "/run/containers/storage" graphroot = "/var/lib/containers/storage" rootless_storage_path = "/mnt/ssd4tb/$USER/containers/storage"
ここではマウントポイント(/mnt/ssd4tb/
)の直下に各ユーザーのディレクトリが存在する前提でrootless_storage_path
を指定しました。ディレクトリを作成する際には、rootlessユーザーに権限を付与することを忘れないでください。
$ mkdir /mnt/ssd4tb $ chown -R azuma /mnt/ssd4tb/azuma/ $ chgrp -R azuma /mnt/ssd4tb/azuma/
対象のフィールドを変更しない限りは設定を適用するためのpodman system reset
は不要です。
This command must be run before changing any of the following fields in the
containers.conf
orstorage.conf
files:driver
,static_dir
,tmp_dir
orvolume_path
. podman-system-reset — Podman documentation
rootlessユーザーに切り替えてPodmanのシステム情報を再度確認します。
$ podman info -f json | jq -r '.store' { "configFile": "/home/azuma/.config/containers/storage.conf", "containerStore": { "number": 0, "paused": 0, "running": 0, "stopped": 0 }, "graphDriverName": "overlay", "graphOptions": {}, "graphRoot": "/mnt/ssd4tb/azuma/containers/storage", "graphRootAllocated": 3936819662848, "graphRootUsed": 178059599872, "graphStatus": { "Backing Filesystem": "extfs", "Native Overlay Diff": "true", "Supports d_type": "true", "Using metacopy": "false" }, "imageCopyTmpDir": "/var/tmp", "imageStore": { "number": 0 }, "runRoot": "/run/user/2401/containers", "volumePath": "/mnt/ssd4tb/azuma/containers/storage/volumes", "transientStore": false }
無事にrootlessユーザーのgraphRoot
やvolumePath
がSSD上のパスに変更されました。
3-3. Ansibleによる自動化
ユーザーやサーバーが追加される度に3-2.のオペレーションを行うのは面倒なので、以前#2で紹介したAnsibleで自動化します。
まず、ユーザー追加のPlaybookに以下のように追記します。
--- - name: Ensure groups are present ansible.builtin.group: name: "{{ item.name }}" gid: "{{ item.uid }}" state: "present" with_items: "{{ users }}" - name: Ensure "docker" group is present ansible.builtin.group: name: "docker" state: "present" system: true - name: Ensure "nas_access" group is present ansible.builtin.group: name: "nas_access" state: "present" system: true - name: Ensure users are present ansible.builtin.user: name: "{{ item.name }}" uid: "{{ item.uid }}" password: "{{ item.password | password_hash('sha512') }}" update_password: "on_create" group: "{{ item.name }}" append: true groups: - "docker" - "nas_access" state: "present" with_items: "{{ users }}" # =====以下を追記===== - name: Ensure user directories exist on SSD ansible.builtin.file: path: "{{ ssd_path }}/{{ user.name }}" state: directory owner: "{{ user.name }}" group: "{{ user.name }}" mode: "755"
具体的には、ansible.builtin.fileモジュールでディレクトリを適切な権限で作成しています。SSDのマウントポイントはサーバーによって異なる可能性があるのでホスト変数として{{ ssd_path }}
を利用しています。
docker
グループに全ユーザーを追加している既存の部分は分離して、これから徐々にグループからユーザーを削除していく予定です。
それから、各サーバーでstorage.conf
ファイルを作成するPlaybookは以下の通りです。
--- - name: Check if the storage.conf file exists ansible.builtin.stat: path: "/etc/containers/storage.conf" register: storage_conf - name: Create the storage.conf file ansible.builtin.blockinfile: path: "/etc/containers/storage.conf" block: | [storage] driver = "overlay" runroot = "/run/containers/storage" graphroot = "/var/lib/containers/storage" rootless_storage_path = "{{ ssd_path }}/$USER/containers/storage" create: true mode: "644" when: not storage_conf.stat.exists
ここでは、/etc/containers/storage.conf
ファイルが存在しない場合にansible.builtin.blockinfileモジュールを使用して内容をファイルに書き込んでいます。
これらの変更をGitHubにpushしてジョブが完了したら、3-2.で設定したサーバー以外でもPodmanが正しく設定されていることを確認しましょう。
$ podman info | grep graphRoot graphRoot: /mnt/ssd4tb/azuma/containers/storage $ podman run quay.io/podman/hello Trying to pull quay.io/podman/hello:latest... # 省略 !... Hello Podman World ...! .--"--. / - - \ / (O) (O) \ ~~~| -=(,Y,)=- | .---. /` \ |~~ ~/ o o \~~~~.----. ~~ | =(X)= |~ / (O (O) \ ~~~~~~~ ~| =(Y_)=- | ~~~~ ~~~| U |~~ # 省略
ちなみに、rootlessユーザーがイメージやコンテナを作成すればするほど、自身の{{ ssd_path }}/$USER
ディレクトリの容量が増加していく仕組みです。#3ではDockerオブジェクトにユーザーラベルを付与していましたが、Podmanでは各rootlessユーザーのオブジェクトが異なるストレージパスに保存されるので、ディスク使用量の把握も簡単になりそうです。
まとめ
本記事では、DockerからPodmanへの段階的移行とそれに向けた両者の共存戦略について紹介しました。
全員がPodmanを利用できる状況にはなりましたが、ドキュメントの整備やPodman Composeの検証はこれから進めていかなければなりません。
安心して研究に集中できる環境を提供するため、今後もフィードバックを得ながらResearchOps(造語)を継続していきたいです。