Aim_
After this lesson, you will:
- Install Python and necessary dependencies
- Install Patroni via pip_
- Understanding file structure
patroni.yml - Create systemd service for Patroni_
- Install Patroni on 3 nodes
1. Introduction
Patroni is a Python application, so it requires a Python runtime and dependencies. In this article, we will:
- Install Python 3 and pip
- Install Patroni package
- Patroni configuration
- Create systemd service to manage Patroni daemon
Target architecture:
┌──────────────────────────────────┐
│ etcd Cluster (3 nodes) │
│ ✅ RUNNING │
└──────────────────────────────────┘
│ │ │
┌────┴────┐ │ ┌─────┴─────┐
▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Patroni 1│ │Patroni 2│ │Patroni 3│ ← Installing now
│ + PG │ │ + PG │ │ + PG │
└─────────┘ └─────────┘ └─────────┘
2. Install Python Dependencies
2.1. Install Python on Ubuntu/Debian
Perform on ALL 3 nodes.
Step 1: Install Python 3 and pip_
# Update package list sudo apt updateCài Python 3, pip, và development tools
sudo apt install -y python3 python3-pip python3-dev python3-venv
Kiểm tra version
python3 --version
Output: Python 3.10.x (hoặc 3.11.x hoặc 3.12.x)
pip3 --version
Output: pip 22.x.x
Step 2: Install system dependencies_
# Cài các libraries cần thiết cho Patroni và PostgreSQL Python modules sudo apt install -y
libpq-dev
gcc
python3-psycopg2libpq-dev: PostgreSQL client library headers
gcc: Compiler cho building Python packages
python3-psycopg2: PostgreSQL adapter cho Python
2.2. Install Python on CentOS/RHEL
# Python 3 thường đã có sẵn, nhưng cần pip và dev tools sudo dnf install -y python3 python3-pip python3-develSystem dependencies
sudo dnf install -y
postgresql18-devel
gcc
python3-psycopg2Kiểm tra
python3 --version pip3 --version
2.3. Upgrade pip (recommended)
# Upgrade pip to latest version sudo pip3 install --upgrade pipVerify
pip3 --version
3. Install Patroni via pip
3.1. Install Patroni_
Execute on ALL 3 nodes._
# Cài Patroni với etcd support sudo pip3 install patroni[etcd]Hoặc specify version cụ thể
sudo pip3 install patroni[etcd]==3.2.0
Kiểm tra installation
patroni --version
Output: patroni 3.2.0
patronictl --version
Output: patronictl 3.2.0
Solution like [etcd]:
- Patroni supports many DCS backends (etcd, consul, zookeeper)
[etcd]install addpython-etcdclient library- Can install multiple backends:
patroni[etcd,consul,zookeeper]
3.2. Installed packages
# Liệt kê các packages liên quan pip3 list | grep -E "(patroni|etcd|psycopg)"Output:
patroni 3.2.0
python-etcd 0.4.5
psycopg2 2.9.x
psycopg2-binary 2.9.x
3.3. Verify Patroni commands
# Check patroni binary which patroniOutput: /usr/local/bin/patroni
Check patronictl binary
which patronictl
Output: /usr/local/bin/patronictl
List patronictl commands
patronictl --help
Output:
Usage: patronictl [OPTIONS] COMMAND [ARGS]...
Commands: list Show cluster members switchover Perform planned switchover failover Perform manual failover reinit Reinitialize cluster member restart Restart cluster member reload Reload cluster member configuration pause Disable auto-failover resume Enable auto-failover edit-config Edit cluster configuration ...
4. File structure patroni.yml
4.1. Overview of patroni.yml
File patroni.yml is the main configuration file of Patroni, including includes:
- Scope: Cluster name
- Namespace: Prefix for keys in DCS
- Node information: Node name, REST API config
- DCS connection_: etcd endpoints_
- Bootstrap: Initial cluster setup_
- PostgreSQL: Database configuration_
- Tags: Node metadata
- Watchdog: Optional hardware watchdog
4.2. Basic structure
scope: postgres namespace: /service/ name: node1restapi: listen: 0.0.0.0:8008 connect_address: 10.0.1.11:8008
etcd: hosts: 10.0.1.11:2379,10.0.1.12:2379,10.0.1.13:2379
bootstrap: dcs: ttl: 30 loop_wait: 10 retry_timeout: 10 maximum_lag_on_failover: 1048576 postgresql: use_pg_rewind: true parameters: wal_level: replica hot_standby: "on" max_wal_senders: 10 max_replication_slots: 10
initdb: - encoding: UTF8 - data-checksums
pg_hba: - host replication replicator 10.0.1.0/24 scram-sha-256 - host all all 0.0.0.0/0 scram-sha-256
postgresql: listen: 0.0.0.0:5432 connect_address: 10.0.1.11:5432 data_dir: /var/lib/postgresql/18/data bin_dir: /usr/lib/postgresql/18/bin authentication: replication: username: replicator password: replicator_password superuser: username: postgres password: postgres_password
tags: nofailover: false noloadbalance: false clonefrom: false nosync: false
4.3. Explanation of main sections_
Section: Global
scope: postgres # Cluster name (unique identifier)
namespace: /service/ # DCS key prefix
name: node1 # Unique node name trong cluster
Section: REST API_
restapi: listen: 0.0.0.0:8008 # Listen trên tất cả interfaces connect_address: 10.0.1.11:8008 # Address để nodes khác connectauthentication: # Optional: Basic auth
username: admin
password: secret
REST API endpoints:
GET /: Cluster infoGET /health: Health check (200 = healthy)GET /primary: Check if node is primary_GET /replica: Check if node is replicaPOST /restart_: Restart PostgreSQL_POST /reload: Reload configuration
Section: etcd (DCS)
etcd: hosts: 10.0.1.11:2379,10.0.1.12:2379,10.0.1.13:2379 # etcd endpointsprotocol: http # http hoặc https
username: user # Optional: etcd authentication
password: pass
Section: Bootstrap_
bootstrap: dcs: ttl: 30 # Leader lock TTL (seconds) loop_wait: 10 # Check interval (seconds) retry_timeout: 10 # DCS operation timeout maximum_lag_on_failover: 1048576 # Max lag (bytes) để eligible for failover postgresql: use_pg_rewind: true # Enable pg_rewind for fast recovery parameters: # PostgreSQL parameters wal_level: replica hot_standby: "on" wal_keep_size: "1GB" max_wal_senders: 10 max_replication_slots: 10 wal_log_hints: "on" # Required for pg_rewindinitdb: # pg_initdb options - encoding: UTF8 - data-checksums # Enable page checksums - locale: en_US.UTF-8
pg_hba: # pg_hba.conf entries - host replication replicator 10.0.1.0/24 scram-sha-256 - host all all 0.0.0.0/0 scram-sha-256
users: # Create users during bootstrap admin: password: admin_password options: - createrole - createdb
Note: Bootstrap section only applies when initializing the cluster for the first time.
Section: PostgreSQL
postgresql: listen: 0.0.0.0:5432 # PostgreSQL listen address connect_address: 10.0.1.11:5432 # Address để connect từ bên ngoài data_dir: /var/lib/postgresql/18/data # Data directory bin_dir: /usr/lib/postgresql/18/bin # PostgreSQL binaries pgpass: /tmp/pgpass # Optional: pgpass fileauthentication: replication: username: replicator password: replicator_password superuser: username: postgres password: postgres_password rewind: # Optional: dedicated user for pg_rewind username: rewind_user password: rewind_password
parameters: # PostgreSQL runtime parameters max_connections: 100 shared_buffers: 2GB effective_cache_size: 6GB maintenance_work_mem: 512MB checkpoint_completion_target: 0.9 wal_buffers: 16MB default_statistics_target: 100 random_page_cost: 1.1 effective_io_concurrency: 200 work_mem: 16MB min_wal_size: 1GB max_wal_size: 2GB
pg_hba: # Additional pg_hba.conf entries - host all all 10.0.2.0/24 scram-sha-256
callbacks: # Optional: callback scripts on_start: /etc/patroni/scripts/on_start.sh on_stop: /etc/patroni/scripts/on_stop.sh on_role_change: /etc/patroni/scripts/on_role_change.sh
Section: Tags
tags: nofailover: false # false = có thể become primary noloadbalance: false # false = có thể nhận read traffic clonefrom: false # false = có thể clone từ node này nosync: false # false = có thể become synchronous standbyCustom tags
datacenter: dc1 environment: production
Section: Watchdog (Optional)
watchdog:
mode: required # required, automatic, hoặc off
device: /dev/watchdog # Hardware watchdog device
safety_margin: 5 # Seconds before watchdog triggers
5. Create Patroni configuration files
5.1. Create configuration directory
On ALL 3 nodes:
# Tạo directory cho Patroni config sudo mkdir -p /etc/patroniTạo directory cho callback scripts (optional)
sudo mkdir -p /etc/patroni/scripts
Set ownership
sudo chown -R postgres:postgres /etc/patroni
5.2. Node 1 - /etc/patroni/patroni.yml
scope: postgres namespace: /service/ name: node1restapi: listen: 0.0.0.0:8008 connect_address: 10.0.1.11:8008
etcd: hosts: 10.0.1.11:2379,10.0.1.12:2379,10.0.1.13:2379
bootstrap: dcs: ttl: 30 loop_wait: 10 retry_timeout: 10 maximum_lag_on_failover: 1048576 postgresql: use_pg_rewind: true parameters: wal_level: replica hot_standby: "on" wal_keep_size: "1GB" max_wal_senders: 10 max_replication_slots: 10 wal_log_hints: "on"
initdb: - encoding: UTF8 - data-checksums
pg_hba: - host replication replicator 10.0.1.11/32 scram-sha-256 - host replication replicator 10.0.1.12/32 scram-sha-256 - host replication replicator 10.0.1.13/32 scram-sha-256 - host all all 10.0.1.0/24 scram-sha-256 - host all all 0.0.0.0/0 md5
users: admin: password: admin123 options: - createrole - createdb
postgresql: listen: 0.0.0.0:5432 connect_address: 10.0.1.11:5432 data_dir: /var/lib/postgresql/18/data bin_dir: /usr/lib/postgresql/18/bin authentication: replication: username: replicator password: replicator_password superuser: username: postgres password: postgres_password parameters: max_connections: 100 shared_buffers: 2GB effective_cache_size: 6GB maintenance_work_mem: 512MB checkpoint_completion_target: 0.9
tags: nofailover: false noloadbalance: false clonefrom: false nosync: false
5.3. Node 2 - /etc/patroni/patroni.yml
scope: postgres namespace: /service/ name: node2restapi: listen: 0.0.0.0:8008 connect_address: 10.0.1.12:8008
etcd: hosts: 10.0.1.11:2379,10.0.1.12:2379,10.0.1.13:2379
bootstrap: dcs: ttl: 30 loop_wait: 10 retry_timeout: 10 maximum_lag_on_failover: 1048576 postgresql: use_pg_rewind: true parameters: wal_level: replica hot_standby: "on" wal_keep_size: "1GB" max_wal_senders: 10 max_replication_slots: 10 wal_log_hints: "on"
initdb: - encoding: UTF8 - data-checksums
pg_hba: - host replication replicator 10.0.1.11/32 scram-sha-256 - host replication replicator 10.0.1.12/32 scram-sha-256 - host replication replicator 10.0.1.13/32 scram-sha-256 - host all all 10.0.1.0/24 scram-sha-256 - host all all 0.0.0.0/0 md5
users: admin: password: admin123 options: - createrole - createdb
postgresql: listen: 0.0.0.0:5432 connect_address: 10.0.1.12:5432 data_dir: /var/lib/postgresql/18/data bin_dir: /usr/lib/postgresql/18/bin authentication: replication: username: replicator password: replicator_password superuser: username: postgres password: postgres_password parameters: max_connections: 100 shared_buffers: 2GB effective_cache_size: 6GB maintenance_work_mem: 512MB checkpoint_completion_target: 0.9
tags: nofailover: false noloadbalance: false clonefrom: false nosync: false
5.4. Node 3 - /etc/patroni/patroni.yml
scope: postgres namespace: /service/ name: node3restapi: listen: 0.0.0.0:8008 connect_address: 10.0.1.13:8008
etcd: hosts: 10.0.1.11:2379,10.0.1.12:2379,10.0.1.13:2379
bootstrap: dcs: ttl: 30 loop_wait: 10 retry_timeout: 10 maximum_lag_on_failover: 1048576 postgresql: use_pg_rewind: true parameters: wal_level: replica hot_standby: "on" wal_keep_size: "1GB" max_wal_senders: 10 max_replication_slots: 10 wal_log_hints: "on"
initdb: - encoding: UTF8 - data-checksums
pg_hba: - host replication replicator 10.0.1.11/32 scram-sha-256 - host replication replicator 10.0.1.12/32 scram-sha-256 - host replication replicator 10.0.1.13/32 scram-sha-256 - host all all 10.0.1.0/24 scram-sha-256 - host all all 0.0.0.0/0 md5
users: admin: password: admin123 options: - createrole - createdb
postgresql: listen: 0.0.0.0:5432 connect_address: 10.0.1.13:5432 data_dir: /var/lib/postgresql/18/data bin_dir: /usr/lib/postgresql/18/bin authentication: replication: username: replicator password: replicator_password superuser: username: postgres password: postgres_password parameters: max_connections: 100 shared_buffers: 2GB effective_cache_size: 6GB maintenance_work_mem: 512MB checkpoint_completion_target: 0.9
tags: nofailover: false noloadbalance: false clonefrom: false nosync: false
5.5. Set permissions
On ALL 3 nodes:
# Set ownership sudo chown postgres:postgres /etc/patroni/patroni.ymlSecure permissions (file chứa passwords)
sudo chmod 600 /etc/patroni/patroni.yml
Verify
ls -l /etc/patroni/patroni.yml
Output: -rw------- 1 postgres postgres ... patroni.yml
6. Create systemd service for Patroni
6.1. Create systemd unit file
Create file /etc/systemd/system/patroni.service on ALL 3 nodes:
[Unit] Description=Patroni PostgreSQL HA manager Documentation=https://patroni.readthedocs.io/ After=syslog.target network.target etcd.service[Service] Type=simple User=postgres Group=postgres
Start Patroni
ExecStart=/usr/local/bin/patroni /etc/patroni/patroni.yml
Reload configuration
ExecReload=/bin/kill -HUP $MAINPID
Restart behavior
Restart=on-failure RestartSec=5
Limits
LimitNOFILE=65536 LimitNPROC=65536
Logging
StandardOutput=journal StandardError=journal
Working directory
WorkingDirectory=/var/lib/postgresql
Environment
Environment=PATH=/usr/lib/postgresql/18/bin:/usr/local/bin:/usr/bin:/bin
[Install] WantedBy=multi-user.target
6.2. Explain systemd unit file
| _Directive | Solution like_ |
|---|---|
After=etcd.service_ | Start after etcd ready |
Type=simple_ | Process running foreground |
User=postgres_ | _Runs with user postgres |
ExecStart | Command to start Patroni |
ExecReload_ | Send HUP signal to reload config_ |
Restart=on-failure_ | Auto restart if fail |
_RestartSec=5 | Wait 5 seconds before restart_ |
_LimitNOFILE=65536_ | Max open files_ |
_StandardOutput=journal_ | Log into systemd journal |
6.3. Enable and verify service
On ALL 3 nodes:
# Reload systemd sudo systemctl daemon-reloadEnable Patroni service (auto-start on boot)
sudo systemctl enable patroni
Verify service file
systemctl cat patroni
Check status (should be inactive/dead - not started yet)
systemctl status patroni
7. Verify installation
7.1. Check Patroni installation
On ALL 3 nodes:
# Check Patroni version patroni --versionCheck patronictl
patronictl --help
Verify config file
sudo -u postgres cat /etc/patroni/patroni.yml
Validate YAML syntax
python3 -c "import yaml; yaml.safe_load(open('/etc/patroni/patroni.yml'))"
No output = valid YAML
7.2. Check etcd connectivity
# Test etcd từ mỗi node etcdctl endpoint health --clusterShould see all 3 etcd nodes healthy
7.3. Pre-flight checklist
Before starting Patroni, verify:
# ✅ PostgreSQL installed nhưng NOT running systemctl status postgresqlShould be: inactive (dead)
✅ etcd cluster healthy
etcdctl endpoint health --cluster
✅ Patroni config file exists và có permissions đúng
ls -l /etc/patroni/patroni.yml
✅ Data directory có ownership đúng
ls -ld /var/lib/postgresql/18/data
Owner: postgres:postgres
✅ systemd service enabled
systemctl is-enabled patroni
Output: enabled
✅ Firewall rules (nếu có firewall)
sudo ufw status | grep -E "(5432|8008)"
Hoặc
sudo firewall-cmd --list-ports | grep -E "(5432|8008)"
8. Troubleshooting
8.1. Issue: pip install fails
# Error: "No module named 'setuptools'"Solution:
sudo apt install python3-setuptools
Or upgrade pip
sudo pip3 install --upgrade pip setuptools
8.2. Issue: Cannot find PostgreSQL binaries_
# Error: "could not find postgres binary"Solution: Check bin_dir in patroni.yml
Find PostgreSQL bin directory
which postgres
Output: /usr/lib/postgresql/18/bin/postgres
Update patroni.yml
postgresql: bin_dir: /usr/lib/postgresql/18/bin
8.3. Issue: Permission denied on data directory_
# Error: "FATAL: data directory ... has wrong ownership"Solution:
sudo chown -R postgres:postgres /var/lib/postgresql/18/data sudo chmod 700 /var/lib/postgresql/18/data
8.4. Issue: YAML syntax error
# Validate YAML python3 -c "import yaml; yaml.safe_load(open('/etc/patroni/patroni.yml'))"Common issues:
- Mixed tabs and spaces (use spaces only)
- Incorrect indentation
- Missing quotes around special characters
- Duplicate keys
9. Summary
Key Takeaways
✅ Patroni: Python application, install pip
✅ Dependencies: Python 3, pip, psycopg2, python-etcd
✅ Configuration: patroni.yml contains all settings
✅ systemd service: Patroni daemon management
✅ Security: The config file has permissions 600 (contains passwords)
Checklist after Lab
- Python 3 and pip installed on all 3 nodes
- Patroni 3.2.0+ installed on all 3 nodes
- File
/etc/patroni/patroni.ymlalready create on each node with its own config - systemd service
patroni.servicecreated and enabled - Permissions true for config file (600, owner postgres)
- etcd cluster is running and healthy
Current architecture_
✅ 3 VMs prepared (Bài 4) ✅ PostgreSQL 18 installed (Bài 5) ✅ etcd cluster running (Bài 6) ✅ Patroni installed and configured (Bài 7)
Next: Bootstrap cluster lần đầu
Preparing for Lesson 8_
Lesson 8 will go deeper Go to detailed Patroni configuration:
- Analyze each section in
patroni.yml - Bootstrap configuration options
- PostgreSQL parameters tuning
- Authentication setup_
- Tags and constraints_
Lesson 9: Bootstrap cluster
After Patroni has been installed and configured, Lesson 9 will guide:
- Starting Patroni for the first time
- Automatic bootstrap process dynamic
- Check cluster status
- Troubleshootin