Postgres có tính sẵn sàng cao với Patroni/ Spilo (Phần 2)
Bất kỳ hệ thống nào, cho dù là Ứng dụng, Trang web, Công nhân/Người tiêu dùng, Cơ sở dữ liệu, … khi được xem là quan trọng thì công việc cần đảm bảo tính sẵn sàng cao (Tính sẵn sàng cao) vẫn luôn là tiêu chuẩn được đưa ra mỗi khi khai triển. Ngày hôm nay, mình muốn nói về High Availability cho Postgres bởi vì:
- Postgres chưa được hỗ trợ chính thức cho High Availability
- Có rất nhiều công cụ hỗ trợ của bên thứ 3 để xây dựng hệ thống Tính sẵn sàng cao cho Postgres
Trong khoảng thời gian gần đây, cô ấy đã tìm thấy một bài toán liên quan đến Postgres và yêu cầu bài toán là xây dựng phương án, giải pháp cho công việc Postgres có thể lăn ra dừng hoạt động bất cứ lúc nào. Sau khi tham khảo ý kiến của anh em System/ Cloud Engineer hay Devops thì mình thấy công việc sử dụng Patroni (Spilo) là một giải pháp hoàn thiện để xây dựng hệ thống High Availability cho Postgres.
Vậy thì cách phát triển khai báo và kết nối ở đây như thế nào?
Dưới đây sẽ là nội dung tiếp theo của bài viết Postgres có tính sẵn sàng cao với Patroni/ Spilo (Phần 1)
1. Khai báo Mô hình phát triển
Dưới đây là mô hình mà mình sẽ thực hiện phát triển khai:
in which:
- ETCD là thành phần khóa phân phối được sử dụng cho Patroni và được cấu hình kết nối với nhau như một cụm ETCD
Các thành phần Spilo & ETCD được cài đặt cùng nhau trên một máy chủ nhắm đến mục tiêu phức tạp hóa mô hình hệ thống và tiết kiệm chi phí, giảm thiểu rủi ro phát hiện sinh học trong tương lai.
2. Lợi ích của việc sử dụng Spilo
Đây là một số lợi ích của việc sử dụng spilo mà chúng tôi có thể thấy rõ:
- Hỗ trợ cài đặt sẵn các tiện ích mở rộng cho Postgres
- Hỗ trợ các script trước/sau tự động đối với các hành động của Patroni
- Hỗ trợ nhiều phương thức sao lưu/ tạo bản sao khi thêm nút vào cụm Patroni
- Cấu hình Crontab/Lịch sao lưu
- Vì chạy trên môi trường Docker nên sẽ có ưu điểm không gây ảnh hưởng đến các gói đang cài đặt trong OS
3. Thực hiện cấu hình
Trong phần nội dung này, mình sẽ lựa chọn sử dụng với docker-compose để quản lý vì một số lý do sau đây:
- Có thể xem lại chúng tôi đã tạo ra container như thế nào, bao gồm các thông tin gì
- Dễ dàng cập nhật, điều chỉnh cấu hình cho container
Với thông tin ban đầu chúng ta có 3 node như sau:
Tên nút | Địa chỉ IP | ———-|———— yanfei-spilo-1 | 10.26.235.206 | yanfei-spilo-2 | 10.26.235.157 | yanfei-spilo-3 | 10.26.235.75 | Sau khi chúng tôi cài đặt docker/ docker-compose và etcd cluster (etcd cluster có thể chạy trên container). Chúng tôi thực hiện như sau:
Tạo cụm etcd trên các nút:
- trên nút yanfei-spilo-1:
version: "3.0" services: etcd: image: 'quay.io/coreos/etcd:v3.5.9' environment: ALLOW_NONE_AUTHENTICATION: "yes" ETCD_NAME: yanfei-spilo-1 ETCD_ADVERTISE_CLIENT_URLS: http://10.26.235.206:2379 ETCD_INITIAL_ADVERTISE_PEER_URLS: http://10.26.235.206:2380 ETCD_LISTEN_PEER_URLS: http://10.26.235.206:2380 ETCD_LISTEN_CLIENT_URLS: http://10.26.235.206:2379,http://127.0.0.1:2379 ETCD_INITIAL_CLUSTER: yanfei-spilo-1=http://10.26.235.206:2380,yanfei-spilo-2=http://10.26.235.157:2380,yanfei-spilo-3=http://10.26.235.75:2380 ETCD_INITIAL_CLUSTER_TOKEN: etcd-cluster-yanfei-spilo ETCD_INITIAL_CLUSTER_STATE: new ETCD_DATA_DIR: /var/lib/etcd network_mode: host volumes: - /var/lib/etcd:/var/lib/etcd
- trên nút yanfei-spilo-2:
version: "3.0" services: etcd: image: 'quay.io/coreos/etcd:v3.5.9' environment: ALLOW_NONE_AUTHENTICATION: "yes" ETCD_NAME: yanfei-spilo-2 ETCD_ADVERTISE_CLIENT_URLS: http://10.26.235.157:2379 ETCD_INITIAL_ADVERTISE_PEER_URLS: http://10.26.235.157:2380 ETCD_LISTEN_PEER_URLS: http://10.26.235.157:2380 ETCD_LISTEN_CLIENT_URLS: http://10.26.235.157:2379,http://127.0.0.1:2379 ETCD_INITIAL_CLUSTER: yanfei-spilo-1=http://10.26.235.206:2380,yanfei-spilo-2=http://10.26.235.157:2380,yanfei-spilo-3=http://10.26.235.75:2380 ETCD_INITIAL_CLUSTER_TOKEN: etcd-cluster-yanfei-spilo ETCD_INITIAL_CLUSTER_STATE: new ETCD_DATA_DIR: /var/lib/etcd network_mode: host volumes: - /var/lib/etcd:/var/lib/etcd
- trên nút yanfei-spilo-3:
version: "3.0" services: etcd: image: 'quay.io/coreos/etcd:v3.5.9' environment: ALLOW_NONE_AUTHENTICATION: "yes" ETCD_NAME: yanfei-spilo-3 ETCD_ADVERTISE_CLIENT_URLS: http://10.26.235.75:2379 ETCD_INITIAL_ADVERTISE_PEER_URLS: http://10.26.235.75:2380 ETCD_LISTEN_PEER_URLS: http://10.26.235.75:2380 ETCD_LISTEN_CLIENT_URLS: http://10.26.235.75:2379,http://127.0.0.1:2379 ETCD_INITIAL_CLUSTER: yanfei-spilo-1=http://10.26.235.206:2380,yanfei-spilo-2=http://10.26.235.157:2380,yanfei-spilo-3=http://10.26.235.75:2380 ETCD_INITIAL_CLUSTER_TOKEN: etcd-cluster-yanfei-spilo ETCD_INITIAL_CLUSTER_STATE: new ETCD_DATA_DIR: /var/lib/etcd network_mode: host volumes: - /var/lib/etcd:/var/lib/etcd
Hãy thay thế các IP địa chỉ tương ứng với các nút đang có và thực hiện bắt đầu trên các nút như sau:
bash docker-compose up -d etcd
- trên nút yanfei-spilo-1:
Tạo cơ sở dữ liệu vùng chứa trên các nút
- Tạo cấu hình tệp cho người bảo trợ
/etc/spilo/run/postgres.yml
trên các nút như sau:bootstrap: dcs: failsafe_mode: true loop_wait: 15 maximum_lag_on_failover: 33554432 postgresql: parameters: archive_command: test ! -f /var/lib/postgresql/data/wal_archive/%f && cp %p /var/lib/postgresql/data/wal_archive/%f archive_mode: 'on' archive_timeout: 1800s datestyle: iso, mdy default_text_search_config: pg_catalog.english effective_cache_size: 512MB hot_standby: 'on' lc_messages: en_US.UTF-8 lc_monetary: en_US.UTF-8 lc_numeric: en_US.UTF-8 lc_time: en_US.UTF-8 log_destination: stderr log_directory: /var/log/postgresql log_file_mode: 384 log_filename: postgresql.log log_line_prefix: '%t [%p]: [%l-1] %c %x %d %u %a %h ' log_rotation_age: 1d log_rotation_size: 100MB log_timezone: Etc/UTC log_truncate_on_rotation: 'on' logging_collector: 'on' max_connections: 200 max_wal_size: 1GB min_wal_size: 80MB timezone: Etc/UTC update_process_title: 'off' wal_keep_size: 1600MB wal_log_hints: 'on' use_pg_rewind: true use_slots: true retry_timeout: 15 ttl: 45 initdb: - encoding: UTF8 - locale: en_US.UTF-8 - data-checksums post_init: /scripts/post_init.sh "cloud" etcd3: hosts: - 10.26.235.157:2379 - 10.26.235.206:2379 - 10.26.235.75:2379 log: dir: /var/log/postgresql postgresql: authentication: replication: password: <password> username: replicator rewind: password: <password> username: replicator superuser: password: <password> username: postgres basebackup_fast_xlog: command: /scripts/basebackup.sh retries: 2 callbacks: on_role_change: /scripts/on_role_change.sh cloud true connect_address: <IPAddress>:5432 create_replica_method: - basebackup_fast_xlog data_dir: /var/lib/postgresql/data/pgdata listen: '*:5432' name: <node_name> parameters: bg_mon.history_buckets: 120 bg_mon.listen_address: '::' dynamic_shared_memory_type: posix extwlist.custom_path: /scripts extwlist.extensions: btree_gin,btree_gist,citext,extra_window_functions,first_last_agg,hll,hstore,hypopg,intarray,ltree,pgcrypto,pgq,pgq_node,pg_trgm,postgres_fdw,tablefunc,uuid-ossp,pg_partman listen_addresses: '*' log_autovacuum_min_duration: 0 log_checkpoints: false log_connections: false log_disconnections: false max_stack_depth: 7MB pg_stat_statements.track_utility: 'off' port: 5432 shared_buffers: 102MB shared_preload_libraries: bg_mon,pg_stat_statements,pgextwlist,pg_auth_mon,set_user,pg_cron,pg_stat_kcache,pglogical ssl: 'on' ssl_cert_file: /run/certs/server.crt ssl_key_file: /run/certs/server.key unix_socket_directories: /var/run/postgresql wal_buffers: -1 wal_sync_method: fsync pg_hba: - local all postgres trust - local replication postgres trust - local all all trust - local replication replicator trust - host all all ::1/128 md5 - host all postgres 127.0.0.1/32 trust - host all postgres ::1/128 trust - host all postgres all reject - host replication replicator 0.0.0.0/0 md5 - hostnossl all postgres all reject - hostssl all +cloud 127.0.0.1/32 pam - hostssl all +cloud ::1/128 pam - hostssl all +cloud all pam - hostssl all all all md5 - hostssl replication replicator 0.0.0.0/0 md5 pgpass: /run/postgresql/pgpass use_unix_socket: true use_unix_socket_repl: true restapi: authentication: password: <password> username: postgres connect_address: <IPAddress>:8008 listen: :8008 scope: postgres-patroni-cb5o3izc
Lưu ý :
- bạn cần phải tạo các người dùng/vai trò tương ứng trong cơ sở dữ liệu với thông báo khai báo thông tin ở:
postgresql.authentication
. Chi tiết về các tùy chọn cấu hình có thể tìm thấy ở đây . Thông tin về người dùng/ vai trò trên các nút phải giống nhau - Cần thay đổi địa chỉ IP tương ứng của các nút
IPAddress
- bạn cần phải tạo các người dùng/vai trò tương ứng trong cơ sở dữ liệu với thông báo khai báo thông tin ở:
- Tạo thư mục lưu trữ dữ liệu như sau:
mkdir -p /var/lib/postgresql/data/pgdata
- Tạo cơ sở dữ liệu vùng chứa trên các nút như sau:
spilo: image: ghcr.io/zalando/spilo-15:3.0-p1 network_mode: host environment: PGDATA: /var/lib/postgresql/data/pgdata PGLOG: /var/lib/postgresql/data/pg_log PGPASSWORD_SUPERUSER: <password> PGUSER_SUPERUSER: postgres PGVERSION: 15 PGHOME: /home/postgres RW_DIR: /run PGROOT: /home/postgres/pgdata/pgroot volumes: - /dev/shm:/dev/shm - /run:/etc/spilo - /var/lib/postgresql:/var/lib/postgresql - /var/lib/postgresql/data:/var/lib/postgresql/data - /var/log/postgresql:/var/log/postgresql - /var/run/postgresql:/var/run/postgresql - /etc/passwd:/etc/passwd
Bởi vì chạy trong container và postgres có một số cấu hình cần phải cấp quyền nên chúng tôi sẽ gắn thư mục trực tiếp
/dev/shm
và trong container. Các thư mục trên đều chạy dưới quyền của người dùngpostgres
, nên chúng tôi cần bổ sung thêm/etc/passwd
lần trên các nút thực hiện bắt đầu spilo như sau:
bash docker-compose up -d spilo
quá trình khởi tạo cụm sẽ được diễn ra tương tự như ở Postgres có tính sẵn sàng cao với Patroni/ Spilo (Phần 1)
- Tạo cấu hình tệp cho người bảo trợ
3. Thực hiện kết nối
Như vấn đề đã được đặt ra ở bài Postgres High Availability with Patroni/ Spilo (Part 1) . Chúng ta cần phải xử lý để các ứng dụng có thể kết nối đến nút chính (dẫn đầu) mỗi khi xảy ra quá trình chuyển đổi dự phòng. Mình đã tìm hiểu và thấy trong các lib sử dụng để kết nối với Postgres có một kết nối tham số đi target_session_attrs
kèm là các giá trị được mô tả như sau:
- bất kỳ: Chấp nhận bất kỳ loại máy chủ nào, bao gồm máy chủ đọc-ghi, bản sao và máy chủ chỉ đọc.
- ưu tiên chế độ chờ: Nếu có sẵn bản sao chỉ đọc, hãy kết nối với nó. Nếu không, hãy kết nối với máy chủ chính.
- chính: Kết nối với máy chủ chính
- chỉ đọc: Chỉ chấp nhận máy chủ chỉ đọc.
- đọc-ghi: Chỉ chấp nhận máy chủ đọc-ghi.
- chế độ chờ: Chỉ chấp nhận máy chủ dự phòng.
Đây là một ví dụ về code trong python:
import psycopg2 hosts = [ <list of host> ] hosts = ",".join(hosts) database_name = "postgres" username = "root" password = "password" port = 5432 target_session_attrs = "any" conn = psycopg2.connect( database=database_name, host=hosts, user=username, password=password, port=port, target_session_attrs=target_session_attrs ) cur = conn.cursor() cur.execute("select pg_is_in_recovery(), pg_postmaster_start_time()") row = cur.fetchone() print("recovery = ", row[0]) print("time = ", row[1])
Chúng tôi có thể tùy chọn tắt bất kỳ vùng chứa nào để có thể kiểm tra chuyển đổi dự phòng và đoạn mã trên.