LinuxでDockerコンテナをsystemdに統合する方法:コンテナを本格的なサービスとして運用する

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

DockerにRestart Policyがあるのに、なぜsystemdを使うのか?

Labを始めたばかりの頃は、docker runコマンドに--restart alwaysフラグを追加するか、Docker Composeでrestart: alwaysを宣言すれば十分だと思っていた。コンテナが落ちればDockerが自動で再起動し、サーバーを再起動してもDockerデーモンがコンテナを立ち上げてくれる。一見問題なさそうに見える。しかし本番環境では、必ずしもそうはいかないのだ。

サーバーが突然停電したことがあった。再起動時、ネットワークストレージ(NFS)のマウントが完了する前にDockerデーモンが起動してしまい、コンテナがデータを見つけられず起動に失敗した。また別の機会には、Host OSに直接インストールされたデータベース(Dockerを使わない構成)の準備が整ってからアプリケーションコンテナを起動させる必要があった。こうしたシナリオでは、Docker Restart Policyはほぼ手も足も出ない。

そこで、Dockerコンテナの管理にsystemdを使うようにした。systemdは現在の主要なLinuxディストリビューション(Ubuntu、Debian、CentOS…)のデフォルトシステムマネージャーだ。Dockerをsystemdに統合することで、コンテナは他のOSサービスと同様に管理できる — 起動順序の制御、journaldによるログの一元化、そして監視がずっと簡単になる。

起動管理方法の比較

機能 Docker Restart Policy Docker Compose Systemd Integration
使いやすさ 非常に高い 高い 中程度
依存関係の管理 なし Composeファイル内のみ 非常に強力(すべてのOSサービスに対応)
自動再起動 あり あり あり(より多くのオプション)
ログ管理 Docker logs Docker logs Journald(一元管理)

systemdのメリット

  • 起動順序の制御(Dependencies):Network、MySQL、または特定のディスクの準備が整ってからコンテナを起動するよう指定できる。
  • 自動回復:systemdはよりインテリジェントな再起動メカニズムを持つ。例えば、10秒間隔で5回リトライし、それでも失敗すれば無限クラッシュループを防ぐために停止する。
  • 監視との統合:ZabbixやPrometheusなどのツールからsystemctl経由でサービスの状態を簡単に確認できる。

デメリット

  • コマンドを一行入力するのと比べて、unitの設定ファイルを書く手間がかかる。
  • コンテナごとに個別のサービスファイルが必要になる。

実装ガイド:コンテナをSystemdサービスに変換する

Nginxで動くシンプルなWebアプリケーションがあるとする。docker runの代わりに、それ用のサービスを作成する。

ステップ1:Unitファイルの準備

/etc/systemd/system/ディレクトリに.service拡張子のファイルを作成する。ここではwebapp.serviceという名前にする。

sudo nano /etc/systemd/system/webapp.service

ステップ2:設定内容の記述

個人のVPSプロジェクトで実際に使用している設定だ。ファイルにコピーしてほしい:

[Unit]
Description=My Web Application Container
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
Restart=always
# 新規起動前に既存のコンテナを削除する
ExecStartPre=-/usr/bin/docker stop %p
ExecStartPre=-/usr/bin/docker rm %p
ExecStartPre=/usr/bin/docker pull nginx:latest

# メインの起動コマンド
ExecStart=/usr/bin/docker run --name %p \
    -p 8080:80 \
    nginx:latest

# コンテナ停止コマンド
ExecStop=/usr/bin/docker stop %p

[Install]
WantedBy=multi-user.target

重要なパラメータの説明:

  • After=docker.service:Dockerデーモンが先に起動していることを確認してからこのサービスを起動する。
  • ExecStartPre=-/usr/bin/docker stop %p:コマンドの前の-記号は、このコマンドが失敗しても(例:コンテナがまだ存在しない場合)、systemdが処理を中断せず次のコマンドを続けて実行することを意味する。
  • %p:拡張子なしのサービス名を取得するsystemd変数(ここではwebapp)。コンテナ名として使用することで識別しやすくなる。
  • Restart=always:コンテナがクラッシュした場合に自動で再起動する。

ステップ3:サービスの有効化

ファイルを保存したら、変更があったことをsystemdに通知して有効化する必要がある:

# 新しいファイルをシステムに認識させるためにReloadする
sudo systemctl daemon-reload

# OS起動時にサービスを自動起動させる
sudo systemctl enable webapp.service

# サービスをすぐに起動する
sudo systemctl start webapp.service

ステップ4:状態の確認

コンテナが動いているか確認する:

sudo systemctl status webapp.service

緑色でactive (running)と表示されれば成功だ。failedと表示された場合は、journalctl -u webapp.service -n 50を実行して具体的なエラーを確認しよう。

実践的なヒント:ログ管理とデバッグ

systemdに移行すると、docker logsも不要になる。コンテナの全出力がjournaldに流れ込む。最も便利なのはデバッグ時で、アプリケーションログとシステムログが同じタイムラインに並ぶため、順序を推測する必要がない。

リアルタイムでログを確認:

journalctl -u webapp.service -f

コードや新しいイメージに更新する際は、以下を実行するだけだ:

sudo systemctl restart webapp.service

systemdがExecStartPreのステップを自動的に実行してくれる — 新しいイメージのPull、古いコンテナの削除、そして再起動。手順を手動で覚える必要はない。

この方法を使うべきでない場面

systemdは本来の課題をうまく解決してくれるが、すべての問題にsystemdが必要なわけではない。

  1. 開発環境:手軽さを優先してDocker Composeを使えばいい。ポートを変更するたびにサービスファイルを編集してデーモンをリロードするのは時間の無駄だ。
  2. 複雑なMicroservicesシステム:数十のコンテナが相互に連携している場合、数十のサービスファイルを手動で管理するのは悪夢になる。この場合はDocker ComposeかKubernetesが正解だ。

この方法は、VPS上のsingle-instanceサービスに最も適している。例えば、Telegram Bot、個人ブログ、あるいはリバースプロキシとして動くNginx/Traefik。独立した2〜3個のコンテナだけであれば、Docker Composeよりもずっとシンプルで安定した選択肢だ。

実際にこのセットアップを使って、個人VPS上でNginxリバースプロキシとTelegram Botを動かしている — ここ数ヶ月、手動で対応が必要な事態は一度も起きていない。再起動後にコンテナが起動しない問題で困っているなら、ぜひこの方法を試してみてほしい。

Share: