Podman Quadlet 完全ガイド — systemdでコンテナを宣言的に運用する方法

podman run の一行でコンテナを起動するのは簡単です。しかし、サーバー再起動後の自動起動、異常終了時の再起動、実行順序、ログ参照、リソース制限といった運用上の問題を付け加え始めると、シェルスクリプトは急速に複雑になります。

Podman Quadletはこのギャップを埋めます。.container、.network、.volumeのような短い宣言ファイルを記述すると、systemdジェネレーターがそれらを通常のサービスユニットに変換します。その後は、コンテナもsystemctlで起動・停止し、journalctlで監視できるようになります。

結論から言えば、一台または少数のLinuxサーバーでコンテナをOSサービスのように運用したい場合、Quadletが最も自然な選択肢です。Kubernetesのように複数のノードにワークロードをスケジューリングすることはありませんが、systemdがすでに提供している起動順序、再起動ポリシー、依存関係、ログ、権限モデルをそのまま活用できます。

Quadletとは何か

Podman公式ドキュメントは、Quadletをコンテナ、Pod、ボリューム、ネットワーク、イメージをsystemdユニットファイルとして宣言的に管理する機能と説明しています。ここで重要なキーワードは宣言的とsystemdです。

一般的なpodman runコマンドは、「今、このオプションでコンテナを実行せよ」という手続きです。Quadletファイルは、「このホストには、このイメージ、ポート、ボリューム、再起動ポリシーを持つサービスが存在すべきである」という望ましい状態を記録します。実際の実行コマンドはQuadletジェネレーターが作成します。

過去のpodman generate systemdは、すでに作成されたコンテナやPodを基に長い.serviceファイルを生成する方式でした。現在、公式コマンドリストではこの機能がdeprecatedと表示されており、新しい構成にはQuadletの使用が推奨されています。

動作原理: 元はQuadlet、実行主体はsystemd

Quadletは常時実行される別のデーモンではありません。systemdが起動するか、systemctl daemon-reloadが実行される際に、Podmanのジェネレーターが検索パスからQuadletファイルを読み込みます。そして、systemdが理解するサービスユニットをランタイムに生成します。このプロセスと検索パスはpodman-systemd.unit公式ドキュメントに定義されています。

図: Quadlet生成フロー · Podman systemd unit公式ドキュメントを基に再構成

例えば、web.containerはweb.serviceになり、app.networkはapp-network.service、data.volumeはdata-volume.serviceになります。生成結果は一時的なユニット(transient unit)であるため、直接編集しません。設定を変更するには、元のQuadletまたは.d/*.confドロップインを修正し、再度daemon-reloadを実行する必要があります。

この構造のおかげで役割が明確になります。

  • 人間が管理する元ファイル: *.container、*.pod、*.networkなど
  • 変換時点: 起動時またはsystemctl daemon-reload
  • ライフサイクル管理: systemctl start、stop、restart、status
  • ログと障害分析: journalctl、systemd-analyze、Podmanコマンド

サポートするQuadletファイルタイプ

現在、公式ドキュメントには次の8種類のタイプが定義されています。

拡張子 専用セクション 役割
— — —
.container [Container] 単一コンテナをサービスとして実行
.pod [Pod] 複数のコンテナが参加するPodを作成
.network [Network] Podmanネットワークを作成
.volume [Volume] 永続ボリュームの作成・保証
.image [Image] イメージを事前に取得してキャッシュ
.build [Build] Containerfileでローカルイメージをビルド
.kube [Kube] Kubernetes YAMLをpodman kube playで実行
.artifact [Artifact] OCIアーティファクトを取得し、他のユニットで使用

Quadletファイルは、一般的なsystemdの文法と専用セクションを組み合わせて使用します。[Unit]、[Service]、[Install]はsystemdが処理し、[Container]のような専用セクションはPodmanジェネレーターがpodman runオプションに変換します。したがって、Restart=、TimeoutStartSec=のような馴染みのあるsystemdポリシーと、Image=、PublishPort=、Volume=のようなコンテナ設定を一つのファイルで組み合わせることができます。

ファイルはどこに置くのか

運用主体がrootか一般ユーザーかによって検索パスが異なります。

実行方式 主に使うパス systemctlコマンド
— — —
rootlessユーザーサービス ~/.config/containers/systemd/ systemctl --user ...
rootfulシステムサービス /etc/containers/systemd/ sudo systemctl ...
rootful一時テスト /run/containers/systemd/ sudo systemctl ...
ディストリビューション提供rootfulユニット /usr/share/containers/systemd/ sudo systemctl ...
管理者がユーザー別に配布 /etc/containers/systemd/users/${UID}/ 該当ユーザーのsystemctl --user ...

rootlessには、$XDG_RUNTIME_DIR/containers/systemd/、$XDG_CONFIG_HOME/containers/systemd/、/etc/containers/systemd/users/、/usr/share/containers/systemd/users/系も含まれます。同じ名前が複数のパスにある場合、優先順位が適用されるため、運用ファイルと一時テストファイルの名前衝突を避ける必要があります。

Quadletはcgroup v2を要求します。開始前に次の2つのコマンドで環境を確認することをお勧めします。

podman --version
podman info --format '{{.Host.CgroupsVersion}}'

2番目の結果がv2である必要があります。また、Quadletはsystemd機能であるため、macOSやWindowsホストで直接実行されるものではありません。この環境では、対象のLinuxサーバーまたはPodman Machine内部のLinux VMを基準に設定する必要があります。

実習: rootless NginxをQuadletで実行する

最も安全な出発点は、一般ユーザー権限のrootlessサービスです。次の例では、ローカルアドレス127.0.0.1:8080にのみNginxを公開し、ホームディレクトリの静的ファイルを読み取り専用でマウントします。

まず、ディレクトリとテストページを作成します。

mkdir -p ~/.config/containers/systemd ~/quadlet/site
printf '<h1>Hello from Podman Quadlet</h1>
' > ~/quadlet/site/index.html

~/.config/containers/systemd/web.containerファイルを作成します。

[Unit]
Description=Rootless Nginx managed by Podman Quadlet

[Container]
Image=docker.io/library/nginx:alpine
PublishPort=127.0.0.1:8080:80
Volume=%h/quadlet/site:/usr/share/nginx/html:ro,Z
LogDriver=journald

[Service]
Restart=on-failure
TimeoutStartSec=900

[Install]
WantedBy=default.target

各項目の意味は次のとおりです。

  • Image=は短い名前ではなく、レジストリまで含んだイメージを指定します。
  • PublishPort=127.0.0.1:8080:80は、すべてのインターフェースではなく、ローカルホストにのみバインドします。
  • %hはsystemdのユーザーホームディレクトリ指定子です。
  • ro,Zは読み取り専用マウントとSELinuxプライベートリラベルを意味します。複数のコンテナが同じパスを共有する場合、zの意味を検討する必要があります。
  • 最初のイメージプルがsystemdのデフォルトの開始制限時間を超える可能性があるため、TimeoutStartSec=900で余裕を持たせます。公式ドキュメントも遅いプル・ビルドのためにこの設定を例に挙げています。
  • [Install] WantedBy=default.targetは、ユーザーsystemdが起動する際にこのサービスを望ましい状態にします。

ファイルを読み込ませ、サービスを開始します。

systemctl --user daemon-reload
systemctl --user start web.service
systemctl --user status web.service --no-pager
curl http://127.0.0.1:8080

ログとコンテナの状態は次のように確認します。

journalctl --user -u web.service -n 50 --no-pager
podman ps --filter name=systemd-web

ContainerName=を別途指定しない場合、Quadletは通常、systemd-接頭辞が付いた名前を使用し、ユーザーが手動で作成したコンテナとの衝突を減らします。

systemctl enableを実行しない理由

ここで最もよくある間違いは、生成されたweb.serviceに対してsystemctl --user enableを実行することです。Quadletが作成したサービスは一時的なユニット(transient unit)であるため、通常のサービスのようにenableすることはできません。代わりに、ジェネレーターが元のQuadletの[Install]を読み込み、enableと同じ接続を生成します。

したがって、起動・ログイン時に自動起動が必要な場合は、元のファイルに以下を記述し、その後daemon-reloadを実行します。

[Install]
WantedBy=default.target

rootlessユーザーマネージャーは通常、ログインセッションとともに起動します。ログイン前からサービスを起動し、ログアウト後も維持する必要がある場合は、管理ポリシーを確認した後、systemdのlingeringを使用します。loginctl公式ドキュメントは、lingeringを有効にすると、該当ユーザーのユーザーマネージャーを起動時に作成し、ログアウト後も維持すると説明しています。

sudo loginctl enable-linger USER_NAME

lingeringは、ユーザーがログインしていなくても長期実行プロセスを維持できるようにするため、必要なサービスアカウントにのみ適用することをお勧めします。

ネットワークとボリュームを別々のQuadletで接続する

規模が大きくなると、ネットワークとボリュームも宣言ファイルで分離できます。app.networkとdata.volumeを作成し、コンテナからファイル名で参照すると、QuadletはPodman引数だけでなく、生成サービス間の依存関係も追加します。

図: Quadletリソース参照と依存関係 · Podman systemd unit公式ドキュメントを基に再構成

app.networkは次のように最小限で作成できます。

[Network]
Label=app=quadlet-demo

data.volumeも別途名前を強制しない場合、Quadletが衝突を避ける名前を生成します。

[Volume]
Label=app=quadlet-demo

コンテナでは、リソースの実際のPodman名ではなく、Quadletファイル名を参照します。

[Container]
Image=docker.io/library/alpine:latest
Exec=sh -c "while true; do date >> /data/heartbeat.log; sleep 60; done"
Network=app.network
Volume=data.volume:/data

[Service]
Restart=always

[Install]
WantedBy=default.target

このファイルをworker.containerとして保存し、systemctl --user daemon-reloadを実行すると、worker.serviceがネットワーク・ボリュームサービスの後で起動するように関係が作成されます。ユーザーが長いRequires=とAfter=リストを手動で合わせる必要がないのがQuadletの大きな利点です。

同様に、Pod=backend.pod、Image=app.build、Image=base.imageのように他のQuadletを参照できます。複雑なサービス間の実行順序は[Unit]のRequires=、After=、Before=で明示でき、Quadletファイル名間の依存関係も生成サービス名に変換されます。

インストール・修正・削除の運用フロー

手動で検索パスにコピーする方法以外に、サポートされているPodmanバージョンではpodman quadletコマンドを使用できます。

podman quadlet install ./web.container
podman quadlet list
podman quadlet print web.container
podman quadlet rm web.container

podman quadlet installは、現在のユーザーパスにファイルをインストールし、デフォルトでsystemd reloadも実行します。複数のQuadletと設定ファイルを一つのアプリケーションとしてインストールする機能もあるため、配布ツールでは個別のファイルコピーよりも一貫したパッケージングに有用です。

手動管理の場合、修正順序は単純です。

# 1. 元のweb.containerを修正
# 2. 生成されたサービスを更新
systemctl --user daemon-reload

# 3. 実行中のサービスに新しい設定を適用
systemctl --user restart web.service

daemon-reloadは定義を再生成するだけで、すでに実行中のコンテナを自動的に置き換えることはありません。変更を適用するためには、別途restartが必要です。

障害分析: service not foundから確認する

Quadletの文法が間違っているか、インストールされているPodmanよりも新しいオプションを使用している場合、ジェネレーターが.serviceを作成できないことがあります。このとき、「Unit web.service not found」だけを見てイメージの問題だと誤解しやすいです。

まず、公式ドキュメントが案内する検証コマンドを実行します。

systemd-analyze --user --generators=true verify web.service

生成結果とエラーを一緒に見たい場合は、ジェネレーターのドライランを使用します。ディストリビューションによって実行ファイルのパスが異なる場合があるため、実際のインストールパスも確認する必要があります。

/usr/lib/systemd/system-generators/podman-system-generator --user --dryrun

次に、階層別に確認します。

# 生成されたsystemd定義を確認
systemctl --user cat web.service

# systemdの状態と最新ログ
systemctl --user status web.service --no-pager
journalctl --user -u web.service -b --no-pager

# Podmanが認識するコンテナの状態
podman ps --all --filter name=systemd-web
podman inspect systemd-web

最初の実行だけが90秒付近で失敗する場合はイメージのプル時間を疑い、ポートバインディングエラーの場合は既存のプロセスやコンテナが同じポートを使用していないか確認します。SELinux環境でバインドマウントアクセスが拒否された場合、無条件にセキュリティラベルをオフにするのではなく、:Zまたは共有目的の:zが適切かどうかから点検すべきです。

自動アップデートは便利だが配布ポリシーである

Quadletの[Container]にはAutoUpdate=registryまたはAutoUpdate=localを指定できます。Podman auto-updateドキュメントによると、registryポリシーはリモートイメージのダイジェストが変更されたかを確認し、新しいイメージをプルした後、該当するsystemdサービスを再起動します。このポリシーには、レジストリを含む完全なイメージ名が必要です。

[Container]
Image=quay.io/example/myapp:stable
AutoUpdate=registry

アップデート可能かどうかは、実際の変更なしに事前に確認できます。

podman auto-update --dry-run

Podmanはpodman-auto-update.serviceとタイマーを提供しますが、自動更新を有効にする前に次のことを決定する必要があります。

  • 可変タグをすぐに受け入れてもよいか、それともダイジェストでバージョンを固定すべきか
  • 新しいイメージが失敗した場合、誰が検知しロールバックするのか
  • データ移行と下位互換性は保証されているか
  • プライベートレジストリ認証ファイルの寿命と権限は適切か

自動アップデートはセキュリティパッチを迅速に反映できますが、検証されていない変更を運用に自動配布する可能性もあります。重要なサービスでは、CIでイメージを検証し、ダイジェストを更新する方式の方が予測可能かもしれません。

セキュリティと安定性を高める設定

Quadletがrootlessを容易にするからといって、コンテナが自動的に安全になるわけではありません。イメージ、ポート、マウント、ケーパビリティ、秘密の境界は依然として設計する必要があります。

1. rootlessをデフォルトにする

特権ポート、デバイスアクセス、ホストネットワークのようにrootが必ず必要な機能がない場合は、一般ユーザーのQuadletから始めます。rootful Quadletに[Service] User=を入れてrootlessに変更する方式はサポートされていません。rootlessはユーザー検索パスとsystemctl --userを使用する必要があります。

2. ポート公開範囲を明示する

PublishPort=8080:80はすべてのIPv4インターフェースに開かれる可能性があります。リバースプロキシの背後にあるサービスやローカル管理UIであれば、127.0.0.1:8080:80のようにバインディングアドレスを明示します。

3. 秘密をEnvironment=に直接書かない

QuadletはPodman secretを接続するSecret=をサポートします。パスワードやトークンを宣言ファイル、生成サービス、状態出力に平文で残すのではなく、podman secret createで別途管理し、必要なコンテナにのみ渡します。

4. 書き込み・権限・リソースを減らす

アプリケーションが許容するならば、次のオプションを検討します。

[Container]
NoNewPrivileges=true
DropCapability=all
ReadOnly=true
Memory=512m
PidsLimit=256

ReadOnly=trueを適用する際は、アプリケーションが書き込む必要があるパスをボリュームやtmpfsで分離する必要があります。ケーパビリティをすべて削除した後には、実際に必要な権限だけをAddCapability=で戻します。

5. イメージ名と変更戦略を固定する

プロダクションでは、短いイメージ名を避け、レジストリ、リポジトリ、タグ、またはダイジェストを明示します。タグベースの自動アップデートとダイジェストベースの再現性は異なる運用戦略であるため、一つのファイルにうっかり混ぜないようにします。

Quadlet、Compose、Kubernetesのうち何を選ぶか

方式 よく合う状況 長所 限界
— — — —
podman run・シェルスクリプト 一時的なテスト、デバッグ 速くて直接的 再起動・依存関係・状態管理が手動
Podman Quadlet 単一Linuxホストの長期実行サービス systemd統合、rootless、宣言ファイル、ジャーナル 多ノードスケジューラーではない
Compose 開発・テスト用複数コンテナの束、Composeエコシステム サービス関係を一つのファイルで表現、高い親和性 ホスト起動・systemd運用は別途統合が必要
Kubernetes 多ノード、レプリケーション、ローリングデプロイ、サービスディスカバリ クラスタレベルの自動化とエコシステム 構築・運用複雑度とコストが大きい

Quadletは「小さなKubernetes」ではありません。ノード障害時に他のサーバーに再配置したり、複数のレプリカの望ましい状態をクラスタ全体で合わせたりはしません。代わりに、systemdがすでに標準であるLinuxホストで、コンテナを他のOSサービスと同じ方法で運用できるようにします。

特に、ホームラボ、エッジデバイス、ビルドサーバー、内部ツール、単一VMのウェブサービス、Kubernetesを導入するには過剰な小規模運用環境によく適合します。

よくある間違い6つ

  1. 生成された.serviceを編集する。 次のdaemon-reloadで再生成されるため、元のQuadletまたはドロップインを修正する必要があります。
  2. systemctl enableを実行する。 Quadlet元の[Install]にWantedBy=を記述します。
  3. rootlessファイルを/etc/systemd/systemに置く。 ~/.config/containers/systemd/とsystemctl --userを使用します。
  4. 短いイメージ名と開かれたポートをそのまま使う。 レジストリを含め、必要なアドレスにのみバインドします。
  5. daemon-reloadだけで適用されたと考える。 実行中のサービスは別途restartが必要です。
  6. Quadletをクラスタオーケストレーターとして期待する。 ホスト間のスケジューリングと高可用性が必要な場合は、Kubernetesなど別のレイヤーが必要です。

まとめ

Podman Quadletの核心は、コンテナをsystemdの外部にある特別な対象としないことにあります。人間が管理する短い宣言ファイルはジェネレーターがサービスに変換し、systemdが起動、順序、再起動、ログを担当し、Podmanがコンテナ・ネットワーク・ボリュームを実行します。

最初はweb.container一つから始めてみてください。次にネットワークとボリュームを別々のQuadletに分け、rootless・ポート範囲・秘密・リソース制限を調整すればよいでしょう。単一のLinuxホストで運用複雑度を大きく増やすことなく、再現可能なコンテナサービスを作成したい場合、Quadletは非常に実用的な基準点となります。

公式参考資料


Comments

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です