Настройка отказоустойчивого управляющего сервера Apache CloudStack с использованием мультимастер-репликации MariaDB Galera

В статье описывается метод развертывания отказоустойчивой конфигурации управляющих серверов Apache CloudStack совместно с мультимастер-кластером MariaDB (Galera).При разработке данной инструкции использовались следующие версии программных продуктов:

  1. Apache CloudStack 4.9.2
  2. CentOS 7
  3. MariaDB 5.5.52 (по-умолчанию в CentOS 7)
  4. Ansible 2.3.1 (по-умолчанию в CentOS 7)

Целевая модель развертывания системы отображена на следующем рисунке.

В рамках данной модели каждый сервер ACS связан с некоторым выбранным сервером MariaDB как с мастером, а остальные серверы назначены как слейвы. В некоторых англоязычных руководствах предлагается использовать промежуточный компонент HAProxy для организации дополнительного слоя соединений к MariaDB таким образом, что каждый сервер ACS может прозрачно переключаться между серверами СУБД при отказе, однако, на наш взгляд, достаточно организации внешнего переключения трафика между серверами ACS, которую можно настроить с помощью Nginx.

Общая схема развертывания

Рассмотрим общую схему процесса развертывания всей системы и исходные допущения.

MariaDB

Для развертывания кофигурации MariaDB будет использоваться плэйбук Ansible, доступный на GitHub. Данный подход позволяет акцентировать внимание статьи именно на том, как установить ACS, но не настройке кластера Galera. Поскольку вышеуказанный плэйбук разработан для использования совместно с CentOS 7, то операционная система выбрана соответствующим образом. В том случае, если читатель планирует выполнить аналогичную настройку на другой совместимой с ACS 4.9.2 операционной системе, общая схема остается такой же.

Apache CloudStack

Как бы странно это не звучало, но установить ACS 4.9.2 на работающий кластер Galera нельзя. Данное ограничение возникает в связи с тем, что сервер управления при установке создает базу данных для версии 4.0, которая преобразуется в базу данных версии 4.9.2 цепочкой миграций. Поскольку в процессе первоначальной установки и миграций используются неподдерживаемые кластерной версией Galera движки баз данных (используемые в младших версиях ACS), то процесс не завершается успешно.

Для преодоления данной проблемы мы будем выполнять установку в несколько этапов:

  1. Установка ACS 4.9.2 на обычный сервер MySQL, снятие дампа баз данных.
  2. Импорт баз данных в кластер Galera.
  3. Подключение серверов управления к кластеру Galera.

На выходе будет получена рабочая топология, отображенная на первом рисунке.

Испытательный стенд

Развертывание будем производить на 4х хостах:

  1. ac — хост CentOS 7 с установленным ansible, с которого мы будем осуществлять конфигурирование оставшихся хостов, и где выполним первоначальное развертывание ACS 4.9.2 без использования Galera;
  2. h1, h2, h3 — хосты CentOS 7, на которых будет установлена кластерная версия ACS 4.9.2 и Galera.

Плэйбук развертывания кластера Galera предполагает, что хосты находятся в защищенной сети (упрощенная настройка файрвола), доступной посредством сетевой карты eth0. Плэйбук предполагает, что служба firewalld используется.

Базовая установка (без HA)

Установим на хост ac базовые компоненты:

# yum install epel-release 
# yum install net-tools git mariadb mariadb-server ansible

Запускаем MariaDB:

# systemctl start mariadb

Проверяем, что MariaDB работает:

# mysql -uroot
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 2
Server version: 5.5.52-MariaDB MariaDB Server

Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]>

Вносим в настройки сервера MariaDB (/etc/my.cnf.d/server.cnf) рекомендуемые ACS опции в секцию [mysql]:

[mysql]
innodb_rollback_on_timeout=1
innodb_lock_wait_timeout=600
max_connections=350
log-bin=mysql-bin
binlog-format = 'ROW'

Перезапускаем MariaDB:

# systemctl restart mariadb

Создадим файл репозитория Yum для Apache CloudStack /etc/yum.repos.d/cloudstack.repo со следующим содержимым:

[cloudstack]
name=cloudstack
baseurl=http://cloudstack.apt-get.eu/centos/7/4.9/
enabled=1
gpgcheck=0

Установим пакет управляющего сервера ACS:

# yum install cloudstack-management

Редактируем файл безопасности Java:

# grep -l '/dev/random' /usr/lib/jvm/java-*/jre/lib/security/java.security /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-3.b12.el7_3.x86_64/jre/lib/security/java.security

где заменяем генератор случайных чисел (требуется для библиотеки шифрования, используемой в ACS):

# sed -i 's#securerandom.source=file:/dev/random#securerandom.source=file:/dev/urandom#' /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-3.b12.el7_3.x86_64/jre/lib/security/java.security

Изменяем политику SELinux на permissive в файле:

# sed -i 's#SELINUX=enforcing#SELINUX=permissive#' /etc/selinux/config

Запускаем установку БД ACS (с доступом к MariaDB cloud/secret) от пользователя root (локальный пароль доступа к СУБД не требуется):

# cloudstack-setup-databases cloud:secret@localhost --deploy-as=root
Mysql user name:cloud                                                           [ OK ]
Mysql user password:******                                                      [ OK ]
Mysql server ip:localhost                                                       [ OK ]
Mysql server port:3306                                                          [ OK ]
Mysql root user name:root                                                       [ OK ]
Mysql root user password:******                                                 [ OK ]
Checking Cloud database files ...                                               [ OK ]
Checking local machine hostname ...                                             [ OK ]
Checking SELinux setup ...                                                      [ OK ]
Detected local IP address as 176.120.25.66, will use as cluster management server node IP[ OK ]
Preparing /etc/cloudstack/management/db.properties                              [ OK ]
Applying /usr/share/cloudstack-management/setup/create-database.sql             [ OK ]
Applying /usr/share/cloudstack-management/setup/create-schema.sql               [ OK ]
Applying /usr/share/cloudstack-management/setup/create-database-premium.sql     [ OK ]
Applying /usr/share/cloudstack-management/setup/create-schema-premium.sql       [ OK ]
Applying /usr/share/cloudstack-management/setup/server-setup.sql                [ OK ]
Applying /usr/share/cloudstack-management/setup/templates.sql                   [ OK ]
Processing encryption ...                                                       [ OK ]
Finalizing setup ...                                                            [ OK ]

CloudStack has successfully initialized database, you can check your database configuration in 
/etc/cloudstack/management/db.properties

Запускаем установку сервера управления:

# cloudstack-setup-management --tomcat7
Starting to configure CloudStack Management Server:
Configure Firewall ...        [OK]
Configure CloudStack Management Server ...[OK]
CloudStack Management Server setup is Done!

Проверяем через несколько секунд доступность сервера (http://ac:8080/client):

# LANG=C wget -O /dev/null http://ac:8080/client 2>&1 | grep '200 OK'
HTTP request sent, awaiting response... 200 OK

Рекомендуем зайти с помощью браузера на URL и убедиться, что сервер работает и позволяет аутентифицироваться с помощью пары admin/password. Если все отрабатывает, значит установка сервера управления успешно произведена.

Теперь сохраним дампы баз данных для дальнейшего импорта на кластер Galera:

# mysqldump -uroot cloud >cloud.sql
# mysqldump -uroot cloud_usage >cloud_usage.sql

Убедимся, что все таблицы имеют формат InnoDB:

# grep ENGINE *.sql | grep -v InnoDB | grep -v -c '*/'
0

Настройка реплицируемой среды Galera

Развертывание кластера Galera будем производить с использованием Ansible. Ansible работает по протоколу SSH, поэтому необходимо на хосты h1, h2, h3 положить публичную часть ключей SSH, для хоста ac.

Сгенерируем ключ SSH на хосте ac:

# ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
30:81:f7:90:f0:4f:e0:d4:8a:74:ca:40:ba:d9:c7:8e root@ac
The key's randomart image is:
+--[ RSA 2048]----+
| .. .o+o         |
| .. o+=o.        |
|.  + ==+.        |
| + .+ .=.        |
|o . o   S        |
|   +             |
|  E .            |
|                 |
|                 |
+-----------------+

Распространим ключ на все хосты:

# ssh-copy-id h1
The authenticity of host 'h1 (X.Y.Z.C)' can\'t be established.
ECDSA key fingerprint is 27:f7:34:23:ea:b4:d2:61:8c:ec:d8:13:c2:9f:8a:ef.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@h1\'s password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'h1'"
and check to make sure that only the key(s) you wanted were added.

Аналогичные действия проделаем для хостов h2 и h3.

Теперь необходимо клонировать Git-репозиторий с плэйбуком Ansible:

# git clone https://github.com/bwsw/mariadb-ansible-galera-cluster.git
Cloning into 'mariadb-ansible-galera-cluster'...
remote: Counting objects: 194, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 194 (delta 0), reused 2 (delta 0), pack-reused 188
Receiving objects: 100% (194/194), 29.91 KiB | 0 bytes/s, done.
Resolving deltas: 100% (68/68), done.

Сменим каталог на mariadb-ansible-galera-cluster:

# cd mariadb-ansible-galera-cluster
# pwd
/root/mariadb-ansible-galera-cluster

Отредактируем файл galera.hosts, где укажим наши серверы h1, h2, h3:

[galera_cluster]
h[1:3] ansible_user=root

Проверим корректность конфигурации:

# ansible -i galera.hosts all -m ping
h3 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
h2 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
h1 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}

Установим необходимые зависимости:

# ansible -i galera.hosts all -m raw -s -a "yum install -y epel-release firewalld ntpd"

Обновим системное время:

# ansible -i galera.hosts all -m raw -s -a "chkconfig ntpd on && service ntpd stop && ntpdate 165.193.126.229 0.ru.pool.ntp.org 1.ru.pool.ntp.org 2.ru.pool.ntp.org 3.ru.pool.ntp.org && service ntpd start"

Внесем необходимые изменения в файл конфигурации ansible (/etc/ansible/ansible.cfg), как описано в README.md к плэйбуку:

[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = ~/.ansible/cache

Приступим к установке кластера Galera согласно README.md плэйбука:

# ansible-playbook -i galera.hosts galera.yml --tags setup

Это займет несколько минут, по итогам выполнения мы получим установленные серверы MariaDB на хостах h1, h2, h3 без сборки кластера. Следующим шагом необходимо запустить оставшиеся шаги плэйбука:

# ansible-playbook -i galera.hosts galera.yml --skip-tags setup

Последний шаг — запуск кластера:

# ansible-playbook -i galera.hosts galera_bootstrap.yml

Проверим статус кластера. Для этого зайдем на хосты h1, h2, h3 и в консоли mysql выполним:

# mysql -uroot
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 6
Server version: 10.1.25-MariaDB MariaDB Server

Copyright (c) 2000, 2017, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> SHOW GLOBAL STATUS LIKE 'wsrep_%';
+------------------------------+------------------------------------------------------------+
| Variable_name                | Value                                                      |
+------------------------------+------------------------------------------------------------+
| wsrep_apply_oooe             | 0.000000                                                   |
| wsrep_apply_oool             | 0.000000                                                   |
| wsrep_apply_window           | 0.000000                                                   |
| wsrep_causal_reads           | 0                                                          |
| wsrep_cert_deps_distance     | 0.000000                                                   |
| wsrep_cert_index_size        | 0                                                          |
| wsrep_cert_interval          | 0.000000                                                   |
| wsrep_cluster_conf_id        | 3                                                          |
| wsrep_cluster_size           | 3                                                          |
| wsrep_cluster_state_uuid     | 230ed410-6b9b-11e7-8aa5-4b041fceb486                       |
| wsrep_cluster_status         | Primary                                                    |
| wsrep_commit_oooe            | 0.000000                                                   |
| wsrep_commit_oool            | 0.000000                                                   |
| wsrep_commit_window          | 0.000000                                                   |
| wsrep_connected              | ON                                                         |
| wsrep_desync_count           | 0                                                          |
| wsrep_evs_delayed            |                                                            |
| wsrep_evs_evict_list         |                                                            |
| wsrep_evs_repl_latency       | 0/0/0/0/0                                                  |
| wsrep_evs_state              | OPERATIONAL                                                |
| wsrep_flow_control_paused    | 0.000000                                                   |
| wsrep_flow_control_paused_ns | 0                                                          |
| wsrep_flow_control_recv      | 0                                                          |
| wsrep_flow_control_sent      | 0                                                          |
| wsrep_gcomm_uuid             | 230d9f4f-6b9b-11e7-ba99-fab39514a7e8                       |
| wsrep_incoming_addresses     | 111.120.25.96:3306,111.120.25.229:3306,111.120.25.152:3306 |
| wsrep_last_committed         | 0                                                          |
| wsrep_local_bf_aborts        | 0                                                          |
| wsrep_local_cached_downto    | 18446744073709551615                                       |
| wsrep_local_cert_failures    | 0                                                          |
| wsrep_local_commits          | 0                                                          |
| wsrep_local_index            | 0                                                          |
| wsrep_local_recv_queue       | 0                                                          |
| wsrep_local_recv_queue_avg   | 0.000000                                                   |
| wsrep_local_recv_queue_max   | 1                                                          |
| wsrep_local_recv_queue_min   | 0                                                          |
| wsrep_local_replays          | 0                                                          |
| wsrep_local_send_queue       | 0                                                          |
| wsrep_local_send_queue_avg   | 0.000000                                                   |
| wsrep_local_send_queue_max   | 1                                                          |
| wsrep_local_send_queue_min   | 0                                                          |
| wsrep_local_state            | 4                                                          |
| wsrep_local_state_comment    | Synced                                                     |
| wsrep_local_state_uuid       | 230ed410-6b9b-11e7-8aa5-4b041fceb486                       |
| wsrep_protocol_version       | 7                                                          |
| wsrep_provider_name          | Galera                                                     |
| wsrep_provider_vendor        | Codership Oy <info@codership.com>                          |
| wsrep_provider_version       | 25.3.20(r3703)                                             |
| wsrep_ready                  | ON                                                         |
| wsrep_received               | 10                                                         |
| wsrep_received_bytes         | 769                                                        |
| wsrep_repl_data_bytes        | 0                                                          |
| wsrep_repl_keys              | 0                                                          |
| wsrep_repl_keys_bytes        | 0                                                          |
| wsrep_repl_other_bytes       | 0                                                          |
| wsrep_replicated             | 0                                                          |
| wsrep_replicated_bytes       | 0                                                          |
| wsrep_thread_count           | 2                                                          |
+------------------------------+------------------------------------------------------------+
58 rows in set (0.01 sec)

MariaDB [(none)]> 

Обратите внимание на поле wsrep_incoming_addresses, там должны быть IP-адреса трех наших серверов h1, h2, h3.

Установка серверов управления для реплицируемой среды Galera

Следующий шаг — импорт дампов cloud.sql и cloud_usage.sql в кластер.

Копируем дампы:

# scp ../*.sql h1:
cloud.sql                                     100% 1020KB   1.0MB/s   00:00
cloud_usage.sql                               100%   33KB  32.7KB/s   00:00

Откроем SSH-соединение на хост h1, где выполним все операции по импорту баз данных:

# ssh h1

Создадим базы данных, выдадим привилегии пользователю и импортируем дампы:

[root@h1 ~]# echo "CREATE DATABASE cloud;" | mysql -uroot
[root@h1 ~]# echo "CREATE DATABASE cloud_usage;" | mysql -uroot
[root@h1 ~]# echo "GRANT ALL PRIVILEGES ON cloud.* TO  cloud@'localhost' identified by 'secret'" | mysql -uroot
[root@h1 ~]# echo "GRANT ALL PRIVILEGES ON cloud_usage.* TO  cloud@'localhost' identified by 'secret'" | mysql -uroot
[root@h1 ~]# echo "GRANT ALL PRIVILEGES ON cloud.* TO  cloud@'h1' identified by 'secret'" | mysql -uroot
[root@h1 ~]# echo "GRANT ALL PRIVILEGES ON cloud_usage.* TO  cloud@'h1' identified by 'secret'" | mysql -uroot
[root@h1 ~]# echo "GRANT ALL PRIVILEGES ON cloud.* TO  cloud@'h2' identified by 'secret'" | mysql -uroot
[root@h1 ~]# echo "GRANT ALL PRIVILEGES ON cloud_usage.* TO  cloud@'h2' identified by 'secret'" | mysql -uroot
[root@h1 ~]# echo "GRANT ALL PRIVILEGES ON cloud.* TO  cloud@'h3' identified by 'secret'" | mysql -uroot
[root@h1 ~]# echo "GRANT ALL PRIVILEGES ON cloud_usage.* TO  cloud@'h3' identified by 'secret'" | mysql -uroot
[root@h1 ~]# cat cloud.sql | mysql -uroot cloud
[root@h1 ~]# cat cloud_usage.sql | mysql -uroot cloud_usage

Стоит убедиться в том, что наши базы cloud, cloud_usage реплицируются на серверы h2 и h3.

Теперь будем устанавливать управляющие серверы ACS. Сначала с хоста ac скопируем настройки репозитория ACS:

# ansible -i galera.hosts all -m copy -a "src=/etc/yum.repos.d/cloudstack.repo dest=/etc/yum.repos.d/cloudstack.repo"

Затем установим пакеты управляющего сервера Apache CloudStack:

# ansible -i galera.hosts all -m raw -s -a "yum install -y cloudstack-management"

Изменим уже известную нам настройку безопасности Java:

# [root@ac mariadb-ansible-galera-cluster]# ansible -i galera.hosts all -m raw -s -a "sed -i 's#securerandom.source=file:/dev/random#securerandom.source=file:/dev/urandom#' /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-3.b12.el7_3.x86_64/jre/lib/security/java.security"

Выполним настройку подключения управляющего сервера ACS к СУБД на локальном хосте:

# ansible -i galera.hosts all -m raw -s -a "cloudstack-setup-databases cloud:secret@h1"

Теперь все серверы будут использовать хост h1 в качестве основного сервера СУБД. Далее, мы выполним дополнительную настройку вспомогательных серверов.

Установим пакет высокодоступного подключения Apache CloudStack к MySQL (по непонятной причине в CentOS 7 отсутствует):

# ansible -i galera.hosts all -m raw -s -a "rpm -i http://packages.shapeblue.com.s3-eu-west-1.amazonaws.com/cloudstack/upstream/centos7/4.9/cloudstack-mysql-ha-4.9.2.0-shapeblue0.el7.centos.x86_64.rpm"

Запускаем управляющие серверы:

# ansible -i galera.hosts all -m raw -s -a "cloudstack-setup-management --tomcat7"

Убедимся, что все запущено и работает:

# ansible -i galera.hosts all -m raw -s -a "ps xa | grep java"

Теперь, на адресах http://h1:8080/client, http://h2:8080/client, http://h3:8080/client выполняются отказоустойчивые управляющие серверы, которые можно “закрыть” nginx для обеспечения отказоустойчивости. При этом в журнале /var/log/cloudstack/management/management-server.log можно увидеть записи о том, что серверы знают друг о друге:

# cat /var/log/cloudstack/management/management-server.log | grep 'management node'
2017-07-18 17:05:27,209 DEBUG [c.c.c.ClusterManagerImpl] (Cluster-Heartbeat-1:ctx-b1d64593) (logid:18f29c84) Detected 
management node joined, id:7, nodeIP:111.120.25.96
2017-07-18 17:05:27,231 DEBUG [c.c.c.ClusterManagerImpl] (Cluster-Heartbeat-1:ctx-b1d64593) (logid:18f29c84) Detected 
management node joined, id:12, nodeIP:111.120.25.152
2017-07-18 17:05:27,231 DEBUG [c.c.c.ClusterManagerImpl] (Cluster-Heartbeat-1:ctx-b1d64593) (logid:18f29c84) Detected 
management node joined, id:17, nodeIP:111.120.25.229
2017-07-18 17:05:33,195 DEBUG [c.c.c.ClusterManagerImpl] (Cluster-Heartbeat-1:ctx-76484aea) (logid:b7416e7b) Detected 
management node left and rejoined quickly, id:7, nodeIP:111.120.25.96
2017-07-18 17:07:22,582 INFO  [c.c.c.ClusterManagerImpl] (localhost-startStop-1:null) (logid:) Detected that another 
management node with the same IP 111.120.25.229 is considered as running in DB, however it is not pingable, we will 
continue cluster initialization with this management server node
2017-07-18 17:07:33,292 DEBUG [c.c.c.ClusterManagerImpl] (Cluster-Heartbeat-1:ctx-8880f35d) (logid:037a1c4d) Detected 
management node joined, id:7, nodeIP:111.120.25.96
2017-07-18 17:07:33,312 DEBUG [c.c.c.ClusterManagerImpl] (Cluster-Heartbeat-1:ctx-8880f35d) (logid:037a1c4d) Detected 
management node joined, id:12, nodeIP:111.120.25.152
2017-07-18 17:07:33,312 DEBUG [c.c.c.ClusterManagerImpl] (Cluster-Heartbeat-1:ctx-8880f35d) (logid:037a1c4d) Detected 
management node joined, id:17, nodeIP:111.120.25.229
2017-07-18 17:12:41,927 DEBUG [c.c.c.ClusterManagerImpl] (Cluster-Heartbeat-1:ctx-7e3e2d82) (logid:e63ffa72) Detected 
management node joined, id:7, nodeIP:111.120.25.96
2017-07-18 17:12:41,935 DEBUG [c.c.c.ClusterManagerImpl] (Cluster-Heartbeat-1:ctx-7e3e2d82) (logid:e63ffa72) Detected 
management node joined, id:12, nodeIP:111.120.25.152
2017-07-18 17:12:41,935 DEBUG [c.c.c.ClusterManagerImpl] (Cluster-Heartbeat-1:ctx-7e3e2d82) (logid:e63ffa72) Detected 
management node joined, id:17, nodeIP:111.120.25.229

В конце настройки выполним конфигурирование управляющих серверов ACS для использования дополнительных серверов Galera (h2, h3) в качестве слейв-серверов и перезапустим управляющие серверы ACS.

# ansible -i galera.hosts all -m raw -s -a "service cloudstack-management stop"
# ansible -i galera.hosts all -m raw -s -a "sed -i 's#db.ha.enabled=false#db.ha.enabled=true#' /etc/cloudstack/management/db.properties"
# ansible -i galera.hosts all -m raw -s -a "sed -i 's#db.cloud.slaves=.*#db.cloud.slaves=h2,h3#' /etc/cloudstack/management/db.properties"
# ansible -i galera.hosts all -m raw -s -a "sed -i 's#db.usage.slaves=.*#db.usage.slaves=h2,h3#' /etc/cloudstack/management/db.properties"
# ansible -i galera.hosts all -m raw -s -a "service cloudstack-management start"

Заключение

Настройка достаточно простая и не должна вызвать каких-либо сложностей. Несколько расстраивает, что невозможно выполнить установку на кластер Galera сразу, без выполнения процедуры переноса из нереплицируемой среды в реплицируемую после установки.