Help us understand the problem. What is going on with this article?

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

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


以上です。

hidekuro
雑食。私がQiitaで公開する独自コードは、特に記載がない限り CC0 https://creativecommons.org/publicdomain/zero/1.0/deed.ja とします。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away