100個を超えるDockerコンテナ:月次の健全性点検
数年かけてサービスを増やしてきたなら、本番環境で100個を超えるDockerコンテナを動かすことは珍しくありません。セルフホスト型のSupabaseスタックだけで13個のコンテナを使います。Webフロントエンド、複数のAPIサービス、バックグラウンドワーカー、監視基盤、ログ集約を加えると、想定より速く数が増えます。難しくなるのはデプロイではなく、保守です。
多くの文書はコンテナを起動するまでを説明しています。しかし、6か月後の日曜日にディスクが満杯になった場合、コンテナの3分の1に再起動ポリシーがないと分かった場合、監視アラートをミュートしている間にSSL証明書が切れた場合については、ほとんど説明していません。本稿では、100個を超えるコンテナを稼働させる本番サーバーで当社が行う月次点検を紹介します。
ディスクから始める
長期稼働するDocker環境で、最もよくある急な障害はディスク容量の不足です。Dockerには、標準のシステムツールでは見えない形でデータが蓄積します。df -hはファイルシステムの使用量を表示しますが、停止済みコンテナのレイヤー、タグのないイメージ、6か月間の繰り返しのデプロイで生じたビルドキャッシュに、Dockerが50ギガバイトを使っていることまでは分かりません。
点検の出発点として適切なのは、docker system dfです。イメージ、コンテナ、ローカルボリューム、ビルドキャッシュ別に使用量が分かります。結果は意外なことが多く、当社はビルドキャッシュだけで稼働中の全コンテナレイヤーの合計を上回るサーバーを見てきました。CIからのビルドを何か月も繰り返し、清掃せずに蓄積した結果です。
実際に最も重要なのは、回収可能な容量の欄です。削除する前に、現在どれだけ回収でき、月ごとにどう推移しているかをベースラインとして把握します。回収可能容量が増え続けていれば、削除範囲を広げるか、頻度を上げる必要があります。
清掃は次の順序で行います。まず、どのコンテナからも参照されていない匿名ボリュームです。Docker 23.0以降、docker volume pruneは--allを追加しない限り、未使用の名前付きボリュームを削除しません。このフラグを追加する前に、接続されていないボリュームに必要なデータが残っていないことを確認してください。
docker volume prune -f
次に、タグがなく、稼働中・停止中のどのコンテナからも参照されないイメージを削除します。
docker image prune -f
最後に、復旧に許容する時間内で全体を再構築できると確認した場合に限り、7日より前の未使用イメージをすべて削除します。
docker image prune -a --filter "until=168h" -f
-aフラグは、タグのないものだけでなく、未使用のイメージをすべて削除します。許容する復旧時間内に、全サービスをレジストリから再構築できると確認してから実行します。確認は実行後ではなく、実行前に行います。
再起動ポリシー
再起動ポリシーは、コンテナの予期しない終了時や、ホスト再起動後にDockerデーモンが再起動した際の動作を定めます。多くのデプロイガイドは簡単に触れるだけです。しかし設定を誤ると、サービスが2週間停止していたのに、監視先のエンドポイントが間違っていて誰も気付かない、という事態になります。
Dockerには4つの再起動ポリシーがあります。noが既定値で、どの状況でも再起動しません。alwaysは、終了コードにかかわらず、コンテナが停止すると再起動します。ただし、手動でdocker stopを実行した後は、デーモンが再起動するか、コンテナを再度起動するまで起動しません。unless-stoppedはalwaysに似ていますが、明示的な停止を尊重します。再起動前にdocker stopを実行すると、ホスト再起動後も停止したままです。on-failure[:max-retries]は、終了コードがゼロ以外のときだけ再起動し、必要に応じて再試行回数の上限も指定できます。
ステートレスなWebサービスやAPIワーカーには、unless-stoppedを使います。保守作業で意図的に停止したコンテナは、次のホスト再起動後も、予期せず起動せず停止したままであるべきです。alwaysでは、意図的に停止していても、次のデーモン再起動時に再び起動します。
データベース移行用コンテナや一度だけ実行する初期化ジョブには、noが適切です。失敗した移行を繰り返すべきではありません。on-failure:3は、一時的に使えない依存先への接続を短時間だけ再試行し、無期限には繰り返さないコンテナに適しています。例えば、ブローカーへの接続を待つ外部キューのコンシューマーです。
月次点検では、全コンテナに対して一つのコマンドを実行します。
docker inspect --format '{{.Name}} {{.HostConfig.RestartPolicy.Name}}' $(docker ps -aq)
意図した一回限りのジョブ以外で、ポリシーがnoになっているコンテナは確認します。多くの場合、障害対応中にdocker runで起動したサービスが、適切な再起動ポリシー付きのCompose設定に正式に追加されていないことを意味します。
ログローテーション
Dockerの既定のログドライバーはjson-fileです。既定ではサイズ上限がありません。ログの出力量がそれほど多くないコンテナでも、数か月で数百ギガバイトになることがあります。これは机上の懸念ではなく、ログ管理を明確に設計せず構築した本番サーバーで、ディスクが満杯になるよくある原因です。
対策は、/etc/docker/daemon.jsonで全体のポリシーを設定することです。
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "5"
}
}
これにより、コンテナごとのログは100メガバイトのファイル5個、最大500メガバイトに制限されます。変更後はDockerデーモンの再起動が必要です。さらに、新しい設定を有効にするには、コンテナも単に再起動するのではなく、再作成する必要があります。
daemon.jsonの設定が適用されるのは、変更後に作成されたコンテナだけです。既存コンテナは元のログ設定を保持し続けます。当社が最もよく見る誤りは、ポリシーを設定しデーモンを再起動しただけで、全コンテナに適用されたと思い込むことです。月次点検ではコンテナごとのログ設定を確認します。
docker inspect --format '{{.Name}} {{.HostConfig.LogConfig}}' $(docker ps -q)
サイズ上限が明示されていないコンテナは、次の保守作業時に新しい設定で再作成します。順序にも注意が必要です。状態を持つサービスのデータボリュームを維持し、依存関係に沿ってサービスを起動します。
SSL証明書の期限切れ
SSL証明書は期限が切れます。自動監視で多くは検出できますが、監視にも設定ミス、アラート疲れ、監視対象のサービスと同時に気付かれず停止する、といった問題があります。当社の月次点検には、自動システムから独立した手動確認を含めています。
外部公開している各ドメインの証明書を直接確認します。
echo | openssl s_client -connect domain.com:443 -servername domain.com 2>/dev/null | openssl x509 -noout -enddate
このコマンドでnotAfterの日付が表示されます。30日以内に期限が切れる証明書は、監視画面の表示にかかわらず、直ちに更新対象に入れます。手動確認が最後の確認手段です。
当社が複数の内部サービスで運用する自社管理の証明書基盤では、末端証明書とは別に中間証明書も確認します。末端証明書が有効でも、中間証明書が切れると証明書チェーン全体の検証が失敗します。末端証明書の期限切れより分かりにくく、ブラウザーやクライアントが、技術者が予想する明確な「証明書の期限切れ」ではなく、理解しにくいエラーを表示することがあります。
当社は、ドメイン一覧を巡回し、opensslで有効期限を取得して、30日以内なら警告、7日以内なら重大アラートを出すシェルスクリプトを管理しています。cronジョブとして実行するほか、月次点検でも手動実行し、cronの出力が正確だったことを再確認します。cronジョブは、想像以上に気付かれず失敗します。
コンテナのリソース制限
メモリ制限がないと、異常動作したコンテナがホストのRAMを使い切り、カーネルのOOM killerが無関係なプロセスを終了させることがあります。CPU制限がないと、暴走したプロセスが他のコンテナのCPU時間を長く奪い、連鎖障害を起こすことがあります。どちらもまれな例外ではありません。
月次点検で、すべての稼働中コンテナのリソース制限を確認します。
docker stats --no-stream --format "table {{.Name}} {{.CPUPerc}} {{.MemUsage}}"
メモリ欄には使用量と上限がまとめて表示されます。メモリ制限のないコンテナでは、ホストの総メモリ容量が上限として表示され、0B / 0Bは稼働していないコンテナにのみ表示されます。それぞれを確認し、適切な上限を決めます。ステートレスなHTTPサービスなら、観測したワーキングセットの2〜4倍をメモリ上限の出発点とするのが妥当です。目的は厳密さではなく、無制限な増加で同じホストのサービスが停止するのを防ぐことです。
同時にCPU使用率も確認します。CPU使用率が常に100%近いコンテナ(docker statsでは100%が1コアを使い切る状態に相当するため、マルチコアのホストではそれを超える値も表示されます)は、プロセスが暴走しているか、処理量に対してリソースが不足している可能性があります。どちらも翌月を待たずに調査する必要があります。
イメージ更新チェック
ベースイメージのセキュリティパッチは、不定期に提供されます。6か月前に最新だったイメージでも、既知の脆弱性があるnginxやPostgreSQLのバージョンを使っているかもしれません。当社は毎月、すべてを自動で取得・再デプロイすることはしません。減らす以上のリスクを生むためです。ただし、稼働中のものと最新のものは比較します。
具体的には、バージョンを固定したサービスは、月1回、上流の変更履歴と照合します。latestや16-alpineのような参照先が変わるタグを使うサービスでは、イメージを取得し、ダイジェストの差分で変更の有無を調べます。変更があれば、デプロイ前に内容を確認します。
さらに重要なのは、参照先が変わるタグから脱却することです。latestタグが新しいメジャーバージョンを指し、互換性のない変更を含んだまま再デプロイされたサービスは、古いと分かっているイメージのサービスより診断が難しくなります。バージョンを固定し、意図的に更新してください。
運用規律
月次点検は、ディスク使用量と不要データ削除、再起動ポリシー、ログローテーション、SSL証明書の有効期限、コンテナのリソース制限、イメージの更新状況の6分野を扱います。文書が整ったサーバーなら、全体で90分を超える必要はありません。価値は個々の確認よりも、故障後ではなく故障前に、決まった日程で行うことにあります。
本番システムは徐々に状態が悪くなります。ディスクにデータが蓄積し、ログが増え、証明書の期限が近づき、イメージが古くなります。どれも閾値を超えるまでアラートは出ません。月次点検は、事後対応だった保守を予測可能な作業に変え、運用のあり方を変えます。
関連記事
- FastAPIとTimescaleDBによるAmazonデータウェアハウスの構築 —同じインフラ運用の規律をデータ層に適用する例
- 小規模チームで数十のプロジェクトを運営する —多数のプロジェクトで定期保守をどう組織するか