Edited at

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 でも動くかもしれないレベルですね。


以上です。