Вот уже около года я веду разработку своего веб-сайта (используемый стек технологий: Yii 2/PHP/MySQL) исключительно с использованием Docker. Однажды попробовав, я понял, что Docker — это лучшее, что могло случиться в сфере разработки. Ну, после Vim, конечно.
Решил написать краткую статью о своём опыте. Надеюсь, она окажется полезной для тех, кто ещё не пробовал Docker или испытывает сложности с его настройкой.
Вступление
Для начала немного азбучных истин. Если ваше веб-приложение простое, а изменять его требуется редко, вносить эти изменения можно прямо в работающую его «боевую» версию (то, что называется Production). Однако, такой подход неприемлем, если объём изменений велик. Каждую функцию, в том числе неизменённую, необходимо тщательно протестировать перед выпуском в Production, иначе вы рискуете оказаться с неработающим продуктом.
Очень часто веб-приложения используют базу данных для хранения различной информации. В минувшие годы большое распространение получил стек (набор технологий) под названием LAMP (Linux, Apache, MySQL, PHP), которым в своё время воспользовался для создания этого сайта и я.
В идеале, конфигурация системы разработчика должна совпадать с конфигурацией «боевой» системы, как минимум в части версий ПО и особенностей его сборки (ключей компиляции и т.п.). Всё это можно, конечно, установить прямо на свой компьютер или Development-сервер, но могут возникнуть сложности с доступностью требуемых версий софта, а особенно с настройкой и обновлением нужных компонентов, файлов конфигураций и так далее. Например, обновили вы свою Убунту — упс, а PHP 5.x там больше нет, надо самому компилировать.
Контейнеры Docker
Одно из возможных решений данной проблемы — использование виртуальной машины, но и здесь возникают сложности: необходимость тщательной настройки, высокие требования к оперативной памяти, проблемы с производительностью, но самое главное — трудность в воспроизведении желаемой конфигурации, если её нужно повторить на другой машине. Можно, конечно, целиком скопировать весь виртуальный диск, но это долго, дорого и неудобно.
С выходом Docker наконец-то появилась отличная, почти идеальная альтернатива. Docker позволяет запускать отдельные компоненты, такие как PHP, Apache HTTP Server и MySQL, в стандартизованных легковесных контейнерах, со стопроцентно воспроизводимыми конфигурациями, и соединять эти контейнеры между собой с помощью виртуальных сетей. Не нужно больше устанавливать многочисленные пакеты и компоненты — достаточно установить только сам Docker Engine. Ну и, разумеется, самое приятное состоит в том, что Docker — это свободное ПО с открытым исходным кодом.
Конфигурация каждого контейнера описывается в простом текстовом файле, чаще всего называемом Dockerfile
и состоящем из набора специальных команд. Контейнер строится на основе образа (image); в репозитории Docker доступно огромное количество готовых образов на все случаи жизни. Поэтому первая команда в Dockerfile
— это FROM
, задающая образ, на основе которого будет построен контейнер.
Конфигурация контейнера для Apache HTTP Server
Итак, перейдём от слов к делу. Как я уже упоминал, данный веб-сайт построен на стеке LAMP и реализован с использованием PHP-фреймворка Yii. Для запуска веб-приложения я использую два отдельных контейнера:
- Контейнер
yktoo-app
с Apache HTTP Server + PHP. - Контейнер
yktoo-db
с MySQL.
Первый контейнер описывается следующим файлом (чтобы различать файлы для разных контейнеров, назовём его Dockerfile-app
):
# Файл Dockerfile-app
# Используем PHP 5.6 с Apache в качестве базового образа
FROM php:5.6-apache
# Подключаем модуль Apache Rewrite
RUN cd /etc/apache2/mods-enabled && \
ln -s ../mods-available/rewrite.load
# Устанавливаем требуемые разширения PHP
# -- GD
RUN apt-get update && \
apt-get install -y libfreetype6-dev && \
docker-php-ext-configure gd --with-freetype-dir=/usr/include/ && \
docker-php-ext-install -j$(nproc) gd
# -- mysql
RUN docker-php-ext-install -j$(nproc) mysql pdo_mysql
# Копируем конфигурацию сервера HTTP
COPY 000-default.conf /etc/apache2/sites-available/
Команда COPY
в файле выше копирует внутрь контейнера конфигурацию сайта (000-default.conf
), обслуживаемого сервером; файл выглядит следующим образом:
# Файл 000-default.conf
<VirtualHost *:80>
ServerName localhost
ServerAdmin wizard@localhost
DocumentRoot /var/www/html/web
LogLevel info php5:debug
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
<Directory "/var/www/html/web">
# Disable .htaccess
AllowOverride None
</Directory>
# Set up rewrites so that all requests go to index.php
RewriteEngine on
# if a directory or a file exists, use it directly
RewriteCond /var/www/html/web%{REQUEST_FILENAME} !-f
RewriteCond /var/www/html/web%{REQUEST_FILENAME} !-d
# otherwise forward it to index.php
RewriteRule . /var/www/html/web/index.php
</VirtualHost>
Здесь важным моментом является использование директивы AllowOverride None
, запрещающей использование файла .htaccess
из исходного кода сайта. У меня в нём стоит, например, перенаправление пользователя на HTTPS-адрес (и ещё куча всяких разных редиректов), что для разработки совершенно не требуется.
Конфигурация контейнера для MySQL
В контейнере №2 крутится жизненно важный компонент приложения, сервер баз данных MySQL. Его Dockerfile
(назовём его Dockerfile-db
) имеет следующее содержимое:
# Файл Dockerfile-db
# Используем MySQL 5.7 в качестве базового образа
FROM mysql:5.7.16
# Копируем скрипты для создания исходной базы данных
COPY init.sql /docker-entrypoint-initdb.d/
COPY database.sql /db/
Здесь с помощью команды COPY
в контейнер помещаются два SQL-скрипта. Первый производит создание и начальную настройку БД:
# init.sql
create database yktoo;
use yktoo;
source /db/database.sql;
create user appuser identified by "appuserPasswd";
grant all privileges on yktoo.* to appuser@'%';
Он же вызывает и второй скрипт, database.sql
, представляющий собой просто дамп «боевой» БД со всеми таблицами и их содержимым. Таким образом, я получаю точную копию моего приложения, работающего по адресу https://yktoo.com/.
Запуск контейнеров
Мало контейнеры создать, их ещё нужно должным образом запустить, — так, чтобы БД была доступна для HTTP-сервера. Первое, о чём нужно позаботиться, — это конфигурация базы данных в Yii:
<?php // Файл config/db.php
return [
'class' =>'yii\db\Connection',
'dsn' =>'mysql:host=yktoo-db;dbname=yktoo',
'username' =>'appuser',
'password' =>'appuserPasswd',
'charset' =>'utf8',
'tablePrefix'=>'t_',
];
?>
Как нетрудно заметить, тут используются те же название БД (yktoo
), имя пользователя (appuser
) и его пароль (appuserPasswd
), которые заданы в вышеприведённом файле init.sql
. Но что также важно, это имя хоста yktoo-db
, которое совпадает с именем контейнера MySQL. По этому имени его можно «видеть» из контейнера с Apache HTTP Server, если их соответствующим образом связать при запуске (см. ниже).
Но перед тем как контейнеры запускать, нужно построить соответствующие им образы, используя созданные ранее файлы Dockerfile-app
и Dockerfile-db
:
# build.sh
# Путь к корню вашего проекта
PROJ_ROOT=/path/to/your/yii/app
# Строим образ контейнера приложения
docker build -t yktoo-app-image -f "Dockerfile-app" "$PROJ_ROOT"
# Строим образ контейнера БД
docker build -t yktoo-db-image -f "Dockerfile-db" "$PROJ_ROOT"
В результате выполнения этих команд будут созданы два образа: yktoo-app-image
и yktoo-db-image
. Теперь можно запускать контейнеры:
# run.sh
# Путь к корню вашего проекта
PROJ_ROOT=/path/to/your/yii/app
# Сначала стартуем контейнер с БД
docker run -d -e MYSQL_ROOT_PASSWORD=root --name yktoo-db yktoo-db-image
# Теперь контейнер с приложением, связав его с БД-контейнером
docker run -d \
-p 80:80 \
-v "$PROJ_ROOT":/var/www/html \
--name yktoo-app \
--link yktoo-db \
yktoo-app-image
Опция -p 80:80
привязывает порт 80 внутри контейнера к порту 80 на хост-машине, после чего приложение становится доступно по адресу http://localhost/
.
Здесь мы называем контейнер с БД yktoo-db
, и это очень важно, поскольку именно это имя используется в конфигурационном файле Yii config/db.php
выше; кроме того, это же имя передаётся Docker-у и при запуске контейнера с HTTP-сервером (опция --link
).
Второй важный момент — это использование опции -v
, подмонтирующей дерево исходных кодов вашего проекта прямо в каталог /var/www/html
Apache-сервера. Это позволяет вносить изменения в проект и сразу же видеть их в работающем приложении без перезапуска контейнеров. Очень удобно.
Остановка контейнеров
Чтобы остановить контейнеры, я использую следующий скрипт:
# stop.sh
set -e
echo "Stopping containers..."
docker stop yktoo-db yktoo-app
echo "Removing containers..."
docker rm yktoo-db yktoo-app
echo "Done."
Он останавливает и сразу удаляет контейнеры, так что в следующий раз при запуске run.sh
вы вновь получите свежую, на 100% идентичную копию окружения, включая базу данных.
Поэтому, если ваша разработка требует внесения изменений в БД, их нужно записать в отдельном SQL-файле (например, upgrade.sql
), после чего скопировать его внутрь БД-контейнера (добавив команду COPY upgrade.sql /db/
в Dockerfile-db
) и вызвать в init.sql
(добавив source /db/upgrade.sql;
после строчки с source /db/database.sql;
).
Бонус: docker-compose
Рулить несколькими контейнерами с помощью вышеприведённых скриптов довольно просто, но можно продвинуться ещё на один шаг в плане удобства. Для управления мультиконтейнерными конфигурациями у Docker есть специальный инструмент под названием docker-compose. Он позволяет описать все взаимосвязанные сервисы в одном файле, чаще всего называемом docker-compose.yml
, после чего управлять всей этой связкой с помощью ещё более простых, чем у Docker, команд.
В Linux docker-compose
необходимо устанавливать отдельно, вместе с Docker Engine он не ставится.
Файл docker-compose.yml
может выглядеть так:
version: "3"
services:
app:
build:
context: .
dockerfile: ./Dockerfile-app
container_name: yktoo-app
ports:
- "80:80"
volumes:
- .:/var/www/html
db:
build:
context: .
dockerfile: ./Dockerfile-db
container_name: yktoo-db
environment:
MYSQL_ROOT_PASSWORD: "root"
В нём пути в context
и volumes
должны указывать на (относительный) путь к корню вашего Yii-проекта. Единожды создав этот файл, можно запускать контейнеры командой:
docker-compose up
При этом консольный вывод обоих контейнеров будет виден прямо в вашей консоли. Останавливаются контейнеры нажатием Ctrl+C, или же командой docker-compose stop
из другой консоли, а удаляются — командой docker-compose rm
. Всё, как видите, просто и логично.
На этом, пожалуй, всё. Надеюсь, мой опыт кому-нибудь пригодится. ■
Комментарии