Monkey patch

Настройка локально

В этой статье я предполагаю, что служба docker запущена на той же машине, на которой выполняются команды, и у процесса есть доступ на чтение к текущей папке. Еще я подразумеваю, что вы умеете настраивать связку PHP-FPM и Nginx.

Беру образы Nginx и PHP 7.

~$ docker pull nginx
...
~$ docker pull php:7-fpm
Status: Downloaded newer image for php:7-fpm

Теперь у меня есть два чужих класса, которые надо связать вместе через внедрение зависимостей. Самый простой способ добавлять зависимости в чужой код, конечно же, monkeypatching! Сначала создаю контейнеры. Помню о второй сложности программирования - даю контейнерам вразумительные имена, они будут нужны, чтобы контейнеры могли взаимодействовать между собой.

~$ docker create --name=php7 php:7-fpm
3d1b737edfcc3f1102fa54c91f9120da4b86d8cbba3092b6f80156c0e31b4d8f
~$ docker create --name=nginx nginx
80be81b27e012fd061ff4b682f0b7b8803500bc38a4b9f787f91661603b2d4b7

PHP

Начну с PHP - его настроить сложнее. Где лежат конфиги для PHP - можно увидеть в его Dockerfile:

    ENV PHP_INI_DIR /usr/local/etc/php
       --with-config-file-scan-dir="$PHP_INI_DIR/conf.d" \
    WORKDIR /var/www/html
    COPY php-fpm.conf /usr/local/etc/

Копирую себе из контенера содержимое каталога с файлами конфигурации php

~$ mkdir monkeypatch
~$ cd monkeypatch/
$ docker cp php7:/usr/local/etc localetc
$ ls localetc/
pear.conf        php            php-fpm.conf        php-fpm.conf.default    php-fpm.d
$ ls localetc/php
conf.d

Мейнтейнеры положили в образ php-fpm.conf, но не положили дефолтный php.ini. Придется взять его из исходников php.

$ docker cp "$PHP7:/usr/src/php/php.ini-development" localetc/php/php.ini

Правлю конфиги, как обычно. В какой папке PHP ищет расширения? Узнать можно, запустив php, например, во временном контейнере.

$ docker run --rm php:7-fpm php -i |grep extension_dir
extension_dir => /usr/local/lib/php/extensions/no-debug-non-zts-20141001 => /usr/local/lib/php/extensions/no-debug-non-zts-20141001
$ docker run --rm php:7-fpm ls /usr/local/lib/php/extensions/no-debug-non-zts-20141001
opcache.a
opcache.so

В расширениях только opcache, можно подключить его.

$ echo extension_dir = "/usr/local/lib/php/extensions/no-debug-non-zts-20141001" >>  localetc/php/php.ini
$ echo zend_extension = opcache.so >> localetc/php/php.ini

Пересоздаю контейнер php и монтирую в него папку с конифгами. Путь к монтируемой папке должен быть от корня - служба не знает, из какой папки вызывается клиент docker.

$ docker rm php7
php7
$ docker run -v "$(pwd)/localetc:/usr/local/etc" --name=php7 php:7-fpm php -i |grep Configuration
Configuration File (php.ini) Path => /usr/local/etc/php
Loaded Configuration File => /usr/local/etc/php/php.ini

Теперь можно пересоздать контейнер php7 с тестовым приложением на php. Создатели образа не позаботились о том, чтобы php-fpm работал как демон, так что надо самим запускать его фоном, не освобождая стандартные каналы ввода-вывода.

$ docker rm php7
$ mkdir scripts
$ echo "<?php echo 'Hello world! ',PHP_VERSION,PHP_EOL;" > scripts/test.php
$ docker run -v "$(pwd)/localetc:/usr/local/etc" \
    -v "$(pwd)/scripts:/scripts" \
    --name=php7 php:7-fpm &
[29-Aug-2015 15:19:25] NOTICE: fpm is running, pid 1
[29-Aug-2015 15:19:25] NOTICE: ready to handle connections

Пока что для удобства отладки я оставляю вывод из контейнера php-fpm в свою консоль.

NGINX

С Nginx всё просто и стандартно. Копирую на диск папку конфигов:

$ docker run --name=nginx nginx
$ docker cp nginx:/etc/nginx .    
$ docker rm nginx

В папке nginx/ надо отредактировать nginx.conf, fastcgi_params по вкусу, и создать конфигурационный файл для своего сайта в nginx/conf.d/. Основное для связи nginx с php - это указать в имени хоста имя контейнера с php, а директивы root и SCRIPT_FILENAME должны указывать на путь, который php поймёт в своём контейнере php7.

    location ~ \.php$ {
        fastcgi_pass   php7:9000;
        fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;

Монтирую конфиги в контейнер nginx и запускаю с маппингом 80-го порта контейнера на локальный 8080.

$ docker run -v "$(pwd)/nginx:/etc/nginx" -p 8080:80 --name=nginx nginx &
$ curl 127.0.0.1:8080/test.php
172.17.0.65 -  29/Aug/2015:15:50:29 +0000 "GET /test.php" 200
Hello world! 7.0.0RC1

Rock'n'Roll!

В версии 1.7 в команде docker run надо указывать параметр --link чтобы в контейнере резолвилось имя другого контейнера. В версии 1.8.1 все работает и без этого параметра.

Логи

Мейнтейнеры образа php решили писать все логи fpm в /proc/self/fd/2, он же STDERR - как error_log, так и access.log. Однако, лог запросов у меня будет писать nginx, а в работе php меня интересуют только ошибки, поэтому предлагаю отредактировать localetc/php-fpm.conf и написать что-то привычное:

error_log = /var/log/php/php-fpm.error.log
;access.log = /proc/self/fd/2 

В Nginx обошлись без самодеятельности, так что включаю access log в конфиге сайта nginx/conf.d/site.ru.conf

access_log  /var/log/nginx/host.access.log  main;

Теперь можно создать папку для логов c правом записи для демона docker и подмонтировать ее в контейнеры. В эту же папку можно писать и вывод контейнеров, при этом контейнеры можно детачить:

$ mkdir log
$ sudo chgrp docker log/
$ sudo chmod g+rwx log/
$ docker stop nginx php7
$ docker rm nginx php7
$ docker run -d --name=php7 \
    -v "$(pwd)/localetc:/usr/local/etc" \
    -v "$(pwd)/scripts:/scripts" \
    -v "$(pwd)/log:/var/log/php" \
    php:7-fpm >>log/docker.php.log 2>&1
$ docker run -d --name=nginx \
    -v "$(pwd)/nginx:/etc/nginx" \
    -v "$(pwd)/log:/var/log/nginx" \
    -p 8080:80 \
    nginx >>log/docker.nginx.log 2>&1
$ curl 127.0.0.1:8080/test.php
Hello world! 7.0.0RC1

Когда надо поменять конфигурацию - можно дать команду перезагрузки php и nginx.

$ docker exec php7 pkill -o -USR2 php-fpm
$ docker exec nginx service nginx reload
Reloading nginx: nginx.

Когда php 7 будет включен в дистрибутив Debian в образе php:7 появится init-скрипт. При желании, можно добавить его самостоятельно из дистрибутива по выбору.