Introducción
Keycloak es una plataforma de gestión de identidades y accesos (IAM) de código abierto desarrollada por Red Hat. Proporciona autenticación y autorización centralizada mediante protocolos estándar como OpenID Connect, OAuth 2.0 y SAML 2.0, permitiendo implementar Single Sign-On (SSO) en toda la organización sin necesidad de modificar las aplicaciones.
En entornos de producción, la identidad es un servicio crítico: si Keycloak cae, los usuarios no pueden autenticarse en ninguna aplicación. Por ello, desplegar Keycloak en alta disponibilidad (HA) es imprescindible para garantizar la continuidad del negocio.
En esta guía desplegaremos una arquitectura HA completa con los siguientes componentes:
- 2 nodos Keycloak en clúster activo-activo con Infinispan para replicación de sesiones.
- 3 nodos PostgreSQL gestionados por Patroni y Etcd para alta disponibilidad de la base de datos con failover automático.
- 2 nodos Nginx como balanceadores de carga con Keepalived para una VIP (Virtual IP) con failover automático.
El diagrama de la arquitectura es el siguiente:
Clientes
|
[VIP: 192.168.1.220]
/ \
[lb1: .212] [lb2: .213] <-- Nginx + Keepalived
\ /
(upstream HTTP/8080)
/ \
[kc1: .210] [kc2: .211] <-- Keycloak (clúster Infinispan)
\ /
(JDBC connection)
/ | \
[db1:.214] [db2:.215] [db3:.216] <-- PostgreSQL + Patroni + Etcd
Requisitos previos
Necesitaremos 7 servidores con Debian 12 (Bookworm) o Ubuntu 22.04. A continuación se describe el rol de cada uno:
Hostname IP Rol
-------- --------------- ---------------------------------
kc1 192.168.1.210 Nodo Keycloak 1
kc2 192.168.1.211 Nodo Keycloak 2
lb1 192.168.1.212 Balanceador Nginx + Keepalived (MASTER)
lb2 192.168.1.213 Balanceador Nginx + Keepalived (BACKUP)
db1 192.168.1.214 PostgreSQL + Patroni + Etcd (nodo 1)
db2 192.168.1.215 PostgreSQL + Patroni + Etcd (nodo 2)
db3 192.168.1.216 PostgreSQL + Patroni + Etcd (nodo 3)
VIP 192.168.1.220 IP virtual gestionada por Keepalived
Requisitos adicionales:
- Java 17+ en los nodos kc1 y kc2.
- Resolución DNS o entradas en
/etc/hostspara todos los hostnames. - Acceso root o sudo en todos los nodos.
- Los siguientes puertos deben estar abiertos en el firewall interno:
Puerto Protocolo Componente Entre
-------- --------- ----------------- ---------------------------
2379 TCP Etcd client API db1, db2, db3 <-> db1, db2, db3
2380 TCP Etcd peer db1, db2, db3 <-> db1, db2, db3
5432 TCP PostgreSQL kc1, kc2 -> db1, db2, db3
8008 TCP Patroni REST API db1, db2, db3 internamente
8080 TCP Keycloak HTTP lb1, lb2 -> kc1, kc2
7800 TCP JGroups (Infinispan) kc1 <-> kc2
80/443 TCP Nginx Clientes -> lb1, lb2
112 VRRP Keepalived lb1 <-> lb2
Añade las entradas en /etc/hosts en todos los nodos:
cat >> /etc/hosts <<EOF
192.168.1.210 kc1
192.168.1.211 kc2
192.168.1.212 lb1
192.168.1.213 lb2
192.168.1.214 db1
192.168.1.215 db2
192.168.1.216 db3
EOF
Instalación de PostgreSQL y Patroni
Ejecuta los siguientes pasos en los tres nodos de base de datos (db1, db2 y db3).
Instalamos PostgreSQL 16, Patroni y sus dependencias:
# Actualizar repositorios
apt update && apt upgrade -y
# Instalar dependencias de Python y PostgreSQL
apt install -y python3 python3-pip python3-dev libpq-dev gcc
# Instalar el repositorio oficial de PostgreSQL
apt install -y curl ca-certificates gnupg
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/postgresql-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/postgresql-keyring.gpg] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list
apt update
# Instalar PostgreSQL 16
apt install -y postgresql-16 postgresql-client-16
# Instalar Patroni con soporte para Etcd y PostgreSQL
pip3 install patroni[etcd] psycopg2-binary
# Instalar Etcd
apt install -y etcd
Roles de cada componente:
- PostgreSQL: el motor de base de datos relacional donde Keycloak almacena sus datos.
- Patroni: agente que gestiona el ciclo de vida de PostgreSQL, realiza la elección de líder y ejecuta el failover automático.
- Etcd: almacén distribuido de clave-valor que Patroni utiliza como almacén de consenso (DCS — Distributed Configuration Store) para coordinar qué nodo es el líder.
Detén PostgreSQL para que sea Patroni quien lo gestione:
systemctl stop postgresql
systemctl disable postgresql
Configuración del clúster Etcd
Configura el servicio Etcd en cada uno de los tres nodos de base de datos. El fichero de configuración se encuentra en /etc/default/etcd.
db1 (192.168.1.214):
ETCD_NAME="db1"
ETCD_DATA_DIR="/var/lib/etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.1.214:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.1.214:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.1.214:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.1.214:2379"
ETCD_INITIAL_CLUSTER="db1=http://192.168.1.214:2380,db2=http://192.168.1.215:2380,db3=http://192.168.1.216:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="keycloak-etcd-cluster"
db2 (192.168.1.215):
ETCD_NAME="db2"
ETCD_DATA_DIR="/var/lib/etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.1.215:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.1.215:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.1.215:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.1.215:2379"
ETCD_INITIAL_CLUSTER="db1=http://192.168.1.214:2380,db2=http://192.168.1.215:2380,db3=http://192.168.1.216:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="keycloak-etcd-cluster"
db3 (192.168.1.216):
ETCD_NAME="db3"
ETCD_DATA_DIR="/var/lib/etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.1.216:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.1.216:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.1.216:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.1.216:2379"
ETCD_INITIAL_CLUSTER="db1=http://192.168.1.214:2380,db2=http://192.168.1.215:2380,db3=http://192.168.1.216:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="keycloak-etcd-cluster"
Inicia y habilita el servicio en los tres nodos:
systemctl enable etcd
systemctl start etcd
Verifica el estado del clúster Etcd desde cualquiera de los nodos:
etcdctl --endpoints=http://192.168.1.214:2379,http://192.168.1.215:2379,http://192.168.1.216:2379 endpoint health
La salida esperada es:
http://192.168.1.214:2379 is healthy: successfully committed proposal: took = 2.3ms
http://192.168.1.215:2379 is healthy: successfully committed proposal: took = 2.5ms
http://192.168.1.216:2379 is healthy: successfully committed proposal: took = 2.7ms
Configuración de Patroni
Crea el directorio de configuración y el fichero de datos en los tres nodos:
mkdir -p /etc/patroni
mkdir -p /data/patroni
chown postgres:postgres /data/patroni
chmod 700 /data/patroni
Configuración de Patroni en db1 — /etc/patroni/config.yml:
scope: keycloak-postgres
namespace: /db/
name: db1
restapi:
listen: 192.168.1.214:8008
connect_address: 192.168.1.214:8008
etcd:
hosts: 192.168.1.214:2379,192.168.1.215:2379,192.168.1.216:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: "on"
max_wal_senders: 5
max_replication_slots: 5
wal_log_hints: "on"
initdb:
- encoding: UTF8
- data-checksums
pg_hba:
- host replication replicator 192.168.1.0/24 md5
- host all all 0.0.0.0/0 md5
users:
admin:
password: "AdminPassword123!" # CAMBIAR EN PRODUCCIÓN
options:
- createrole
- createdb
postgresql:
listen: 192.168.1.214:5432
connect_address: 192.168.1.214:5432
data_dir: /data/patroni
bin_dir: /usr/lib/postgresql/16/bin
pgpass: /tmp/pgpass0
authentication:
replication:
username: replicator
password: "ReplicaPassword123!" # CAMBIAR EN PRODUCCIÓN
superuser:
username: postgres
password: "PostgresPassword123!" # CAMBIAR EN PRODUCCIÓN
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
Configuración de Patroni en db2 — /etc/patroni/config.yml (solo cambian name, listen y connect_address):
scope: keycloak-postgres
namespace: /db/
name: db2
restapi:
listen: 192.168.1.215:8008
connect_address: 192.168.1.215:8008
etcd:
hosts: 192.168.1.214:2379,192.168.1.215:2379,192.168.1.216:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: "on"
max_wal_senders: 5
max_replication_slots: 5
wal_log_hints: "on"
initdb:
- encoding: UTF8
- data-checksums
pg_hba:
- host replication replicator 192.168.1.0/24 md5
- host all all 0.0.0.0/0 md5
users:
admin:
password: "AdminPassword123!" # CAMBIAR EN PRODUCCIÓN
options:
- createrole
- createdb
postgresql:
listen: 192.168.1.215:5432
connect_address: 192.168.1.215:5432
data_dir: /data/patroni
bin_dir: /usr/lib/postgresql/16/bin
pgpass: /tmp/pgpass0
authentication:
replication:
username: replicator
password: "ReplicaPassword123!" # CAMBIAR EN PRODUCCIÓN
superuser:
username: postgres
password: "PostgresPassword123!" # CAMBIAR EN PRODUCCIÓN
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
Configuración de Patroni en db3 — /etc/patroni/config.yml:
scope: keycloak-postgres
namespace: /db/
name: db3
restapi:
listen: 192.168.1.216:8008
connect_address: 192.168.1.216:8008
etcd:
hosts: 192.168.1.214:2379,192.168.1.215:2379,192.168.1.216:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: "on"
max_wal_senders: 5
max_replication_slots: 5
wal_log_hints: "on"
initdb:
- encoding: UTF8
- data-checksums
pg_hba:
- host replication replicator 192.168.1.0/24 md5
- host all all 0.0.0.0/0 md5
users:
admin:
password: "AdminPassword123!" # CAMBIAR EN PRODUCCIÓN
options:
- createrole
- createdb
postgresql:
listen: 192.168.1.216:5432
connect_address: 192.168.1.216:5432
data_dir: /data/patroni
bin_dir: /usr/lib/postgresql/16/bin
pgpass: /tmp/pgpass0
authentication:
replication:
username: replicator
password: "ReplicaPassword123!" # CAMBIAR EN PRODUCCIÓN
superuser:
username: postgres
password: "PostgresPassword123!" # CAMBIAR EN PRODUCCIÓN
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
Crea el servicio systemd para Patroni en los tres nodos (/etc/systemd/system/patroni.service):
[Unit]
Description=Patroni - High Availability PostgreSQL
After=syslog.target network.target etcd.service
Wants=etcd.service
[Service]
Type=simple
User=postgres
Group=postgres
ExecStart=/usr/local/bin/patroni /etc/patroni/config.yml
KillMode=process
TimeoutSec=30
Restart=no
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable patroni
systemctl start patroni
Verifica el estado del clúster Patroni:
patronictl -c /etc/patroni/config.yml list
Deberías ver una salida similar a esta, donde uno de los nodos es el líder (Leader) y los demás son réplicas (Replica):
+ Cluster: keycloak-postgres (7890123456789012345) +----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+-----------------+---------+---------+----+-----------+
| db1 | 192.168.1.214:5432 | Leader | running | 1 | |
| db2 | 192.168.1.215:5432 | Replica | running | 1 | 0 |
| db3 | 192.168.1.216:5432 | Replica | running | 1 | 0 |
+--------+-----------------+---------+---------+----+-----------+
Creación de la base de datos de Keycloak
Conéctate al nodo líder de Patroni (en el ejemplo, db1) y crea el usuario y la base de datos para Keycloak:
# Conectar como superusuario de PostgreSQL
sudo -u postgres psql -h 192.168.1.214 -p 5432
Una vez dentro de la sesión psql, ejecuta:
-- Crear usuario de Keycloak (cambia la contraseña en producción)
CREATE USER keycloak WITH PASSWORD 'KeycloakDBPassword123!';
-- Crear base de datos
CREATE DATABASE keycloak OWNER keycloak ENCODING 'UTF8' LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0;
-- Otorgar todos los privilegios
GRANT ALL PRIVILEGES ON DATABASE keycloak TO keycloak;
-- Verificar
\l keycloak
\q
Instalación de Keycloak
Ejecuta los siguientes pasos en los dos nodos Keycloak (kc1 y kc2).
Instala Java 17, que es el requisito mínimo para Keycloak 24.x:
apt update
apt install -y openjdk-17-jdk curl
# Verificar versión de Java
java -version
Descarga e instala Keycloak 24.0.5 (la última versión estable disponible):
# Descargar Keycloak
cd /opt
curl -LO https://github.com/keycloak/keycloak/releases/download/24.0.5/keycloak-24.0.5.tar.gz
# Extraer
tar -xzf keycloak-24.0.5.tar.gz
mv keycloak-24.0.5 keycloak
rm keycloak-24.0.5.tar.gz
# Crear usuario del sistema
groupadd -r keycloak
useradd -r -g keycloak -d /opt/keycloak -s /sbin/nologin keycloak
# Asignar permisos
chown -R keycloak:keycloak /opt/keycloak
chmod -R 750 /opt/keycloak
Configuración de Keycloak en modo clúster
Edita el fichero de configuración principal /opt/keycloak/conf/keycloak.conf en ambos nodos. La única diferencia entre kc1 y kc2 es la IP en el parámetro hostname y el parámetro de Infinispan.
Configuración en kc1 (192.168.1.210):
# Base de datos
db=postgres
db-url=jdbc:postgresql://192.168.1.214:5432,192.168.1.215:5432,192.168.1.216:5432/keycloak?targetServerType=primary&loadBalanceHosts=false
db-username=keycloak
db-password=KeycloakDBPassword123!
db-pool-min-size=5
db-pool-initial-size=5
db-pool-max-size=20
# HTTP
http-enabled=true
http-host=0.0.0.0
http-port=8080
https-port=8443
# Hostname (usa la URL pública expuesta por el balanceador)
hostname=https://keycloak.example.com
hostname-admin=https://keycloak.example.com
hostname-strict=false
hostname-strict-backchannel=false
# Proxy
proxy=edge
# Clúster Infinispan
cache=ispn
cache-stack=tcp
# Logging
log=console
log-level=INFO
Configuración en kc2 (192.168.1.211): idéntica a kc1.
Para la comunicación entre nodos del clúster, Keycloak usa JGroups con el protocolo TCPPING. Crea el fichero /opt/keycloak/conf/cache-ispn-tcp.xml en ambos nodos:
# Copiar la plantilla incluida con Keycloak
cp /opt/keycloak/conf/cache-ispn.xml /opt/keycloak/conf/cache-ispn-tcp.xml
Edita la sección JGroups en cache-ispn-tcp.xml para definir los nodos del clúster:
# En kc1, añade la variable de entorno para JGroups TCPPING
# En /opt/keycloak/conf/keycloak.conf añade al final:
cache-ispn-config=conf/cache-ispn-tcp.xml
Añade las variables de entorno de JGroups en el fichero /opt/keycloak/conf/keycloak.conf de cada nodo para que Infinispan descubra los peers vía TCPPING:
# En kc1
export JAVA_OPTS_APPEND="-Djgroups.tcpping.initial_hosts=192.168.1.210[7800],192.168.1.211[7800] -Djgroups.bind.address=192.168.1.210"
# En kc2
export JAVA_OPTS_APPEND="-Djgroups.tcpping.initial_hosts=192.168.1.210[7800],192.168.1.211[7800] -Djgroups.bind.address=192.168.1.211"
Compila y arranca Keycloak en ambos nodos:
sudo -u keycloak /opt/keycloak/bin/kc.sh build
sudo -u keycloak /opt/keycloak/bin/kc.sh start
Crea el servicio systemd en ambos nodos (/etc/systemd/system/keycloak.service):
[Unit]
Description=Keycloak Identity Provider
After=network.target
[Service]
User=keycloak
Group=keycloak
WorkingDirectory=/opt/keycloak
Environment=KEYCLOAK_ADMIN=admin
Environment=KEYCLOAK_ADMIN_PASSWORD=AdminPassword123!
# En kc1:
Environment=JAVA_OPTS_APPEND=-Djgroups.tcpping.initial_hosts=192.168.1.210[7800],192.168.1.211[7800] -Djgroups.bind.address=192.168.1.210
ExecStart=/opt/keycloak/bin/kc.sh start
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=keycloak
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable keycloak
systemctl start keycloak
systemctl status keycloak
Configuración del balanceador de carga Nginx
Ejecuta los siguientes pasos en los dos nodos de balanceo (lb1 y lb2).
Instala Nginx:
apt update
apt install -y nginx
systemctl enable nginx
Crea la configuración del virtual host en /etc/nginx/sites-available/keycloak:
upstream keycloak_nodes {
# Distribución round-robin por defecto
# Para sticky sessions basadas en IP:
ip_hash;
server 192.168.1.210:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.211:8080 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 80;
server_name keycloak.example.com;
# Redirigir HTTP a HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name keycloak.example.com;
# Certificados SSL (Let's Encrypt o corporativos)
ssl_certificate /etc/nginx/ssl/keycloak.crt;
ssl_certificate_key /etc/nginx/ssl/keycloak.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Cabeceras de seguridad
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always;
# Health check endpoint
location /health {
access_log off;
return 200 "OK\n";
}
location / {
proxy_pass http://keycloak_nodes;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# Soporte para WebSocket (usado por la consola de administración)
proxy_cache_bypass $http_upgrade;
}
}
Activa la configuración y recarga Nginx:
ln -s /etc/nginx/sites-available/keycloak /etc/nginx/sites-enabled/
rm /etc/nginx/sites-enabled/default
nginx -t
systemctl reload nginx
Alta disponibilidad del balanceador con Keepalived
Keepalived implementa el protocolo VRRP (Virtual Router Redundancy Protocol) para gestionar una IP virtual (VIP) entre los dos nodos Nginx. Si lb1 cae, la VIP migra automáticamente a lb2 en cuestión de segundos.
apt install -y keepalived
Configuración de lb1 (MASTER) — /etc/keepalived/keepalived.conf:
global_defs {
router_id LB1
}
vrrp_script check_nginx {
script "pgrep nginx"
interval 2
weight -10
}
vrrp_instance VI_KEYCLOAK {
state MASTER
interface eth0 # ajusta a tu interfaz de red
virtual_router_id 51
priority 110
advert_int 1
authentication {
auth_type PASS
auth_pass keycloakVRRP # CAMBIAR EN PRODUCCIÓN
}
virtual_ipaddress {
192.168.1.220/24
}
track_script {
check_nginx
}
}
Configuración de lb2 (BACKUP) — /etc/keepalived/keepalived.conf:
global_defs {
router_id LB2
}
vrrp_script check_nginx {
script "pgrep nginx"
interval 2
weight -10
}
vrrp_instance VI_KEYCLOAK {
state BACKUP
interface eth0 # ajusta a tu interfaz de red
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass keycloakVRRP # CAMBIAR EN PRODUCCIÓN
}
virtual_ipaddress {
192.168.1.220/24
}
track_script {
check_nginx
}
}
Habilita e inicia Keepalived en ambos nodos:
systemctl enable keepalived
systemctl start keepalived
# Verificar que la VIP está asignada en lb1
ip addr show eth0 | grep 192.168.1.220
Verificación y pruebas
Con toda la infraestructura levantada, realiza las siguientes comprobaciones.
1. Acceso a la consola de administración:
Abre un navegador y accede a https://keycloak.example.com/admin. Deberías ver la pantalla de login de Keycloak. Inicia sesión con el usuario admin y la contraseña configurada.
2. Verificar el estado del clúster Keycloak:
# Comprobar logs en kc1 y kc2 para ver que se han unido al clúster
journalctl -u keycloak -f | grep -i "cluster\|ispn\|jgroups"
# Debe aparecer algo como:
# ISPN000094: Received new cluster view for channel ISPN: [kc1|1] (2) [kc1, kc2]
3. Prueba de failover de Keycloak:
# En kc1, para el servicio
systemctl stop keycloak
# Verifica que el servicio sigue respondiendo a través del LB (kc2 debe responder)
curl -s -o /dev/null -w "%{http_code}" https://keycloak.example.com/health/ready
# Respuesta esperada: 200
# Vuelve a arrancar kc1
systemctl start keycloak
4. Prueba de failover de PostgreSQL (Patroni):
# En db1 (líder actual), para Patroni
systemctl stop patroni
# Observa cómo Patroni promueve automáticamente db2 o db3 como nuevo líder
patronictl -c /etc/patroni/config.yml list
# Verifica que Keycloak sigue funcionando (la URL de JDBC apunta a todos los nodos)
curl -s -o /dev/null -w "%{http_code}" https://keycloak.example.com/health/ready
5. Prueba de failover del balanceador (Keepalived):
# En lb1, para Nginx para simular un fallo
systemctl stop nginx
# La VIP debería migrar a lb2 en pocos segundos
# Verificar desde lb2:
ip addr show eth0 | grep 192.168.1.220
# Comprobar conectividad
curl -s -o /dev/null -w "%{http_code}" https://keycloak.example.com/health/ready
Comandos útiles de monitorización:
# Estado del clúster Patroni
patronictl -c /etc/patroni/config.yml list
# Estado detallado de Patroni vía API REST
curl http://192.168.1.214:8008/cluster | python3 -m json.tool
# Estado de Etcd
etcdctl --endpoints=http://192.168.1.214:2379,http://192.168.1.215:2379,http://192.168.1.216:2379 endpoint status --write-out=table
# Logs de Keycloak
journalctl -u keycloak -n 100 --no-pager
# Conexiones activas en Nginx
nginx -T | grep upstream
Consideraciones de seguridad
Una vez que la infraestructura esté funcionando, es fundamental aplicar las siguientes medidas de seguridad antes de pasar a producción:
1. Cambiar todas las contraseñas por defecto:
Todas las contraseñas en este manual son ejemplos. Deben sustituirse por contraseñas seguras generadas aleatoriamente:
# Generar contraseñas seguras de 32 caracteres
openssl rand -base64 32
2. Habilitar TLS en todos los componentes:
- Etcd: configura
ETCD_PEER_CLIENT_CERT_AUTH,ETCD_PEER_CERT_FILEyETCD_PEER_KEY_FILEpara cifrar la comunicación entre peers. - Patroni ↔ Etcd: usa las opciones
certfileykeyfileen la secciónetcddel config de Patroni. - PostgreSQL: habilita SSL en
postgresql.confconssl = ony configura los certificados. - Keycloak: aunque el cifrado SSL se termina en Nginx, considera habilitar HTTPS interno entre Nginx y Keycloak para entornos de alta seguridad.
3. Reglas de firewall entre componentes:
# En los nodos de base de datos: permitir acceso PostgreSQL solo desde kc1 y kc2
ufw allow from 192.168.1.210 to any port 5432
ufw allow from 192.168.1.211 to any port 5432
# Comunicación Etcd solo entre nodos de DB
ufw allow from 192.168.1.214 to any port 2379:2380
ufw allow from 192.168.1.215 to any port 2379:2380
ufw allow from 192.168.1.216 to any port 2379:2380
# En los nodos Keycloak: permitir acceso al puerto 8080 solo desde LBs
ufw allow from 192.168.1.212 to any port 8080
ufw allow from 192.168.1.213 to any port 8080
# Comunicación JGroups solo entre nodos KC
ufw allow from 192.168.1.210 to any port 7800
ufw allow from 192.168.1.211 to any port 7800
4. Restringir el acceso a la consola de administración de Keycloak:
Es recomendable limitar el acceso a /admin solo desde redes de confianza (por ejemplo, la red de administración). En la configuración de Nginx:
location /admin {
allow 192.168.10.0/24; # red de administración
deny all;
proxy_pass http://keycloak_nodes;
# ... resto de directivas proxy ...
}
5. Rotar secretos regularmente y almacenarlos en un gestor de secretos como HashiCorp Vault o el gestor de secretos de tu proveedor cloud.
:wq!