nginx
concrete5
docker
docker-compose

Concrete5 on Nginx + php7-fpm を docker-compose でやってみた

More than 1 year has passed since last update.

追記 : 2017/01/23

Concrete5 を最新版にし、各種イメージの定義を改善しました。

目的

  • Concrete5 を Nginx + php-fpm で動かす。
  • docker-compose で起動できるようにする。

ブツだけ欲しい

https://github.com/hidekuro/concrete5-docker

作業

前提環境

  • docker
  • docker-compose

手順

0. システム構成を考える

  • Docker ベースで作ろう
  • フルスタックコンテナは嫌なので docker-compose でオーケストレーションしよう
    • サービスは app, db, web くらいに分かれるかな?
  • 安いサーバーでも動くようになるべく軽くしたい
    • alpine ベースにしよう
    • Web サーバーは Nginx にしよう
    • MySQL はちょっとチューニングしよう
  • 環境によって docker-machine が挟まる部分は共通ユーザーで吸収しよう
    • docker-machine のエージェントユーザーは uid=1000 になるので、各サービスに uid=1000 のユーザーを共通で作って使えば OK

完成予想図

tree-image.png

1. プロジェクトの土台作成

concrete5 だと長いので c5 にしておきます。

mkdir -p /path/to/work/c5
cd c5

サービスごとの Dockerfile や設定ファイルを分別するためにフォルダを作成。

mkdir app db web

2. サービス定義

docker-compose.yml
version: "2"

services:
  # php-fpm, Concrete5ソース
  app:
    build: ./app
    volumes:
      - /var/run/php-fpm
      - c5src:/var/www/concrete5
    user: c5agent
    working_dir: /var/www/concrete5

  # Nginx
  web:
    build: ./web
    volumes_from:
      - app
    ports:
      - 80:80
      - 443:443
    environment:
      SERVER_NAME: 192.168.99.100
      PHP_SOCKET: /var/run/php-fpm/php-fpm.sock
    command: sh -c "envsubst '$$SERVER_NAME $$PHP_SOCKET' < /etc/nginx/conf.d/concrete5.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"

  # MySQL
  db:
    build: ./db
    volumes:
      - c5db:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: mysql

# 名前付きデータボリューム
volumes:
  c5src:
  c5db:

web サービスのコマンドが激しいですが、あとで詳しく解説します。

Concrete5 のソースと MySQL データファイルは、名前付きボリューム内のディレクトリに置くようにしています。
単一ホストのローカル環境で、各サービスを1コンテナずつだけ起動して使うような場合はサービス側のボリューム定義だけでも十分ですが、名前付きボリュームで切り離して置いたほうが「あの時切り離しておけば…」とならなくて済むことが多いです。

各サービスの概要は次の通り。

  • app ... Concrete5 のソースを保有し、 php-fpm の Unix ソケットを提供するサービス。
  • web ... Nginx のサービス。ボリューム経由で app の php-fpm ソケットを利用します。
  • db ... MySQL のサービス。設定ファイルと初期化用 SQL を与えてカスタムします。

これらの Dockerfile を書いていきます。

2.1. app

app/Dockerfile
FROM php:7-fpm-alpine
MAINTAINER hidekuro

# agent user
ARG AGENT_UID=1000
RUN adduser -D -H -s /bin/sh -u ${AGENT_UID} c5agent

# setup php
RUN apk --no-cache add \
  curl-dev \
  libmcrypt-dev \
  freetype-dev \
  libjpeg-turbo-dev \
  libpng-dev \
  libxml2-dev \
  zlib-dev \
  && NPROC=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) \
  && docker-php-ext-install -j${NPROC} \
    curl \
    dom \
    mbstring \
    mcrypt \
    pdo \
    pdo_mysql \
    simplexml \
    zip \
  && docker-php-ext-configure gd \
    --with-freetype-dir=/usr/include/ \
    --with-jpeg-dir=/usr/include/ \
    --with-png-dir=/usr/include/ \
  && docker-php-ext-install -j${NPROC} gd \
  && mkdir -p /var/run/php-fpm \
  && chown c5agent:c5agent /var/run/php-fpm

# download concrete5
ARG C5_FILE_REV=93074

RUN mkdir -p /var/www \
  && curl -sSL -o /tmp/concrete5.zip http://www.concrete5.org/download_file/-/view/${C5_FILE_REV}/ \
  && unzip -q /tmp/concrete5.zip -d /var/www \
  && rm -f /tmp/concrete5.zip \
  && mv /var/www/concrete* /var/www/concrete5 \
  && chown -R c5agent:c5agent /var/www/concrete5 \
  && chmod -R 700 /var/www/concrete5

# put config
COPY ./php/php.ini /usr/local/etc/php/php.ini
COPY ./php-fpm/www.conf /usr/local/etc/php-fpm.d/zzz-www.conf

VOLUME ["/var/run/php-fpm"]
VOLUME ["/var/www/concrete5"]

やはり PHP の闇は深い

いきなり激しいですが、ざっくり表現すると

RUN 共通ユーザーを作る

COPY PHP と FPM の設定ファイルを置く

RUN php 拡張のための依存ライブラリのインストール,
    php 拡張の有効化,
    php-fpm ソケットのディレクトリパーミッション変更

RUN /var/www/concrete5 に Concrete5 を DL して展開,
    所有者とパーミッション変更

VOLUME php-fpm ソケットのボリューム定義
VOLUME Concrete5 ソースディレクトリのボリューム定義

こういうことです。

PHP まわり

設定ファイルの設置を最後にしているのは、あとで設定ファイルだけ変えたくなった時に、時間がかかるステップをキャッシュで済ませるためです。

fpm の設定ファイル を zzz-www.conf で設置しているのは、 公式イメージの設定ファイル より後に読ませて優先させるためです。
公式のやつが zz-docker.conf で設置されているので、それより辞書的に後ろの名前を指定。

設定ファイルの中身は次のようなもの

app/php/php.ini
post_max_size = 20M
upload_max_filesize = 20M
memory_limit = 128M

コンテンツアップロードを考慮してPOSTサイズを拡大し、メモリ上限を指定。
あとからで変更が必要になってくることもあると思います。

app/php-fpm/www.conf
[www]
listen = /var/run/php-fpm/php-fpm.sock
listen.owner = c5agent
listen.group = c5agent
listen.mode = 0660
user = c5agent
group = c5agent

実行ユーザーを共通ユーザー c5agent に変更して unix ソケットを使用するようにしています。

Concrete5 まわり

Concrete5 の本体は、ダウンロード URL のファイルID部分を ARG で変数にしているので、 docker-compose.yml を次のように変更することでビルド時に差し込むことが出来ます。
(番号の確認は Concrete5 公式 Download ページ の各種ダウンロード URL を参照)

docker-compose.yml
  app:
-   build: ./app
+   build:
+     context: ./app
+     args:
+       C5_FILE_REV: "92910"
    volumes:
      - /var/run/php-fpm

2.2. db

Dockerfile は単純です。

db/Dockerfile
FROM mysql:5.6
MAINTAINER hidekuro

COPY ./conf.d/my.cnf /etc/mysql/conf.d/zzz-my.cnf
COPY ./docker-entrypoint-initdb.d /docker-entrypoint-initdb.d

# treat Windows
RUN chmod 644 /etc/mysql/conf.d/*

1つだけポイントとして、最後の行で設定ファイルのパーミッションを変えています。
Windows で作業すると、ホストから与えるファイルが全て 666 になってしまい、 MySQL の設定ファイルはそのようなパーミッションのファイルを無視してしまうためです。

MySQL は PHP ファイルやディレクトリのパーミッションについては関わらないので、共通ユーザーは無し。

設置しているファイルの中身がこちら。

db/conf.d/my.cnf
[mysqld]
character_set_server = utf8mb4
collation_server = utf8mb4_unicode_ci

log-error = /dev/stdout
log_queries_not_using_indexes = true
general_log = true
general_log_file = /dev/stdout
slow_query_log = true
slow_query_log_file = /dev/stdout
long_query_time = 1.0

performance_schema = false
table_definition_cache = 400
  • latin1 では困るので文字コード類を変えています。
  • よく見そうなログを標準出力に吐いています。
  • パフォーマンススキーマを切ってメモリ使用量を節約します。
  • MySQL 5.6 系には table_definition_cache がデフォ400のはずが1400になっているバグがあるので、明示的に下げてメモリ使用量を節約します。

続いて、初期化用 SQL がこちら。

db\docker-entrypoint-initdb.d\00-init.sql
CREATE USER 'c5agent'@'%' IDENTIFIED BY 'Concrete#5';
CREATE DATABASE concrete5 DEFAULT CHARACTER SET utf8mb4;
GRANT ALL ON concrete5.* TO 'c5agent'@'%';

これは説明いりませんな

ユーザー名、パスワード、データベース名あたりはお好みで。

ワイルドカードホストが気になる方は、 Docker のブリッジネットワーク内に絞って 172.0.% でもいいかもしれませんが、どちらかというと Docker ホストのネットワーキング設計やインフラのレイヤーで制御すべきなので、 MySQL ユーザーはザルにしておいたほうがトラブルが減ると思います。

2.3. web

これも Dockerfile は超単純

web/Dockerfile
FROM nginx:1.11-alpine
MAINTAINER hidekuro

# agent user
ARG AGENT_UID=1000
RUN adduser -D -H -s /bin/sh -u ${AGENT_UID} c5agent

# put conf
COPY ./nginx/conf.d/concrete5.conf.template /etc/nginx/conf.d/concrete5.conf.template
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf

共通ユーザー作って設定置いてるだけです。

設定ファイルの中身

nginx/nginx.conf
user c5agent;
worker_processes 1;
pid /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /dev/stdout main;
    error_log /dev/stdout;

    server_tokens off;

    gzip on;
    gzip_types  text/css
                image/jpeg
                image/png
                image/gif
                image/x-icon
                image/x-ms-bmp
                image/svg+xml
                image/webp
                application/font-woff
                application/json
                application/javascript
                ;

    sendfile on;
    keepalive_timeout 65;

    include /etc/nginx/conf.d/*.conf;
}

通常と違うポイントは、

  • user c5agent; ... 共通ユーザーで実行させる。PHP実行ユーザーやConcrete5のソース所有ユーザーと一致させることで無用なトラブルを避けられます。
  • gzip_types のMIMEタイプをちょっと細かめに指定している…かも。ここはサイト用途に合わせて変更していく必要があると思います。

で、肝心の location やらを含むやつがこちら

nginx/conf.d/concrete5.conf.template
server {
    listen 80;
    server_name ${SERVER_NAME};

    root /var/www/concrete5;
    index index.php index.html;

    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    location ~ /\. {
        deny all;
    }

    location / {
        try_files $uri $uri/ @php;
    }

    location @php {
        rewrite ^/(.*)$ /index.php/$1 last;
    }

    location ~ \.php(/|$) {
        fastcgi_pass unix:${PHP_SOCKET};
        fastcgi_split_path_info ^(.+\.php)(.+)$;
        fastcgi_index index.php;

        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
    }
}

ポイントとしては

  • サーバー名と php-fpm ソケットのパスを ${SERVER_NAME}, ${PHP_SOCKET} という変数表記にしており、コンテナ起動時に置換するためのテンプレートになっている。
  • rewrite で Concrete5 のプリティーURLに対応。

docker-compose.yml の長いコマンドは、変数表記にした部分を置換するためのものです。

コマンド部分のみ抜粋

/bin/sh-c "envsubst '$$SERVER_NAME $$PHP_SOCKET' < /etc/nginx/conf.d/concrete5.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"

environment: で注入する値を環境変数として定義します。
command: の行はちょっと長いですが、公式のNginxイメージ説明にもある envsubst で置換しつつ起動する記述です。

日本語で表現すると

/bin/sh -c "envsubst '置換対象の変数列挙' < 入力ファイル > 出力ファイル && nginxフォアグラウンド起動"

ということをやってます。
$$ は docker-compose コマンドの段階でYAMLファイル上に展開されないようにするためのエスケープです。

起動

docker-compose で起動してみます。
up するとき -d を忘れるとフォアグラウンドにへばりつくのでお忘れなく。

docker-compose up --build -d

docker-machine の人はとりあえず IP を確認
(当然ですが環境によって違う場合があります)

[you@host] ~$ docker-machine ip
192.168.99.100

ブラウザで開いてみる

c5-01.png

ok :thumbsup:

日本語を選択して進んでみます

c5-02.png

ここで緑の平和なチェック :white_check_mark: 以外の不吉な警告が見えていたら何かがおかしいです。

次の画面に行ってみます

c5-03.png

ここでポイントなのが データベース情報のサーバー です。

docker-compose で定義したサービス同士は、サービス名でホストを解決できるため、 MySQL のホスト名はサービス名である "db" となります。


最終的なソース

https://github.com/hidekuro/concrete5-docker

軽さチェック

Concrete5 のインストールが終わったあとの状態を見てみます。

bash
docker stats $(docker ps --format={{.Names}})
CONTAINER    CPU %    MEM USAGE / LIMIT        MEM %     NET I/O                BLOCK I/O             PIDS
c5_web_1     0.00%    2.977 MiB / 995.8 MiB    0.30%     299.1 kB / 4.914 MB    1.421 MB / 4.096 kB   3
c5_app_1     0.00%    95.13 MiB / 995.8 MiB    9.55%     65.81 MB / 31.18 MB    15.14 MB / 11.24 MB   4
c5_db_1      0.02%    259.6 MiB / 995.8 MiB    26.07%    31.18 MB / 65.78 MB    15.97 MB / 441 MB     24

合計 360MB くらい。

起動直後だと c5_db_1 (MySQL) は 50MB くらいですが、色々仕事させると 260~280MB くらいに落ち着くようです。

ファイルアップロード等を考慮しても、そんなにアクセスの激しくないサイトなら
EC2 t2.nano や GCE f1-micro でも動くかもしれないレベルですね。


以上です。