WordPress: blue-green деплой и слияние баз MySQL с помощью dbForge Studio

Автор: | 09/23/2016
 

wordpressИмеется WordPress в Azure App Service как WebApp.

Приложение развёрнуто на Dev окружении, задача — задеплоить его на Stage, который является swap-слотом для Prod, переместить Stage на Prod, после чего — внести изменения на Dev, которые затронут базу — и внести эти изменения в базу Stage, что бы их можно было выкатить на Prod.

Используем dbForge Studio for MySQL. Есть Trial, и требуется полная Professional версия, т.к. Express не имеет необходимых возможностей (сравнение и объединение схем и данных в базах MySQL).

Подготовка — деплой кода

Код приложения деплоится через Travis, с помощью скрипта, через Git-деплой, аналог вот такого>>> решения. Версия для Dev окружения — в develop бранче, для Prod — в бранче release.

Файл .travis.yml выглядит так:

$ cat .travis.yml 
language: php
install:
  - true
branches:
  only:
    - develop
    - release
before_script:
  - git config --global user.email "user@domain.com"
  - git config --global user.name "Travis-CI"
script:
  - if [ $TRAVIS_BRANCH = "develop" ]; then ./ci/deploy.sh jm-wp-dev; fi
  - if [ $TRAVIS_BRANCH = "release" ]; then ./ci/deploy.sh jm-wp-prod-staging jm-wp-prod; fi

Содержимое репозитория:

$ ls -l
total 204
drwxrwxr-x  2 setevoy setevoy  4096 вер 22 17:02 ci
-rw-rw-r--  1 setevoy setevoy   693 вер 22 17:03 getdatabase.php
-rw-rw-r--  1 setevoy setevoy   418 вер 22 17:02 index.php
-rw-rw-r--  1 setevoy setevoy   169 вер 22 17:03 info.php
-rw-rw-r--  1 setevoy setevoy 19550 вер 22 17:03 license.txt
-rw-rw-r--  1 setevoy setevoy  7344 вер 22 17:03 readme.html
-rw-rw-r--  1 setevoy setevoy   692 вер 22 17:03 web.config
...
-rw-rw-r--  1 setevoy setevoy 29890 вер 22 17:03 wp-signup.php
-rw-rw-r--  1 setevoy setevoy  4035 вер 22 17:02 wp-trackback.php
-rw-rw-r--  1 setevoy setevoy  3064 вер 22 17:03 xmlrpc.php

Текущий бранч:

$ git branch
* develop

Готовим мердж.

Обновляем бранчи:

$ git pull
Already up-to-date.
$ git checkout release && git pull
Switched to branch 'release'
Your branch is up-to-date with 'origin/release'.
Already up-to-date.

Мерджим develop в release:

$ git merge develop
Auto-merging wp-config.php
CONFLICT (add/add): Merge conflict in wp-config.php
Automatic merge failed; fix conflicts and then commit the result.

Решаем конфликт (см. тут>>>):

huber_release_1

Первым идут старые настройки подключения, до попыток использования MySQL In App, ниже — новые настройки.

Коммитим:

$ git add wp-config.php && git commit -m "Old connections"
[release 10166a4] Old connections

Пушим:

$ git push
Counting objects: 7960, done.
Delta compression using up to 4 threads.
...

Ждём билда:

huber_release_2

Тем временем — обновляем параметры подключения.

Раньше для Prod и Stage использовались базы ClearDB, которые показали свою несостоятельность для production-решений (раз>>>, два>>>), поэтому был насетаплен свой бокс с MySQL.

Переходим в WebApp > Application Settins > Connections Strings и обновляем строку:

wtf_connections_3

Устанавливаем её в новое значение:

Database=jm_huber_stage;Data Source=13.**.***.102;User Id=jm_huber_stage;Password=password

И ставим галочку Slot Setting (для Prod будет своя Connection String)

wtf_connections_4

Так как Azure любит удалять Connection Strings из настроек — можно добавить скрипт (только для WordPress на Azure из-за использования переменной %MYSQLCONNSTR_localdb% для получения данных для подключения)  для проверки — какая база данных используется в настоящий момент:

<?php

$connectstr_dbhost = '';
$connectstr_dbname = '';
$connectstr_dbusername = '';
$connectstr_dbpassword = '';

foreach ($_SERVER as $key => $value) {
    if (strpos($key, "MYSQLCONNSTR_") !== 0) {
        continue;
    }

    $connectstr_dbhost = preg_replace("/^.*Data Source=(.+?);.*$/", "\\1", $value);
    $connectstr_dbname = preg_replace("/^.*Database=(.+?);.*$/", "\\1", $value);
    $connectstr_dbusername = preg_replace("/^.*User Id=(.+?);.*$/", "\\1", $value);
    $connectstr_dbpassword = preg_replace("/^.*Password=(.+?)$/", "\\1", $value);
}

print("DB host: $connectstr_dbhost\n");
print("DB database: $connectstr_dbname\n");
print("DB user: $connectstr_dbusername\n");

?>

Проверяем:

$ curl http://jm-wp-prod-staging.azurewebsites.net/getdatabase.php
DB host: 13.***.***.102
DB database: jm_huber_stage
DB user: jm_huber_stage

Повторяем для Prod.

Переходим к базам.

Миграция баз с помощью dbForge

Дальше начинается жесткая «мануальщина», но это лучшее, что удалось придумать за имевшееся время.

Если у кого-то есть опыт в решении подобных задач (деплой с апдейтами баз) — прошу в комменты.

Проверяем текущее состояние Stage базы:

mysql> use jm_huber_stage;
Database changed
mysql> show tables;
Empty set (0.00 sec)

Качаем и устанавливаем dbForge (русс. тут>>>). Увы — только Windows версия, придётся через RDP (под wine не завелось).

Запускаем:

huber_release_3

Создаём два подключения — к Dev и Stage базам:

huber_release_4

huber_release_5

Первым — надо скопировать схему таблиц из Dev базы в Stage базу, т.к. Stage и Prod базы — пустые.

Выделяем оба подключения, правой кнопкой — New Schema Comparison:

huber_release_6

В выпадающих списках Connection — выбираем «откуда» (Source, слева) и «куда» (Target, справа).

Выбираем из Devв Stage:

huber_release_8

Жмём Compare.

dbForge выведет таблицу сравнения схем обеих баз, и запросы, которые будут выполнены при синхронизации:

 

huber_release_9

Если всё ОК — сверху жмём зеленую стрелку «Synchronize objects to target databse«, выбираем «Execute script directly…» и внизу жмём кнопку Synchronize:

huber_release_42

Ещё раз выполняем сравнение — базы одинаковы:

huber_release_11

Проверяем:

mysql> show tables;
+-------------------------------+
| Tables_in_jm_huber_stage      |
+-------------------------------+
| wp_bro_images                 |
| wp_cf7dbplugin_st             |
| wp_cf7dbplugin_submits        |
| wp_commentmeta                |
| wp_comments                   |
| wp_ctimelines                 |
| wp_layerslider                |
| wp_links                      |
| wp_options                    |
| wp_postmeta                   |
| wp_posts                      |
| wp_revslider_css              |
| wp_revslider_layer_animations |
| wp_revslider_navigations      |
| wp_revslider_sliders          |
| wp_revslider_slides           |
| wp_revslider_static_slides    |
| wp_term_relationships         |
| wp_term_taxonomy              |
| wp_termmeta                   |
| wp_terms                      |
| wp_usermeta                   |
| wp_users                      |
+-------------------------------+

Синхронизация данных в таблицах баз MySQL

Данных в Stage базе сейчас нет:

mysql> select * from wp_users;
Empty set (0.00 sec)

Возвращаемся к dbForge, выбираем обе базы — и New Data Comparison:

huber_release_12

Не забываем поменять местами Dev и Stage базы:

huber_release_13

Тут результаты куда интереснее:

huber_release_14

wp-options и пару других таблиц мы потом обновим руками, для URL-ов, в остальном же — сейчас можно синхронизировать всё сразу:

huber_release_15

Снова жмём Большую Зелёную Кнопку:

huber_release_16

Готово:

huber_release_17

Проверяем:

mysql> select * from wp_users;
+----+---------------+------------------------------------+---------------+---------------------------------+----------+---------------------+-----------------------------------------------+-------------+---------------+
| ID | user_login    | user_pass                          | user_nicename | user_email                      | user_url | user_registered     | user_activation_key                           | user_status | display_name  |
+----+---------------+------------------------------------+---------------+---------------------------------+----------+---------------------+-----------------------------------------------+-------------+---------------+
|  1 | useradmin     | $P$B2s9Hnxe.***        .           |   useradmin   | mail@domain.tld                 |          | 2016-06-27 11:45:24 |                                               |           0 | useradmin     |
...
+----+---------------+------------------------------------+---------------+---------------------------------+----------+---------------------+-----------------------------------------------+-------------+---------------+

Руками — обновляем URL-ы в базе — в будущем эти поля синхронизироваться не будут:

mysql> UPDATE wp_options SET option_value = replace(option_value, 'http://jm-wp-dev.azurewebsites.net', 'http://jm-wp-prod-staging.azurewebsites.net') WHERE option_name = 'home' OR option_name = 'siteurl';
...
mysql> UPDATE wp_posts SET guid = replace(guid, 'http://jm-wp-dev.azurewebsites.net','http://jm-wp-prod-staging.azurewebsites.net');
Query OK, 402 rows affected (0.01 sec)
...
mysql> UPDATE wp_posts SET post_content = replace(post_content, 'http://jm-wp-dev.azurewebsites.net', 'http://jm-wp-prod-staging.azurewebsites.net');
Query OK, 5 rows affected (0.01 sec)
...
mysql> UPDATE wp_postmeta SET meta_value = replace(meta_value,'http://jm-wp-dev.azurewebsites.net','http://jm-wp-prod-staging.azurewebsites.net');
Query OK, 9 rows affected (0.02 sec)
...

Проверяем:

huber_release_18

Следующий шаг — свапнуть (про swap-слоты — см. тут>>>) Stage на Prod.

Для Prod — задаём его Connection Strings:

huber_release_19

Не забываем про галочку Slot Setting, жмём Save, свапаем слоты:

huber_release_21

Возвращаемся к dbForge, добавляем Prod базу (правой кнопкой на Stage соединениии — Dubplicate connection, потом Modify):

huber_release_22

И повторяем то же, что делали с Dev/Stage — сначала копируем схему базы, потом — данные, только сейчас выбираем Stage и Prod базы (хотя на данный момент — без разницы, в качестве Source можно использовать и Dev базу, но в будущем это будет играть роль):

 

huber_release_23

 

huber_release_24

Повторяем для данных:

huber_release_25

Сейчас не выбираем никакие таблицы для синхронизации, т.к. базы новые.

Далее — сделаем апдейт на Dev — и задеплоим его на Stage.

huber_release_26

Повторяем обновление URL для Prod:

mysql> use jm_huber_prod;
Database changed
mysql> UPDATE wp_options SET option_value = replace(option_value, 'http://jm-wp-prod-staging.azurewebsites.net', 'http://jm-wp-prod.azurewebsites.net') WHERE option_name = 'home' OR option_name = 'siteurl';
...
mysql> UPDATE wp_posts SET guid = replace(guid, 'http://jm-wp-prod-staging.azurewebsites.net','http://jm-wp-prod.azurewebsites.net');
Query OK, 402 rows affected (0.01 sec)
...
mysql> UPDATE wp_posts SET post_content = replace(post_content, 'http://jm-wp-prod-staging.azurewebsites.net', 'http://jm-wp-prod.azurewebsites.net');
...
mysql> UPDATE wp_postmeta SET meta_value = replace(meta_value,'http://jm-wp-prod-staging.azurewebsites.net','http://jm-wp-prod.azurewebsites.net');
Query OK, 9 rows affected (0.01 sec)
...

Проверяем:

huber_release_27

Отлично.

А теперь — смоделируем деплой.

На Dev — добаляем пост:

huber_release_32

Вовращаемся к dbForge, и выполняем сравнение данных между Dev и Stage (сначала деплоим на стейдж, потом свапаем на прод):

huber_release_29

Теперь картина уже куда интереснее:

huber_release_34

Во-первых — обращаем внимание на таблицу wp_options и её поля siteurl и home — они должны различаться, т.к. URL-лы разные:

huber_release_35

Снимаем с wp_options галочку для синхронизации.

Во-вторых — в wp_posts всё посты различаются. По той же причине:

huber_release_36

Снимаем отметку с Different records.

Нас интересует пост с ID 3992, во влакдке Only in Source:

huber_release_37

Оставляем отметку только для него:

huber_release_38

Жмём Next:

huber_release_39

huber_release_40

Передеплоиваем при необходимости код (Stage сейчас — это бывший Prod, на котором старый код голого WordPress).

Синхронизируем, проверяем:

huber_release_41

Готово.