swarmexec_ docs
Operar el cliente — comandos, flags, configuración y la TUI.
swarmexec te ofrece docker exec -it, logs, reenvío de puertos y
gestión de volúmenes contra cualquier contenedor de un Docker
Swarm, desde una sola terminal. Un pequeño agente por nodo (un
servicio global de Swarm) hace el trabajo en su nodo; el cliente
pregunta al manager de Swarm qué nodo ejecuta tu destino y luego se conecta
directamente al agente de ese nodo por mTLS. Esta página documenta el cliente.
Contenido
1. Cómo funciona
Dos binarios, un protocolo de comunicación:
- agent — se ejecuta como un servicio
globalde Swarm (una tarea por nodo), monta el socket de Docker de ese nodo y expone una API gRPC acotada que redirige exec / logs / reenvío de puertos hacia los contenedores de su propio nodo. Lo despliegas una vez conswarmexec init. - client (esta CLI) — se ejecuta en tu estación de trabajo.
Habla con la API del manager de Swarm para averiguar qué nodo
ejecuta tu destino y el id del contenedor, y luego marca directamente al agente
de ese nodo en el puerto
9443. No existe una malla entre agentes.
La conexión con el manager usa tu contexto de la CLI de Docker (de modo que
respeta --context, los bastiones ssh://, mTLS, etc.). La
conexión con el agente se autentica mediante TLS mutuo, o con un secreto compartido
contra un agente autofirmado — consulta Autenticación.
2. Instalación
Requiere Docker Engine 19.03 o posterior (API 1.40) en el manager y en cada nodo. swarmexec rechaza un daemon más antiguo ya al conectar, nombrando ambas versiones, en lugar de conectarse y luego ir fallando vista por vista. Dos funciones piden algo más al daemon del nodo: el uso de recursos en vivo y la vista de imágenes por nodo necesitan Docker 23.0 (API 1.41/1.42); por debajo quedan vacías y lo dicen, en vez de fallar.
El cliente es un único binario estático. Descarga el build de la última versión para tu plataforma:
Builds: linux-amd64,
linux-arm64,
darwin-arm64,
darwin-amd64,
windows-amd64.
La imagen del agente es pública en Docker Hub como
logleio/swarmexec-agent.
3. Inicio rápido
Apunta tu contexto de Docker a un manager de Swarm y luego aprovisiona los agentes
una sola vez. init crea un secreto compartido, despliega el agente en
cada nodo en modo autofirmado y escribe una configuración de cliente coincidente —
de modo que los demás comandos funcionan de inmediato.
Retira los agentes de nuevo con swarmexec down (deja intacta tu
configuración de cliente).
4. Selectores de destino
exec, logs y port-forward reciben un
destino como primer argumento. Se resuelve en este orden:
| Forma | Ejemplo | Significado |
|---|---|---|
service | web | La tarea en ejecución del servicio. Si tiene más de una réplica, es ambiguo — véase más abajo. |
service.slot | web.2 | Un slot de réplica concreto. Durante una actualización progresiva gana la tarea más nueva del slot. |
task-id | xxh8k1… | Un ID de tarea de Swarm. |
container-id | 3f9a2b… | Un prefijo de ID de contenedor. Necesita un nodo: pasa --node, o se encuentra escaneando las tareas en ejecución. |
Ambigüedad. Un nombre de servicio sin más, con varias réplicas, no
puede resolverse a una única tarea. exec te pide elegir de una lista
numerada cuando se ejecuta de forma interactiva; logs y
port-forward nunca preguntan — imprimen la lista de candidatos y
terminan. Desambigua con un slot (web.0, web.1, …).
5. Configuración
Los ajustes provienen de cuatro capas, cada una sobrescribiendo a la anterior:
Un flag solo sobrescribe cuando realmente lo pasas, así que un valor del archivo o del entorno se mantiene salvo que se sobrescriba explícitamente.
Archivo de configuración
La ruta por defecto es la primera de estas que esté definida:
$SWARMEXEC_CONFIG$XDG_CONFIG_HOME/swarmexec/config.yaml~/.config/swarmexec/config.yaml
Cámbiala con --config <path>. El archivo es YAML; un archivo
ausente no es problema, uno malformado es un error. Se escribe con permisos
0600 (puede contener el secreto compartido). El archivo de log se
ubica junto a él por defecto — ~/.config/swarmexec/swarmexec.log —
salvo que definas --log-file. Claves:
| Clave | Tipo | Significado |
|---|---|---|
ca | string | certificado CA que verifica el certificado de servidor del agente (mTLS) |
cert | string | certificado de cliente — su CN es tu identidad de operador |
key | string | clave privada del cliente |
port | int | puerto del agente (por defecto 9443) |
addr_mode | string | hostname (por defecto) o ip — cómo marcar a un nodo |
server_name | string | sobrescribe el nombre de servidor TLS usado para verificar al agente |
agent_secret | string | secreto compartido para un agente autofirmado |
agent_secret_file | string | lee el secreto de este archivo (tiene prioridad sobre agent_secret) |
insecure | bool | omite la verificación del certificado de servidor del agente (agentes autofirmados) |
legacy_secret | bool | envía además el secreto en bruto, para agentes anteriores a v1.17.3 — desactivado por defecto; ver abajo |
operator | string | identidad de auditoría cuando no se usa certificado de cliente (por defecto: usuario del SO) |
logs.format | string | formato de log por defecto para logs y la TUI: classic | json | logfmt | gelf | raw (vacío = classic) |
logs.min_level | string | nivel mínimo por defecto: trace..fatal (omítelo para no filtrar por nivel) |
ui.dim | float | cuánto se atenúa el fondo detrás de un overlay abierto, una fracción 0–1 (por defecto 0.6; 0 = sin atenuación) |
La sección logs: define valores por defecto para el análisis y el
filtrado de logs según formato; los flags --log-format /
--min-level del comando logs los sobrescriben:
Inspecciona la configuración efectiva y combinada (con el secreto enmascarado) con:
addr-mode
hostname (por defecto) marca al hostname reportado por el nodo;
ip marca a su dirección anunciada. Usa ip cuando los
hostnames de los nodos no sean resolubles desde tu estación de trabajo —
init escribe addr_mode: ip en la configuración generada
precisamente por ese motivo. El líder del swarm reporta 0.0.0.0 para sí
mismo; el cliente recupera su dirección real automáticamente a partir de la lista de
pares de raft.
Variables de entorno
| Variable | Define |
|---|---|
SWARMEXEC_CONFIG | ruta del archivo de configuración |
SWARMEXEC_CA / _CERT / _KEY | material mTLS |
SWARMEXEC_PORT | puerto del agente |
SWARMEXEC_ADDR_MODE | hostname / ip |
SWARMEXEC_SERVER_NAME | nombre de servidor TLS |
SWARMEXEC_AGENT_SECRET / _FILE | secreto compartido / archivo del secreto |
SWARMEXEC_INSECURE | omite la verificación (1/true/yes/on) |
SWARMEXEC_OPERATOR | identidad de auditoría |
SWARMEXEC_UI_DIM | atenuación del fondo del overlay (ui.dim) |
SWARMEXEC_KEYS | ruta del archivo de atajos de la TUI (por defecto keys.yaml junto a la configuración) |
SWARMEXEC_SSH_MULTIPLEX | 0/off/false/no desactiva las conexiones ssh compartidas (véase Conexiones ssh compartidas) |
DOCKER_CONTEXT | contexto de Docker para la API del manager |
XDG_CONFIG_HOME | base para la ruta de configuración por defecto |
6. Autenticación
El cliente se autentica ante el agente en uno de dos modos.
Modo A — TLS mutuo (por defecto)
Define ca, cert y key (los tres son
obligatorios). El cliente verifica al agente contra tu CA y presenta su
certificado; el agente te autoriza y te audita por el CN del
certificado. Este es el modo por defecto y la postura recomendada.
Modo B — agente autofirmado + secreto compartido
Más sencillo de operar (un solo secreto, sin PKI) — es lo que configura
init. Define agent_secret (o
agent_secret_file), y bien una ca para verificar al agente
o insecure: true para omitir la verificación. Un certificado de
cliente es opcional (pero cert y key deben definirse juntos
o ambos estar vacíos). Tu identidad de auditoría es el valor de operator
(por defecto: tu usuario del SO).
ca en cualquier red en la que no confíes, y mantén el secreto
fuera del historial de tu shell (usa agent_secret_file o el archivo
de configuración).
Actualizar a v1.17.3 — primero los agentes
invalid or missing agent secret aunque tu secreto sea correcto.
Actualízalos primero:
legacy_secret: true en la
configuración del cliente envía también el secreto en bruto, con la exposición
descrita arriba. Quítalo en cuanto los agentes estén al día, y añade
-allow-legacy-secret=false al agente para que ningún cliente pueda
poner la credencial en el cable por accidente.
7. Contexto de Docker y SSH
--context selecciona el contexto de la CLI de Docker usado para la API
del manager. Orden de resolución: --context →
$DOCKER_CONTEXT → $DOCKER_HOST → el contexto activo en
~/.docker/config.json → el socket local
unix:///var/run/docker.sock.
docker — enlaza el SDK de Docker en Go y habla directamente con la API
del manager, leyendo por sí mismo cualquier metadato de contexto desde los archivos.
Todo lo que necesita es un endpoint de manager de Swarm accesible
(las llamadas de resolución de nodos solo funcionan contra un manager):
$DOCKER_HOSTapuntando a un manager remoto portcp://(mTLS) — en ese caso no hay Docker instalado en tu estación de trabajo en absoluto;- un contexto
ssh://— necesita el clientessh(no docker); tanto el tráfico del manager como el del agente se tunelizan por él; - el socket local
unix:///var/run/docker.sock— solo el respaldo por defecto, y la única opción que implica un daemon local.
docker solo es necesaria para crear contextos
con nombre (docker context create) — o créalos con
swarmexec context create <name> --docker-host …, de modo que la
CLI de docker ya no hace falta ni siquiera para eso; usa $DOCKER_HOST
para prescindir por completo de los contextos con nombre. (init lee las
credenciales locales de docker login solo para una imagen de agente
privada — no para la pública por defecto.)
Bastión SSH. Si el host del contexto es un endpoint
ssh://, la API del manager se tuneliza por SSH — y también la conexión
con el agente: dado que los endpoints node:9443 de los nodos
normalmente no son enrutables desde tu estación de trabajo, el cliente tuneliza el
tráfico gRPC del agente por el mismo host SSH automáticamente. No hacen falta flags
adicionales.
8. Flags globales
Estos flags persistentes se aplican a todos los comandos:
| Flag | Por defecto | Descripción |
|---|---|---|
--config | — | ruta del archivo de configuración (por defecto ~/.config/swarmexec/config.yaml) |
--context | — | contexto de docker para la API del manager; admite ssh:// (también $DOCKER_CONTEXT) |
--port | 9443 | puerto del agente |
--addr-mode | hostname | dirección para marcar al nodo: hostname | ip |
--ca | — | certificado CA para verificar al agente (mTLS) |
--cert | — | certificado de cliente (mTLS; el CN es la identidad de operador) |
--key | — | clave privada del cliente (mTLS) |
--server-name | — | sobrescribe el nombre de servidor TLS para la verificación del agente |
--agent-secret | — | secreto compartido para un agente autofirmado |
--agent-secret-file | — | archivo del que leer el secreto compartido |
--insecure | false | omite la verificación del certificado de servidor del agente |
--operator | usuario del SO | identidad de operador reportada para la auditoría |
--log-level | info | verbosidad del log: debug | info | warn | error | off (off desactiva por completo el logging) |
--log-file | — | ruta del archivo de log (por defecto swarmexec.log junto al archivo de configuración) |
--info | — | muestra la versión, la licencia y los datos de contacto |
--version | — | imprime la versión del cliente y del protocolo |
slog, formato de texto) tanto en el archivo de log como en un
buffer circular en memoria. En la TUI, los logs nunca se escriben en la terminal
(eso corrompería la pantalla) — en su lugar el buffer circular alimenta un visor en
vivo dentro de la aplicación (véase La TUI). --log-level
off desactiva por completo el logging; un archivo de log que no puede abrirse
no es fatal (recae solo en el buffer circular).
9. Comandos
init — aprovisionar los agentes
Despliega el agente como un servicio global de Swarm a través de la API del manager: crea un secreto de Docker con el secreto compartido, ejecuta el agente en modo autofirmado en cada nodo (puerto de host 9443) y escribe la configuración de cliente coincidente. Ejecútalo una vez por swarm.
| Flag | Por defecto | Descripción |
|---|---|---|
--image | docker.io/logleio/swarmexec-agent:latest | imagen del agente a desplegar |
--secret | aleatorio | secreto compartido a usar (por defecto: genera uno aleatorio) |
--service-name | swarmexec_agent | nombre del servicio del agente |
--port | 9443 | puerto de host que publica el agente |
--force | false | actualiza el servicio si ya existe |
--save-config | true | escribe la configuración de cliente |
--registry-auth | true | pasa las credenciales locales de registro para que los nodos puedan descargar una imagen privada |
--wait | true | espera a que los agentes arranquen e informa del progreso |
--rollout-timeout | 90s | cuánto esperar a que arranquen los agentes |
--force para
desplegar una nueva versión del agente — véase
volver a desplegar los agentes sin cambiar el secreto.
down — eliminar los agentes
La operación inversa de init: elimina el servicio global del agente y,
por defecto, el secreto de Docker con el secreto compartido. No
toca tu configuración de cliente.
| Flag | Por defecto | Descripción |
|---|---|---|
--service-name | swarmexec_agent | nombre del servicio del agente a eliminar |
--keep-secret | false | no eliminar el secreto de Docker con el secreto compartido |
-y, --yes | false | no pedir confirmación |
doctor — diagnosticar el swarm
Comprueba la conexión con el manager, si el servicio del agente está desplegado, y
sondea el agente de cada nodo listo para verificar su accesibilidad y la
discrepancia de versiones. Imprime una tabla por nodo
(NODE AGENT VERSION PROTO); termina con
código distinto de cero si el manager es inaccesible o algún agente no está sano.
Un nodo con el estado too old (init --force) ejecuta un agente más
antiguo que tu cliente — véase
volver a desplegar los agentes sin cambiar el secreto.
| Flag | Por defecto | Descripción |
|---|---|---|
--connect-timeout | 10s | tiempo de espera de conexión por nodo |
--json | false | emite JSON en lugar de una tabla |
security report — un informe Markdown de todo el clúster
El overlay de riesgos de seguridad analiza las especificaciones
de los servicios y muestra el resultado en pantalla. Esta orden escribe ese mismo
análisis — más las comprobaciones que pertenecen al clúster y no a
ningún servicio concreto — en Markdown, para poder revisarlo lejos de la terminal,
adjuntarlo a un ticket, o guardarlo junto a tus ficheros de stack y compararlo
versión a versión. Sale por stdout salvo que se indique
-o, así que se canaliza tan fácilmente como se guarda.
Cuatro comprobaciones se ejecutan a nivel de clúster, y ninguna de ellas puede responderla la especificación de un servicio:
| Comprobación | Severidad | Qué señala |
|---|---|---|
network-unencrypted — «el tráfico overlay no está cifrado» | medium | una red overlay que transporta tráfico de servicios sin cifrado del plano de datos. Swarm tuneliza el tráfico entre nodos sobre VXLAN en claro a menos que la red se creara con --opt encrypted, y el servicio en ejecución no se ve distinto en ningún caso. Las redes sin nada conectado no se señalan — no transportan nada — y tampoco ingress, que no puede cifrarse en absoluto, de modo que un hallazgo sobre ella jamás podría resolverse |
autolock-disabled — «los managers no tienen autolock» | medium | el almacén raft de los managers no está cifrado en reposo. Contiene cada secreto, cada config y la clave de la CA del clúster, y su clave está en el mismo disco: quien se lleve el disco de un manager se lo lleva todo. Actívalo con docker swarm update --autolock=true y guarda la clave de desbloqueo: un manager reiniciado la pedirá. Si no se puede leer la configuración del swarm, el informe dice autolock-unknown en lugar de suponer — dar por hecho «desactivado» inventaría un hallazgo y dar por hecho «activado» sería una falsa tranquilidad |
agent-proto-mismatch / agent-version-skew / agent-too-old / agent-unreachable | high / medium / low | el agente es lo que aplica la autorización en cada exec, cada log y cada port-forward, así que uno más antiguo que tu cliente puede no aplicar una regla que el cliente da por vigente. Una discrepancia de protocolo es high y pesa más que una diferencia de versión: los dos extremos discrepan sobre el contrato mismo, no sobre qué build lo implementa. Un agente que no respondió se reporta como laguna, no como aprobado. No se reporta desfase para un cliente sin publicar (dev), que difiere de todo agente publicado por construcción — la misma excepción que hace doctor |
unused-secret / unused-config | low | un secreto o config al que ningún servicio hace referencia. Sigue distribuyéndose por el almacén raft y sigue siendo legible para cualquier cosa que alcance un manager — normalmente una credencial que alguien rotó y nunca eliminó, con lo cual el valor antiguo sigue vivo en el clúster mucho después de que todos lo den por ido |
Lo que el informe dice de sí mismo. Se lee lejos de la terminal, por alguien que no estaba cuando se ejecutó, así que lleva su propio contexto: qué clúster, cuándo y con qué build. También tiene una sección Not covered — agentes inalcanzables, configuración del swarm ilegible, lista de nodos vacía — porque el silencio en un informe de seguridad se lee como una tranquilidad sobre terreno que nunca pisó. El orden depende únicamente de los hallazgos, de modo que dos informes de un clúster sin cambios sólo difieren en la marca de tiempo y pueden compararse entre sí; y eso sólo vale algo porque un hallazgo ausente significa no encontrado y nunca no mirado.
El fichero se escribe con 0600, creando los
directorios padre con 0700. Nombra cada servicio, cada red y cada
secreto del clúster junto con cada debilidad encontrada: es un mapa de por dónde
atacar, y no tiene por qué ser legible para cualquier cuenta de la máquina.
| Flag | Por defecto | Descripción |
|---|---|---|
-o, --output | stdout | escribir en este fichero en lugar de stdout |
--connect-timeout | 10s | tiempo de conexión por nodo al comprobar los agentes |
--skip-agents | false | no contactar con los agentes de los nodos. Más rápido, y el desfase de agentes aparece entonces en Not covered en vez de omitirse en silencio |
El mismo informe puede escribirse desde la TUI: pulsa w en el overlay de riesgos de seguridad. Es una recogida nueva de todo el clúster, no un volcado de lo que muestra el overlay, y se ejecuta fuera de la goroutine de la interfaz, de modo que ésta sigue respondiendo mientras habla con cada nodo.
stack export / stack diff — un stack desplegado como fichero
stack export lee un stack desplegado desde el clúster y lo escribe
como YAML con forma de compose. stack diff compara un fichero de
stack con lo que está realmente desplegado e imprime un
diff unificado: una línea + es algo que desplegar el
fichero añadiría, una - es algo que
quitaría. Como git diff --exit-code, termina con
1 cuando hay cualquier diferencia, así que sirve en CI.
En la TUI, E en la pestaña Stacks/Servicios ofrece ambas cosas para el stack bajo el cursor — la fila del stack, un servicio suyo o uno de sus contenedores.
Por qué no compara texto. Un stack desplegado lleva cosas que el
fichero nunca tuvo: el digest de la imagen que el daemon resolvió al desplegar,
sus propios valores por defecto de política de reinicio y actualización, y el
espacio de nombres del stack antepuesto a cada red, secreto y volumen. Comparar
ambos como texto reporta decenas de diferencias para un stack que está exactamente
sincronizado — y eso es peor que no tener herramienta, porque enseña a ignorar la
salida. En su lugar el fichero pasa por el propio cargador y conversor de
compose de docker, el mismo código que usa
docker stack deploy, y después ambos lados se reducen con la
misma función. La pregunta que se responde es «¿cambiaría
algo desplegar este fichero?», no «¿están escritos igual estos dos
ficheros?». Los valores por defecto del daemon se descartan en ambos lados;
un valor que no es el predeterminado sigue apareciendo, así que
no se oculta nada real.
Un export es una descripción, no una copia de seguridad. El valor
de un secreto es de solo escritura en la API del engine —nunca
puede volver a leerse— y el contenido de un volumen vive en los nodos. Por eso
ambos se declaran external, y el fichero exportado lo dice en su
propia cabecera: recrear el stack en otro sitio significa crear antes los
secretos. Los campos de la especificación que este renderizado no transporta
(tty, ulimits, ajustes de seccomp y AppArmor y algunos
más) se nombran en el mismo lugar, porque el fallo peligroso de una herramienta de
comparación no es una respuesta equivocada sino un silencio confiado: «sin
diferencias» llega siempre con sus salvedades.
Las variables se interpolan desde tu entorno, exactamente como hace
docker stack deploy. Un fichero con ${TAG} describe por
tanto un stack distinto para un TAG distinto, y el diff depende del
entorno en el que se ejecuta — lo cual es correcto: fingir lo contrario reportaría
«sin cambios» para un despliegue que cambiaría la imagen.
| Orden | Descripción |
|---|---|
stack ls | los stacks desplegados en el clúster |
stack export <stack> | escribe el stack como YAML de compose; con -o a un fichero, si no a stdout |
stack diff <fichero> [stack] | compara un fichero con el stack desplegado. El nombre del stack es por defecto el del fichero sin extensión; --stack o un segundo argumento lo sustituye. Termina con 1 ante cualquier diferencia |
stack deploy — aplicar un fichero de stack, tras comprobarlo
Carga un fichero de stack, ejecuta las comprobaciones de
seguridad sobre lo que realmente desplegaría, y lo aplica. Lo que hace que
merezca la pena frente a docker stack deploy es el
control previo: el fichero se comprueba antes de crear
nada, y si aparece algo por encima de lo informativo el despliegue
se detiene y pregunta.
En la TUI, E → Deploy a file hace lo mismo: primero los hallazgos, d continúa, Esc se retira. En ese momento no se ha creado nada, que es justamente para lo que sirve la pausa.
Las comprobaciones no son un segundo conjunto de reglas. El
fichero se convierte exactamente en los valores ServiceSpec que el
despliegue enviaría, y sobre ellos corren los ocho analizadores
existentes — las mismas comprobaciones que ponen el escudo en el árbol y
llenan el overlay ! y el informe de seguridad. Dos conjuntos se
separarían: una regla endurecida en un sitio y no en el otro significa que el
control deja pasar un fichero que el árbol marca en cuanto está corriendo, y a
quien se le dijo «nada encontrado» se le dijo algo falso. Además así las
comprobaciones ven lo que el clúster hará, no lo que el fichero
dice; los valores por defecto y las abreviaturas de compose están
en medio.
--yes no significa «ignora los hallazgos». Significa
«no preguntes», y una ejecución que encuentra algo por encima de lo informativo
se niega igualmente y termina con código distinto de cero.
Cualquier otra cosa convertiría el control en un trámite la primera vez que alguien
lo pusiera en CI. Para desplegar pese a los hallazgos hay que escribir
--force: otra cosa que teclear y otra cosa que explicar después. Una
ejecución no interactiva sin respuesta en stdin cuenta como no.
| Flag | Descripción |
|---|---|
--check | comprueba y para. Termina con 1 si encontró algo por encima de lo informativo, así que sirve de control en una pipeline |
-y, --yes | no preguntar. Despliega si las comprobaciones están limpias; se niega si no |
--force | desplegar pese a los hallazgos, sin preguntar |
--prune | elimina los servicios del stack que el fichero ya no declara. Desactivado por defecto, como en docker: un fichero que es un subconjunto del stack es mucho más a menudo un descuido que una orden de borrar |
Qué hace un despliegue, en este orden: redes, luego secretos, luego configs, luego servicios — un servicio que referencia algo que aún no existe falla, y el stack queda aplicado a medias. Las redes externas se comprueban primero, porque una que no existe es la forma más común de que un despliegue se quede a mitad. Un secreto existente nunca se sobrescribe: su valor es inmutable en swarm, así que cambiarlo significa crear uno nuevo con otro nombre. Si aun así un despliegue falla a medias, lo ya aplicado se informa junto al error en lugar de dejarlo a la adivinación.
Volver a desplegar los agentes sin cambiar el secreto
Tarde o temprano tendrás que volver a desplegar los agentes en un swarm ya
aprovisionado: un agente se queda colgado, doctor muestra un nodo
como too old (init --force), un comando falla con «agent is
older than this client (missing RPC) — update it with swarmexec init
--force», o un nodo se ha reinstalado y su agente nunca volvió. La
solución es volver a ejecutar init con --force, que
significa exactamente actualizar el servicio si ya existe. Sin ese flag,
ejecutar init sobre un servicio existente es un error de uso que te
pide añadir --force; no se cambia nada.
Esto vuelve a desplegar el agente en cada nodo y deja intacto el secreto
compartido. Los secretos de Docker son inmutables, así que
init encuentra el swarmexec_agent_secret existente, lo
informa como reusing existing y conserva el valor que ya está en el
clúster. Todo cliente que funcionaba antes sigue funcionando: no hay que
redistribuir nada.
--secret al volver a desplegar. Si el
secreto ya existe no se puede sobrescribir, así que tu valor no se aplica
al clúster, pero sí se escribe en tu configuración de
cliente. A menos que coincida con el valor real del secreto existente, los agentes
te rechazarán a partir de ese momento, y el fallo aparece más tarde con la
apariencia de un agente roto en vez de una configuración local equivocada.
init avisa justo en ese caso; hazle caso. Para cambiar de verdad el
secreto del clúster tienes que eliminar primero el secreto (ningún servicio puede
referenciarlo) y volver a ejecutar init.
--save-config vale true por defecto, así que un
redespliegue también reescribe ~/.config/swarmexec/config.yaml. Pasa
--save-config=false siempre que la configuración local del cliente
deba quedar intacta: por ejemplo, cuando vuelves a desplegar los agentes desde una
máquina cuya configuración ya es correcta, o desde CI.
Después, comprueba el resultado. doctor informa de un estado por
nodo; ok en todos los nodos significa que cliente, agente y secreto
vuelven a estar alineados.
ps — listar tareas
Lista las tareas/contenedores candidatos y el nodo en que se ejecuta cada uno. Acepta
un filtro de servicio opcional. Columnas:
SERVICE SLOT CONTAINER NODE IP UPTIME.
Esto habla solo con la API del manager — funciona incluso antes de que los agentes
sean accesibles.
| Flag | Por defecto | Descripción |
|---|---|---|
--json | false | emite JSON en lugar de una tabla |
exec — ejecutar un comando / abrir una shell
Ejecuta dentro de un contenedor que corre en cualquier lugar del swarm. Sin comando,
abre /bin/sh. Se asigna una TTY automáticamente cuando stdin es una
terminal y no diste ningún comando; fuérzala con -t. El código de salida
propio del comando remoto se propaga tal cual.
| Flag | Por defecto | Descripción |
|---|---|---|
-i, --stdin | true | mantiene stdin abierto |
-t, --tty | auto | asigna una TTY (auto: verdadero solo si stdin es una terminal y no hay comando) |
-u, --user | — | nombre de usuario o UID (p. ej. 1000:1000) |
-w, --workdir | — | directorio de trabajo dentro del contenedor |
-e, --env | — | define variables de entorno (KEY=VALUE, repetible) |
--node | — | pista/anulación de nodo para destinos de tipo container-id |
--connect-timeout | 10s | tiempo de espera para conectar con el agente |
logs — transmitir logs
Transmite los logs de un contenedor desde cualquier lugar del swarm.
| Flag | Por defecto | Descripción |
|---|---|---|
-f, --follow | false | sigue transmitiendo las nuevas líneas de log |
--tail | 0 | líneas desde el final con las que empezar (0 = todas) |
-t, --timestamps | false | antepone una marca de tiempo a cada línea |
--since | 0 | solo logs más nuevos que esto (p. ej. 10m, 1h) |
--log-format | classic | analiza las líneas como classic | json | logfmt | gelf | raw (por defecto desde la configuración, si no classic) |
--min-level | — | solo muestra este nivel y superiores: trace | debug | info | warn | error | fatal |
--grep | — | solo muestra las líneas cuyo mensaje (analizado) coincide con esta expresión regular de Go |
--node | — | pista/anulación de nodo para destinos de tipo container-id |
--connect-timeout | 10s | tiempo de espera para conectar con el agente |
Análisis y filtrado según formato. --log-format le
dice al cliente cómo leer cada línea para poder extraer un nivel y un
mensaje: classic extrae un nivel de una línea de texto plano,
json analiza JSON estilo logstash (campos
level/message), logfmt analiza el estilo
clave=valor que usan muchas aplicaciones Go, el daemon de Docker y las
herramientas de HashiCorp (msg/message,
level/lvl/severity, ts/time),
gelf analiza el JSON GELF de
Graylog (nivel de syslog numérico), y raw deja las líneas sin cambios.
--min-level descarta entonces todo lo que esté por debajo del nivel
elegido, y --grep conserva solo las líneas cuyo mensaje analizado
coincide con la expresión regular.
--min-level, de modo que no se pierden los stack traces multilínea. Los
mismos valores por defecto pueden fijarse una vez en la sección de
configuración logs: (los flags tienen prioridad).
Seguimiento a través del reemplazo de contenedores. Con
-f sobre un destino de tipo service o service.slot,
logs sigue el seguimiento cuando el contenedor que está transmitiendo es
reemplazado por una actualización progresiva, un reinicio o una reprogramación:
vuelve a resolver el contenedor actual en ejecución del servicio (el mismo slot, o el
mismo nodo para un servicio global), se reconecta automáticamente —como
docker service logs -f— e imprime una línea de aviso atenuada
(container replaced; reconnected to <id> on <node>). Espera
hasta ~30s a que se programe un reemplazo antes de rendirse, y se detiene
limpiamente si el servicio se elimina. Un destino de tipo container-id sin
más no tiene sucesor, así que simplemente se detiene como antes.
port-forward (alias pf) — reenviar un puerto local
Enlaza un puerto TCP local y lo reenvía a un puerto dentro de un contenedor, sin
publicar ese puerto en el clúster. El puerto local es por defecto el mismo que el
remoto. Apuntar a un servicio reenvía a exactamente una de sus
tareas (la que resuelve el destino), no entre réplicas. Enlazado a
127.0.0.1 por defecto. Pulsa Ctrl-C para detener.
| Flag | Por defecto | Descripción |
|---|---|---|
--address | 127.0.0.1 | dirección local a enlazar (loopback mantiene el puerto fuera de tu red) |
--node | — | pista/anulación de nodo para destinos de tipo container-id |
--connect-timeout | 10s | tiempo de espera para conectar con el agente |
volume ls — listar volúmenes
Lista los volúmenes de todos los nodos y qué nodos contienen cada uno. Los volúmenes
de Swarm son locales al nodo, así que el cliente consulta cada nodo y los agrega.
Filtro de nombre por subcadena opcional. Columnas:
VOLUME DRIVER NODES USED BY AGE
(más SIZE con --size).
| Flag | Por defecto | Descripción |
|---|---|---|
--size | false | calcula también el tamaño en disco de cada volumen (más lento: du por volumen) |
--sort | name | ordena por: name | nodes | used | age | size (size implica --size) |
--reverse | false | invierte la dirección de ordenación |
--connect-timeout | 10s | tiempo de espera de conexión por nodo |
--json | false | emite JSON en lugar de una tabla |
volume rm — eliminar un volumen
Elimina un volumen en cada nodo que lo contiene (--all) o en nodos
concretos (--node, repetible). Uno de los dos es obligatorio.
| Flag | Por defecto | Descripción |
|---|---|---|
--all | false | elimina en cada nodo que contiene el volumen |
--node | — | elimina solo en estos nodos (repetible) |
--force | false | pasa el flag force de docker |
-y, --yes | false | no pedir confirmación |
--connect-timeout | 10s | tiempo de espera de conexión por nodo |
ui — TUI interactiva
Una vista interactiva de contenedores y volúmenes, con exec, logs y reenvío de puertos integrados. Necesita una terminal interactiva. Consulta La TUI para las teclas.
| Flag | Por defecto | Descripción |
|---|---|---|
--connect-timeout | 10s | tiempo de espera para conectar con un agente |
config show — inspeccionar la configuración
Imprime la configuración de cliente efectiva y combinada con el secreto enmascarado.
context (alias ctx) — gestionar contextos de Docker
Crea y gestiona los contextos de Docker que --context (y
$DOCKER_CONTEXT) resuelven para la API del manager. swarmexec escribe en
el propio almacén en disco de docker, así que los contextos creados aquí son
intercambiables con la CLI de docker — y ya no necesitas docker
instalado ni siquiera para crear uno. El contexto integrado default no
se puede eliminar.
context create <name> — crea un contexto que apunta a un host de manager (recibe exactamente un argumento de nombre):
| Flag | Por defecto | Descripción |
|---|---|---|
--docker-host | — | obligatorio — endpoint del daemon de docker: ssh:// | tcp:// | unix:// | npipe:// |
--description | — | descripción opcional |
--ssh-jump | — | host(s) de salto SSH para un contexto ssh://, separados por comas (ProxyJump / -J multi-salto); se inyectan tanto en la conexión a la API de Docker como en el túnel al agente |
--use | false | además, conviértelo en el contexto actual |
context ls (alias list) — lista los contextos; columnas NAME CURRENT DOCKER ENDPOINT (el activo marcado con *):
context use <name> — fija el contexto actual:
context rm <name> [name...] (alias remove) — elimina uno o más contextos:
| Flag | Por defecto | Descripción |
|---|---|---|
-f, --force | false | obligatorio para eliminar el contexto actual (su selección se restablece a default) |
Conexiones ssh compartidas
Con un contexto ssh://, swarmexec llega al clúster dos veces por
ssh: la API del manager de Docker a través del asistente de conexión de ssh, y
cada agente de nodo por su propio túnel hacia el mismo host. Cada exec, cada flujo
de logs, cada redirección de puertos, cada sondeo de estadísticas y cada ciclo de
refresco abría hasta ahora una conexión ssh nueva — un saludo TCP, un intercambio
de claves y una autenticación por llamada, y otro tanto por host de salto —
contra un bastión que estaba conectado un instante antes.
swarmexec ahora les hace compartir un único transporte, usando el propio
ControlMaster de OpenSSH: la primera conexión a un destino lo abre y
cada una posterior pasa a ser un canal sobre él. Medido en un clúster de tres
nodos detrás de un host de salto, swarmexec doctor pasó de
8 autenticaciones y 3,6 s a 2 y 1,0 s.
El tráfico en sí no cambia, y el efecto crece con lo que se haga en una sesión.
-
Los sockets de control viven en
$XDG_RUNTIME_DIR/swarmexec/ssh/(o, en su defecto, en el directorio de caché), creados con0700— un socket de control es una sesión autenticada viva, así que se queda en su propio árbol de directorios y nunca en/tmp. - Un socket por destino y ruta: dos contextos que alcanzan el mismo manager por hosts de salto distintos no comparten nada, porque no son la misma conexión.
- La conexión compartida sobrevive al comando 60 s, de modo que el siguiente sale barato, y luego termina por sí sola. Después, nada de swarmexec sigue reteniendo una conexión.
- Un socket que deje atrás un maestro terminado de forma abrupta es inofensivo: ssh detecta que está muerto y abre una conexión normal en su lugar.
-
No disponible en Windows, cuyo OpenSSH no implementa conexiones compartidas. En
el resto,
SWARMEXEC_SSH_MULTIPLEX=0lo desactiva — conviene saberlo si usted configuraControlPathpor su cuenta, ya que el ajuste de swarmexec va en la línea de comandos de ssh y por tanto tiene prioridad sobre~/.ssh/config.
10. La TUI
swarmexec ui tiene siete pestañas — Stacks/Services (1),
Volumes (2), Forwards (3),
Networks (4), Secrets (5),
Nodes (6), Configs (7) — una
barra lateral de contextos en el borde derecho, y un pie de dos
líneas: arriba las
indicaciones de teclas por pestaña, y luego una línea de estado con el contexto de docker activo
(ctx <name>, para que siempre quede claro en qué clúster
estás), un resumen del clúster en vivo, el recuento de reenvíos y —en la pestaña Volumes— cuántos
volúmenes tienes seleccionados. Estas teclas funcionan en todas las pestañas:
La barra de pestañas es adaptable: en un terminal más estrecho cambia a etiquetas cortas — St/Sv, Vol, Fwd, Net, Sec, Node, Cfg — para que las siete pestañas sigan visibles en lugar de que las últimas queden recortadas. (Cfg es Configs.) Los dígitos de atajo y las zonas de clic del ratón no cambian.
| Tecla | Acción |
|---|---|
| ? | abre el overlay de atajos — la referencia de teclas completa y viva (se genera a partir de tu mapa de teclas, así que los atajos remapeados aparecen correctamente); el pie de una sola línea solo tiene sitio para las teclas más usadas |
| Tab | pasa a la siguiente pestaña |
| 1–8 | salta a Stacks/Services / Volumes / Forwards / Networks / Secrets / Contexts / Nodes / Configs |
| j k | mueve abajo / arriba (también ↓ ↑) |
| r | refresca la pestaña activa y el resumen del clúster |
| y | copia la lista actual al portapapeles (OSC52) |
| m | alterna la captura del ratón (off = selección/copia propia de tu terminal) |
| ` | abre / cierra el visor de logs en vivo (véase más abajo) |
| q | salir |
Pestaña Stacks/Services
La pestaña 1, antes llamada Containers. Es un único árbol, stack → servicio → contenedor, y en ella viven exec, logs, reenvío de puertos, inspección y los editores de servicio.
Comprobación de versión de :latest. Swarm fija :latest a un digest en el
momento del despliegue, así que un servicio etiquetado :latest en realidad ejecuta una
imagen fija. swarmexec lo resuelve contra el registro (usando tus credenciales locales de docker) y
anota la fila del servicio justo después de la URI de la imagen: la
versión real entre paréntesis —leída de la etiqueta
org.opencontainers.image.version de la imagen— y una
flecha hacia arriba ↑ cuando el :latest actual del registro es un
digest más nuevo que aquel al que está fijado el servicio (p. ej.
nginx:latest (1.4.0) ↑).
Es de mejor esfuerzo y se cachea: los errores de registro simplemente dejan la fila sin anotar. La
sección IMAGE del overlay de inspección muestra la misma versión y, cuando existe una
imagen más nueva, una fila seleccionable nueva versión disponible — muévete sobre
ella y pulsa u (o Enter) para actualizar el servicio.
Fijar una versión con u: no solo cuando existe una actualización.
u ya no depende de que se ofrezca una actualización. Está
disponible en cualquier inspección de servicio cuyos tags de imagen swarmexec haya
podido leer del registro —tanto en servicios fijados a una versión como en los que
van en :latest— y desde cualquier fila del overlay, así que nunca
tienes que buscar la pista. Volver a fijar la misma versión, fijar lo que
está corriendo o volver a un tag más antiguo son usos completamente
normales; no hace falta esperar a que exista una actualización. El texto del pie
te dice en qué situación estás: u update version cuando se
encontró una versión más nueva (entonces también está la fila
nueva versión disponible, que lleva un destino concreto) y
u set version cuando no.
Lo que abre u depende de lo que sepa el registro. En un servicio
fijado a una versión con una versión más nueva es el
selector de versión: un campo cuyo autocompletado sugiere los
tags más nuevos de la misma familia, el más alto primero y precargado con
el más alto. En uno que ya va por el tag más nuevo es el mismo selector,
solo que las sugerencias recurren a todos los tags que lista el
repositorio (precargado con el tag en ejecución) — así es como fijas o reviertes.
Un servicio en :latest también recibe el selector, con todos los tags
conocidos: elegir ahí una versión concreta es la manera de sacar un servicio
:latest del tag flotante y fijarlo, que es justo lo que pide el
hallazgo de riesgo unpinned-image (véase el overlay de riesgos de seguridad
más abajo). La única excepción es un servicio :latest con una
actualización de digest pendiente: ese sigue siendo la única
confirmación sobre el nuevo digest del registro que siempre fue, porque un
selector ahí invitaría a escribir latest, que resuelve al tag pelado y
descartaría en silencio precisamente la fijación por digest que la actualización viene a
refrescar.
En el selector la lista de sugerencias está limitada a 25 entradas (un
repositorio muy activo puede listar cientos) y el campo sigue siendo de texto
libre, así que puedes escribir cualquier tag existente, incluido uno más
antiguo para fijarlo o revertir a una versión conocida buena. Un tag escrito a
mano se valida contra los tags del repositorio (uno que no aparezca se rechaza), y elegir
uno más antiguo muestra una advertencia de degradación antes de aplicarlo.
En todos los casos el cambio es un ServiceUpdate / actualización progresiva,
tras una confirmación. Si el registro no devolvió ningún tag y tampoco hay
un destino concreto al que recurrir —un registro privado o inalcanzable, o un servicio
fijado por digest— recibes un aviso explicativo y no se cambia nada.
| Tecla | Acción |
|---|---|
| / | abre la barra de búsqueda (filtra por servicio / contenedor / nodo) |
| h l | colapsa / expande recorriendo los tres niveles. h colapsa la fila bajo el cursor —un stack colapsa todo el grupo, un servicio expandido colapsa sus contenedores— y donde ya no queda nada que colapsar sube hacia fuera, al padre, de modo que pulsarla repetidamente recorre contenedor → servicio → stack. l expande la fila bajo el cursor, o desciende a su primer hijo si ya está abierta |
| Enter | sobre un contenedor: abre el menú de acciones; sobre un servicio o un stack: lo colapsa / expande |
| s | alterna la agrupación por stack — árbol agrupado ⟷ lista plana de servicios (ver más abajo) |
| L | logs (L mayúscula) — sobre un contenedor sus propios logs, sobre un servicio los logs agregados de todas sus tareas |
| ! | abre el overlay de riesgos de seguridad para toda la lista de servicios (ver más abajo) |
| p | reenvía el puerto de la tarea bajo el cursor |
| i | inspecciona el nodo bajo el cursor — un overlay navegable con un resumen tabular; mueve la selección con ↑/↓ o j/k, y/Enter copia la línea seleccionada, una tira de pestañas arriba nombra las tres vistas y 1/2/3 seleccionan directamente tabla / stats / JSON crudo del daemon, mientras que t las rota (Esc/q/i cierra). Sobre un servicio el overlay también lo edita: s escalar, f forzar actualización, p puertos, l etiquetas, e env, n redes, S secretos, v montajes, A alias; D diagnostica por qué no se ejecuta en todas partes; X lo elimina |
| X | elimina un servicio directamente desde el árbol (X mayúscula, es decir Shift+x) — sin pasar por el overlay de inspección. Sobre qué actúa depende de la fila bajo el cursor: en una fila de servicio elimina ese servicio; en una fila de contenedor elimina el servicio propietario del contenedor, porque una tarea suelta no puede eliminarse por sí sola — Swarm la volvería a planificar de inmediato; en una fila de stack no elimina nada y en su lugar avisa brevemente en el pie de que los servicios de un stack se eliminan individualmente, o con docker stack rm <stack>. Pasa por la misma confirmación que la X de la inspección más abajo — borra permanentemente el servicio y detiene todas sus tareas; no se puede deshacer. Tecla mayúscula fija, no una acción remapeable del keymap; el pie la lista en rojo como X remove y el overlay ? también la lista. La X dentro del overlay de inspección no cambia |
Se muestra cada servicio (incluso uno escalado a cero), cada fila renderizada como una
línea de docker service ls —nombre, modo, recuento en ejecución/deseado,
imagen y puertos publicados— y coloreada según cómo le va de verdad: el recuento en
ejecución/deseado fija el color base (aqua = todas las tareas arriba, naranja =
parcial, rojo = caído, gris = escalado a cero) y un healthcheck que
falla lo sobrescribe (véase más abajo). Un marcador
▸/▾ indica si un servicio está colapsado o expandido; sus
contenedores en ejecución se anidan debajo. Un servicio en medio de una
actualización progresiva lleva una insignia de color en su fila del árbol —
⟳ updating o
↺ rolling back — y el mismo estado aparece en el overlay
de inspección del servicio; desaparece en cuanto la actualización se completa.
Coloreada por el healthcheck, no solo por el recuento de réplicas.
El color de una fila de servicio salía únicamente de en ejecución frente a deseado:
así, un servicio con todos sus contenedores fallando el healthcheck seguía
pintándose como un plácido 3/3 en aqua. El recuento era cierto y la
fila engañaba. El estado de tarea de Swarm tampoco ayuda: una tarea figura como
running mientras su contenedor falla cada sonda, de modo que el
veredicto tiene que venir del nodo. Ahora una sonda que falla se trata como una
degradación del mismo tipo que una réplica que falta:
- todos los contenedores comprobados unhealthy → la fila se pone roja, que es tan malo como que no haya ninguno en ejecución;
- algunos unhealthy → naranja, como un despliegue a medias;
- todos healthy → el color que ya le daba el recuento de réplicas, sin tocar;
- contenedores que no declaran healthcheck → la fila no se recolorea en absoluto. No se sabe nada de ellos, y adivinar sería el mismo error en la dirección contraria.
Los marcadores. Una fila de servicio lleva
✖ N unhealthy en rojo, o
◌ N starting en amarillo mientras las
sondas todavía no han pasado; una hoja de contenedor lleva
✖ unhealthy /
◌ starting. Unhealthy manda sobre starting:
es lo que exige actuar. Una fila de stack agrega ese mismo marcador sobre todo lo que
hay dentro (véase Agrupado por stack más abajo). Un contenedor
sano no recibe
ningún marcador, y tampoco lo recibe uno sin
healthcheck, así que el marcador sigue significando algo. Conviven con los
marcadores de recursos cpu/mem de más abajo, y la salud va
primero en la fila: es lo que contradice al recuento que tiene al
lado.
Up 3 days (healthy)), la misma cadena que imprime docker ps.
El parser es deliberadamente estricto: lo que no reconoce se
convierte en sin veredicto y no en una suposición, así que si algún día el
daemon reescribe esa línea, swarmexec deja de afirmar que sabe en vez de dar por
sano un contenedor que falla. Rige además el mismo requisito que para las cifras de
uso: hacen falta agentes de esta release o posteriores, de modo que un clúster que
no se ha vuelto a desplegar no muestra ningún marcador de salud — despliégalos con
swarmexec init --force.
Marcadores de CPU y memoria en vivo. Junto a esas insignias una
fila puede mostrar además lo que está usando ahora mismo: hasta ahora
swarmexec solo podía enseñar lo que el planificador había reservado (véase
la pestaña Nodos más abajo). Un contenedor —o un servicio— cuyo uso supera el
70 % recibe un marcador naranja, y al 90 % uno
rojo, al final de la fila: el recurso nombrado en palabras
—cpu para la CPU, mem para la memoria— seguido del
porcentaje (cpu 94 %,
mem 91 %). Si ambos están calientes,
aparecen los dos. Palabras en vez de símbolos, con el mismo ancho: un marcador cuyo
significado hay que ir a buscar no está haciendo su trabajo, y unas letras no pueden
fallar al dibujarse en ningún terminal. Por debajo del 70 % no se dibuja nada, para
que los marcadores
sigan siendo una señal y no papel pintado. Una fila de servicio
toma la peor de sus réplicas, no la media: una media esconde
justamente el contenedor que está a punto de morir, que es el que interesa ver. (La
memoria absoluta de una fila de servicio es la suma entre réplicas; el porcentaje
es el pico.)
De qué son esos porcentajes. Cada uno se mide contra lo que ese contenedor puede usar realmente: su propio límite cuando lo tiene, y la capacidad del nodo cuando no. Ahí está la clave: el 91 % de un límite de 256 MB significa que un OOM kill está cerca; el 91 % de un nodo de 64 GB es otra conversación. La vista de stats del overlay de inspección muestra los dos lados de esa división por contenedor y nombra la base debajo de la tabla.
Stats);
cada agente muestrea sus propios contenedores en segundo plano y responde desde
memoria, así que el cliente simplemente lo consulta en el ciclo de refresco que ya
tenía. Dos consecuencias. Un porcentaje de CPU es un delta entre dos
lecturas, así que no existe hasta que el agente ha tomado dos (unos pocos
segundos) — mientras tanto las vistas muestran …, nunca
0 %; la memoria no necesita delta y aparece desde la primera lectura.
Y un agente solo muestrea mientras alguien está mirando: se detiene
alrededor de un minuto después de la última petición y olvida sus lecturas en lugar
de servirlas caducadas. Por eso las primeras cifras tras abrir la UI tardan un
momento en llenarse. Es deliberado: un agente al que nadie mira no debe costar
nada.
Stats— sencillamente no aporta lecturas. Los marcadores no
salen, el bloque del nodo no aparece y todo lo demás queda intacto; además el
cliente deja en paz a ese nodo un rato en vez de sondearlo en cada refresco. Así
que en un clúster que todavía no se ha vuelto a desplegar no hay ninguna
cifra de uso ni ningún marcador de salud hasta que se actualicen los agentes — despliégalos con
swarmexec init --force. Este es, con diferencia,
el motivo más probable de no ver nada.
Agrupado por stack. docker stack deploy etiqueta cada
servicio que crea con com.docker.stack.namespace. swarmexec lee esa
etiqueta de la spec del servicio que el manager ya devolvió —Swarm no tiene ningún
objeto «stack», y la etiqueta es el único vínculo— y anida el árbol en tres niveles:
stack → servicio → contenedor. Una fila de stack muestra
el nombre del stack, cuántos servicios cuelgan de él y el recuento
sumado en ejecución/deseado de tareas
((3 svc · 7/8)), coloreado exactamente con la misma regla
que una fila de servicio: el recuento sumado en ejecución/deseado, con la
misma sobrescritura por healthcheck encima. Detrás van los contadores
agregados, cuando no son cero:
⟳ n —servicios de ese stack
en medio de una actualización progresiva—,
🛡 n —servicios con un
hallazgo de seguridad que merece acción, el mismo escudo que llevan
las filas de servicio— y después el marcador de salud,
✖ N unhealthy o
◌ N starting, sumado sobre todos los
contenedores de todos los servicios del stack. Así, incluso un stack colapsado te
dice si algo dentro necesita atención. Y que la salud suba de nivel importa sobre
todo aquí: la fila de stack es la que un operador mira primero, así que un
«todo está arriba» engañoso es peor ahí que un nivel más abajo, no mejor.
Los servicios creados con docker service create no llevan etiqueta de
stack; se recogen bajo (no stack), que siempre se
ordena el último para que nunca empuje hacia abajo a los stacks reales.
No se oculta nada: cada servicio sigue apareciendo exactamente una
vez. Los stacks se ordenan por nombre y arrancan expandidos, así que el árbol
agrupado muestra los mismos servicios que el plano; los pliegues que hagas sobreviven
al refresco automático.
La agrupación está activada por defecto, pero solo surte efecto
cuando al menos un servicio del clúster lleva realmente una etiqueta de stack: en un
clúster sin stacks el árbol se ve exactamente como siempre, sin un padre
(no stack) inútil. Pulsa s para cambiar entre el árbol
agrupado y la lista plana de servicios (el pie lo muestra como s stacks);
si no hay ninguna etiqueta de stack, lo dice en vez de redibujar un árbol idéntico.
La tecla es remapeable como stack_group en
keys.yaml.
La búsqueda (/) no cambia: el filtro sigue actuando sobre servicio, contenedor y nodo. Un stack que el filtro deja vacío desaparece del árbol, y los contadores de cada fila de stack describen lo que realmente ha quedado debajo, así que siguen al filtro en lugar de anunciar servicios que has filtrado.
El menú de acciones ofrece Logs, Bash,
Sh, Shell as user… (pide un usuario/UID, como
docker exec -u, para imágenes cuyo usuario por defecto no tiene las
herramientas o los permisos que necesitas) y
Port forward (las shells no disponibles se
muestran atenuadas tras un sondeo). En la barra de búsqueda, Enter
conserva el filtro y vuelve a la lista; Esc borra el filtro y cierra la
barra.
Riesgos de seguridad (!). Cada vez que se obtiene la lista de servicios, swarmexec ejecuta un conjunto de pequeños analizadores estáticos sobre la spec de cada servicio —la misma spec que el manager ya devolvió—, así que no hay ninguna llamada extra a la API, el agente no interviene y no se ejecuta nada dentro de tus contenedores. Un servicio con un hallazgo que merece acción se marca con un 🛡 al principio de su fila en el árbol de servicios, en una ranura de ancho fijo delante del nombre, de modo que los escudos forman una columna vertical de exploración. Pulsa ! para abrir el overlay de riesgos de seguridad, y w dentro de él para escribir un informe Markdown de todo el clúster, que cubre más que el overlay: añade las comprobaciones que pertenecen al clúster y no a un servicio.
Las ocho comprobaciones que existen hoy. Primero las que pueden marcar un servicio; al final, las puramente informativas:
| Comprobación | Severidad | Qué señala |
|---|---|---|
docker-socket — «Docker socket mounted in» | high | el hallazgo más grave de todos. Un bind mount cuyo origen es el socket del demonio Docker — /var/run/docker.sock, /run/docker.sock o cualquier ruta de origen que termine en /docker.sock. Cualquier cosa dentro de ese contenedor puede hablar con el socket, y lo que puede hablar con el socket puede arrancar un contenedor privilegiado en ese nodo: es, en la práctica, root en el host, sea cual sea el usuario con el que corra el propio contenedor. Montarlo en solo lectura no lo mitiga: el socket es una API, no un fichero cuyo contenido importe — el propio hallazgo lo dice cuando el montaje es de solo lectura. Se nombra la ruta destino en la que está montado |
added-capability — «capability NOMBRE added» | high / medium | una capability de Linux que el servicio añade a su contenedor. Swarm no tiene --privileged, así que las capabilities son la vía por la que un servicio pide más poder sobre el host — de ahí que valga la pena mirar el conjunto añadido. Ocho se tratan como high, cada una con el motivo que lleva el hallazgo: ALL (concede todas las capabilities), SYS_ADMIN (casi root: control de montajes, namespaces y cgroups), SYS_MODULE (puede cargar módulos del kernel), SYS_PTRACE (puede inspeccionar y controlar otros procesos), SYS_RAWIO (acceso de E/S en crudo a dispositivos), DAC_READ_SEARCH (salta las comprobaciones de permisos de lectura), NET_ADMIN (control total de la red del nodo), NET_RAW (puede falsificar y capturar paquetes en crudo). Cualquier otra capability añadida es medium: un privilegio por encima del conjunto por defecto. Un hallazgo por cada capability añadida; se reconocen ambas grafías, en cualquier caja (CAP_SYS_ADMIN y SYS_ADMIN) |
host-network — «runs on the host network» | high | el servicio está conectado a la red llamada host: el contenedor comparte la pila de red del nodo, así que no hay aislamiento de red y el mapeo de puertos publicados de swarm deja de aplicarse. Alcanza todo lo que alcanza el nodo — incluidos los servicios escuchando en localhost, que normalmente se dan por inalcanzables desde un contenedor |
unconfined — «seccomp disabled» / «AppArmor disabled» | high | el sandbox a nivel de kernel del contenedor está desactivado: seccomp puesto en unconfined — el contenedor puede hacer cualquier syscall, lo que elimina la principal barrera frente a exploits del kernel — o el confinamiento de AppArmor deshabilitado. Se reportan por separado, así que un servicio que apague ambos obtiene dos hallazgos |
root-user — «runs as root» | high | la spec fija el contenedor a root explícitamente — User=root o UID 0 (también se detectan formas numéricas como 00 o +0; solo se mira la parte de usuario de user:group) |
secret-in-env — «secret in environment variable» | high | una clave de entorno con pinta de credencial contiene un valor literal en la spec del servicio, legible por cualquiera que pueda leer la spec. Usa un secreto de Docker o la convención *_FILE en su lugar |
root-user — «no user set» | low | no hay ningún usuario definido, así que el contenedor corre como el usuario por defecto de la imagen, que a menudo es root. Solo informativo, y no marca el servicio: un usuario sin definir es el valor por defecto de Swarm en casi todos los servicios, y si realmente es root depende del USER de la imagen, que la spec del manager no revela |
no-resource-limits — «no resource limits» | low | la tarea no fija ni un límite de CPU ni uno de memoria, así que un contenedor desbocado puede consumir el nodo entero y dejar sin recursos a todo lo demás. Solo informativo — véase más abajo |
unpinned-image — «image not pinned» | low | la imagen está fijada a :latest, o no lleva ninguna etiqueta (lo que resuelve a :latest): la versión en ejecución puede cambiar sin que cambie la spec, así que el despliegue no es reproducible. Una imagen fijada por digest (…@sha256:…) es exactamente reproducible y nunca se señala, y un signo de dos puntos que pertenece al puerto del host del registro (registry:5000/img) no se confunde con una etiqueta. Solo informativo — véase más abajo |
Por qué las comprobaciones informativas nunca marcan un servicio.
Solo un hallazgo por encima de low —high o medium— hace que un servicio sea
accionable y pone el 🛡 en su fila del árbol. Las de nivel low
(no-resource-limits, unpinned-image y el «no user set» de
root-user) se cumplen en casi todos los servicios de un clúster
real: marcarlas pondría un escudo en casi todas las filas y destruiría
justamente la señal de un vistazo para la que existe el marcador. Aun así no se
esconden: en cuanto un servicio queda marcado por otra cosa, sus hallazgos low se
listan junto al resto en el overlay, es decir, donde de verdad merece la pena leerlos.
secret-in-env mira siquiera las variables de entorno, y su hallazgo
nombra únicamente la clave de la variable; el valor jamás se lee
dentro del hallazgo, ni se renderiza, ni
se copia. La comprobación también está pensada para no hacer ruido: compara tokens
completos delimitados por _ (PASSWORD,
PASSWD, PASS, PASSPHRASE,
SECRET, TOKEN, APIKEY,
CREDENTIAL(S), PRIVATEKEY, y los pares
API_KEY, ACCESS_KEY, PRIVATE_KEY,
SECRET_KEY, CLIENT_SECRET, AUTH_TOKEN), de
modo que COMPASS, PASSENGER_PORT o
BYPASS_AUTH no se reportan. Las claves que solo referencian
una credencial quedan exentas (_FILE, _PATH,
_URL, _URI, _NAME, _ID,
_TYPE, _ENABLED, _REQUIRED,
_LENGTH, _TIMEOUT, _TTL,
_EXPIRY, _ALGORITHM), igual que los valores que
claramente no son una credencial: una ruta absoluta, un booleano, un número.
El overlay lista todos los servicios marcados («N of M service(s) flagged · 8 checks»), agrupados por servicio, cada hallazgo en su propia línea con un punto de severidad — ● high, ● medium, ● low — un título corto y una explicación de una línea. Los hallazgos se ordenan de peor a mejor. Una vez que un servicio está marcado por algo accionable, también se listan sus hallazgos informativos de nivel low. Si el servicio bajo el cursor está entre ellos, se resalta de antemano y se desplaza hasta él. Cuando no hay nada marcado el overlay lo dice explícitamente y nombra todas las comprobaciones que se ejecutaron («Checked: Docker socket mounted in, added Linux capabilities, host network, seccomp / AppArmor disabled, container user, secrets in environment variables, missing resource limits, unpinned image.»), de modo que el «todo en orden» también te dice qué abarca; si la lista de servicios aún no se ha cargado, dice que todavía no se ha escaneado nada en lugar de dar un falso «todo en orden». j/k desplazan, Esc (o q, o ! de nuevo) cierra.
security_risks en
keys.yaml.
i abre un overlay de inspección para el nodo bajo el
cursor — sobre un servicio muestra docker service inspect,
y sobre una hoja de contenedor la inspección de la tarea de
swarm (la vista del manager de esa instancia: estado, slot, nodo, id de
contenedor, spec del contenedor, recursos e historial de estado). Ambas provienen del
manager de swarm. El overlay se abre en un resumen tabular orientado al
operador con las secciones ordenadas por relevancia operativa — redes,
etiquetas, volúmenes/montajes y secretos (y configs) primero, luego puertos, imagen,
modo, env, recursos, colocación y política de actualización (más estado, nodo e id de
contenedor para un contenedor/tarea), con los ids y las marcas de tiempo al final.
La primera línea del overlay, dentro del borde y fija por encima del contenido que
se desplaza, es una tira de pestañas que nombra sus
tres vistas — table 1 stats 2 raw json 3
— con la actual en el color de acento; tiene la misma forma que la barra de pestañas
de la ventana principal: una etiqueta seguida del dígito que la selecciona.
1, 2 y 3 saltan directamente a la
tabla, a stats y al JSON crudo del daemon, y t sigue rotando
tabla → stats → JSON crudo y vuelta. La pista del pie es
simplemente 1-3/t view, y el título del borde ya no repite el nombre de la
vista actual — dice solo inspect service foo, porque la tira ya indica
dónde estás. Ten en cuenta que la vista tabular es la vista de tarea del manager, no
una docker container inspect completa y local del contenedor en
ejecución — una inspección real de contenedor a través del agente está planificada.
La vista de stats. La del medio es el uso de recursos en vivo — las mismas lecturas que marcan el árbol, así que abrirla no cuesta ninguna llamada extra y las cifras se siguen refrescando mientras está abierta. Es una tabla, una fila por contenedor del servicio (o el único contenedor de una inspección de tarea):
Una tabla, porque un porcentaje a secas no se puede leer si no sabes ya cómo mide la
herramienta: ¿94 % de qué? Cada celda lleva sus propias
unidades, así que 0.07 / 4.00 cores no necesita leyenda y el
porcentaje queda junto a la cifra de la que es porcentaje; y varias réplicas se
comparan recorriendo una columna en vez de cotejando bloques
apilados. La columna HEALTH pone el veredicto en palabras:
healthy, unhealthy, starting o
none para un contenedor que no declara healthcheck alguno.
none y healthy son a propósito palabras
distintas y no una palabra y un hueco: «no hay healthcheck configurado» y
«la sonda pasa» son hechos distintos, y un hueco se leería como el segundo. Un
contenedor del que ningún agente informó —nodo inalcanzable, un agente demasiado
antiguo para el RPC Stats, o uno arrancado después de la última
lectura— muestra no reading en las dos columnas de recursos en vez de
un cero, que se leería como «ocioso».
La nota al pie bajo la tabla nombra el denominador: el propio límite del contenedor cuando lo tiene, y si no la capacidad del nodo. Eso es lo que hace que un porcentaje signifique algo — el 85 % de un límite de 8 GiB y el 85 % de un nodo de 64 GiB son conversaciones distintas. La memoria va en unidades binarias (MiB/GiB), las mismas que usa el detalle de nodo y las que reporta el propio docker, así que la misma cifra nunca se lee distinta en dos sitios. Con más de un contenedor medido añade un resumen bajo la tabla: CPU y memoria de la peor réplica —no la media, que esconde justo el contenedor a punto de morir por OOM— más el total de memoria entre todas. La entrada t del overlay de ayuda ? reza, en consecuencia, cycle table / stats / raw JSON.
El overlay es navegable y seleccionable, no un simple desplazamiento: las líneas de datos son individualmente seleccionables y mueves el cursor línea a línea con ↑/↓ o j/k (se omiten los encabezados de sección y las líneas en blanco). y siempre copia la línea seleccionada a tu portapapeles por OSC52 — el mismo mecanismo de copia usado en otros lugares— para que puedas capturar un único id, montaje o dirección sin seleccionar texto a mano. Enter también copia, salvo en una fila colapsable de NETWORKS, donde en su lugar expande/colapsa esa fila (véase más abajo). Las tres vistas (el resumen tabular, la vista de stats y el JSON crudo, a las que se llega con 1–3 o rotando con t) son seleccionables y copiables línea a línea. Un pie siempre lista las teclas disponibles, para que lo que puedes hacer sea visible de un vistazo; para un servicio el pie muestra además las teclas de edición de más abajo.
| Tecla | Acción |
|---|---|
| ↑ ↓ j k | mueve la selección entre líneas de datos (se omiten encabezados/blancos) |
| y | copia la línea seleccionada al portapapeles (OSC52) — siempre |
| Enter | en una fila colapsable de NETWORKS, expande/colapsa ese nivel — las filas anidan en dos niveles, una red y su desglose N containers; en cualquier otra línea, la copia (OSC52) |
| 1 2 3 | selecciona una vista directamente — 1 resumen tabular, 2 stats (CPU/memoria en vivo, véase más abajo), 3 JSON crudo del daemon; son los dígitos que muestra la tira de pestañas |
| t | rota entre las tres vistas — resumen tabular → stats (CPU/memoria en vivo, véase más abajo) → JSON crudo del daemon → vuelta; las tres seleccionables, y la tira de pestañas de arriba marca en cuál estás |
| a | menú de acciones — todos los editores y acciones del servicio en una lista, sin Shift (solo servicios; ver abajo) |
| s f p l e n S v A | edita el servicio — escalar / forzar actualización / puertos / etiquetas / env / redes / secretos / montajes / alias (solo servicio; S es mayúscula, Shift+s, para distinguirla de s escalar; A también es mayúscula; véase más abajo) |
| Esc q i | cierra el overlay |
La sección NETWORKS es colapsable y responde a «¿en qué
dirección se alcanza esto?» antes de expandir nada: cada red conectada se muestra como
una fila colapsada + <network> 🔒 vip 10.0.5.2/24 (2 dns names).
Un icono de candado 🔒 tras el nombre de la red marca una red overlay
cifrada (cifrado del plano de datos, --opt encrypted); su
ausencia significa sin cifrar. En la inspección de un servicio la
dirección se etiqueta vip — la IP virtual del servicio en esa red, la
dirección en la que responde el balanceador de carga del swarm, tomada del
ServiceInspect del manager (Endpoint.VirtualIPs). En la de un
contenedor/tarea se etiqueta addr: la dirección propia de
ese contenedor allí. Un servicio en modo de endpoint dnsrr no tiene VIP
alguna — es así por diseño, no un dato que falte — así que en lugar de un hueco la red
expandida muestra la línea
no vip — dnsrr endpoint mode, the DNS name resolves to the containers. La
red ingress también se lista aquí, aunque ninguna spec la mencione: el
swarm conecta el servicio a ella por su cuenta (ver más abajo). Se añade
después de las redes que declara la spec, nunca intercalada entre ellas,
y su cabecera es + ingress vip 10.0.0.250/24 (routing mesh): la nota
(routing mesh) ocupa el lugar de un recuento de nombres DNS que solo podría
decir (0 dns names).
Muévete sobre una fila de red y pulsa Enter para expandirla o colapsarla.
Expandida, lista los nombres DNS que resuelven al servicio/contenedor en esa red — el
nombre del servicio, tasks.<service> y cualquier alias personalizado —
para que puedas ver bajo qué nombre DNS se alcanza y qué alias lleva, por red. Debajo de
ellos hay una segunda fila colapsable, + 2 containers (en singular,
+ 1 container), y Enter sobre esa abre el nivel más
profundo: una fila por contenedor, alineada en columnas,
web.1 10.0.1.5/24 host-a — nombre de la tarea, su dirección en esta red y el
nodo en el que corre (las tareas replicadas se llaman
<service>.<slot>, las globales
<service>.<node>, al no tener slot).
El filtro es el estado deseado de la tarea, no el actual: se lista toda
tarea que el manager sigue queriendo en ejecución, incluidas las que solo están
preparing, assigned o starting — ya tienen su
dirección, y durante una actualización progresiva esas son la mayoría, así que filtrar
por el estado actual vaciaría el desglose justo cuando más interesa. Una tarea que el
manager ha dado por perdida — estado deseado shutdown, es decir, sustituida
por una actualización progresiva, eliminada al escalar o fallida — no se lista: su
dirección ya se ha liberado y puede pertenecer a otro contenedor, así que mostrarla sería
activamente erróneo. Una tarea que aún no sirve tráfico lleva su estado entre paréntesis
al final de la fila, web.1 10.0.1.5/24 host-a (starting); una en ejecución no
lleva sufijo. Las filas vienen de un TaskList de la API del manager filtrado
por el servicio y son best-effort: un error de la API simplemente deja el desglose vacío.
Los dos niveles se colapsan de forma independiente; colapsar la red oculta con ella el
nivel de contenedores.
La fila de ingress es la que no está en ninguna spec: el swarm conecta el
servicio a la red ingress por su cuenta en cuanto este publica un puerto en modo
ingress, y esa conexión no aparece por ningún lado en la spec del
servicio — pero su vip es la dirección en la que responde realmente la
malla de enrutamiento (routing mesh), que es lo primero que quieres saber
cuando un puerto publicado se porta mal. Por eso tiene una fila propia, añadida después de
las redes que declara la spec y con una forma algo distinta: en ingress no resuelve ningún
nombre DNS del servicio, así que donde otra red cuenta sus nombres esta cabecera lleva la
nota (routing mesh). Expandida, lista en su lugar los puertos
publicados por ingress que han llevado allí al servicio — la razón de ser de la
fila —, uno por línea: published 2222 -> 22/tcp. Los puertos publicados en
modo host se saltan la malla de enrutamiento y deliberadamente no se
listan ahí. Por debajo baja hasta los contenedores igual que cualquier otra fila de red:
el mismo nivel + 1 container, con la dirección propia de cada contenedor en la
red ingress. Un servicio que no publica ningún puerto por ingress no tiene VIP allí — y
por tanto tampoco esa fila.
Un servicio GitLab que publica SSH en 2222, con ambos niveles expandidos:
Copiar dentro de NETWORKS da la dirección sin su
máscara — 10.0.5.2, la forma que pegas en un curl o un
ping: en una fila de red su vip/addr, en una fila de contenedor la dirección
de ese contenedor. Una fila de red sin dirección (dnsrr) copia el nombre de la red, como
antes. En ambos tipos de fila colapsable Enter alterna; en cualquier otra línea
copia (y y siempre copia).
Cuando inspeccionas un servicio (no una hoja de contenedor/tarea,
cuyo overlay permanece de solo lectura), el pie del overlay expone además varias
teclas de edición, cada una un ServiceUpdate de la API del manager que
dispara una actualización progresiva / reconciliación de las tareas del servicio.
Empieza por a: el menú de acciones. En la inspección de
un servicio (solo ahí), a abre una única lista con todos los editores y
acciones que ofrece el overlay, así no tienes que recordar ~14 teclas sensibles a
mayúsculas (d/D, s/f,
p/l/e, n/S/v,
r/P, R, X). Es el camino descubrible y
sin Shift — y no uno recortado: elegir una entrada cierra el menú y
abre exactamente lo mismo que la tecla directa, así que ambos son
equivalentes, no variantes con comportamientos distintos. Las
entradas, en orden:
Update image version… (Set image version… cuando no hay una más
nueva — fijar una versión a tu elección o volver a ella es igual de válido),
Diff spec (previous → current), Roll back to the previous version,
Why — placement diagnosis, Scale, Force-update,
Edit ports, Edit labels, Edit env, Edit networks,
Edit secrets, Edit mounts, Edit resources,
Edit placement, Remove service
— la destructiva, marcada en rojo — y Cancel. Te mueves con
j/k (g/G saltan a la primera/última
entrada), Enter selecciona; Esc, o la entrada Cancel,
cierra el menú sin hacer nada. El selector de versión de imagen va
el primero, porque fijar una versión es el cambio de servicio más
corriente que hay; su tecla directa u sigue funcionando desde cualquier
fila de la inspección. Si la imagen es de aquellas para las que no se puede elegir
versión — una referencia sin tag, o un registro que no lista los tags del repo,
típicamente uno privado sin credenciales —, la entrada permanece en la lista y
lo dice en lugar de desaparecer en silencio, y apunta al
docker service update --image que sí funciona. La tecla directa de cada
entrada:
| Tecla | Acción |
|---|---|
| d | diff — muestra un diff unificado del spec actual del servicio frente al anterior (lo que cambió la última actualización progresiva), usando el PreviousSpec de Swarm. Los campos modificados se listan agrupados por campo con líneas - eliminado / + añadido (imagen, modo/réplicas, env, etiquetas, redes, alias, secretos, montajes, puertos, restricciones de colocación, preferencias de distribución, límites/reservas de recursos); los campos sin cambios se omiten. Si el servicio nunca se ha actualizado (sin spec anterior) lo indica. Overlay de solo lectura — j/k desplaza, Esc cierra |
| R | revertir a la versión anterior (R mayúscula, es decir Shift+r, para distinguirla de r límites de recursos) — la contraparte del diff d de arriba: deshace la última actualización progresiva. El diálogo de confirmación muestra ese mismo diff invertido bajo «This will undo:», porque eso es lo que la reversión va a cambiar realmente — una línea que la actualización añadió aparece como - eliminado, y una que eliminó vuelve como + añadido. La vista previa se limita a 12 entradas, seguidas de una nota «… and N more» que remite a d para el diff completo (si solo cambiaron metadatos indica que no hay diferencias a nivel de campo). Al confirmar se hace una reversión del lado del servidor — un ServiceUpdate de la API del manager con ServiceUpdateOptions{Rollback: "previous"}, no una reaplicación del PreviousSpec desde el cliente — así que es el manager quien la ejecuta y respeta la configuración de reversión del propio servicio (paralelismo, retardo, acción ante fallo), y reporta el progreso como el estado de actualización rollback_started: exactamente la insignia ↺ rolling back descrita arriba, en la fila del árbol y en este overlay. Un servicio que nunca se ha actualizado no tiene spec anterior — obtienes un aviso explicativo, no un error. También accesible desde el menú de acciones a. Como d, D y X, es una tecla fija de la inspección, no una acción remapeable del keymap |
| s | escalar — pide un nuevo recuento de réplicas y lo aplica (solo servicios replicados; un servicio global informa de que no se puede escalar) |
| f | forzar actualización — redespliega el servicio sin cambiar su spec (el equivalente de docker service update --force: incrementa TaskTemplate.ForceUpdate), tras una confirmación. Cada tarea se reinicia / reprograma, que es cómo desatascas un servicio que está en un estado incompleto (p. ej. 1/2 réplicas). Un ServiceUpdate (actualización progresiva) |
| X | elimina el servicio (X mayúscula) — lo borra permanentemente y detiene todas sus tareas, tras una confirmación; no se puede deshacer. Un ServiceRemove de la API del manager; al tener éxito el overlay de inspección se cierra y el árbol de servicios se refresca |
| D | diagnostica la colocación (D mayúscula) — responde por qué un servicio no se ejecuta en todas partes donde esperas en una sola vista, en lugar de perseguirlo a través de varios comandos docker. Para un servicio global lista cada nodo con ✓ en ejecución o ✗ y el motivo por el que se excluye (disponibilidad drain/pause, un estado caído, una restricción de colocación no satisfecha, o una discrepancia de plataforma) — que es por lo que un clúster de 3 nodos puede mostrar legítimamente 2/2 (el tercer nodo no es elegible). Para un servicio replicado lista cada tarea que no se ejecuta con el propio mensaje del planificador (restricciones, recursos insuficientes, errores de descarga de imagen, …). Las restricciones que no pueden comprobarse en el lado del cliente (engine.labels.*) se señalan, no se adivinan. Overlay de solo lectura |
| p | edita los puertos publicados — abre un editor de lista por etapas de los puertos del servicio, cada uno en la forma PUBLISHED:TARGET[/proto] (proto tcp|udp|sctp, por defecto tcp), p. ej. 8080:80/tcp |
| l | edita etiquetas — el mismo editor de lista por etapas sobre entradas key=value |
| e | edita variables de entorno — el mismo editor de lista por etapas sobre el env del ContainerSpec, cada una introducida como KEY=VALUE (p. ej. LOG_LEVEL=debug); la clave es obligatoria, el valor puede estar vacío y puede contener él mismo =. Como puertos/etiquetas/montajes este editor sí permite e editar, para que puedas ajustar un valor in situ. A diferencia de los otros editores, e aquí abre la entrada en un área de texto multilínea desplazable (en lugar de un campo de una línea) para que valores largos como GITLAB_OMNIBUS_CONFIG sean cómodos de editar — escribe libremente (Enter inserta un salto de línea), Ctrl-S guarda, Esc cancela. Aplicar reemplaza el env del servicio en un ServiceUpdate (actualización progresiva) |
| n | edita redes — un editor de lista por etapas sobre las conexiones de red del servicio; como una entrada es solo un nombre, es solo añadir/eliminar (sin e editar). La entrada de añadir autocompleta nombres de red (sugiriendo redes a las que el servicio aún no está conectado, o escribe una tú mismo). Aplicar reemplaza las conexiones en un ServiceUpdate. Los alias DNS también se editan desde aquí: selecciona una red en este editor y pulsa A para abrir una lista por etapas de los alias DNS del servicio en esa red (a añadir / e editar / d eliminar / y copiar / u deshacer / w aplicar / Esc cancelar); aplicar reemplaza los alias de esa red en un ServiceUpdate. Funciona incluso cuando el servicio aún no tiene alias. (Debes aplicar primero una red recién añadida antes de poder fijar sus alias.) |
| S | edita secretos (S mayúscula, es decir Shift+s, para distinguirla de s escalar) — el mismo editor de lista por etapas que redes sobre los secretos que el servicio referencia; una entrada es solo un nombre, así que es solo añadir/eliminar (sin e editar). La entrada de añadir autocompleta nombres de secreto (sugiriendo secretos que el servicio aún no usa, o escribe uno tú mismo). Aplicar reemplaza las referencias de secreto del servicio en un ServiceUpdate (actualización progresiva); cada secreto se monta en /run/secrets/<name>. Funciona incluso cuando el servicio actualmente no tiene secretos |
| v | edita montajes — un editor de lista por etapas sobre los volúmenes y bind mounts del servicio. Añadir (a) o editar (e) abre un pequeño formulario en lugar de un único campo de texto: una casilla de bind mount, un origen (cuando la casilla está sin marcar es un nombre de volumen con autocompletado de los volúmenes existentes del clúster; cuando está marcada es una ruta de host), una ruta de contenedor, y una casilla de solo lectura. (Internamente cada entrada sigue siendo volume:NAME:TARGET[:ro] / bind:/host/path:TARGET[:ro].) Los orígenes bind y todos los destinos deben ser rutas absolutas. Aplicar reemplaza los montajes en un ServiceUpdate (actualización progresiva). Salvaguarda de bind mount (blanda): si la lista por etapas contiene algún bind mount, aplicar muestra primero una advertencia que lista los nodos en los que podría programarse el servicio (calculados a partir de las restricciones de colocación más el rol/etiquetas/disponibilidad del nodo) y te recuerda que cada origen bind debe ya existir en todos ellos — swarmexec no puede verificar rutas de host (el agente no tiene acceso al sistema de archivos del host), así que las rutas bind no se autocompletan ni se comprueba su existencia; es solo orientativa |
| r | edita los límites de recursos — un pequeño formulario para fijar, cambiar o borrar los límites de CPU y memoria del servicio (y, opcionalmente, las reservas). La CPU se da en núcleos (p. ej. 0.5, 2); la memoria como un tamaño legible (p. ej. 512m, 2g, 1.5GiB). Dejar un campo vacío borra ese límite (Swarm lo trata entonces como ilimitado). Una reserva no puede exceder su límite. Aplicar hace un ServiceUpdate (actualización progresiva); se preservan los límites de Pids y las reservas de dispositivo/genéricas existentes |
| P | edita la colocación (P mayúscula, es decir Shift+p, para distinguirla de p puertos) — abre un pequeño menú con dos editores por etapas: Restricciones y Preferencias de distribución.
ServiceUpdate (actualización progresiva); la otra parte se preserva. |
Estos editores son por etapas: a añade una entrada,
d elimina la que está bajo el cursor, y copia la entrada
seleccionada a tu portapapeles por OSC52 (para que puedas capturar, p. ej., una única
variable de entorno mientras la ves), u deshace el último cambio por etapas
(añadir / editar / eliminar) siempre que aún no hayas aplicado — púlsala
repetidamente para retroceder por el historial por etapas— y w aplica todos
los cambios de una vez en un solo ServiceUpdate (de modo que añadir,
digamos, varios volúmenes y luego pulsar w dispara una
actualización progresiva, no una por entrada), tras una confirmación. Si pulsas
Esc mientras aún hay cambios por etapas, el editor no los descarta en
silencio — pregunta si aplicar, descartar o
seguir editando.
Mientras los cambios siguen por etapas se resaltan para que veas
exactamente qué cambiará: las entradas añadidas o editadas aparecen en
verde (nuevas) y las entradas
eliminadas permanecen como filas atenuadas en
rojo «eliminadas» —
el resaltado desaparece una vez que aplicas. Los editores de
puertos, etiquetas, montajes,
env y alias
(los alias se alcanzan desde el editor de redes con A)
también tienen e para editar la entrada bajo el cursor; los editores de
redes y secretos no tienen e —
una entrada de red o secreto es solo un nombre, así que es solo añadir (a) /
eliminar (d). El e del editor de env abre un
área de texto multilínea desplazable (Ctrl-S guardar / Esc
cancelar) para valores largos; los demás editores mantienen la entrada de una línea
con Enter para confirmar.
El árbol se auto-refresca cada ~10s, así que un contenedor reemplazado o un servicio escalado en segundo plano aparece por sí solo — se preservan el cursor y cualquier servicio expandido. r sigue forzando un refresco inmediato.
Pestaña Volumes
| Tecla | Acción |
|---|---|
| / | búsqueda — filtra la lista por nombre de volumen, driver o nodo |
| n | crea un volumen — un formulario con nombre, driver (por defecto local), etiquetas (k=v,k=v) y un nodo de destino (autocompleta nombres de nodo; déjalo en blanco para crearlo en cada nodo, ya que los volúmenes son locales al nodo). Lo crea en el agente de cada nodo de destino e informa del éxito/fallo por nodo. Tab mueve entre campos, Esc cancela |
| space | selecciona / deselecciona el volumen (marcado ▣) para un borrado masivo |
| a | selecciona / deselecciona todos los volúmenes mostrados actualmente |
| d | elimina los volúmenes seleccionados — o el que está bajo el cursor— en cada nodo que los contiene, tras una confirmación (con un overlay de progreso; los borrados se ejecutan con paralelismo acotado) |
| P | prune: elimina cada volumen que ningún contenedor en ejecución monta y ningún servicio declara, tras una confirmación |
| Enter | muestra qué nodos contienen el volumen; las etiquetas del volumen se listan de solo lectura encima de los nodos (Docker no tiene API de actualización de volúmenes, así que las etiquetas no pueden editarse tras la creación — fíjalas al crear el volumen) |
| i | muestra qué servicios/contenedores lo usan |
| A | adjunta el volumen a un servicio: elige un servicio (autocompletado de nombre) → introduce la ruta de destino del contenedor (absoluta) → elige Adjuntar o Adjuntar de solo lectura. Añade el montaje mediante un ServiceUpdate (actualización progresiva) — la contraparte de volumen de las acciones de adjuntar red/secreto; la inversa vive en el editor de montajes v de la inspección del servicio |
| s S | cicla el campo de ordenación (name → nodes → used → age → size) / invierte |
El borrado tiene en cuenta los nodos: un volumen se elimina en cada nodo que lo contiene. Prune respeta los volúmenes que un servicio declara incluso cuando no hay ninguna tarea en ejecución, de modo que no borra los datos de un stack detenido. Para un control más fino, Enter abre la lista por nodo donde space selecciona nodos, d elimina la copia del nodo resaltado y a elimina en todos los nodos — cada uno tras una confirmación.
Pestaña Forwards
| Tecla | Acción |
|---|---|
| Enter | muestra el detalle completo del reenvío (incluido cualquier error) |
| d | detiene el reenvío seleccionado |
| o | copia la URL del reenvío (http://127.0.0.1:<port>) |
Un contenedor en ejecución se anota en el árbol como local→remote
(p. ej. 9090→8080). Los reenvíos siguen ejecutándose cuando su overlay
se cierra y cuando cambias a otro clúster; se detienen con d o
cuando sales de la UI. La columna CLUSTER nombra el contexto al que
pertenece cada uno, atenuada para el que estás mirando — un reenvío en otro clúster
sigue siendo tuyo y sigue escuchando, pero su CONTAINER y su
NODE nombran cosas que no puedes ver desde aquí. La anotación en el
árbol es por clúster por la misma razón: un id de contenedor solo es único dentro
de su propio demonio.
Por eso, un puerto local que ya estés usando se rechaza de entrada, nombrando el reenvío que lo ocupa y el clúster al que pertenece — el diálogo sigue abierto para que elijas otro puerto. El «address already in use» del sistema queda para el caso en que acierta: un puerto ocupado por otro programa, al que swarmexec no puede poner nombre.
Pestaña Networks
Una lista de las redes del swarm — nombre, driver, ámbito, tipo
(ingress / internal / attachable), si la red está cifrada y cuántos
servicios se conectan a cada una. La columna TYPE muestra
overlay o el nombre del driver (p. ej. bridge) para las
redes planas en lugar de un guion. La columna ENC muestra
🔒 yes cuando la red overlay tiene el cifrado
del plano de datos activado (creada con --opt encrypted), si no un guion.
El nombre de cada red y su celda TYPE se codifican por color según el
tipo (mayor prioridad primero):
- attachable — verde
- internal — amarillo
- ingress — gris
- otras overlay / con ámbito de swarm — aqua
- local (
bridge,host, …) — gris atenuado
| Tecla | Acción |
|---|---|
| n | crea una red — abre un formulario (driver por defecto overlay) con las opciones comunes de swarm: attachable (permite que se unan contenedores independientes), encrypted (cifrado del plano de datos overlay), internal (sin enrutamiento externo), IPv6, una MTU opcional, una subred/gateway opcional (IPAM) y etiquetas (k=v,k=v). Tab mueve entre campos, Enter sobre Create confirma, Esc cancela. La crea mediante un NetworkCreate de la API del manager y luego refresca la pestaña |
| Enter / i | muestra los servicios conectados, cada uno con sus contenedores en ejecución anidados debajo. Las etiquetas propias de la red se listan de solo lectura arriba (Docker no tiene API de actualización de redes, así que las etiquetas de red no pueden editarse tras la creación — fíjalas al crear la red). Cada encabezado de servicio muestra además cuántos alias DNS tiene en esta red (o sin alias) |
| Enter | en la vista de miembros: expande / colapsa los alias DNS del servicio seleccionado (mantenidos colapsados por defecto para que la lista siga compacta); los alias aparecen indentados bajo el encabezado del servicio |
| A | en la vista de miembros: añade / edita los alias DNS del servicio seleccionado en esta red — abre el mismo editor de alias por etapas que la vista de inspección (a añadir / e editar / d eliminar / w aplicar / Esc cancelar); aplicar reemplaza los alias de esa red en un ServiceUpdate y refresca la vista. Funciona incluso cuando el servicio aún no tiene alias. (A es mayúscula, Shift+a, para distinguirla de a adjuntar; los contenedores independientes no gestionados por swarm no son editables — los alias son un ServiceUpdate por servicio) |
| a | en la vista de miembros: adjunta un servicio a esta red — una entrada con autocompletado (sugiriendo servicios aún no conectados; también puedes escribir cualquier nombre), luego una confirmación de actualización progresiva |
| d | en la vista de miembros: desconecta un servicio de esta red — una entrada con autocompletado (sugiriendo los servicios actualmente conectados; también puedes escribir cualquier nombre), luego una confirmación de actualización progresiva |
Adjuntar o desconectar hace una actualización de servicio que añade o elimina la red
en el spec del servicio, y luego refresca la pestaña Networks. Esta es una operación
de la API del manager (ServiceUpdate) — el mismo canal que el cliente usa
para la topología— y dispara una actualización progresiva que
reinicia las tareas del servicio.
Pestaña Secrets
Una lista de solo lectura de los secretos del swarm — nombre, cuántos servicios usa cada uno, antigüedad, última actualización y recuento de etiquetas. Los valores de los secretos nunca se muestran: la API de Docker no los expone.
| Tecla | Acción |
|---|---|
| Enter | muestra los metadatos del secreto y los servicios/contenedores que lo usan |
| a | en la vista de detalle: adjunta el secreto a un servicio — una entrada con autocompletado (sugiriendo servicios que aún no lo usan; también puedes escribir cualquier nombre), luego una confirmación de actualización progresiva |
| d | en la vista de detalle: desvincula el secreto de un servicio — una entrada con autocompletado (sugiriendo servicios que actualmente lo usan; también puedes escribir cualquier nombre), luego una confirmación de actualización progresiva |
Adjuntar añade el secreto al servicio, montado en
/run/secrets/<name> (como docker service update
--secret-add); desvincular elimina la referencia al secreto. Luego la pestaña
Secrets se refresca. Igual que el adjuntar/desconectar de red, cada uno es un
ServiceUpdate de la API del manager que dispara una
actualización progresiva que reinicia las tareas del servicio.
Barra lateral de contextos
Los clústeres ocupan una columna en el borde derecho, no una pestaña. Cambiar de clúster es algo que se hace desde donde uno está — y la lista de clústeres es justo lo que dice dónde es eso. Le corresponde estar en pantalla, no en una página a la que haya que viajar.
c le da el teclado desde cualquier pestaña; c otra vez o
Esc lo devuelve a la pestaña en la que estabas. El clúster activo está
marcado con ▶ — el mismo nombre que muestra el pie. Un clúster ya
visitado en esta sesión lleva un · verde: sigue conectado, así que
cambiar a él es instantáneo. Uno que rechazó la conexión lleva una ✗
roja. El contexto integrado default está protegido.
La barra lateral se ajusta al nombre de contexto más largo y se aparta en un terminal estrecho: por debajo de unas 92 columnas se oculta en lugar de recortar el árbol, y vuelve mientras c la sostenga. Los endpoints no aparecen en la columna — no hay sitio, y i muestra el detalle completo.
Cambiar de clúster conserva tu sesión. La UI no se reconstruye:
dónde estaba el cursor, qué stacks y servicios estaban desplegados y el filtro
/ se recuerdan por clúster, de modo que al volver apareces
exactamente donde lo dejaste. Los reenvíos de puertos siguen
funcionando; la pestaña Forwards gana una columna CLUSTER
para ver de un vistazo cuáles pertenecen al clúster que estás mirando y cuáles no.
Solo se sondea el clúster que estás mirando: al cambiar, el otro deja de refrescarse y su conexión ssh expira sola poco después. El primer cambio a un clúster tarda lo que tarde alcanzarlo, porque la conexión se establece antes de que cambie nada en pantalla — si falla, se te dice de qué clúster se trata y te quedas en el que funciona. Cada vuelta posterior es inmediata.
| Tecla | Acción |
|---|---|
| c | enfocar la barra lateral desde cualquier pestaña; c otra vez o Esc devuelve el teclado |
| i | detalles del contexto — endpoint y jump hosts, para los que la columna no tiene sitio |
| u / Enter | activa el contexto seleccionado — lo convierte en el actual (también el contexto actual almacenado por docker) y cambia la UI a ese clúster, conservando tu posición, tus filtros y tus reenvíos de puertos |
| n | crea un contexto — un formulario guiado. Una casilla «Connect to Docker over SSH» decide el endpoint: cuando está activada, rellenas usuario / host / puerto de SSH y una segunda casilla «Use a jump host» revela un campo de jump host(s) (separados por comas, ProxyJump de múltiples saltos) — swarmexec los almacena en el contexto e inyecta -J en ambas conexiones ssh, la de la API de Docker y la del túnel del agente, para que los bastiones funcionen sin editar ~/.ssh/config. Cuando está desactivada, introduces un host tcp:// / unix:// simple. El formulario ensambla el docker host por ti, y Test hace ping al endpoint ensamblado antes de que guardes. También en la CLI: swarmexec context create <name> --docker-host ssh://ops@mgr --ssh-jump bastion1,bastion2 |
| d | elimina el contexto seleccionado (tras una confirmación); eliminar el actual restablece la selección a default |
Pestaña Nodes
Lista los nodos del swarm con información general y algunas agregaciones — NODE
(hostname; los managers en aqua, el líder marcado con ★), ROLE
(manager / worker), AVAIL (active / pause / drain, codificado por color),
STATE (ready / down), versión del ENGINE, TASKS
(tareas en ejecución programadas en el nodo), VOLS (volúmenes que contiene el
nodo — locales al nodo, así que se rellena un instante después del resto) y
LABELS (recuento).
| Tecla | Acción |
|---|---|
| Enter / i | detalles del nodo — un overlay de solo lectura con hostname, id, rol/líder, disponibilidad, estado, dirección, engine, plataforma, CPUs, memoria, recuento de tareas en ejecución, los bloques de recursos reservados, de uso real y de imágenes en este nodo (véase más abajo), recuento de volúmenes y la lista completa de etiquetas |
| l | edita las etiquetas del nodo seleccionado — un editor de lista por etapas (a añadir / e editar / d eliminar / w aplicar / Esc cancelar) sobre entradas key=value. Aplicar hace un NodeUpdate — a diferencia de un servicio, una actualización de nodo se aplica de inmediato (sin actualización progresiva). Las etiquetas de nodo se usan comúnmente como objetivos de restricción de colocación (node.labels.<k>) |
| a | fija la disponibilidad del nodo — un menú con Active / Pause / Drain que marca el estado en el que está el nodo ahora mismo. Active programa tareas aquí con normalidad; Pause conserva las tareas en ejecución y solo impide colocaciones nuevas; Drain saca todas las tareas del nodo. Active y Pause se aplican al instante; Drain pasa por una confirmación que indica cuántas tareas en ejecución detendrá el swarm y reprogramará en otros nodos (los servicios cuyas tareas no puedan colocarse en ningún otro sitio quedarán unschedulable). Igual que un cambio de etiquetas, es un único NodeUpdate (lectura-modificación-escritura sobre la spec del nodo) y se aplica de inmediato: los nodos no tienen actualización progresiva. Reasignable como node_availability |
| P | recupera espacio de disco de imágenes en el nodo (P mayúscula, es decir Shift+p — el mismo gesto que la pestaña Volumes usa para su prune). Abre un pequeño menú con los dos modos que se describen más abajo, cada uno tras su propia confirmación; conviene leer cuál es cuál antes de elegir. El pie de página termina con P reclaim images y el overlay ? lo lista como reclaim image disk space on the node. Reasignable como node_prune_images |
Recursos reservados. Los detalles del nodo incluyen un bloque
reserved by tasks (scheduler's view): para CPU y
memoria, una barra más reservado / capacidad y cuánto
queda libre. La barra es verde, pasa a amarillo a partir del 75 % y a rojo al 100 %
o por encima; la sobrerreserva se señala explícitamente y «libre» nunca se vuelve
negativo. Un nodo que no informa de capacidad lo dice en lugar de dibujar una barra.
El bloque no cuesta ninguna llamada de API adicional — la lista de nodos ya obtiene
la lista de tareas.
Reservations declaradas por las tareas — exactamente la aritmética que
hace el propio planificador de swarm al decidir si una tarea cabe en un nodo, y el
motivo habitual de que un servicio quede unschedulable. El consumo real de CPU/RAM
es local al nodo y no es lo que se muestra aquí: tiene un bloque propio
justo debajo. Las tareas que no declaran
ninguna reserva se cuentan y se informan por separado: aquí no
reservan nada, pero pueden consumir el nodo entero, así que un porcentaje bajo no
significa que el nodo esté ocioso — y en la mayoría de los clústeres muy pocos
servicios definen reservas. Una tarea retiene su reserva desde el momento en que se
le asigna un nodo hasta que alcanza un estado terminal: por tanto, las tareas en
preparing o starting cuentan, y las shutdown / failed / complete / rejected no (el
swarm las conserva en la lista de tareas como historial).
En uso por los contenedores. Debajo hay un segundo bloque — in use by containers (measured on the node) — dibujado con las mismas barras proporcionales: lo que los contenedores del nodo están usando de verdad ahora mismo, CPU en núcleos y memoria, cada uno frente a la capacidad del propio nodo. Los dos bloques se mantienen aparte a propósito, porque responden a preguntas distintas y suelen estar muy lejos el uno del otro: un nodo puede estar reservado del todo y ocioso, o apenas reservado y en llamas. Las cifras vienen del agente de ese nodo, la misma fuente que los marcadores de uso del árbol de servicios — mientras el delta de CPU no está listo la línea dice measuring…, y un nodo cuyo agente sea inalcanzable o demasiado antiguo no recibe bloque alguno en lugar de una fila de ceros que se leería como «ocioso».
Imágenes en este nodo. Al final de los detalles aparece un bloque images on this node: cuánto ocupa el almacén de capas del nodo y en cuántas imágenes, y cuánto de eso se puede recuperar en restos sin etiquetar (un nodo sin nada sin etiquetar lo dice, en vez de ofrecer un cero). Cuando hay algo más, añade una línea: cuánto hay además etiquetado pero sin usar — redactado como un coste, eliminarlo significa volver a descargar esas imágenes, no como una oferta. El bloque existe porque las imágenes son el único recurso local al nodo que el lado del clúster no puede ver en absoluto: la API del manager no tiene ninguna visión de las imágenes, a diferencia de los volúmenes, de los que al menos sabe que están montados. Los volúmenes ya se podían gestionar desde swarmexec en todo el clúster; un nodo cuyo disco se llenaba en silencio de capas viejas ni siquiera se veía. El bloque se carga por su cuenta, en paralelo con la lista de nodos, así que aparece un instante después del resto del overlay.
Recuperarlo — P en la pestaña Nodes. El menú que abre tiene dos entradas, deliberadamente no una acción con casilla, porque son actos distintos y una casilla invita a acabar sin querer en el destructivo:
| Entrada | Qué elimina |
|---|---|
| Untagged leftovers | las capas que dejan atrás las reconstrucciones y desde las que no se puede arrancar nada. Seguro: no puede parar un servicio ni forzar una descarga |
| Every unused image | elimina además imágenes etiquetadas que ningún contenedor está ejecutando ahora mismo. En un nodo de swarm eso incluye todo servicio escalado a cero y toda tarea entre reinicios: cada una tendrá que volver a descargar su imagen |
Dos modos, dos permisos. El agente los autoriza como dos acciones
distintas — image.prune para los restos sin etiquetar e
image.prune.all para el barrido —, de modo que una política puede
permitir recuperar capas sin etiquetar sin permitir la destructiva. Cada
prune se escribe en el registro de auditoría del agente con qué
modo se usó, cuánto se recuperó y cuántas imágenes se fueron.
docker system df, y coinciden exactamente con él — comprobado
en un nodo real con 56 imágenes, 32,70 GB en disco y 22,99 GB
recuperables. En particular, swarmexec no suma los tamaños de las
imágenes individuales: el tamaño de una imagen incluye todas las capas con las que
está construida, y las capas se comparten, así que esa suma exagera mucho — durante
el desarrollo afirmaba 32,7 GB donde el demonio decía 22,2 GB. Una
salvedad que toca decir: la cifra de solo sin etiquetar es una
cota inferior — una capa compartida por dos imágenes sin etiquetar
no pertenece al tamaño exclusivo de ninguna de las dos —, así que un prune de las
sin etiquetar puede liberar algo más de lo anunciado, nunca menos.
Mismo requisito previo que las cifras de uso y de salud de más arriba: necesita
agentes de esta versión o más nuevos. Un nodo cuyo agente sea más
antiguo sencillamente no muestra bloque de imágenes, e informa de que no respondió
si pulsas P — despliégalos con
swarmexec init --force.
Pestaña Configs
Pestaña 8 — la gemela de la pestaña Secrets para el otro objeto que el
swarm entrega a los contenedores, con una diferencia decisiva: el contenido de
un config sí se puede leer. La pestaña Secrets nunca puede mostrar un valor
(la API de Docker no lo devuelve); el detalle de un config muestra el
contenido real, así que ves lo que un servicio recibe de verdad sin
recurrir a docker config inspect. Ese es el sentido de esta pestaña.
La lista es de solo lectura: CONFIG (nombre), USED BY
(cuántos servicios lo montan), SIZE, AGE,
UPDATED y LABELS (recuento).
| Tecla | Acción |
|---|---|
| Enter / i | abre la vista de detalle: nombre, id, tamaño, creación, actualización, los servicios que lo montan, sus etiquetas — y el contenido del config |
| j k / g G | en la vista de detalle: desplazarse · ir al principio / al final (Esc cierra) |
El contenido se obtiene bajo demanda, al abrir el detalle — no junto
con la lista — porque un config puede ser un nginx.conf entero; hasta que
llega se lee loading…, y un config vacío indica (empty). Lo que se
muestra es una vista previa, limitada a la vez a
64 KiB y a 500 líneas. Ambos límites hacen
falta: acotar solo los bytes todavía deja que un archivo de líneas muy cortas se
convierta en decenas de miles de líneas renderizadas —cada una con su sangría—, que es
lo que de verdad hace que la vista se arrastre. El truncado siempre se
anuncia («… truncated at 500 lines», o en el límite de bytes), nunca
se corta en silencio, y el contenido binario se informa, no se vuelca
(«(binary content, N — not shown)», detectado buscando bytes NUL en la cabecera de los
datos). El config completo está a un docker config inspect de distancia.
USED BY se deriva igual que en la pestaña Secrets: el swarm no
mantiene ningún índice inverso de un config a quienes lo usan, así
que swarmexec lee las especificaciones de los servicios (un ServiceList)
y reconoce una referencia por ID o por nombre del config — una
especificación puede llevar cualquiera de las dos formas, así que se consultan y se
fusionan ambas. Con el mejor esfuerzo: si no se puede leer la lista de servicios, los
configs se muestran igual, solo que sin esa correspondencia.
Shell y logs embebidos
En una shell embebida, Ctrl-] se desconecta (sin terminar el proceso). La
vista de logs — un único contenedor o los logs agregados de un servicio— admite el
mismo análisis y filtrado según formato que el comando logs:
| Tecla | Acción |
|---|---|
| f | alterna el seguimiento |
| F | cicla el formato de log (classic → json → logfmt → gelf → raw) |
| l | cicla el filtro de nivel mínimo (off → trace → … → fatal → off) |
| / | introduce una expresión regular grep sobre el mensaje (vacía la borra) |
| m | alterna la captura del ratón (off = selección/copia propia de tu terminal) |
| ↑ ↓ | desplaza |
| Esc / q | cierra |
La vista vuelve a renderizar las líneas del buffer en vivo cuando cambias el formato,
el nivel o el grep, y la barra de título muestra el estado activo
fmt:… lvl:… grep:….
Al seguir, la vista de logs se reconecta a través del reemplazo de contenedores de la
misma forma que el comando logs — tanto para los
logs de un único contenedor como para los logs agregados de un servicio (cada réplica
seguida por slot). Los avisos de reconexión aparecen en línea en la vista de logs.
Visor de logs
Pulsa ` (comilla invertida) desde cualquier pestaña para alternar un overlay de logs en vivo. Muestra los registros más recientes del buffer circular en memoria — los más nuevos abajo— refrescándose mientras está abierto y codificado por color según el nivel (rojo = error, amarillo = warn, gris = debug). ↑/↓ desplaza; Esc, q o ` lo cierran. Esta es la ventana de la TUI a los mismos logs que van al archivo de log, ya que la propia terminal no puede mostrarlos mientras la UI está dibujando.
Teclas personalizadas
Las teclas de atajo son reasignables en ~/.config/swarmexec/keys.yaml
(junto a config.yaml; cambia la ruta con $SWARMEXEC_KEYS).
Cada valor es una única tecla, o la palabra space. Las teclas
estructurales — Enter, Esc, Tab, las flechas, las
teclas numéricas de pestaña 1–8 y los alias vim
j/k/g/G — son fijas. El pie siempre muestra
tus teclas reales, y el overlay ? las lista todas.
La carga nunca falla: una tecla inválida o reservada, una acción desconocida, o dos acciones vinculadas a la misma tecla en una pestaña recaen cada una en el valor por defecto, y la UI muestra las advertencias una vez al arrancar.
11. Códigos de salida
| Código | Significado |
|---|---|
0 | éxito |
2 | error de uso / flag / configuración, o una selección interactiva abortada |
125 | fallo de transporte — manager o agente inaccesible, error de dial/TLS, error de stream, confirmación abortada (refleja el 125 de Docker) |
N | para exec, el propio código de salida distinto de cero del comando remoto, propagado tal cual |