EQProxy Load balancer y reverse proxy en Rust

Operación

Cómo se sabe si EQProxy está sano, qué se puede cambiar sin reiniciar y qué mirar cuando algo falla. Todo lo de esta página se apoya en contadores, líneas de log y comandos que el binario produce.

Comprobaciones de salud

Un backend sin [backend.*.health_check] considera operativos todos sus servidores. Con sección, las comprobaciones son activas y periódicas: type = "tcp" solo conecta; type = "http" hace un intercambio real y evalúa expect_status y, si está configurado, expect_rstring.

Un servidor cae tras fall comprobaciones fallidas consecutivas y vuelve tras rise éxitos. Los backup = true se comprueban también mientras no se usan, para que la primera petición tras fallar un primario no espere a una sonda. Con passive = true, el tráfico real —5xx y fallos de conexión— alimenta la misma máquina de estados.

Cuando no queda ningún servidor elegible: 503 en modo HTTP, cierre en modo TCP, y el contador no_available_server sube en ambos casos. Los estados drain y maintenance se ponen por la API; un drenaje termina cuando el último intercambio en vuelo suelta su guarda, y eso se registra una sola vez.

Métricas

GET /metrics en el plano de administración sirve texto Prometheus de cardinalidad acotada: 62 contadores sin etiquetas, más eqproxy_connections_active, eqproxy_config_reload_last_swap_seconds y eqproxy_build_info{version=…}.

ContadorQué mide
eqproxy_http_requests_proxied_totalIntercambios HTTP servidos (h1, h2, h3, gRPC)
eqproxy_connections_accepted_total, _rejected_totalAceptadas y rechazadas por maxconn
eqproxy_no_available_server_totalPeticiones sin servidor elegible
eqproxy_connect_failures_totalFallos al conectar con el backend
eqproxy_header_timeouts_totalTimeouts de cabecera
eqproxy_rate_limited_total, eqproxy_auth_denied_totalRechazos por rate limit y por autenticación
eqproxy_config_reloads_total, eqproxy_config_reload_failures_totalRecargas aplicadas y rechazadas
eqproxy_backend_connections_reused_totalConexiones reaprovechadas del pool
eqproxy_cache_hits_total, _misses_total, _revalidations_totalRendimiento de la caché
eqproxy_map_reload_failures_totalActualizaciones de mapa rechazadas
eqproxy_access_log_dropped_totalLíneas de access log perdidas por contrapresión

Con [metrics] detailed = true se añaden eqproxy_server_requests_total y eqproxy_server_errors_total (etiquetas backend, server) y eqproxy_route_requests_total (listener, route). max_series cuenta hojas entre las dos familias: una serie que no cabe se rechaza y se cuenta en eqproxy_metrics_detail_dropped_total, nunca se admite en silencio. Cuatro series sin etiquetas —_enabled, _series, _budget y _dropped_total— permiten distinguir «sin tráfico», «detalle apagado» y «el presupuesto lo rechazó».

Sin plano de administración también hay observabilidad: cada --metrics-interval (30 s por defecto) el proceso escribe METRICS con el snapshot de contadores en la salida de error, y una última línea METRICS … (final) al apagarse.

Logs

Hay dos flujos distintos.

{"time":"2026-10-25T03:31:17.518Z","listener":"web","client":"127.0.0.1:51632","protocol":"http/1.1","method":"GET","path":"/probe","host":"127.0.0.1:18181","status":200,"duration_ms":0,"backend":"api","server":"127.0.0.1:18180","user_agent":"curl/8.7.1","bytes":22}

La cadena de consulta nunca se registra. Una cabecera que no llegó a parsearse se registra con campos -; un intercambio que acabó antes de la respuesta lleva estado 0. El sumidero es un canal acotado de 4096 entradas con su propia tarea: si se llena o falla, se pierden líneas y sube access_log_dropped, pero nunca se retrasa una petición.

Recarga en caliente

QuéCómo
Configuración completaSIGHUP al proceso: relee el fichero, valida, resuelve y cambia de generación de forma atómica
Certificados TLSPOST /api/v1/certificates/reload o SIGHUP; los handshakes nuevos usan el certificado nuevo sin tocar las conexiones establecidas
Mapas de datosSolos, cada reload_interval, sin recargar la configuración
Backends y listeners concretosPUT /api/v1/backends/{name} y PUT /api/v1/listeners/{name}
docker kill --signal=HUP eqproxy     # el contenedor reenvía la señal

Un candidato que no valida no cambia nada: se registra el motivo, sube config_reload_failures y la generación anterior sigue sirviendo. Lo que no es recargable: el endpoint y los tokens del plano de administración, el destino y el formato del access log, max_series y, en un listener QUIC vivo, ALPN, client_ca y el rango de versiones TLS — un cambio ahí se rechaza y pide reinicio, nunca se aplica a medias.

Señales

SeñalEfecto
SIGINTApagado ordenado: deja de aceptar, drena lo vivo hasta shutdown_grace y sale con código 0
SIGHUPRecarga en caliente de la configuración

El binario instala manejadores para esas dos señales; no hay manejador propio para SIGTERM, así que su acción por defecto termina el proceso sin drenaje. Si necesitas apagado ordenado, envía SIGINT.

Límites y errores típicos

SíntomaCausa probableDónde mirar
503 con no_available_serverTodos los servidores caídos, drenados o en su maxconnGET /api/v1/status, eqproxy_no_available_server_total
502 inmediatoconnect_timeout o conexión rechazada por el backendeqproxy_connect_failures_total
504server_header_timeout o max_stream_durationeqproxy_header_timeouts_total
408 y cierreEl cliente no manda la cabecera a tiempoclient_header_timeout
413limits.max_body_bytes superadoconfig explain
429Rate limit de la reglaeqproxy_rate_limited_total
400 en una petición válida para otro proxyFraming ambiguo: EQProxy rechaza lo que no tiene una sola lecturaLog de proceso
Recarga que no cambia nadaCandidato inválido o 409 en la APIeqproxy_config_reload_failures_total y el log
Líneas de access log que faltanContrapresión del sumideroeqproxy_access_log_dropped_total

Checklist antes de dar un cambio por bueno

  1. eqproxy config validate --config eqproxy.toml devuelve config OK.
  2. eqproxy config diff actual.toml candidata.toml se lee y no hay sorpresas.
  3. eqproxy doctor --config eqproxy.toml termina en doctor: PASS.
  4. eqproxy config explain sobre las rutas críticas elige el backend esperado y los timeouts son los que crees.
  5. GET /api/v1/status informa la revisión nueva y los servidores en el estado correcto.
  6. Los contadores de rechazo (no_available_server, connect_failures, rate_limited) no se mueven tras el cambio.