イケてる SaaS を作りたい (koni blog)

SNS管理ツール「SocialDog」を運営する株式会社AutoScale代表 小西将史のブログです。イケてるSaaSを目指して日々奮闘しています。

【超簡単】Docker でモダンな PHP 開発環境を作る (PHP, MySQL, PHP-FPM, nginx, memcached)

追記 (2018.12.30)

PHP5.6, PHP7.1 に加えて、PHP7.2, PHP7.3 にも対応しました! また、PHP から memcached につなぐサンプルを追加しました。

はじめに

こんにちは。小西です。開発環境の構築って面倒ですよねー。

今回、PHP, MySQL, PHP-FPM, nginx, memcached のローカル開発環境を、Docker を使ってコマンド一発で作られるようにしたところ、あまりに簡単で驚いたので、その方法をご紹介します。

ソースコードをgithubにおいておきます ので、すぐに起動できます!

開発環境構築のめんどくささ

僕はPHP+MySQL+nginx+PHP-FPMの環境をよく使うのですが、こういった構成をそれぞれのマシンで再現するのって結構面倒なんですよね。1プロジェクトならまだいいですが、大体プロジェクトによってそれぞれのバージョンや設定ファイルのパラメータなどが微妙に違うので、phpenvでの切り替えなきゃ・・・なんて話になってきて、なかなか面倒です。

やはりプロジェクトごとに環境作成手順を共有したほうが効率がよさそうです。

なぜ Docker ?

Docker を使うと、

  • プロジェクトごとに開発環境を作ることができる
  • イメージを共有するわけではなく、設計図だけの共有なのでリポジトリが重くならない
  • Vagrant などと違い、ビルドのたびに VM が起動したりしないので軽い

Docker Toolbox ではなく Docker for Mac (Docker for Windows)を使う理由は、

  • GUI でインストールが完結するので、導入が簡単
  • Docker 社も今後はこちらを進めていく方針っぽい

2つの違いは以前こちらに書きました:これからは Docker Toolbox よりも Docker for Mac を使おう - koni blog

Docker、Docker Compose とは

今回作るものの概念をすごくざっくり説明すると、

  • 動かしたいプロセスごとに Docker イメージ を作ります
    • Docker イメージは、Dockerfile というファイルをもとにビルドします
    • プロジェクトメンバーには、イメージ ではなく、Dockerfile を共有します(Dockerfile はテキストファイルなので軽い)
    • 基本的には、1イメージにつき1つのデーモンが動くように作ります
      • 今回の場合は、nginx, php-fpm, mysqldがデーモンになります
    • イメージはゼロから作ることもできるし、他のイメージを元にして作ることもできます
    • Docker HubDocker イメージ が多数共有されているので、それを活用することも可能
  • イメージ から コンテナ を起動します
    • コンテナ は1つのプロセスで、1つのデーモンが動いています。このデーモンが止まると、コンテナは終了します
  • docker-compose.yaml という yaml のファイルを書いて、docker-compose コマンドを実行することで、複数の コンテナ をまとめて起動し、協調して動けるようにします

という感じです。

Docker 初めてだとよくわからないかもしれませんが、設定ファイル見て触って覚えたほうが早いですので、よくわからなくても次進みましょう。

ディレクトリ構成

以下のような構成にします。

├── application (ここにプロジェクトファイルが入る想定)
│   └── initial.sql (データベースの初期のSQLが入っているファイル)
├── docker
│   ├── docker-compose.yml (docker-compose の設定ファイル)
│   ├── misc
│   │   └── data (MySQLのデータフォルダが入るディレクトリ)
│   ├── nginx
│   │   ├── Dockerfile
│   │   ├── fastcgi.conf
│   │   ├── localhost.crt
│   │   ├── localhost.csr
│   │   ├── localhost.key
│   │   ├── nginx.conf
│   │   └── server.conf
│   └── php-fpm
│       ├── Dockerfile
│       └── php.ini
└── public (nginx のドキュメントルート)
    └── index.php (テスト用の PHP ファイル)

さっそく動かしてみよう

今回の記事で作成するファイルを github においておきますので、こちらをベースに説明していきます。

Docker for Mac / Docker for Windows のインストール

まずは 以下のページから Docker for Mac (または Docker for Windows )をインストールします。安定版とベータ版がありますが、安定版(stable)を選んでおきましょう。

Get started with Docker for Mac | Docker Documentation

指示に従ってインストールします。 これで、docker コマンド、docker-compose コマンドが使えるようになりました。

Mac の場合、右上で状況が確認できるようになります。クジラのアイコンがキュートですね。

f:id:konisimple:20170128133109p:plain:w300

ソースコードの展開

続いて、今回作成するファイルが置かれたリポジトリから、ファイルをダウンロードします。

$ git clone git@github.com:koni/docker-php-nginx-mysql-memcached.git

ビルド&起動

続いてビルド&起動します。初回は多少時間がかかりますが、2回目からは数秒で起動します。

$ cd docker-php-nginx-mysql-memcached/docker
$ docker-compose up

無事に成功すると、ログが出始めます。

ここで MAMP や Apache, nginx など、TCP80, 443, 13306 ポートをLIESTENしているプロセスがいると、以下のようなエラーがでます。 他のプロセスを終了してから再度 docker-compose up を実行しましょう。

ERROR: for nginx  Cannot start service nginx: driver failed programming external connectivity on endpoint nginx (9ad3d10cef4673ee7a48fbd6c7c59fbe6f3043eeac8eb0e27797da03cfbfa60b): Bind for 0.0.0.0:443 failed: port is already allocated
ERROR: Encountered errors while bringing up the project.

動作確認

ログが止まったら、ブラウザで http://localhost/ にアクセスします。 以下のような表示が出たら成功です。

f:id:konisimple:20170128133559p:plain

nginx のドキュメントルートにあるテスト用の PHPファイル(public/index.php)が実行されます。このファイルでは MySQL につないで、データを INSERT し、SELECT するので、リロードするたびに数字が増えていくはずです。

今回の動作の概念図

f:id:konisimple:20170128145930p:plain

各ファイルの説明

では、各ファイルについて見ていきましょう。

docker-compose.yaml

このファイルがあるディレクトリで、docker-compose up コマンドを実行すると、このファイルを使ってコンテナの起動を試みます。対応するイメージがまだない場合は、自動的にイメージが生成されます。必要なファイルをダウンロードしてイメージを作成するので、初回は時間がかかりますが、一度イメージが作られるとその作業はないので1,2秒で起動できるようになります。

docker-compose.yaml には、起動したいコンテナと、それぞれが協調して動くための設定を記述します。

docker-compose の書き方

version は '2' で固定です。

services以下がそれぞれのコンテナの設定です。

ports を使うと、コンテナ内のポートとローカルのポートにバインドできます。コロン(:)の左がローカルのポート、右がコンテナ内のポートです。MySQL コンテナでは、ローカルから直接コンテナの MySQL にアクセスできると便利なので、コンテナの 3306 をローカルの 13306 にバインドします。3306 でなく 13306 にしているのは、既に MySQL がローカルで動いているとバッティングしてしまうためです。この数字はなんでもかまいません。

volumes と使うと、コンテナ内のディレクトリをローカルのディレクトリにバインドできます。コロン(:)の左がローカルのディレクトリ、右がコンテナのディレクトリです。

container_name でコンテナに名前をつけることができます。これは必須ではないですが、docker-compose exec でコンテナ内に入る時に、打つ文字数が減るので便利です。ただし、同じコンテナ名で複数のコンテナが起動できないので、1つに固定しておくと、同時に1つしか同じコンテナを起動できなくなります。

links を使うと、その名前で他のコンテナにアクセスできるようになります。 これを使えばコンテナのIPを知らなくても他のコンテナにアクセスできます。

例えば、weblinks: mysqlとあるので、nginx コンテナ内からは、mysqlというホスト名でmysqlにアクセスできるようになります。この場合、アプリ側のDBの設定ファイルのホストの欄には mysqlと書けば接続できるようになります。こんな感じです。この例では`docker-compose.yaml から環境変数でホスト名を渡しています。

memcached
services:
  memcached:
    image: memcached:1.4

これは、memcached:1.4 イメージで起動しろ、という設定です。ローカルにイメージがない場合は、Docker Hub から自動的に取得されます。今回は、ここから取得されます。

mysql
  data:
    image: busybox
    volumes:
      - ./misc/data:/var/lib/mysql
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: mysql_database
      MYSQL_USER: mysql_user
      MYSQL_PASSWORD: mysql_pw
    ports:
      - "13306:3306"
    volumes_from:
      - data
    volumes:
      - ../application/initial.sql:/docker-entrypoint-initdb.d/initial.sql
    container_name: mysql

こちらもイメージは Docker Hubのオフィシャルリポジトリから取得されます。

このイメージでは、MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORDという環境変数を設定しておくと、自動的にROOTパスワードを設定し、ユーザーとデータベースを作成してくれる機能があるので、環境変数を設定しておきます。ちなみに、この処理はデータベースがないときだけ行われます。一回起動するとこの処理は行われません。misc/data以下を消すと、再度ユーザーとデータベースが作成されます。

またこのイメージには、/docker-entrypoint-initdb.d/*.sql というファイルを置いておくと、起動時にそれを実行してくれる機能があるので、volumesを使って、そこにアプリケーションのDBスキーマをおいておきます。今回はテストなので、hoges というテーブルを作成するCREATE TABLE 文だけおいておきます。実際にはここでアプリケーションのDBスキーマが書かれたファイルのパスを指定します。

コンテナは終了するとデータが消えてしまいます。コンテナを終了するたびデータが消えると開発上不便なので、busybox を使ってMySQLのデータはコンテナ外においておくことにします。 ローカルの./misc/dataを、コンテナ内の /var/lib/mysql にバインドします。これで、misc/dataにMySQLのデータがある状態になるので、コンテナを終了してもデータは消えません。データを消したいときは、misc/data 以下を削除すればOKです。 git で管理する場合は、このディレクトリは .gitignore しておくことをおすすめします。

nginx

  nginx:
    build: ./nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ../:/var/www/html
    links:
      - web
    container_name: nginx

今度は image ではなく、 build があります。 イメージ内のnginxの設定ファイルに変更を加えたいので、別途 Dockerfileを使ってイメージをビルドするようにします。

nginx/Dockerfile

このファイルで nginx イメージをビルドします。

FROM nginx

ADD nginx.conf /etc/nginx/nginx.conf
ADD fastcgi.conf /etc/nginx/fastcgi.conf
ADD server.conf /etc/nginx/conf.d/default.conf
ADD localhost.key /etc/ssl/private/localhost.key
ADD localhost.crt /etc/ssl/certs/localhost.crt

FROM nginx とあるので、Docker Hub のリポジトリからベースのイメージがダウンロードされます。

それから、設定ファイルをいくつかを変更します。 今回は nginxの設定ファイルである、nginx.conf、ポートと処理の対応の設定であるserver.conf、nginxとPHP-FPMとの連携の設定である fastcgi.conf を変更します。

また、おまけとして、自己署名したSSL証明書もおいておきます。記事最後の手順を実行すると、https://localhostでもアクセスできるようになります。

web (PHP-FPM)

  web:
    build: ./php-fpm
    volumes:
      - ../:/var/www/html
    links:
      - mysql
    environment:
      DATABASE_HOST: 'mysql'
      DATABASE_NAME: 'mysql_database'
      DATABASE_USER: 'mysql_user'
      DATABASE_PASSWORD: 'mysql_pw'
    container_name: web

volumes で、プロジェクトルートと /var/www/htmlをバインドしておきます。これで、コンテナ起動中にソースコードを修正して、すぐに確認することができます。

environment を利用して、アプリの実行設定を環境変数で渡すことができます。今回はDBへの接続の設定を渡しました。

よく使うコマンド

イメージのビルド

$ docker-compose build

起動

$ docker-compose up

終了

$ docker-compose stop

起動中のコンテナを見る

$ docker ps

コンテナに入る

ログファイルや、設定ファイルの位置の確認などで、コンテナに入るときは以下を使います。

$ docker exec -it mysql bash
root@a0d91988b7db:/#  # コンテナに入れる

mysql の部分には、コンテナ名 (docker-compose.yamlcontainer_name)か、コンテナID (docker ps コマンドの出力の CONTAINER ID)を入れます。

その他使い方メモ

コンテナのMySQLに接続したい

MySQL クライアントで以下のように設定すると、コンテナの MySQL に入れます。もちろん mysql コンテナが起動していないと入れません。

Host: 127.0.0.1:13306
User: mysql_user
PW: mysql_pw
Database: mysql_database

みんな大好きSequelProならこんな感じですね。

f:id:konisimple:20170128141637p:plain:w300

SSL で入れるようにしたい

今回、こちらで生成したSSL自己署名証明書が入っているので、この証明書を信頼する設定にすれば、SSLで接続できます。

この証明書は、自己署名なのでそのままだとブラウザでエラーが出てしまいます。 これを回避するために、localost.crt をキーチェーンアクセスの「システム」として証明書を登録します。

$ open docker/nginx/localhost.crt

上のコマンドを実行するとキーチェーンアクセスが起動するので、「システム」を選択し、証明書を追加する。 「キーチェーンアクセス」を起動し、「システム」を選択、今追加した証明書(名前が「localhost」になっている)をダブルクリックし、「この証明書を使用するとき」欄を「常に信頼」にし、画面を閉じる。画面を閉じるときに保存されるので、必ず画面を閉じる。

SSL 自己署名証明書の作成手順

リポジトリに含まれている証明書は、以下の手順で作成したものです。

$ openssl genrsa 2048 > localhost.key
$ openssl req -new -key localhost.key > localhost.csr
$ openssl x509 -req -days 3650 -signkey localhost.key < localhost.csr > localhost.crt

まとめ

というわけで、これで開発環境をコマンド一発で起動できるようになりました。

これを git などでメンバーに共有して、メンバーには「Docker for Mac 入れて、docker-compose up 実行して」と伝えるだけで開発環境の構築が済むようになりました。

<宣伝> 株式会社AutoScaleでは、この環境を使って、Twitterフォロワー管理・運用支援ツール Cheetah というサービスを開発しています! Twitter アカウントをお持ちの方は、ぜひお試しください!

参考

DevOps導入指南 Infrastructure as Codeでチーム開発・サービス運用を効率化する

この本は、2016年10月発売と新しいので、Docker for Mac についても書いてあり、開発の運用についてかなり具体的に書いてありおすすめです。

Compose ファイル・リファレンス — Docker-docs-ja 17.06.Beta ドキュメント

追記 (2017.9.28)

「モダン」と行っておきながら PHP 5.6 だったので、PHP 7.1 に対応させました。 PHP5.6 に対応させたい場合は、docker-compose.ymlbuild: ./php-fpm56 にしてください。