Configuración de sesiones pegajosas en equilibradores de carga
Las sesiones pegajosas (persistencia de sesión) aseguran que las solicitudes del mismo cliente se enruten al mismo servidor backend durante la duración de una sesión. Esto previene la pérdida de sesión cuando las aplicaciones mantienen estado en memoria en lugar de usar almacenamiento de sesión centralizado. Esta guía cubre persistencia basada en cookies en Nginx y HAProxy, técnicas de hash de IP, cookies específicas de aplicación y alternativas de replicación de sesión.
Tabla de contenidos
- Descripción general de sesiones pegajosas
- Persistencia basada en cookies en Nginx
- Persistencia basada en cookies en HAProxy
- Equilibrio de carga con hash de IP
- Cookies específicas de aplicación
- Replicación de sesión
- Tiempos de espera de afinidad de sesión
- Prueba de sesiones pegajosas
- Solución de problemas
Descripción general de sesiones pegajosas
Las sesiones pegajosas utilizan múltiples mecanismos:
- Basado en cookies: El proxy establece/modifica la cookie para enrutar solicitudes
- Basado en IP: Hash de IP de cliente para determinar servidor
- Basado en cookie de aplicación: Usa cookie existente de aplicación para enrutamiento
- IP de origen + Puerto: Hash de fuente de conexión y puerto
Limitaciones de sesiones pegajosas:
- Previene cambios en distribución de carga
- Dificulta el mantenimiento del servidor
- Reduce capacidad efectiva
- Aumenta latencia de solicitud (cálculos de hash)
- Pérdida de sesión en falla del servidor
Mejores alternativas:
- Almacenamiento de sesión distribuido (Redis, Memcached)
- Diseño de aplicación sin estado
- Base de datos de sesión (PostgreSQL, MySQL)
- Caché centralizado
Usar sesiones pegajosas solo cuando sea necesario para aplicaciones con estado.
Persistencia basada en cookies en Nginx
Nginx requiere módulos de terceros para persistencia nativa basada en cookies. Usar la directiva sticky o implementar con map y ruta:
Usando módulo Sticky (si está compilado)
upstream backend {
least_conn;
server 192.168.1.100:8000;
server 192.168.1.101:8000;
server 192.168.1.102:8000;
sticky cookie srv_route expires=1h domain=.example.com path=/ httponly secure;
}
server {
listen 443 ssl http2;
server_name app.example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Usando enrutamiento basado en mapa (sin módulo)
# Define upstream servers with identifiers
upstream backend_1 { server 192.168.1.100:8000; }
upstream backend_2 { server 192.168.1.101:8000; }
upstream backend_3 { server 192.168.1.102:8000; }
# Create map to determine backend based on session ID
map $cookie_session_id $upstream {
# Hash the session cookie to one of three backends
~*^(?<prefix>[a-f0-9]{2}) $prefix;
default "00";
}
server {
listen 80;
server_name app.example.com;
# Extract session hash value
set $session_route $upstream;
location / {
# Route based on session hash
if ($session_route ~* "^00$") { proxy_pass http://backend_1; }
if ($session_route ~* "^01$") { proxy_pass http://backend_2; }
if ($session_route ~* "^02$") { proxy_pass http://backend_3; }
# Set session cookie if not exists
add_header Set-Cookie "session_id=$request_id; Path=/; HttpOnly; Max-Age=3600" always;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Usando hash consistente
Implementar hash consistente para mejor distribución:
# Lua-based consistent hashing (requires ngx_lua module)
upstream backend {
server 192.168.1.100:8000;
server 192.168.1.101:8000;
server 192.168.1.102:8000;
}
# Nginx Lua script for consistent hashing
location / {
set_by_lua_file $backend_pool /etc/nginx/lua/consistent_hash.lua $cookie_session_id;
proxy_pass http://$backend_pool;
}
# Create /etc/nginx/lua/consistent_hash.lua
# Local consistent hash function
local function crc32(data)
local CRC32_POLY = 0xEDB88320
local crc = 0xFFFFFFFF
for i = 1, #data do
crc = bit.bxor(crc, string.byte(data, i))
for _ = 1, 8 do
if bit.band(crc, 1) == 1 then
crc = bit.bxor(bit.rshift(crc, 1), CRC32_POLY)
else
crc = bit.rshift(crc, 1)
end
end
end
return bit.bxor(crc, 0xFFFFFFFF)
end
local session_id = ngx.arg[1]
local servers = {"192.168.1.100", "192.168.1.101", "192.168.1.102"}
local hash = crc32(session_id)
local selected = servers[hash % #servers + 1]
return selected .. ":8000"
Persistencia basada en cookies en HAProxy
HAProxy proporciona persistencia nativa basada en cookies:
Persistencia básica de cookies
global
log stdout local0
stats socket /run/haproxy/admin.sock
defaults
mode http
timeout connect 5000
timeout client 50000
timeout server 50000
frontend web_in
bind *:80
default_backend web_servers
backend web_servers
balance roundrobin
# Enable cookie-based persistence
cookie SERVERID insert indirect secure httponly
server srv1 192.168.1.100:8000 check cookie srv1
server srv2 192.168.1.101:8000 check cookie srv2
server srv3 192.168.1.102:8000 check cookie srv3
Parámetros:
SERVERID: Nombre de cookieinsert: Añadir nueva cookie si faltaindirect: No eliminar cookie establecida por HAProxysecure: Establecer bandera segura para HTTPShttponly: Establecer bandera HttpOnlycookie srv1: Identificador del servidor
Configuración avanzada de cookies
backend web_servers
balance roundrobin
# Cookie with domain, path, and expiration
cookie SERVERID insert indirect secure httponly nocache domain .example.com path /
# Use existing application cookie for routing
appsession JSESSIONID len 52 timeout 1h
server srv1 192.168.1.100:8000 check cookie srv1
server srv2 192.168.1.101:8000 check cookie srv2
Cookie con manejo de fallo
backend web_servers
balance roundrobin
cookie SERVERID insert indirect httponly
# Primary servers
server srv1 192.168.1.100:8000 check cookie srv1
server srv2 192.168.1.101:8000 check cookie srv2
# Backup servers (if session lost)
server srv3 192.168.1.102:8000 check cookie srv3 backup
server srv4 192.168.1.103:8000 check cookie srv4 backup
Sesiones pegajosas con múltiples rutas
backend web_servers
balance roundrobin
cookie SERVERID insert indirect secure httponly
stick-table type string len 32 size 100k expire 30m
stick on cookie(JSESSIONID)
server srv1 192.168.1.100:8000 check cookie srv1
server srv2 192.168.1.101:8000 check cookie srv2
server srv3 192.168.1.102:8000 check cookie srv3
Equilibrio de carga con hash de IP
Hash de IP (enrutamiento basado en IP de origen) proporciona persistencia sin cookies:
Hash de IP en Nginx
upstream backend {
ip_hash;
server 192.168.1.100:8000 weight=3;
server 192.168.1.101:8000 weight=2;
server 192.168.1.102:8000 weight=1;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://backend;
# Important: Use X-Forwarded-For only if trusted sources
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Características del hash de IP:
- Determinista: La misma IP de cliente siempre se enruta al mismo servidor
- No requiere cookies
- Sobrevive transiciones proxy/NAT (si está detrás del mismo NAT)
- Servidor inactivo causa remapeo para aproximadamente 1/N clientes
Hash de origen en HAProxy
backend web_servers
balance source
server srv1 192.168.1.100:8000 check
server srv2 192.168.1.101:8000 check
server srv3 192.168.1.102:8000 check
Con seguimiento de conexión de cliente:
backend web_servers
balance source
# Track connections by source IP
stick-table type ip size 100k expire 1h
stick on src
server srv1 192.168.1.100:8000 check
server srv2 192.168.1.101:8000 check
Cookies específicas de aplicación
Enrutamiento basado en cookies de sesión de aplicación:
HAProxy appsession
backend web_servers
# Use existing JSESSIONID (Java)
appsession JSESSIONID len 52 timeout 1h
server srv1 192.168.1.100:8000 check
server srv2 192.168.1.101:8000 check
server srv3 192.168.1.102:8000 check
Nginx con cookie de aplicación
# Map application session ID to backend
map $cookie_phpsessionid $php_backend {
~(?P<hash>.+) http://backend;
}
upstream backend {
server 192.168.1.100:8000;
server 192.168.1.101:8000;
server 192.168.1.102:8000;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header X-Session-ID $cookie_phpsessionid;
}
}
Enrutamiento basado en valor de cookie
frontend web_in
bind *:80
# Extract customer ID from session cookie
set-var(sess.customer_id) cookie(sessionid)
use_backend gold_servers if { var(sess.customer_id) -m reg -i ^gold_ }
use_backend silver_servers if { var(sess.customer_id) -m reg -i ^silver_ }
default_backend bronze_servers
backend gold_servers
balance roundrobin
server srv1 192.168.1.110:8000 check
server srv2 192.168.1.111:8000 check
backend silver_servers
balance roundrobin
server srv3 192.168.1.120:8000 check
backend bronze_servers
balance roundrobin
server srv4 192.168.1.130:8000 check
Replicación de sesión
Alejarse de sesiones pegajosas replicando sesiones:
Almacenamiento de sesión basado en Redis
Configurar aplicación para usar Redis:
# Install Redis
sudo apt install redis-server
sudo systemctl start redis-server
# Verify Redis
redis-cli ping
Ejemplo con Spring Boot (Java):
# application.yml
spring:
session:
store-type: redis
redis:
host: localhost
port: 6379
timeout: 2000ms
Ejemplo con Node.js (Express):
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');
const redisClient = createClient();
redisClient.connect();
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: 'secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
maxAge: 1800000
}
}));
Almacenamiento de sesión en Memcached
Usar Memcached para caché de sesión distribuido:
# Install Memcached
sudo apt install memcached
sudo systemctl start memcached
Configurar aplicación (ejemplo PHP):
<?php
ini_set('session.save_handler', 'memcached');
ini_set('session.save_path', 'localhost:11211');
session_start();
$_SESSION['user_id'] = 123;
?>
Almacenamiento de sesión en base de datos
Almacenar sesiones en base de datos compartida:
-- Create sessions table
CREATE TABLE sessions (
id VARCHAR(255) PRIMARY KEY,
user_id INT,
data TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
expires_at TIMESTAMP,
INDEX(expires_at)
);
Configurar HAProxy para usar backend de base de datos:
backend session_db
mode tcp
balance roundrobin
server db1 192.168.1.200:5432 check
server db2 192.168.1.201:5432 check
Tiempos de espera de afinidad de sesión
Configurar duraciones de persistencia de sesión:
Tiempo de espera en Nginx
upstream backend {
least_conn;
keepalive 32;
keepalive_timeout 60s;
server 192.168.1.100:8000;
server 192.168.1.101:8000;
}
# Sticky using Traefik-style header
map $http_x_session_id $session_backend {
~(?P<sid>.+) http://backend;
}
server {
location / {
proxy_pass http://backend;
# Session timeout
proxy_read_timeout 30s;
proxy_send_timeout 30s;
proxy_connect_timeout 5s;
}
}
Tiempo de espera en HAProxy
backend web_servers
balance roundrobin
# Cookie expires in 1 hour
cookie SERVERID insert indirect max-age 3600
# Stick table expires in 30 minutes
stick-table type ip size 100k expire 1800s
stick on src
timeout server 30s
timeout connect 5s
server srv1 192.168.1.100:8000 check inter 2000
Prueba de sesiones pegajosas
Probar persistencia basada en cookies:
# Extract session cookie
COOKIE=$(curl -s -c - http://app.example.com/ | grep -i server | awk '{print $NF}')
# Make multiple requests with same cookie
for i in {1..5}; do
curl -s -b "SERVERID=$COOKIE" http://app.example.com/ | grep -i server
done
Probar enrutamiento con hash de IP:
# Multiple requests from same IP should go to same server
for i in {1..5}; do
curl -s http://app.example.com/ | head -1
done
# Test from different IPs (using VPN or proxy)
for ip in 1.2.3.4 5.6.7.8 9.10.11.12; do
curl -s --interface $ip http://app.example.com/ | head -1
done
Verificar replicación de sesión:
# Check Redis sessions
redis-cli
> KEYS *
> GET session:*
# Check session count
> DBSIZE
# Monitor session access
redis-cli MONITOR
Solución de problemas
Verificar configuración de sesiones pegajosas:
# Nginx check
nginx -T | grep -A 10 "upstream"
# HAProxy check
haproxy -f /etc/haproxy/haproxy.cfg -c
echo "show backend" | socat - /run/haproxy/admin.sock
Monitorear cookies de sesión:
# Capture cookie traffic
tcpdump -A -s 1024 'tcp port 80' | grep -i "set-cookie"
# Monitor cookie with curl
curl -v -c cookies.txt http://app.example.com/ | head -20
cat cookies.txt
# Verify cookie attributes
curl -i http://app.example.com/ | grep -i "set-cookie"
Verificar enrutamiento del servidor:
# Add tracking headers in proxy
# Test multiple requests capture the Server header
for i in {1..10}; do
curl -s -b "SERVERID=srv1" http://app.example.com/ | grep -i "X-Backend-Server"
done
# Check HAProxy stats
curl http://localhost:8404/stats | grep -i server
Probar escenarios de pérdida de sesión:
# Kill a backend server
ssh 192.168.1.100 "sudo systemctl stop application"
# Attempt request with existing session
curl -b "SERVERID=srv1" http://app.example.com/
# Verify failover to backup
curl -b "SERVERID=srv1" http://app.example.com/ | grep -i server
Conclusión
Las sesiones pegajosas permiten despliegues de aplicaciones con estado pero limitan la escalabilidad y flexibilidad operativa. Aunque la persistencia basada en cookies y hash de IP resuelven problemas de sesión inmediatos, el almacenamiento de sesión distribuido con Redis o Memcached proporciona escalabilidad y resiliencia superiores. Evalúa arquitectura de aplicación sin estado como la solución preferida, implementando sesiones pegajosas solo cuando sea necesario y con políticas de tiempo de espera claras y mecanismos de respaldo.


