DockerからPodmanへの段階的移行(#12)

はじめに

#9ではPodmanというコンテナエンジンに入門しながら、rootlessコンテナの特徴について紹介しました。

alvinvin.hatenablog.jp

Podmanはセキュリティの観点から非常に優れている一方、知名度やメンテナンスコストの観点から研究室サーバーのPodman移行には消極的でした。

しかし最近、研究室サーバーでDockerからPodmanへの移行を後押しするようなインシデントが発生しました。本記事では、DockerからPodmanへの段階的移行とそれに向けた両者の共存戦略について紹介します。

1. rootlessコンテナの必要性

1-1. rootlessコンテナとは

rootlessコンテナは、非特権ユーザーがコンテナを作成、実行、管理できることを指します。

本記事ではrootlessコンテナについて解説を行いません。詳しく知りたい方は、#9や『Podman in Action』を参考にしてください。また、手前味噌ですが、LTでの発表資料も合わせて共有させていただきます。

speakerdeck.com

1-2. インシデントの概要と解決策

rootlessコンテナの概要が掴めたところで、今回研究室サーバーで発生してしまったインシデントを振り返ります。

端的に言うと、あるマシン上のイメージやコンテナがほとんど削除されてしまいました。事後調査によって、原因は非特権ユーザーがdocker system pruneコマンドを偶発的に実行してしまったことだと分かりました。

ここで強調したいのは、インシデントの責任が間違ってコマンドを実行してしまったユーザーではなく、最小権限の原則で設計できなかったインフラエンジニアにあるということです。

さらに今回は、誤って削除されたコンテナの中にバックアップの取れていないソースコード・データが含まれていました。研究室には、Dockerに初めて挑戦する学生や不慣れな学生も多いです。それにも関わらずバックアップに関するドキュメント整備を怠っていたのは私の責任でもあります。

以上のことから、2つの解決策を考えました。

  1. DockerからPodmanへの移行
  2. GitHubNASへのバックアップ方法のドキュメント化

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で何らかの不都合が生じた場合は切り戻しも検討しています。

www.redhat.com

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_pathSSD上の任意の(ただし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 or storage.conf files: driver, static_dir, tmp_dir or volume_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ユーザーのgraphRootvolumePathSSD上のパスに変更されました。

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(造語)を継続していきたいです。

*1:私は普段、SSH Agent ForwardingしつつコンテナにSSH接続して、コンテナ内から定期的にGitHubにpushしています