H.2. pg_cluster#

H.2. pg_cluster

H.2. pg_cluster

Этот раздел описывает, как развернуть pg_cluster с использованием автоматизированных инструментов на базе ansible. В следующем тексте будут представлены примеры команд, которые необходимо ввести в терминале для подготовки SSH-сессии, проверки правильности настроек ansible и запуска playbook. В качестве примера пользователя будет использоваться учетная запись admin_user. При запуске команд в цикле клиента этот пользователь должен быть изменен на учетную запись, которая имеет доступ по SSH без пароля ко всем серверам (виртуальным машинам), указанным в файле my_inventory, а также доступ в привилегированный режим (root). В результате работы playbook на серверах, указанных в файле my_inventory, будет развернут кластер Tantor Certified, управляемый через patroni.

H.2.1. О pg_cluster

Версия: 1.5

GitHub

H.2.2. Архитектура

Диаграмма H.1. Архитектура

Architecture

H.2.3. Требования

Плейбук требует установки компонентов следующих версий:

  • Ansible >= 2.9.10

  • Python3 (с модулем pip) >= 3.10.0

  • psycopg2 >= 2.5.1 (рекомендуется устанавливать через pip)

  • packaging >= 24 (рекомендуется устанавливать через pip)

H.2.4. Использование (на основе ОС Astra Linux 1.7)

  1. Создайте пользователя admin_user (выполняется на каждом узле из файла inventory):

    sudo adduser admin_user
    
  2. Предоставьте пользователю admin_user возможность входить в привилегированный режим (root) без ввода пароля (выполняется на каждом узле из файла инвентаризации).

    cat >> /etc/sudoers <<EOF
    admin_user ALL=(ALL)       NOPASSWD: ALL
    EOF
    
  3. Проверьте наличие в системе сертифицированных репозиториев.

  4. Примонтируйте переданный ISO-образ в каталог /mnt/iso (выполняется на каждом узле из файла inventory и на узле, с которого будет запущен ansible-playbook).

  5. Подключите репозиторий из примонтированного ISO-образа в систему (выполняется на каждом узле из файла inventory и на узле, с которого будет запущен ansible-playbook).

    echo 'deb [trusted=yes] file:///mnt/iso/repo 1.8_x86-64 main' | && \
    sudo tee /etc/apt/sources.list.d/tantorlabs.list
    
  6. Установите ansible (выполняется на узле, с которого будет запущен ansible-playbook).

    sudo apt install ansible-tantor-all
    
  7. Скопируйте файлы контрольного примера из примонтированного ISO-образа в каталог /opt/pg_cluster (выполняется на узле, с которого будет запущен ansible-playbook):

    sudo cp -r /mnt/iso/pg_cluster /opt
    cd /opt/pg_cluster/
    sudo chmod +w /opt/pg_cluster/inventory/my_inventory
    sudo chmod +w /opt/pg_cluster/inventory/group_vars/keepalived.yml
    
  8. Заполните файл конфигурации inventory /opt/pg_cluster/inventory/my_inventory, содержащий список хостов и их IP-адресов (выполняется на узле, с которого будет запущен ansible-playbook).

  9. В файле inventory/group_vars/keepalived.yml измените значение переменной cluster_vip_1 на IP, который будет использоваться keepalived для выделенного виртуального адреса — единой точки входа в кластер СУБД (выполняется на узле, с которого будет запущен ansible-playbook):

    sed -i \
      's/cluster_vip_1: "xxx\.xxx\.xxx\.xxx"/cluster_vip_1: "<cluster IP address>"/' \
      /opt/pg_cluster/inventory/group_vars/keepalived.yml
    
  10. Сгенерируйте SSH-ключи и загрузите их на узлы кластера из файла конфигурации inventory (выполняется на узле, с которого будет запущен ansible-playbook):

    ssh-keygen -t rsa -b 4096
    ssh-copy-id admin_user@<IP address of the first node>
    ssh-copy-id admin_user@<IP address of the second node>
    ssh-copy-id admin_user@<IP address of the third node>
    
  11. Проверьте SSH-соединение от имени admin_user (выполняется на узле, с которого будет запущен ansible-playbook):

    ssh admin_user@<IP address of the first node>
    ssh admin_user@<IP address of the second node>
    ssh admin_user@<IP address of the third node>
    
  12. Убедитесь, что все серверы доступны для подключения по SSH с указанием требуемого пользователя:

    /opt/tantor/usr/bin/ansible all -i inventory/my_inventory \
      -m ansible.builtin.setup -a "filter=ansible_hostname" -u admin_user
    

    Результатом выполнения вышеуказанной команды будет ответ от каждого из доступных серверов в следующем формате::

    <hostname_from_inventory_file> | SUCCESS => {
        "ansible_facts": {
            "ansible_hostname": "<device_hostname>",
            "discovered_interpreter_python": "/usr/bin/<host_python_version>"
        },
        "changed": false
    }
    

    Этот вывод для каждого сервера, описанного в файле my_inventory, означает успешное подключение к нему через SSH. Если в результате ответа с какого-либо сервера сообщение отличается от приведенного выше шаблона - проверьте, возможно ли подключение к нему с использованием ключа от имени пользователя, переданного с использованием флага -u. Если необходимо подключаться только с вводом пароля (без использования ключей) - необходимо добавить флаги -kK к запуску команды и ввести пароль для подключения по SSH (флаг -k) и для перехода пользователя в привилегированный режим (root) (флаг -K).

    Обратите внимание на значение переменной ansible_hostname в выводе команды. Если значение localhost или localhost.localdomain, проверьте файл /etc/hosts на машинах с некорректным выводом. Убедитесь, что реальное имя хоста устройства установлено перед localhost в строке, содержащей 127.0.0.1.

  13. Запустите ansible-playbook для установки базы данных TantorDB (выполняется на узле, с которого будет запущен ansible-playbook):

    /opt/tantor/usr/bin/ansible-playbook -i inventory/my_inventory \ 
      -u admin_user -e "postgresql_vendor=tantordb edition=certified major_version=15" pg-cluster.yaml
    

H.2.5. Функции запуска

Плейбук позволяет разделять каталоги pg_data, pg_wal и pg_log. Если необходимо разместить WAL логи в отдельной папке, необходимо внести изменения в файл inventory/groupvars/patroni.yml:

  • удалите комментарий для переменной patroni_pg_wal_dir и укажите каталог для размещения в нем WAL логов;

  • для переменной patroni_bootstrap_initdb добавьте параметр waldir и проверьте, что он ссылается на переменную patroni_pg_wal_dir;

  • для выбранного метода создания реплики (по умолчанию patroni_pg_basebackup) добавьте параметр waldir со значением bulk_wal_dir;

В случае необходимости размещения LOGов: удалите комментарий для переменной patron_pg_pg_log_dir и укажите в ней каталог для размещения журналов LOG;

H.2.6. Запуск плейбука

Одна из задач плейбука выполняется на том же узле, с которого запускается ansible (управляющий сервер). Если пользователь, под которым запускается ansible, не имеет доступа к режиму root без пароля на этом сервере, необходимо добавить флаг -K к команде запуска и ввести пароль.

По умолчанию плейбук не пытается подключиться к репозиториям Tantor и требует наличия в системе следующих пакетов:

  • etcd-tantor-all

  • python3-tantor-all

  • patroni-tantor-all

  • pg_configurator-tantor-all

  • haproxy-тантор-все

  • keepalived-tantor-all

  • pgbouncer-tantor-all

  • wal-g-tantor-all

  • СУБД tantor

Обратите внимание на последний пункт из приведенного выше списка. Пакет Tantor должен соответствовать среде, используемой во время запуска плейбука. Например, если вы хотите установить tantor-certified-server-15 СУБД с помощью команды ansible-playbook -i inventory/my_inventory -u admin_user -e "postgresql_vendor=tantordb edition=certified major_version=15" pg-cluster.yaml -K, убедитесь, что пакет tantor-certified-server-15 доступен в вашем локальном репозитории.

H.2.7. Примеры использования

Ниже вы можете найти некоторые общие команды для работы с программными продуктами, включенными в решение pg_cluster. Обратите внимание, что команды и их результаты могут отличаться в зависимости от используемых версий программного обеспечения.

H.2.7.1. Работа с etcd:

# on NODE_1
e_host=(
  /opt/tantor/usr/bin/etcdctl
  --endpoints=https://<HOST_1_IP>:2379,https://<HOST_2_IP>:2379,https://<HOST_N_IP>:2379
  --cacert=/opt/tantor/etc/patroni/ca.pem
  --cert=/opt/tantor/etc/patroni/<NODE1_HOSTNAME>.pem  
  --key=/opt/tantor/etc/patroni/<NODE1_HOSTNAME>-key.pem
)

# list etcd members
ETCDCTL_API=3 "${e_host[@]}" member list --debug

# check version
ETCDCTL_API=3  "${e_host[@]}" version

# get key value ("main" is "patroni_scope")
ETCDCTL_API=3  "${e_host[@]}" get /service/main/config

# cleanup patroni cluster configuration
ETCDCTL_API=3  "${e_host[@]}" del /service/main --prefix

H.2.7.2. Ручное создание пользователя:

# create user
su - postgres -c "psql -A -t -d postgres -c \"CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'repuserpasswd'\""
# check user
su - postgres -c "psql -A -t -d postgres -c \"select * from pg_roles where rolname = 'replicator'\""

H.2.7.3. Управление кластером Patroni

Patroni включает команду под названием patronictl, которая может использоваться для управления кластером. Давайте проверим статус кластера:

root@node1:~# patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml list
+ Cluster: main (7351350415269982209) --+---------+-----------+----+-----------+
| Member  | Host            | Role    | State     | TL | Lag in MB |
+---------+-----------------+---------+-----------+----+-----------+
| node1   | xxx.xxx.xxx.xxx | Leader  | running   |  1 |           |
| node2   | yyy.yyy.yyy.yyy | Replica | streaming |  1 |         0 |
| node3   | zzz.zzz.zzz.zzz | Replica | streaming |  1 |         0 |
+---------+-----------------+---------+-----------+----+-----------+

patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml edit-config следует использовать только для управления глобальной конфигурацией кластера. Он не должен содержать никаких настроек, специфичных для узлов, таких как connect_address, listen, data_dir и так далее.

Обновить настройки DCS pg_hba:

cat > pg_hba.conf << EOL
host replication replicator 0.0.0.0/0 md5
local all all  trust
host all all 127.0.0.1/32 trust
host all all localhost trust
EOL

cat pg_hba.conf | jq -R -s 'split("\n") | .[0:-1] | {"postgresql": {"pg_hba": .}}' | \
patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml edit-config --apply - --force main

patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml show-config

Изменить настройки postgresql.conf:

cat > postgresql.conf << EOL
"postgresql": {
"parameters": {
    "max_connections" : 101
}
}
EOL

cat postgresql.conf | patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml edit-config --apply - --force main
patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml list
patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml restart main

Сделать переключение switchover:

root@node1:~# patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml switchover
Current cluster topology
+ Cluster: main (7351350415269982209) --+---------+-----------+----+-----------+
| Member| Host            | Role    | State     | TL | Lag in MB |
+-------+-----------------+---------+-----------+----+-----------+
| node1 | xxx.xxx.xxx.xxx | Leader  | running   |  1 |           |
| node2 | yyy.yyy.yyy.yyy | Replica | streaming |  1 |         0 |
| node3 | zzz.zzz.zzz.zzz | Replica | streaming |  1 |         0 |
+-------+-----------------+---------+-----------+----+-----------+
Primary [node1]:
Candidate ['node2', 'node3'] []: node2
When should the switchover take place (e.g. 2024-04-02T13:51 )  [now]:
Are you sure you want to switchover cluster main, demoting current leader node1? [y/N]: y
2024-04-02 12:51:28.04774 Successfully switched over to "node2"
+ Cluster: main (7351350415269982209) --+---------+-----------+----+-----------+
| Member| Host            | Role    | State     | TL | Lag in MB |
+-------+-----------------+---------+-----------+----+-----------+
| node1 | xxx.xxx.xxx.xxx | Leader  | streaming |  2 |           |
| node2 | yyy.yyy.yyy.yyy | Replica | running   |  2 |         0 |
| node3 | zzz.zzz.zzz.zzz | Replica | streaming |  2 |         0 |
+-------+-----------------+---------+-----------+----+-----------+

Переключиться в асинхронный режим (режим по умолчанию):

cat > postgresql.conf << EOL
"postgresql": {
"parameters": {
    "synchronous_commit" : "local"
}
}
"synchronous_mode": false
"synchronous_mode_strict": false
EOL
cat postgresql.conf | patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml edit-config --apply - --force main

Переключиться в синхронный режим:

cat > postgresql.conf << EOL
"postgresql": {
"parameters": {
    "synchronous_commit" : "remote_apply"
}
}
"synchronous_mode": true
"synchronous_mode_strict": true
EOL
cat postgresql.conf | patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml edit-config --apply - --force main

Диаграмма H.2. synchronous_commit

synchronous_commit

Повторная инициализация неудавшегося узла, в случае если вывод patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml list предоставляет информацию о неудачном состоянии узла:

patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml list
>>
root@node1:~# patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml list
+ Cluster: main (7351350415269982209) --+------------+-----------+----+-----------+
| Member  | Host            | Role    | State        | TL | Lag in MB |
+---------+-----------------+---------+--------------+----+-----------+
| node1   | xxx.xxx.xxx.xxx | Leader  | running      |  1 |           |
| node2   | yyy.yyy.yyy.yyy | Replica | streaming    |  1 |         0 |
| node3   | zzz.zzz.zzz.zzz | Replica | start failed |  1 |         0 |
+---------+-----------------+---------+--------------+----+-----------+

Вышедший из строя узел можно перенастроить для присоединения к кластеру с помощью:

patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml reinit node3
>>
root@node1:~# patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml list
+ Cluster: main (7351350415269982209) --+---------+-----------+----+-----------+
| Member  | Host            | Role    | State     | TL | Lag in MB |
+---------+-----------------+---------+-----------+----+-----------+
| node1   | xxx.xxx.xxx.xxx | Leader  | running   |  1 |           |
| node2   | yyy.yyy.yyy.yyy | Replica | streaming |  1 |         0 |
| node3   | zzz.zzz.zzz.zzz | Replica | streaming |  1 |         0 |
+---------+-----------------+---------+-----------+----+-----------+

H.2.8. Кластерный тест

После успешного развертывания кластера:

# on deployment node run test, the test will take about 5 minutes
# please use the latest possible version of python3
# please run commands from the pg_cluster folder
python3 tools/pg_cluster_backend/pg_cluster_backend.py --operations=10000

Чтобы эмулировать взаимоблокировки, необходимо изменить параметр test.accounts = 100 -> 10 в tools/pg_cluster_backend/conf/pg_cluster_backend.conf.

Одновременно с тестированием вы должны выполнить действия с кластером:

# on NODE_1
patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml list
patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml restart main
shutdown -r now

# on NODE_2
patronictl -c /opt/tantor/etc/patroni/<NODE2_HOSTNAME>.yml list
patronictl -c /opt/tantor/etc/patroni/<NODE2_HOSTNAME>.yml restart main
shutdown -r now

# on NODE_3
patronictl -c /opt/tantor/etc/patroni/<NODE3_HOSTNAME>.yml list
patronictl -c /opt/tantor/etc/patroni/<NODE3_HOSTNAME>.yml restart main
shutdown -r now

# on NODE_1
patronictl -c /opt/tantor/etc/patroni/<NODE1_HOSTNAME>.yml switchover

# on primary node
su - postgres -c "psql -A -t -d test_db -c \"
    select pg_terminate_backend(pid)
    from pg_stat_activity
    where application_name = 'pg_cluster_backend'\""

# on NODE_1
systemctl stop patroni

# on NODE_2
systemctl stop patroni

# on NODE_1
systemctl start patroni

# on NODE_2
systemctl start patroni

# restart all nodes in random order

После выполнения этих действий тестовый сервер должен продолжить работу.
Проверьте, сколько транзакций было потеряно при переключении на асинхронную репликацию:

SELECT
    sum(balance)::numeric - -- result balance
    ((select count(1) from public.accounts) * 100 + 10000) -- where "--operations=10000"
FROM public.accounts

-- positive value means lost transactions
-- negative value means successfully committed transactions in which the backend received an exception