Ни один из моих блогов на WordPress не установлен в Docker контейнерах, и об этом я жалею с самого момента их создания. Мне хватило всего пары недель, чтобы забыть, как именно настроены сервера, зачем на них включена та или иная фича, и теперь во время каждого апдэйда приходится молиться всем известным богам программирования, потому что если что-то пойдёт не так, я уже без понятия, как это разруливать. Даже просто перенести сайт на новый сервер было бы подвигом.
С Докер контейнерами таких проблем бы не было. Взял Dockerfile или docker-compose.yml, взял volumes с данными, перенёс на другую машину, и всё. Можно было бы даже запустить реплики блогов дома, чтобы на них экспериментировать, проверять апдэйты и надругивать новые фичи.
Но не всё потеряно. Прогрессирующее старпёрство мне уже не позволит просто так вот взять и выкатить Docker в свой «продакшен», но создать локальную докеризированную реплику одного из блогов определённо можно. А там, если всё пойдёт нормально, можно и в большой интернет.
Шаг 0: Задумка
Теоретически, задача не сложная, так как у Вордпресса уже есть свой собственный Докер образ. Даже больше, у них есть и пример docker-compose.yml файла, который и WordPress запустит, и MySQL к нему добавит, так что сразу есть с чего начать:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
version: '3.1' services: wordpress: image: wordpress ports: - 8080:80 environment: WORDPRESS_DB_PASSWORD: example mysql: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: example |
Если к полученной паре контейнеров прикрутить backup сайта, то, по идее, это должно сгодиться в качестве полноценной реплики. Конечно, могут начаться какие-нибудь проблемы, связанные с отсутствием доменного имени на локальной машине, но, думаю, что-нибудь можно придумать. Итак, начнём.
Шаг 1: Делаем бэкап блога
Вот что мне нравится в Вордпрессе, так это как легко с ним сделать полный бэкап. Архивируем www
папку, архивируем дамп базы данных, и всё, гештальт завершён. В качестве подопытной морской свинки я возьму свой более свежий блог — codeblog.dotsandbrackets.com, и без лишних церемоний сделаю ему бэкап веб-контента с tar
и дамп базы данных с mysqldump
и gzip:
1 2 |
$ tar -czf codeblog.dotsandbrackets.com.`date +%Y%m%d-%H%M%S`.tar.gz ~/codeblog.dotsandbrackets.com # codeblog.dotsandbrackets.com.20170808-0233746.tar.gz |
1 2 3 |
$ mysqldump --add-drop-table -u **** -p **** | gzip > codeblog.dotsandbrackets.com.`date +%Y%m%d-%h%M%S`.sql.gz # Enter password: # codeblog.dotsandbrackets.com.2070808-024302.sql.gz |
При помощи scp
копирую получившиеся два архива на локальную машину, и понеслась, родная.
Шаг 2: Подключаем веб-контент к контейнеру
Я полазил в Dockerfile для WordPress образа, и, судя по всему, его авторы будут обновлять настройки (имя базы, логин, и т. п.) в вордпрессовском wp-config.php, если те передать в контейнер с переменными окружения. Удобно. Значит я могу не заморачиваться переносом mysql аккаунтов с «продакшен» машины, а создать новые или даже использовать root (значение по умолчанию).
В том же Dockerfile я заметил, что вордпрессовский контент будет лежать в папке /var/www/html
(это ещё и volume). По идее, можно просто подключить туда мой бэкап, и вебовская часть контента будет восстановлена.
Наконец, моя прогрессирующая интернет паранойя больше не позволяет мне ложить имена и пароли открытым текстом в Docker-файлы, так что я лучше их положу в локальные переменные окружения и буду импортировать уже оттуда:
1 2 3 |
echo "export WP_DB_NAME='****'" >> ~/.profile echo "export WP_DB_USER_PASSWORD='****'" >> ~/.profile source ~/.profile |
Теперь распаковываем бэкап с вэб-контентом, подкручиваем его к docker-compose.yml, задаём там же пустую базу данных, и пробуем запустить (кстати, всё это происходит на маке, но на никсе будет то же самое):
1 2 3 |
$ tar -xzf codeblog/codeblog.dotsandbrackets.com.20170808-023746.tar.gz \ --strip-components=2 # skip /home/%USER% path component # Writes to `pwd`/codeblog.dotsandbrackets.com |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
version: '2' services: wordpress: image: wordpress environment: WORDPRESS_DB_PASSWORD: ${WP_DB_USER_PASSWORD} WORDPRESS_DB_NAME: ${WP_DB_NAME} volumes: - ./codeblog.dotsandbrackets.com:/var/www/html mysql: image: mysql environment: MYSQL_ROOT_PASSWORD: ${WP_DB_USER_PASSWORD} MYSQL_DATABASE: ${WP_DB_NAME} |
1 2 3 |
docker-compose up -d # Give ~20-30 seconds to finish open http://127.0.0.1 |
Ну, Вордпресс определённо запустился. Но как определить, это он «мой» контент использует сейчас, или «свой»? Можно, конечно, влезть в контейнер и посмотреть внутри, но лучше я подключу настоящую базу, и всё сразу станет яснее. Так что останавливаем запущенные контейнеры и вперёд к импорту данных:
1 |
docker-compose down |
Шаг 3: Импортируем данные
Краткий сеанс гугла меня очень порадовал. Оказывается, если примонтировать к mysql контейнеру дамп базы вот к этой папке — /docker-entrypoint-initdb.d/
, то при первом запуске mysql его автоматически импортирует. Он даже архивы понимает, так что можно сдать ему мой дамп как он есть:
1 2 3 4 5 6 7 |
mysql: image: mysql environment: MYSQL_ROOT_PASSWORD: ${WP_DB_USER_PASSWORD} MYSQL_DATABASE: ${WP_DB_NAME} volumes: - ./codeblog.dotsandbrackets.com.20170808-024302.sql.gz:/docker-entrypoint-initdb.d/backup.sql.gz |
Снова запускаем контейнеры:
1 2 3 |
docker-compose up -d # give it some time to finish open http://127.0.0.1 |
Ха! Всё запустилось. Но как-то не до конца. Когда я попытаюсь залогиниться, то меня редиректит на живой codeblog.dotsandbrackets.com. То есть где-то в базе жёстко прописано доменное имя, и его нужно оттуда выковырять.
Шаг 4. Исправляем доменное имя
Ещё один сеанс гугла, и решение найдено. Заменить имя может вот такой скрипт:
1 2 3 4 |
UPDATE wp_options SET option_value = replace(option_value, 'http://www.oldurl', 'http://www.newurl') WHERE option_name = 'home' OR option_name = 'siteurl'; UPDATE wp_posts SET guid = replace(guid, 'http://www.oldurl','http://www.newurl'); UPDATE wp_posts SET post_content = replace(post_content, 'http://www.oldurl', 'http://www.newurl'); UPDATE wp_postmeta SET meta_value = replace(meta_value,'http://www.oldurl','http://www.newurl'); |
mysql’овская папка docker-entrypoint-initdb.d
может принимать больше чем один файл (запускает в алфавитном порядке), так что создам-ка я какой-нибудь migrate.sql
, пропишу в нём новые имена, да подключу к контейнеру:
1 2 3 4 |
UPDATE wp_options SET option_value = replace(option_value, 'http://codeblog.dotsandbrackets.com', 'http://127.0.0.1') WHERE option_name = 'home' OR option_name = 'siteurl'; UPDATE wp_posts SET guid = replace(guid, 'http://codeblog.dotsandbrackets.com','http://127.0.0.1'); UPDATE wp_posts SET post_content = replace(post_content, 'http://codeblog.dotsandbrackets.com', 'http://127.0.0.1'); UPDATE wp_postmeta SET meta_value = replace(meta_value,'http://codeblog.dotsandbrackets.com','http://127.0.0.1'); |
1 2 3 |
volumes: - ./codeblog.dotsandbrackets.com.20170808-024302.sql.gz:/docker-entrypoint-initdb.d/backup.sql.gz - ./migrate.sql:/docker-entrypoint-initdb.d/migrate.sql |
Запускаем:
1 2 3 4 |
docker-compose down docker-compose up # Give it some time open https://127.0.0.1 |
Логин и админка теперь работают. Правда, Jetpack плагин выглядит печальным, так как он сразу почувствовал, что где-то его провели, но бедолагу можно успокоить целительной эвтаназией, применённой через кнопку «ВЫКЛ».
Но теперь нарисовалась более крупная проблема. Непонятно, как я не заметил её раньше. Все ссылки на посты возвращают 404:
Я смутно помню, что в конфигах nginx на сервере что-то действительно было про пермалинки и ссылки на посты, так что нужно снова идти туда и выяснять, что за оно.
Шаг 5. Исправляем ссылки
ОК, проблема на самом деле большая. Вот как я заставлял ссылки работать на nginx в «продакшене»:
1 |
try_files $uri $uri/ /index.php?q=$request_uri&$args; |
Но в контейнером WordPress используется Apache! У него наверняка есть что-нибудь похожее, но я не настолько фанат или спец в Апаче, чтобы с ним ковыряться. Я хочу свой nginx назад!
Очередной сеанс гугла, и снова есть решение. Образ WordPress существует в нескольких вариантах, в том числе и fpm, к которому можно прикручивать собственные сервера. А один хороший человек даже подготовил образ с nginx для этого — rault/nginx-wordpress. Буду пробовать.
Опять же, сначала убиваем старые контейнеры:
1 |
docker-compose down |
Теперь, меняем wordpress
образ на wordpress:fpm
и добавляем новый сервис — nginx:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
version: '2' services: nginx: image: raulr/nginx-wordpress ports: - 80:80 volumes: - ./codeblog.dotsandbrackets.com:/var/www/html wordpress: image: wordpress:fpm environment: WORDPRESS_DB_PASSWORD: ${WP_DB_USER_PASSWORD} WORDPRESS_DB_NAME: ${WP_DB_NAME} volumes: - ./codeblog.dotsandbrackets.com:/var/www/html mysql: image: mysql environment: MYSQL_ROOT_PASSWORD: ${WP_DB_USER_PASSWORD} MYSQL_DATABASE: ${WP_DB_NAME} volumes: - ./codeblog.dotsandbrackets.com.20170808-024302.sql.gz:/docker-entrypoint-initdb.d/backup.sql.gz - ./migrate.sql:/docker-entrypoint-initdb.d/migrate.sql |
Наконец, запускаем контейнера и скрещиваем все доступные пальцы на удачу, потому что разродиться альтернативным решением среди ночи я не смогу. Это просто должно сработать.
1 2 |
docker-compose up -d open http://127.0.0.1 |
JetPack снова поругался и снова отправился в страну вечной охоты. Но в остальном всё вроде работает.
Я прокликал все очевидные ссылки, и всё ОК. Всё в докере, и всё ОК!
Мораль
Перевести существующий WordPress в Docker можно и не особо болезненно. Процесс относительно простой, и только конфигурация сервера меня немного поставила в тупик, но, возможно, это только моя проблема.
Я потрачу ещё какое-то время, тестируя эти контейнера. Измерю потерю в производительности, если такая есть, поищу косяки. Но если всё выйдет нормально, эта штука определённо едет в продакшен.
Как всегда познавательно. Хоть и не пользуюсь вордпрессом, но кое-что почерпнул. Спасибо
почему источник не указан?
Источник указан — посмотрите в address bar браузера
чисто так, ради интересу
> Но если всё выйдет нормально, эта штука определённо едет в продакшен.
получилось?
Неа 🙂 Не то, что не получилось, просто я переключился на другие задачи, а потом очень тесно связался в гугловым облаком, и теперь при моих исходных данных — 2 идентичных блога, каждый на своём хосте — намного проще вместо образов контейнеров работать с образами виртуальных машин целиком. Проще мониторить и бэкапить/восстанавливать, а ценник, изоляция и повторяемость настроек такая же.
Если бы были другие исходные данные — менее понятный мне сервис-провайдер, или несколько приложений на один хост, или очень недружелюбный хост — докер имел бы больше смысла. Но тут от него будет только overhead — когнитивный и управленческий.