Protocolo agente–plano de control
Contrato entre el plano de control (cloud-web) y el agente de nodo (cloud-web-node). Los dos viven en
repositorios y se publican por separado, así que este documento es lo que tienen en común.
Versión de protocolo vigente: 1.
Transporte y autenticación
- HTTPS en el puerto del listener de agentes (por defecto
:8443), TLS 1.3. POST /agent/v1/joinsolo exige TLS de servidor: el agente fija la CA por huella (--ca-sha256).- El resto de las rutas exigen certificado de cliente emitido por la CA del clúster para un nodo que siga registrado. Eliminar un nodo revoca su acceso.
- Las órdenes se entregan por una cola persistente con confirmación (
ack); el stream SSE solo avisa de que hay algo nuevo.
Endpoints (protocolo 1)
| Método y ruta | Uso |
|---|---|
POST /agent/v1/join |
Canjea un token de unión; devuelve certificado de cliente, CA y peers |
POST /agent/v1/heartbeat |
Latido y capacidad actual del nodo |
GET /agent/v1/peers |
Peers de la malla WireGuard |
GET /agent/v1/commands |
Órdenes pendientes (deploy, stop) |
POST /agent/v1/commands/{id}/ack |
Confirma una orden |
POST /agent/v1/leave[?force=1] |
El nodo pide su propia baja (desinstalación); 409 si aún corre apps, salvo con force=1: el nodo, que está vivo y es quien se borra, puede forzarla y sus despliegues pasan a fallidos |
POST /agent/v1/report |
Estado y logs de despliegues |
GET /agent/v1/routes |
Tabla de rutas para el ingress |
GET /agent/v1/registry |
Configuración del registro de imágenes interno |
GET /agent/v1/stream |
SSE de avisos |
Los esquemas JSON son los de apps/control-plane/internal/api/agent.go y
apps/node-agent/internal/cpclient/cpclient.go; deben coincidir.
Latido y salud (campo opcional)
POST /agent/v1/heartbeat lleva la capacidad del nodo en el nivel superior (cpu_cores, mem_bytes, disk_bytes) y,
desde la versión de agente 0.2, un campo opcional health:
{"cpu_cores": 8, "mem_bytes": 0, "disk_bytes": 0,
"health": {"registry": {"configured": true, "reachable": false, "error": "i/o timeout"},
"mesh": {"peers": 1, "connected": 0}}}
registry.reachablees si el nodo alcanza el registro de imágenes del clúster; solo se informafalsetras dos chequeos seguidos fallidos.registry.configured=falsesignifica que no hay registro que evaluar.mesh.connectedson los peers con un handshake de los últimos 3 minutos.- Es aditivo: no sube el entero de protocolo. Un servidor anterior ignora el campo y un agente anterior no lo envía; en ese caso la salud del nodo es desconocida y el nodo sigue siendo elegible.
- El plano de control trata una salud de más de 60 s como desconocida y marca un nodo degradado solo si el registro está configurado y el nodo no lo alcanza (la malla se muestra pero no decide). Un nodo degradado no recibe instancias nuevas y se recupera solo cuando vuelve a alcanzar el registro.
Capacidades del agente (health.features)
Desde la versión de agente 0.3 el mismo campo health puede llevar features, la lista de capacidades opcionales de ese agente:
{"health": {"registry": {...}, "mesh": {...}, "features": ["static-sites"]}}
static-sites: sabe construir y servir un sitio estático a partir de los ajustes de build de la orden de despliegue.- Es aditivo, igual que
health: no sube el entero de protocolo. Un agente anterior no lo envía y un plano de control anterior lo ignora. - Como la salud caduca a los 60 s, una capacidad sin salud vigente no se supone.
- El plano de control solo exige una capacidad cuando la app lo necesita (ver abajo); para todo lo demás un agente sin
featuressigue siendo un nodo elegible.
Docker Desktop
Un nodo cuyo motor de Docker es Docker Desktop informa registry.configured=true, reachable=false con el motivo
Docker Desktop is not supported on a node…: el chequeo del registro lo hace el agente desde su red, pero quien sube las
imágenes es el daemon, que en Docker Desktop corre en otra VM y no ve la malla. El nodo queda degradado y no recibe despliegues.
Orden de despliegue y sitios estáticos (build)
La orden deploy puede llevar un bloque opcional build con los ajustes de build que el usuario fijó para un sitio estático:
{"deployment_id": "dep_…", "source": {…}, "env": {…}, "port": 0,
"build": {"install_cmd": "npm ci", "build_cmd": "npm run build:prod", "output_dir": "dist", "spa": "yes"}}
- Todos los campos son opcionales;
spaesauto,yesono. Sin el bloque, el agente detecta todo. - Solo se envía en despliegues desde el código (no en un rollback ni en un reprogramado, que reutilizan una imagen ya construida).
- El plano de control valida los valores y el agente vuelve a validarlos (largo, sin saltos de línea, directorio relativo y dentro del proyecto): no confía en el servidor.
- Un agente anterior ignora el bloque. Para que no construya por buildpacks algo que no puede construirse, el planificador solo envía
una app con ajustes explícitos a nodos que informan
static-sites; si no hay ninguno, el despliegue falla diciendo qué nodos hay que actualizar. Una app sin ajustes se envía a cualquier nodo y el agente decide la estrategia (Dockerfile, sitio estático o buildpacks). - Solo las variables de la app con prefijo público (
VITE_,REACT_APP_,NEXT_PUBLIC_,PUBLIC_,GATSBY_) entran al build de un sitio estático; el resto no.
Versionado
Cada petición del agente lleva dos cabeceras:
| Cabecera | Valor |
|---|---|
X-Cloudweb-Agent-Version |
versión del binario (p. ej. 0.1.0) |
X-Cloudweb-Protocol |
entero de protocolo que habla el agente |
- El entero de protocolo solo sube ante cambios incompatibles (quitar o cambiar el significado de un campo, de una ruta o de un flujo). Agregar campos opcionales o rutas nuevas no lo sube.
- Un agente que no envía la cabecera es anterior al versionado y se trata como protocolo
1. - El plano de control declara el rango que entiende (
MinAgentProtocol–MaxAgentProtocol).
Incompatibilidad
Si el protocolo del agente queda fuera del rango, el plano de control responde antes de ejecutar cualquier acción (no cambia estado, no consume el token de unión):
HTTP/1.1 426 Upgrade Required
{
"error": "protocol 2 is not supported: this control plane supports protocols 1 to 1; upgrade the control-plane",
"agent_version": "0.3.0",
"agent_protocol": 2,
"supported_min": 1,
"supported_max": 1,
"upgrade": "control-plane"
}
upgrade indica qué lado debe actualizarse (agent si el agente es más viejo que el mínimo,
control-plane si es más nuevo que el máximo). Una cabecera mal formada da 400.
El agente, al recibir 426, lo registra como error con el rango soportado y baja el ritmo de
reintento a uno cada 5 minutos; vuelve al ritmo normal en cuanto el plano de control lo acepta.
Cómo evolucionar el protocolo
- Cambios compatibles: se hacen en cualquier lado, sin subir el entero.
- Cambios incompatibles: subir
MaxAgentProtocoldel servidor para que acepte ambos protocolos durante una ventana de transición, publicar el servidor, y recién después el agente que habla el nuevo. Retirar el protocolo viejo (MinAgentProtocol) en una versión posterior, avisándolo en las notas. - Cada repositorio prueba su pipeline contra la última versión publicada del otro (ver
tests/e2e/y el pipeline decloud-web-node).