Blog ·
Cómo un agente elige el servidor de borde
DNS con varias A, bordes de capa 4 y PROXY protocol v2, con las mediciones: qué hace un cliente con varias direcciones y por qué volver es la mitad difícil.
Por Provibr
Quien ejecuta un agente en el hardware de sus clientes tiene la dirección que ese agente marca en dos sitios incómodos: en el DNS público, y en el fichero de credenciales que se repartió durante el alta. Los dos son difíciles de cambiar después, y uno de ellos le cuenta a todo internet dónde está el plano de control.
Nosotros pusimos una capa de servidores de borde delante del nuestro. Esto es lo que costó, incluidas las partes que solo aparecieron cuando pasaron por ahí agentes de verdad.
El problema es la dirección, no el tráfico
El nombre de host de la pasarela resolvía al origen. Lo conocía todo agente dado de alta, y también cualquiera capaz de teclear dig. Una inundación volumétrica apuntada a esa dirección tumba el plano de control para todo el mundo, y después del hecho no queda nada ingenioso que hacer.
El objetivo era estrecho, y enunciarlo mantiene honesto el diseño: que la dirección del origen desaparezca del camino que usan los agentes, de modo que una inundación golpee una máquina reemplazable en diez minutos. Explícitamente no era un objetivo que los bordes protegieran el contenido. El TLS mutuo ya hace eso de extremo a extremo, y un borde capaz de inspeccionar el tráfico sería un retroceso.
Capa cuatro, porque terminar el TLS rompería justo lo que importa
El diseño evidente es un proxy inverso que termina el TLS en el borde. Aquí es el equivocado, por dos razones propias de esta clase de sistema.
La pasarela autentica a los agentes por su certificado de cliente, fijado contra una huella almacenada por agente. Terminar el TLS en el borde acaba ahí el saludo mutuo, así que el origen tendría que fiarse de lo que el borde afirme. Y supondría copiar la clave privada de la pasarela a varias máquinas virtuales baratas cuyo propósito entero es ser desechables.
Así que cada borde es un simple reenviador TCP. Ve texto cifrado, no guarda secretos y no ejecuta ninguna aplicación, lo cual es una superficie lo bastante pequeña como para que «reemplazarlo» responda a casi cualquier problema.
Cuesta una cosa. Ahora toda conexión parece originarse en el borde, así que el origen ya no sabe quién llama.
PROXY protocol v2, y la diferencia entre una afirmación y una medición
El arreglo estándar es PROXY protocol: el reenviador antepone a cada conexión una pequeña cabecera binaria que lleva la dirección original. La pasarela la analiza y devuelve la dirección real a sus registros.
La decisión de diseño que merece copiarse es conservar dos valores en lugar de uno.
- La dirección de la cabecera es lo que el otro extremo dice que es el cliente. Es una afirmación.
- La dirección del socket exterior, la máquina de la que vinieron realmente los bytes, es algo que medimos nosotros.
Una cabecera falsificada puede hacer mentir a la primera. La segunda sigue siendo cierta. Mantenerlas como campos separados hace que una línea de registro se pueda leer años después sin preguntarse cuál de las dos es, y hace fácil la respuesta honesta a «podemos fiarnos de la IP»: de ella no cuelga nada salvo una línea de registro, porque la identidad cuelga del certificado del par.
Dos notas de implementación que nos ahorraron disgustos:
Olfatear, no exigir. Una migración a PROXY protocol no es atómica. Si los primeros bytes parecen un ClientHello de TLS en lugar de la firma del protocolo, se devuelven al flujo y la conexión se reenvía intacta. Eso convierte el despliegue del analizador y su activación en los reenviadores en dos pasos independientes.
Mantener un mapa indexado por la conexión interna. Cuando los bytes llegan a una biblioteca gRPC, la dirección del par es un puerto de bucle local, y tanto el agente como el borde se han vuelto imposibles de encontrar. Indexar el par real por el puerto de origen de la conexión interna, y limpiarlo al cerrar, es el único sitio donde sobrevive esa información.
Ahí dentro se esconde además una pequeña victoria operativa: una conexión que se abre y se cierra sin enviar nada no produce ninguna línea de registro. Las comprobaciones de salud hacen eso constantemente, y no escribirlas eliminó decenas de miles de líneas inútiles al día.
Descubrimiento: el conjunto de A es la lista
¿Cómo se entera el agente de que hay más de un borde? Las opciones eran registros TXT con una carga estructurada, registros SRV, o sencillamente varios registros A sobre el mismo nombre.
Los registros A múltiples ganaron por un solo argumento. El conjunto de A ya es la lista de sitios a los que conectar. Un registro TXT sería una segunda fuente de verdad para el mismo hecho, y las dos pueden discrepar. SRV añadiría pesos que no necesitamos, porque el agente mide por su cuenta en lugar de que se le diga cuál preferir. El TTL es corto, así que retirar un borde es un cambio de DNS más un TTL de paciencia.
Qué hace realmente un cliente con varios registros A
Antes de construir lógica de selección alguna, convenía medir qué se obtiene gratis. Un cliente HTTP moderno prueba las direcciones del conjunto de registros y reparte su tiempo de espera de conexión entre ellas. Pusimos delante una dirección que sabíamos muerta y medimos:
| Comportamiento de la dirección muerta | Tiempo hasta conectar con la viva |
|---|---|
| Conexión rechazada | 7 ms |
| Sin ruta hasta el host | 7,5 ms |
| Descarte silencioso del SYN, espera de conexión de 25 s | 12,5 s |
| Descarte silencioso del SYN, espera de conexión de 10 s | 5,0 s |
El patrón es lo bastante claro como para diseñar contra él: los rechazos duros son gratis, y un agujero negro cuesta aproximadamente el tiempo de espera de conexión dividido entre el número de direcciones. Por tanto la conmutación por error ya funcionaba sin ningún cambio en el agente, de modo que el cambio de DNS pudo salir antes que la lógica de selección. También significa que el tiempo de espera de conexión pasa a ser una función de cuántos registros se publican, cosa que no es evidente hasta tener esta tabla delante.
Conmutar por error no es seleccionar
La conmutación gratuita se queda con la dirección que responda primero, que no es lo mismo que la mejor. Un agente en Ámsterdam se quedó días sentado sobre un borde de otro país, porque ese respondió primero y la sesión vivió luego una semana.
Así que el agente mide. Los detalles que importaron:
Sondear con un saludo TLS completo, no con una conexión TCP. Un borde puede aceptar una conexión y no reenviar nada, que es exactamente el fallo que la sonda tiene que atrapar. Una conexión pelada la contesta con la misma alegría un reenviador roto que uno que funciona.
Dar a cada sonda su propio presupuesto, en paralelo. Un borde que acepta y después se queda callado puede dejar un saludo colgando más allá del tiempo de espera de conexión, así que el presupuesto por dirección va al lado de él en lugar de depender de él.
Persistir la clasificación entera, no el ganador. La dirección elegida y la lista ordenada van al fichero de estado del agente, junto con el conjunto de direcciones sobre el que se midieron. Eso abarata un reinicio, y significa que una conmutación sobrevive a un arranque en frío en lugar de volver alegremente a la dirección que acaba de fallar.
Invalidar por el conjunto, no por la elección. Si se añade o se retira un borde, la medición está rancia aunque la dirección elegida siga dentro.
No hacer nada cuando no hay nada que elegir. Con menos de dos candidatas el mecanismo entero se salta, que es lo que ocurre en desarrollo, donde el nombre resuelve a una sola dirección.
Y la regla que gobierna todo esto: la selección no debe convertirse jamás en un modo de fallo nuevo. El DNS rechaza, no responde nadie, el fichero de estado no se puede escribir: cada uno de esos caminos termina en «sin elección», y el agente conecta exactamente igual que antes de que nada de esto existiera.
Volver es más difícil que conmutar
Conmutar por error es fácil. La mitad difícil es que después el agente se queda en el borde más lento mientras dure esa sesión, que pueden ser semanas.
Así que una vigilancia corre junto a la sesión viva y hace dos cosas y nada más. Prueba las direcciones clasificadas por encima de la actual, con el mismo saludo completo, y vuelve a medirlo todo cuando la última medición es antigua. Los números, y el motivo de cada uno:
- Intervalo de cinco minutos con fluctuación completa, para que las sondas de una flota no se sincronicen. Cada minuto cuesta cinco veces más y no gana nada, porque la histéresis de más abajo hace que cualquier regreso lleve dos rondas de todos modos.
- Una racha de dos antes de volver. Una sonda exitosa suelta es un borde que acaba de terminar de reiniciarse.
- Dos umbrales para la remedición periódica: uno absoluto de 20 ms y uno relativo del 25 %. Hay que cumplir ambos. El absoluto por sí solo aprobaría un salto de 100 ms a 79 ms; el relativo por sí solo aprobaría de 4 ms a 3 ms. Dos bordes del mismo centro de datos se diferencian en menos de un milisegundo, y sin esto intercambiarían el puesto un día sí y otro no.
Cambiar es una reconexión limpia con su propio motivo, colocada junto a los demás finales de sesión previstos en lugar de modelarse como un error. Eso importa más de lo que parece: un error alimentaría la escalera de espera exponencial, así que un agente que vuelve a su borde preferido quedaría castigado con un retardo creciente por hacer lo correcto. El buzón de salida de mensajes se reproduce y el almacén de deduplicación se asegura de que nada se ejecute dos veces.
Mirarlo desde el otro lado
La plataforma vigila lo mismo desde su extremo, y ahí hay dos detalles que merece la pena robar.
Resolver el nombre como lo hace el agente, a través del resolutor del sistema y no con una consulta DNS directa. Una consulta directa falló de plano en una máquina cuyo resolutor apuntaba a sí misma, y sobre todo el resolutor del sistema es lo que usa el tráfico real, así que devuelve la misma lista.
Sacar dos números de un mismo socket: la conexión TCP, que es la ida y vuelta hasta el borde en sí, y el saludo TLS completo, que tiene que viajar por el túnel hasta el origen y volver. La salud cuelga del segundo. Un reenviador con el destino muerto contesta el primero a la perfección.
La última lección es de cortafuegos. Docker se salta por completo el cortafuegos del anfitrión. Las reglas que dicen que solo los bordes pueden alcanzar los puertos de la pasarela tienen que vivir en la cadena que consulta el propio Docker, o serán decorativas mientras parecen activas. Y esa cadena ve el tráfico en ambos sentidos, así que nuestras propias sondas salientes del origen hacia los bordes coincidían con el mismo puerto de destino y se descartaban, con lo que el monitor daba por caído todo borde mientras todos los agentes estaban bien.
Lo que no resuelve
Los bordes no protegen el contenido, y sería un error empezar a tratarlos como una frontera de seguridad. Una dirección que estaba caída durante una medición completa solo se redescubre en la siguiente, un hueco conocido y aceptado. Y el TTL del DNS es un suelo para la rapidez con la que un borde sale de la rotación, que es el precio de dejar que el conjunto de A sea la lista.
Así llega el agente a la plataforma en Provibr, donde solo conecta hacia fuera y guarda las credenciales en el host del propio cliente.