Postgres có tính sẵn sàng cao với Patroni/ Spilo (Phần 2)

1373
12-12-2023
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:

Postgres có tính sẵn sàng cao với Patroni/ Spilo (Phần 2) - Ảnh 1.

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:

  1. 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

  2. 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.ymltrê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útIPAddress
    • 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/shmvà trong container. Các thư mục trên đều chạy dưới quyền của người dùng postgres, 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)

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_attrskè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.

SHARE