第1回:DockerでPHP+Nginx構成を本番運用してみたリアル奮闘記

OSSを業務で活かすシリーズも一段落。ここまで読んでくれた皆さんは、OSSを導入する上での戦略やステップを理解できたはずです。
さて、ここで少し趣向を変えて――。今回は、DockerでPHP+Nginx構成を本番運用したときのリアル体験をお話しします。
OSSの便利さの裏にある「思わぬ落とし穴」を、失敗談を交えながら紹介します。

本番運用前の構想と準備

「いよいよ本番サーバーへのデプロイだ!」
気合いを入れてDocker Composeを立ち上げた瞬間、最初の壁が現れました。ホスト側のディレクトリをマウントしたのに、PHPからファイルが書き込めない…。

開発環境では問題なく動いていたので、一瞬パニックですが、ちょっと考えれば簡単な理由でした。原因はUIDとGIDの不一致。コンテナ内のPHP-FPMはwww-dataユーザで動いていたのに、ホスト側ディレクトリの所有者が異なっていたというものです。

本番構成のベストプラクティス

元々、本番環境のソースコードはホストをマウントする予定はありませんでした。本番環境でのベストプラクティスはコンテナ内に格納です。その理由として

1. 一貫性と再現性の確保

コンテナイメージにアプリケーションの実行に必要なすべての要素(コード、ライブラリ、依存関係)を含ませる事で、一度ビルドすればどこでも同じように動作することが保証されます。つまり、ホスト環境の差異による問題を排除できる。いわゆる、イミュータブルインフラストラクチャというやつです。

さらに、イメージにタグを付けることで、実行されているコードのバージョンを管理できます。問題が発生した場合でも、以前の安定したバージョンのイメージに容易にロールバックが可能です。

2. デプロイとスケーリングの効率化

コードや依存関係の同期ミスがなく、デプロイが容易に、完全に行われます。また、イメージをコンテナレジストリにプッシュすれば、どのホストマシンやクラウド環境でも簡単にプルして実行できます。

3. セキュリティと管理の簡素化

コンテナイメージにアプリケーションに必要な最小限のファイルのみを含める事で、セキュリティリスクを低減できます。さらに、ホスト環境から分離される事で、ホスト側の設定変更やファイルシステムの異常がコンテナの動作に影響を与えにくくなります。

コンテナ分離による本番構成の悩み

先述のような理由から、開発環境ではホストディレクトリをバインドし、本番環境ではコンテナ内に格納するよう、Docker Composeファイルを作成していました。

ところが、Webサーバー(Nginx)とPHPを別々のコンテナにしたことで、思わぬ課題が出てきました。

PHPファイルはPHPコンテナが処理する訳ですが、それ以外の性的ファイルはNginxが処理します。静的ファイル、JS、PHPとディレクトリレベルできれいに分離されていれば問題なかったのですが、今回のプロジェクトの特性上、上手く分ける事ができませんでした。

結果として、ソースファイルをコンテナに格納するためには、PHPファイルと静的ファイルを両方のコンテナに入れる、共通ボリュームを使うといった方法が考えられます。

両方のコンテナに置く場合は、更新時に同期が必要で手間が増えます。共通ボリュームを使う場合は、権限設定やバックアップ、可用性を考慮する必要があります。さらにウェブサーバーをスケールさせると、どの方法でも同じソースにアクセスさせる仕組みを作らないといけません。

逆に、NginxとPHPを一つのコンテナにしてしまうという手もありますが、1プロセス1コンテナというプラクティスにそぐわなくなります。

結論として、両方のコンテナに格納するというのが現実的な解だと感じていますが、今回は様々な理由からホストにソースを配置する事にしました。(社内システムとしての運用であり、実証実験の一環であったため、ソースアップデートの利便性を優先しました。実証実験の終盤で、ベイク方式に変更する予定としました)

UIDとGID

で、話は冒頭に戻って、直前にで本番構成の方式を変更した結果、権限エラーの問題が出たわけです。ベイク方式であれば問題は出なかったと思います。

権限をホストとコンテナで合わせる事でファイル操作可能になり、マウント権限問題は解消しました。

ところで、コンテナ内のwww-dataが、どうしてマウントしたホストのファイル(ホストのwww-dataがオーナー)が読み書きできるのか、不思議ではないですか?コンテナのwww-dataと、ホストのwww-dataは、名前こそ同じですが、UID/GIDが同じ値でないと、同じ権限と言えないはずです。

調べた所、www-dataというユーザー名は、多くのLinuxディストリビューションで特定のデフォルトUID/GID(一般的には33または82)を割り当てるという慣習があるためだそうです。つまり、大雑把に言えば、たまたまUID/GIDが一致するから、が答えのようです。

試行錯誤の順序と小さな失敗

簡単に解決したみたいに書いていますが、実は解決するまでにちょっとだけ悩みました。

  • 最初に権限だけ疑ってchmodで解決しようとした
  • 次にログの場所を探してコンテナ内を彷徨った
  • 最後にUID/GIDのズレに気づき解決

社内の環境でよかったと思う瞬間でした。まぁ、こういった試行錯誤は大きな学びになりますね。

学び:本番運用で心掛けるべきこと

  • ボリュームや権限は最初にチェックリスト化する
  • UID/GIDの違いによる権限問題を想定しておく
  • コンテナ分離による運用課題も意識する

次回は、コンテナ間通信や監視・ログ管理など、本番運用のさらに具体的な悩みを紹介します。