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 BE или PostgreSQL), управляемый через 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. Установите git (выполняется на узле, с которого будет запускаться ansible-playbook):

    sudo apt install git
    
  3. Скачайте проект (выполняется на узле, с которого будет запущен ansible-playbook):

    git clone https://github.com/TantorLabs/pg_cluster
    
    cd pg_cluster
    

    Примечание

    Пункты с 4 по 5 являются необязательными для случаев, когда пользователь уже сгенерировал SSH-ключи. Если вы не хотите хранить SSH-ключи в нестандартном месте, пункты 4 и 5 можно заменить следующими командами, выполненными от имени учетной записи admin_user:

    ssh-keygen -t rsa -b 4096
    ssh-copy-id admin_user@ip_node (repeat the command for each node in the inventory file)
    

    Если вы следовали этому примечанию, пожалуйста, продолжайте с пункта 6 текущей инструкции.

  4. Сгенерируйте SSH-ключи и загрузите их на узлы кластера (выполняется на узле, с которого будет запущен ansible-playbook от имени учетной записи admin_user):

    ssh-keygen -t rsa -b 4096 -C "admin_user" -f /home/admin_user/pg_lab_ansible -q -N ""
    cat /home/admin_user/pg_lab_ansible.pub >> /home/admin_user/.ssh/authorized_keys
    ssh-copy-id -i /home/admin_user/pg_lab_ansible.pub admin_user@ip_node (repeat the command for each node in the inventory file)
    
  5. Запишите параметры подключения каждого сервера из inventory file для пользователя admin_user (выполняется на узле, с которого ansible-playbook будет запущен как учетная запись admin_user):

    mkdir -p $HOME/.ssh/
    
    cat >> $HOME/.ssh/config << EOL  
    Host xxx.xxx.xxx.xxx  
        Port 22  
        User admin_user  
        PreferredAuthentications publickey  
        StrictHostKeyChecking no  
        IdentityFile /home/admin_user/pg_lab_ansible  
    EOL
    
  6. Предоставьте пользователю admin_user возможность входить в привилегированный режим (root) без ввода пароля (выполняется на каждом узле из файла инвентаризации).

  7. Проверьте ssh соединение пользователя admin_user (при запуске и подключении в качестве учетной записи admin_user пароль запрашиваться не должен):

    ssh admin_user@ip_node
    

H.2.5. Подготовка Ansible #

Подготовка к запуску выполняется на узле, с которого будет запущен ansible-playbook, и включает следующие шаги:

  1. Установить ansible

    sudo python3 -m pipX.X install ansible==X.X.X # where X.X(.X) represents the version of pip and Ansible specified in the Requirements block of the current instruction.
    
  2. В файле inventory/group_vars/prepare_nodes.yml измените значение переменных USERNAME:PASSWORD на имя пользователя и пароль для доступа к репозиторию Tantor DB.

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

  4. Заполните файл конфигурации inventory /opt/pg_cluster/inventory/my_inventory, содержащий список хостов и их IP-адресов.

После заполнения файла my_inventory рекомендуется убедиться, что все серверы доступны для подключения к ним через SSH с помощью нужного пользователем. Для этого выполните следующую команду в терминале:

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.

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

Плейбук позволяет разделять каталоги 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.7. Запуск плейбука #

Одна из задач плейбука выполняется на том же узле, с которого запускается 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-be-server-16 СУБД с помощью команды ansible-playbook -i inventory/my_inventory -u admin_user -e "postgresql_vendor=tantordb edition=be major_version=16" pg-cluster.yaml -K, убедитесь, что пакет tantor-be-server-16 доступен в вашем локальном репозитории.

Если плейбук запускается в среде с доступом в интернет, вы можете использовать самые актуальные компоненты, включенные в решение. Для этого добавьте флаг add_nexus_repo=true и укажите данные для подключения к репозиториям в файле inventory/group_vars/prepare_nodes.yml.

Существует несколько вариантов запуска Ansible, с возможностью установки:

  • Tantor BE

  • классического PostgreSQL как СУБД.

Используйте следующую команду для установки Tantor BE:

ansible-playbook -i inventory/my_inventory \
  -u admin_user -e "postgresql_vendor=tantordb edition=be major_version=16" pg-cluster.yaml -K

Используйте следующую команду для установки классической СУБД PostgreSQL:

ansible-playbook -i inventory/my_inventory \
  -u admin_user -e "postgresql_vendor=classic major_version=11" pg-cluster.yaml -K

В приведенных выше командах замените значение параметра major_version на версию СУБД, которую необходимо установить, значение postgresql_vendor на поставщика СУБД и параметр admin_user на пользователя, который имеет доступ к серверам без пароля из файла my_inventory с возможностью переключения в привилегированный режим (root) без запроса пароля. Для Tantor BE также необходимо указать редакцию СУБД.

H.2.8. Запуск с доступом в интернет #

Можно запустить плейбук с внешним доступом в интернет.

ansible-playbook -i inventory/my_inventory \
  -u admin_user -e "postgresql_vendor=tantordb edition=be major_version=16 add_nexus_repo=true" pg-cluster.yaml -K

В этом случае убедитесь, что детали подключения указаны в файле inventory/group_vars/prepare_nodes.yml.

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

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

H.2.9.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.9.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.9.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.10. Кластерный тест #

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

# 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