AWS EBベースのWordPress環境をローカルPCのDockerで再現してみた


はじめに

AWS Elastic Beanstalk(PHPランタイム)を利用したポータルサイトを運営しています。APサーバはWordPress on EC2、DBサーバはRDS(MySQL)という典型的な構成です。その他、ALBやEFSを活用したスケーラブルな構成、CodePipelineを活用したCI/CDの実践など、ベストプラクティスのお世話になっています。先人たち知恵に感謝です。

そんなポータルサイトですが、ローカル開発環境の整備がおざなりになっていました、、、。この度、Docker for Desktop (on Windows 10) を利用して、ローカル開発環境を構築したので、手順やハマったことを共有します。特に、APサーバのOSにAmazon Linux 2を採用したことが、情報の混乱による遠回りを招きました、、、。この記事を読んだ方が、少しでも近道できれば幸いです。

(2019/08/12 追記)
ボリュームマウントするuploadsフォルダの所有者をhttpdプロセスの起動ユーザ(apacheなど)に変更するようにDockerfileを修正しました。これをやっておかないと、メディアのアップロードや物理削除でエラーになります。

開発環境を整理する

まず、開発環境について整理します。AWS上の開発環境を便宜上、リモート開発環境と呼びます。厳密には、リモート/ローカル環境間で各種バージョンを一致させるべきですが、今回はそこまで厳密性にこだわりませんでした。APサーバは、Amazon Linuxのサポート期限が2020年6月30日と迫っていることもあり、Amazon Linux 2を選択しました。これがハマった要因だったのです、、、。

コピー元のリモート開発環境(AWS)

  • APサーバ:64bit Amazon Linux 2018.03 v2.8.12 running PHP 7.2 (AWS Elastic Beanstalk)
  • DBサーバ:Amazon RDS for MySQL 5.6

コピー先のローカル開発環境(Docker for Desktop on Windows 10 Enterprise)

  • APサーバ:Amazon Linux 2(Docker公式イメージ)
  • DBサーバ:MySQL 5.7(Docker公式イメージ)

ローカル開発環境でやりたいこと

ローカル開発環境でやりたいことは、とてもシンプルです。

  • リモート開発環境と同じgitリポジトリを利用したい(ローカルはfeatureブランチで)
  • リモート開発環境と同じデータ(データベースおよびメディアファイル)を利用したい

つまり、(1) APサーバ的なAWS Elastic Beanstalkがやっていることの再現、(2) DBサーバ的なデータ移行です。ただ単に、WordPress環境をローカルPC上に構築するのであれば、WordPressおよびMySQL両方ともDocker公式イメージから構築すれば良いので瞬殺です(クイックスタート・ガイド:Docker Compose と Wordpress)。しかし、AWS Elastic Beankstalk環境と同じようにgitリポジトリを利用するために、Amazon Linux 2から環境構築することにしました。

Dockerコマンドで手順を確認してみる

Dockerfileやdocker-compose.ymlをゴリゴリ書き始める前に、Dockerコマンドで地道に手順を確認することにしました。

まず、最新のAmazon LinuxイメージすなわちAmazon Linux 2イメージを取得して、/sbin/initでOS起動してみましたが、いきなり以下のエラーが発生してしまいました。/sbin/initがない、、、これは先が思いやられます。

$ docker pull amazonlinux:latest
$ docker run -it -d --name hoge-web amazonlinux:latest /sbin/init
4cb36ea9...(省略)
docker: Error response from daemon: OCI runtime create failed: container_linux.g
o:344: starting container process caused "exec: \"/sbin/init\": stat /sbin/init:
 no such file or directory": unknown.

Amazon Linux 2の公式イメージは、かなりスリムアップされており、systemdすら初期インストールされていないようです。Qiitaの記事を検索してみると、「まず/bin/bashで起動してsystemdをインストールしてから/sbin/initで起動せよ」とのことでしたので、やってみました。まず、/bin/bashで起動します。

$ docker rm hoge-web
$ docker run -it -d --name hoge-web amazonlinux:latest /bin/bash
b567ea12...(省略)

初期状態では、httpdが未インストールどころかpsコマンドやpingコマンドすら打てない、ということが分かりましたので、色々追加していきます。細やかなピックアップが面倒だったので、LAMPをインストールしてしまいます。

$ docker exec -it hoge-web /bin/bash
bash-4.2# amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2
bash-4.2# yum install -y systemd xinetd httpd mod_ssl php php-mbstring wget curl tar
bash-4.2# exit

続いて、必要な設定ファイルをコピーしておきます。aws_env.confはAWS Elastic Beanstalkが生成するファイルのひとつで、Apacheプロセスへ環境変数を渡すためのものです。その他、systemctlhttpdを自動起動するための設定ファイルやSSL関連ファイルなどがあります。

$ docker cp web/etc/httpd/conf/httpd.conf hoge-web:/etc/httpd/conf
$ docker cp web/etc/httpd/conf.d/aws_env.conf hoge-web:/etc/httpd/conf.d
$ docker cp web/etc/sysconfig/httpd hoge-web:/etc/sysconfig
$ docker cp web/lib/systemd/system/httpd.service hoge-web:/lib/systemd/system
$ docker cp web/ssl/hoge.crt hoge-web:/etc/pki/tls/certs/localhost.crt
$ docker cp web/ssl/hoge.key hoge-web:/etc/pki/tls/private/localhost.key

続いて、systemdインストール済みのコンテナを/sbin/initで起動します。httpdの自動起動を設定後、再びコンテナイメージをコミットします。これがマスタイメージになります。

$ docker commit hoge-web hoge-web:latest
sha256:2f1d9b04...(省略)

$ docker stop hoge-web && docker rm hoge-web
$ docker run -it -d --name hoge-web hoge-web:latest /sbin/init
$ docker exec hoge-web systemctl enable httpd
$ docker commit hoge-web hoge-web:latest

Dockerコマンドから設定ファイルへ

Dockerコマンドで手順が確認できましたので、その内容を設定ファイルで再現していきます。webサービス(WordPress)とdbサービス(MySQL)を利用するので、docker-compose.ymlを使います。

Dockerコマンドで確認した手順の流れ

簡単にまとめると、以下の4ステップです。

  1. Amazon Linux公式イメージを/bin/bashで起動
  2. systemdhttpdなどをインストールし、マスタイメージをコミット
  3. マスタイメージを/sbin/initで起動
  4. httpdの自動起動を設定し、マスタイメージをコミット

手順をDockerfileに落とし込む

手順1と手順2は、Dockerfileで対応します。先ほどの確認手順に加えて、PHPデバッグ向けにxdebugをインストールしています。また、AWS Elastic BeanstalkのPHPランタイム環境を再現するために、ディレクトリ作成やシンボリックリンク作成も行っています。

docker/web-init/Dockerfile
FROM amazonlinux:latest
CMD /bin/bash

# install software packages
RUN amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2 \
&& yum update -y && yum clean all \
&& yum install -y gcc xinetd httpd mod_ssl php php-mbstring php-devel php-pear wget tar \
&& pecl install xdebug \
&& mkdir -p /var/app/current \
&& cd /var/www \
&& rmdir html \
&& ln -s /var/app/current html \
&& mkdir -p /var/www/hoge/local/wp-content/uploads \
&& chown -R apache:apache /var/www/hoge

# copy configration files
COPY web/etc/httpd/conf/httpd.conf /etc/httpd/conf
COPY web/etc/httpd/conf.d/hoge-alias.conf /etc/httpd/conf.d
COPY web/etc/httpd/conf.d/aws_env.conf /etc/httpd/conf.d
COPY web/etc/php.d/xdebug.ini /etc/php.d
COPY web/etc/sysconfig/httpd /etc/sysconfig
COPY web/lib/systemd/system/httpd.service /lib/systemd/system

# copy certificate & private key
COPY web/ssl/hoge.crt /etc/pki/tls/certs/localhost.crt
COPY web/ssl/hoge.key /etc/pki/tls/private/localhost.key

続いて、手順3と手順4をDockerfileに落とし込みます。

docker/web/Dockerfile
FROM hoge-web:latest
CMD /sbin/init

# enable httpd auto-start
RUN systemctl enable httpd

手順をdocker-compose.ymlに落とし込む

さあ、いよいよdbサービスとwebサービスをまとめるときがやってきました。最終的なdocker-compose.ymlは以下の通りです。何点か工夫を加えました。

  • db/initdb.dに初期化用のSQLスクリプトを配置
    • CREATE DATABASEステートメントでデータベース作成
    • EXPORTしたダンプファイルでDROP/CREATE/INSERT(=データ移行)
  • ローカルPC側のクライアントツールからのアクセスを考慮し、3306ポートを公開
  • buildを二段階にするため、web-initとwebに分割
  • OSに渡す環境変数をenv_fileとして独立
  • 自動起動するsystemctl start httpdがエラーにならないよう特権(privileged: true)を指定
docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    container_name: hoge-db
    volumes:
      - "./db/data:/var/lib/mysql"
      - "./db/initdb.d:/docker-entrypoint-initdb.d"
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: hoge_wordpress
      MYSQL_USER: hoge_wordpress
      MYSQL_PASSWORD: hoge_wordpress
    ports:
      - "3306:3306"
  web-init:
    image: hoge-web:latest
    build:
      context: ./
      dockerfile: ./docker/web-init/Dockerfile
    command: /bin/bash
    container_name: hoge-web-init
  web:
    depends_on:
      - db
    image: hoge-web:latest
    build:
      context: ./
      dockerfile: ./docker/Dockerfile
#   command: /usr/sbin/httpd -DFOREGROUND
    command: /sbin/init
    container_name: hoge-web
    volumes:
      - "./web/HOGE_WP:/var/app/current"
      - "./web/uploads:/var/www/hoge/local/wp-content/uploads"
    links:
      - db
    ports:
      - "80:80"
      - "443:443"
    privileged: true
    restart: always
    env_file: ./web/etc/sysconfig/httpd

Docker Composeで環境構築する

ついに、AWS Elastic Beanstalk(PHPランタイム)を利用したポータルサイトのローカル開発環境を構築するときがやってきました。面倒な手順はすべて設定ファイルにまとめましたので、コマンドはとてもシンプルです。

$ docker-compose build web-init web
$ docker-compose up -d db web

なお、docker-compose upするときにサービス名を明示しないと、web-initサービスもcreateされてしまい、ごみコンテナが残ってしまうので、注意が必要です。

さいごに

docker-compose.ymlのコメントにあるように、試行錯誤を重ねる中で/sbin/initではなく、/usr/sbin/httpdで起動してしまう方法も考えました。この場合、systemctlを利用せずにhttpdを起動/停止するため、/sbin/initでのコンテナ起動が不要となります。そうすると、かなりシンプルになるのですが、Amazon Linux 2上でのsystemdを利用したサービス制御ができなくなってしまうため、/sbin/initでの起動にこだわりました。

また、手順を確認したときのように、Dockerコマンドで対応すれば何とでもなるのですが、Docker Composeの利用にこだわりました。少しトリッキーな気もしますが、環境構築のための手順はかなりシンプル化できたと思います。

参考

https://qiita.com/harukisan/items/6910684bbf2043a29812
https://qiita.com/Targityen/items/6e80b855b79d521412f0
https://qiita.com/issei_0403/items/0dd1ce7407e720338ea8
https://qiita.com/idani/items/79de4a525eaeaa16e497