Tutorial

PostgreSQL en alta disponibilidad con Patroni en VPS

Bases de datos12 min de lectura8 pasos

Un SaaS multi-tenant o una aplicación crítica no puede permitirse un corte de la base de datos. Amazon RDS y Aurora resuelven el problema, pero su modelo de facturación crece con la carga. Patroni 4.1 combinado con etcd 3.6 ofrece el mismo nivel de disponibilidad en tres VPS con acceso root: failover automático en menos de 30 segundos, replicación síncrona configurable y una API REST para pilotar el clúster desde una terminal. Esta guía cubre la instalación completa, la demostración de la conmutación y las operaciones diarias.

Contenido· Por qué alta disponibilidad de PostgreSQL en VPS — y no RDS1/11
  1. 01Por qué alta disponibilidad de PostgreSQL en VPS — y no RDS
  2. 02Lo que este clúster le aporta
  3. 03Arquitectura del clúster: tres nodos, un quórum
  4. 04Requisitos previos: recursos y red
  5. 05Recursos mínimos recomendados por nodo
  6. 06Instalación: de cero al clúster operativo
  7. 07Failover y switchover: demostración
  8. 08Operaciones diarias
  9. 09Endurecimiento: TLS con autenticación mutua entre nodos
  10. 10Solución de problemas: los cinco errores más frecuentes
  11. 11Un clúster que se administra, no que se improvisa

Por qué alta disponibilidad de PostgreSQL en VPS — y no RDS

Amazon RDS Multi-AZ resuelve bien el failover, pero su modelo tarifario está diseñado para que la factura siga el crecimiento de forma no lineal: el almacenamiento, las IOPS aprovisionadas, las conexiones simultáneas y la propia replicación Multi-AZ se facturan por separado. Para un SaaS cuya base de datos crece, el costo adicional supera rápidamente el de un VPS dedicado.

En tres VPS con acceso root, Patroni presta el mismo servicio: un único líder acepta las escrituras, dos réplicas síncronas o asíncronas lo siguen, y etcd mantiene el quórum. Si el líder cae, Patroni detecta la ausencia mediante el lease (concesión) de etcd (TTL configurable, típicamente 30 segundos) y promueve la réplica más actualizada. Sin intervención humana y sin ningún DNS que actualizar manualmente si un balanceador de carga como HAProxy apunta al endpoint de salud de Patroni.

Lo que este clúster le aporta

  • Failover automático en menos de 30 segundos — Patroni detecta la pérdida del líder por expiración del lease de etcd y promueve sin intervención.
  • Replicación síncrona configurablesynchronous_mode: true garantiza que no se pierda ninguna transacción confirmada si el líder falla.
  • API REST integradaGET /leader, GET /health, POST /switchover: el clúster se consulta y se pilota sin cliente PostgreSQL.
  • Costo fijo y previsible — tres VPS con recursos fijos, sin sorpresas de facturación ligadas al tráfico.
  • Extensiones librespg_hba.conf, postgresql.conf, pgvector, PostGIS: ninguna restricción impuesta por un servicio gestionado.
  • Copias de seguridad centralizadas — pgBackRest 2.59 se integra de forma nativa con Patroni para copias incrementales desde la réplica.

Arquitectura del clúster: tres nodos, un quórum

El clúster se apoya en tres capas:

etcd mantiene el registro de configuración distribuido (DCS). Es quien guarda el lease del líder. Si el líder Patroni no renueva ese lease dentro del plazo TTL, etcd lo libera y los standbys se presentan a la elección. Con tres nodos etcd (uno por VPS), el quórum tolera la pérdida de un nodo sin perder la disponibilidad.

Patroni se ejecuta en cada VPS junto a PostgreSQL. Se encarga de la inicialización del clúster, de la configuración de postgresql.conf y de pg_hba.conf, del seguimiento del lag de replicación y del failover. Expone una API REST en el puerto 8008.

PostgreSQL está gestionado por completo por Patroni: no edite postgresql.conf directamente, toda modificación pasa por patronictl edit-config para mantenerse sincronizada en los tres nodos.

El flujo de replicación: el líder recibe las escrituras en WAL, las réplicas se conectan mediante pg_basebackup en el primer arranque y después siguen el flujo WAL de forma continua. En modo síncrono, el líder espera la confirmación de al menos una réplica antes de devolver COMMIT al cliente.

Requisitos previos: recursos y red

Esta guía se ha escrito con Patroni 4.1.5, etcd 3.6.6 y PostgreSQL 17 sobre Debian 12.

Recursos mínimos recomendados por nodo

  • 2 vCPU / 4 GB de RAM — suficiente para empezar; prevea 8 GB de RAM en cuanto la base de datos supere unos pocos GB de shared_buffers.
  • SSD NVMe — la replicación WAL es sensible a la latencia de escritura; un disco magnético degrada el lag de replicación.
  • Red privada entre los tres nodos — la comunicación etcd-etcd y Patroni-PostgreSQL no debe transitar por Internet.
  • IPv4 dedicada — para el acceso de clientes externos y el pg_hba.conf de las réplicas.
  • NTP sincronizado (chrony) — desfase < 1 s — etcd rechaza el quórum si el reloj de un nodo se desvía más de un segundo. Es la trampa más frecuente en VPS.

Instalación: de cero al clúster operativo

  1. Sincronizar el reloj en los tres nodos

    En cada nodo, instale y active chrony antes de cualquier otra operación:

    apt install -y chrony
    systemctl enable --now chronyd
    chronyc tracking

    Compruebe que System time offset sea inferior a 0,1 segundos. Un desfase superior a 1 segundo provoca timeouts de etcd y elecciones en bucle.

  2. Instalar etcd 3.6 en los tres nodos

    Defina las variables de entorno propias de cada nodo (sustituya NODE1_IP, NODE2_IP, NODE3_IP por las IP privadas):

    ETCD_VER=v3.6.6
    curl -L https://github.com/etcd-io/etcd/releases/download/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz \
      | tar xz -C /usr/local/bin --strip-components=1 etcd-${ETCD_VER}-linux-amd64/etcd \
                                                       etcd-${ETCD_VER}-linux-amd64/etcdctl

    Cree /etc/etcd/etcd.conf.yml en cada nodo (ejemplo para pg-node1):

    name: pg-node1
    data-dir: /var/lib/etcd
    listen-peer-urls: http://NODE1_IP:2380
    listen-client-urls: http://NODE1_IP:2379,http://127.0.0.1:2379
    initial-advertise-peer-urls: http://NODE1_IP:2380
    advertise-client-urls: http://NODE1_IP:2379
    initial-cluster: pg-node1=http://NODE1_IP:2380,pg-node2=http://NODE2_IP:2380,pg-node3=http://NODE3_IP:2380
    initial-cluster-token: pg-cluster-token
    initial-cluster-state: new

    Cree la unidad systemd, active e inicie etcd en los tres nodos antes de pasar al paso siguiente.

  3. Verificar el quórum de etcd

    En cualquier nodo:

    etcdctl --endpoints=http://NODE1_IP:2379,http://NODE2_IP:2379,http://NODE3_IP:2379 \
      endpoint status --write-out=table

    Espere a que las tres líneas muestren IS LEADER para una y false para las otras dos, y a que ERRORS esté vacío. Si un nodo no aparece, revise el firewall en los puertos 2379 y 2380.

  4. Instalar PostgreSQL y Patroni

    En los tres nodos:

    # PostgreSQL desde el repositorio oficial PGDG
    apt install -y curl ca-certificates
    curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg
    echo "deb https://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" > /etc/apt/sources.list.d/pgdg.list
    apt update && apt install -y postgresql-17
    
    # Patroni y el driver de etcd
    pip3 install patroni[etcd3] psycopg2-binary

    Detenga PostgreSQL: Patroni se encarga de la inicialización del clúster:

    systemctl stop postgresql
    systemctl disable postgresql
  5. Configurar Patroni en cada nodo

    Cree /etc/patroni/patroni.yml (ejemplo para pg-node1):

    scope: pg-cluster
    namespace: /service/
    name: pg-node1
    
    restapi:
      listen: NODE1_IP:8008
      connect_address: NODE1_IP:8008
    
    etcd3:
      hosts: NODE1_IP:2379,NODE2_IP:2379,NODE3_IP:2379
    
    bootstrap:
      dcs:
        ttl: 30
        loop_wait: 10
        retry_timeout: 10
        maximum_lag_on_failover: 1048576
        synchronous_mode: true
        synchronous_node_count: 1
        postgresql:
          use_pg_rewind: true
          use_slots: true
          parameters:
            wal_level: replica
            hot_standby: "on"
            wal_keep_size: 128MB
            max_wal_senders: 10
            max_replication_slots: 10
    
      initdb:
        - encoding: UTF8
        - data-checksums
    
      pg_hba:
        - host replication replicator 0.0.0.0/0 scram-sha-256
        - host all all 0.0.0.0/0 scram-sha-256
    
    postgresql:
      listen: NODE1_IP:5432
      connect_address: NODE1_IP:5432
      data_dir: /var/lib/postgresql/17/main
      bin_dir: /usr/lib/postgresql/17/bin
      authentication:
        replication:
          username: replicator
          password: 'SU_CONTRASENA_REPLICACION'
        superuser:
          username: postgres
          password: 'SU_CONTRASENA_POSTGRES'

    Adapte NODE1_IP en cada nodo.

  6. Iniciar Patroni e inicializar el clúster

    Cree la unidad systemd /etc/systemd/system/patroni.service:

    [Unit]
    Description=Patroni PostgreSQL HA
    After=network.target etcd.service
    Requires=etcd.service
    
    [Service]
    Type=simple
    User=postgres
    ExecStart=/usr/local/bin/patroni /etc/patroni/patroni.yml
    Restart=on-failure
    RestartSec=5s
    
    [Install]
    WantedBy=multi-user.target

    Inicie primero en pg-node1 (ese nodo ejecuta initdb y se convierte en líder), después en los otros dos con unos segundos de intervalo:

    systemctl daemon-reload
    systemctl enable --now patroni

    Siga la inicialización:

    patronictl -c /etc/patroni/patroni.yml list
  7. Verificar el estado inicial del clúster

    Salida esperada tras la inicialización completa:

    + Cluster: pg-cluster (7234567890123456789) +---------+----+-----------+
    | Member    | Host             | Role    | State   | TL | Lag in MB |
    +-----------+------------------+---------+---------+----+-----------+
    | pg-node1  | NODE1_IP:5432    | Leader  | running |  1 |           |
    | pg-node2  | NODE2_IP:5432    | Sync Standby | running |  1 | 0   |
    | pg-node3  | NODE3_IP:5432    | Replica | running |  1 | 0         |
    +-----------+------------------+---------+---------+----+-----------+

    pg-node2 aparece como Sync Standby: toda transacción confirmada en el líder queda garantizada en ese nodo antes de que el COMMIT se devuelva al cliente.

  8. Configurar pg_hba.conf mediante Patroni

    No modifique nunca pg_hba.conf directamente. Utilice patronictl edit-config para añadir reglas de acceso en la sección pg_hba. Patroni propaga la configuración a todos los nodos y recarga PostgreSQL automáticamente:

    patronictl -c /etc/patroni/patroni.yml edit-config

    Añada sus reglas en el bloque pg_hba del YAML. En VPS, la regla host all all 0.0.0.0/0 scram-sha-256 es un punto de partida que conviene afinar según su red privada.

Failover y switchover: demostración

Failover simulado — parada brusca del líder.

Antes de la parada, anote el estado del clúster:

patronictl -c /etc/patroni/patroni.yml list
# → pg-node1 es Leader, pg-node2 es Sync Standby

Detenga Patroni en el líder:

systemctl stop patroni   # en pg-node1

Siga la promoción en uno de los standbys:

patronictl -c /etc/patroni/patroni.yml list
# → (tras 10 a 30 segundos)
# pg-node2 : Leader  | running | TL 2
# pg-node3 : Replica | running | TL 2 | 0 MB
# pg-node1 : stopped

Patroni espera la expiración del lease de etcd (TTL = 30 s), y después pg-node2 (el sync standby) adquiere el lease y se promueve. El plazo efectivo suele situarse entre 10 y 30 segundos según el valor de loop_wait.

Switchover planificado — conmutación sin corte.

Para un mantenimiento programado, prefiera switchover, que espera a que la réplica de destino esté al día antes de conmutar:

patronictl -c /etc/patroni/patroni.yml switchover pg-cluster \
  --master pg-node1 --candidate pg-node2

Patroni espera a que el lag sea nulo, da la señal de promoción a pg-node2, y después pg-node1 se reconecta como réplica. Duración efectiva: menos de 5 segundos en condiciones normales.

API REST de estado.

Sin cliente PostgreSQL, consulte el estado desde un balanceador de carga o un script de monitorización:

curl -s http://NODE1_IP:8008/leader    # 200 = es el líder
curl -s http://NODE2_IP:8008/replica   # 200 = es una réplica sana
curl -s http://NODE1_IP:8008/health    # JSON: state, role, lag

HAProxy puede apuntar sus health checks a /leader y /replica para enrutar las escrituras y las lecturas a los nodos correctos. Consulte la guía HAProxy en VPS para el cableado completo.

Operaciones diarias

Copias de seguridad con pgBackRest 2.59.

Instale pgBackRest en los tres nodos y designe un repositorio compartido (objeto S3, NFS o directorio local dedicado). La configuración recomendada toma las copias desde una réplica para no cargar el líder:

pgbackrest --stanza=pg-cluster --type=full backup

Active la compresión y las copias incrementales diarias en pgbackrest.conf (repo1-retention-full=7). Consulte la guía copias de seguridad en VPS para las estrategias complementarias.

Monitorización del clúster.

patronictl list indica el lag en MB por réplica. Genere una alerta en cuanto el lag supere un umbral (por ejemplo: 50 MB): eso señala o bien una réplica lenta, o bien un problema de red. El endpoint GET /patroni devuelve un JSON completo que incluye xlog_location y replication_state.

Escalado vertical.

Para aumentar los recursos de un nodo: detenga Patroni en ese nodo (pasa a réplica desconectada), redimensione el VPS y reinicie. Patroni se reconecta y recupera el lag automáticamente mediante pg_rewind o pg_basebackup según la amplitud del desfase.

Compromiso de synchronous_commit.

Con synchronous_mode: true, cada COMMIT espera la confirmación del sync standby. En una red privada local, cada COMMIT espera la confirmación del standby síncrono: el plazo depende de la latencia de red entre nodos (compruebe pg_stat_replication.replay_lag). En una red más extensa (nodos en centros de datos diferentes), esa latencia puede afectar a las aplicaciones con escrituras intensivas. En ese caso, pase a synchronous_mode: false + replicación asíncrona: pierde la garantía de cero pérdida de datos si el líder falla, pero las escrituras siguen siendo rápidas. Es un arbitraje que conviene documentar explícitamente en su configuración.

Endurecimiento: TLS con autenticación mutua entre nodos

Por defecto, la comunicación de etcd y las conexiones de replicación de PostgreSQL circulan en claro por la red privada. En una red compartida o en un entorno multi-tenant, active TLS con autenticación mutua.

Para etcd, genere una CA y certificados por nodo, y después añada en etcd.conf.yml:

client-transport-security:
  cert-file: /etc/etcd/tls/server.crt
  key-file: /etc/etcd/tls/server.key
  trusted-ca-file: /etc/etcd/tls/ca.crt
  client-cert-auth: true
peer-transport-security:
  cert-file: /etc/etcd/tls/peer.crt
  key-file: /etc/etcd/tls/peer.key
  trusted-ca-file: /etc/etcd/tls/ca.crt
  peer-client-cert-auth: true

Para la replicación de PostgreSQL, utilice sslmode=verify-full en los parámetros de conexión primary_conninfo de Patroni. Así, cada réplica verifica el certificado del líder.

Solución de problemas: los cinco errores más frecuentes

1. Quórum de etcd perdido: el clúster se niega a elegir un líder.

Síntoma: patronictl list muestra todos los nodos en running pero ningún Leader. Causa: un nodo etcd es inaccesible y ya no se alcanza el quórum (2 de 3). Compruébelo con etcdctl endpoint status: el nodo defectuoso aparece sin respuesta o con un error de conexión. Corrija el nodo o retírelo provisionalmente del clúster (etcdctl member remove).

2. Desfase NTP: elecciones en bucle.

Síntoma: el líder cambia cada 30 segundos y los logs de Patroni muestran failed to update leader key. Causa: el reloj de un nodo se desvía más de un segundo. Compruébelo con chronyc tracking en cada nodo y corríjalo antes de reiniciar Patroni.

3. Split-brain potencial: pg_rewind se niega a aplicarse.

Síntoma: un antiguo líder se reinicia y Patroni se niega a reintegrarlo como réplica, con el error could not connect to the target server: pg_rewind target server must be in standby mode. El servidor siguió escribiendo tras la pérdida del lease. Solución: pg_rewind --target-pgdata=/var/lib/postgresql/17/main --source-server='host=NEW_LEADER_IP ...', y después reinicie Patroni.

4. Conexión peer rechazada: falta la entrada en pg_hba.conf.

Síntoma: la replicación se inicializa pero falla con FATAL: no pg_hba.conf entry for replication connection. La regla host replication replicator 0.0.0.0/0 scram-sha-256 no está presente en la sección pg_hba del patroni.yml. Añádala mediante patronictl edit-config, no directamente en pg_hba.conf.

5. Lag persistente tras el failover: max_wal_senders insuficiente.

Síntoma: la réplica muestra un lag que no disminuye tras la promoción. Causa frecuente: max_wal_senders es demasiado bajo (valor por defecto de 10 en algunas versiones) y el slot de replicación está saturado. Auméntelo a 20 mediante patronictl edit-config (parámetro max_wal_senders) y recargue.

Un clúster que se administra, no que se improvisa

Patroni 4.1 con etcd 3.6 cubre lo esencial de lo que aporta un servicio gestionado en materia de disponibilidad: elección automática, replicación síncrona, API de pilotaje. La diferencia está en el control: acceso root, extensiones libres, costo fijo y la posibilidad de depurar el nodo que flaquea en lugar de esperar a un soporte externo.

El requisito operativo no es la complejidad de Patroni — el procedimiento anterior lo demuestra. Es la disciplina en tres puntos: NTP sincronizado, copias de seguridad verificadas con regularidad y un runbook de failover probado antes de que llegue la avería.

Para empezar, un clúster Patroni de 3 nodos requiere tres VPS con acceso root, IPv4 dedicada y red privada. Consulte la guía de instalación básica PostgreSQL en VPS y la comparativa autoalojado frente a Amazon RDS para elegir el enfoque que corresponda a su carga.

Tres VPS para un clúster Patroni

Un clúster PostgreSQL Patroni de 3 nodos requiere tres VPS con acceso root, IPv4 dedicada y red privada. Todas las plantillas VPS de ServOrbit ofrecen acceso root completo y una red privada entre instancias.

¿Necesita ayuda?

Consulte nuestro centro de ayuda y nuestra FAQ, o contacte con nuestro equipo: llamada, WhatsApp o correo electrónico. Soporte en francés, inglés y árabe.

Escribir por WhatsAppse abre en una pestaña nueva