← swarm-exec.cloud-surfers.net

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.

1. Cómo funciona

Dos binarios, un protocolo de comunicación:

terminal del operador ──gRPC/mTLS──> agent(nodoN) ──docker.sock──> contenedor └── API del manager de Docker (resuelve nodo + id de contenedor)

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:

$ curl -fsSLo swarmexec \ https://gitlab.logle.io/cs-public/swarm-remote-exec/-/releases/permalink/latest/downloads/bin/swarmexec-linux-amd64 $ chmod +x swarmexec && sudo mv swarmexec /usr/local/bin/ $ swarmexec --version

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.

$ swarmexec --context my-swarm init # despliega agentes en cada nodo (una vez) $ swarmexec ps # lista las tareas y el nodo en que se ejecuta cada una $ swarmexec exec web -- sh # abre una shell en el servicio "web", en cualquier nodo $ swarmexec logs -f web # sigue los logs de un servicio $ swarmexec port-forward db 5432 # localhost:5432 → contenedor:5432 $ swarmexec ui # TUI interactiva

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:

FormaEjemploSignificado
servicewebLa tarea en ejecución del servicio. Si tiene más de una réplica, es ambiguo — véase más abajo.
service.slotweb.2Un slot de réplica concreto. Durante una actualización progresiva gana la tarea más nueva del slot.
task-idxxh8k1…Un ID de tarea de Swarm.
container-id3f9a2b…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, …).

--node solo se consulta para destinos de tipo container-id y puede ser un hostname, una IP o un ID de nodo. Los destinos de servicio / slot / tarea se localizan a través de la API del manager, por lo que nunca lo necesitan.

5. Configuración

Los ajustes provienen de cuatro capas, cada una sobrescribiendo a la anterior:

valores por defecto integrados archivo de configuración entorno flags de línea de comandos

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:

  1. $SWARMEXEC_CONFIG
  2. $XDG_CONFIG_HOME/swarmexec/config.yaml
  3. ~/.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:

ClaveTipoSignificado
castringcertificado CA que verifica el certificado de servidor del agente (mTLS)
certstringcertificado de cliente — su CN es tu identidad de operador
keystringclave privada del cliente
portintpuerto del agente (por defecto 9443)
addr_modestringhostname (por defecto) o ip — cómo marcar a un nodo
server_namestringsobrescribe el nombre de servidor TLS usado para verificar al agente
agent_secretstringsecreto compartido para un agente autofirmado
agent_secret_filestringlee el secreto de este archivo (tiene prioridad sobre agent_secret)
insecureboolomite la verificación del certificado de servidor del agente (agentes autofirmados)
legacy_secretboolenvía además el secreto en bruto, para agentes anteriores a v1.17.3 — desactivado por defecto; ver abajo
operatorstringidentidad de auditoría cuando no se usa certificado de cliente (por defecto: usuario del SO)
logs.formatstringformato de log por defecto para logs y la TUI: classic | json | logfmt | gelf | raw (vacío = classic)
logs.min_levelstringnivel mínimo por defecto: trace..fatal (omítelo para no filtrar por nivel)
ui.dimfloatcuánto se atenúa el fondo detrás de un overlay abierto, una fracción 01 (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:

logs: format: json # classic | json | logfmt | gelf | raw min_level: warn # trace..fatal; omítelo para no filtrar por nivel

Inspecciona la configuración efectiva y combinada (con el secreto enmascarado) con:

$ swarmexec config show # o: --json

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

VariableDefine
SWARMEXEC_CONFIGruta del archivo de configuración
SWARMEXEC_CA / _CERT / _KEYmaterial mTLS
SWARMEXEC_PORTpuerto del agente
SWARMEXEC_ADDR_MODEhostname / ip
SWARMEXEC_SERVER_NAMEnombre de servidor TLS
SWARMEXEC_AGENT_SECRET / _FILEsecreto compartido / archivo del secreto
SWARMEXEC_INSECUREomite la verificación (1/true/yes/on)
SWARMEXEC_OPERATORidentidad de auditoría
SWARMEXEC_UI_DIMatenuación del fondo del overlay (ui.dim)
SWARMEXEC_KEYSruta del archivo de atajos de la TUI (por defecto keys.yaml junto a la configuración)
SWARMEXEC_SSH_MULTIPLEX0/off/false/no desactiva las conexiones ssh compartidas (véase Conexiones ssh compartidas)
DOCKER_CONTEXTcontexto de Docker para la API del manager
XDG_CONFIG_HOMEbase 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.

$ swarmexec --ca ca.crt --cert me.crt --key me.key ps

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).

$ swarmexec --agent-secret "$SECRET" --insecure ps
insecure omite la comprobación del certificado de servidor del agente. Esto no pone en riesgo el secreto: el cliente nunca lo envía, solo una prueba calculada a partir del secreto y del certificado de la conexión por la que viaja, de modo que un servidor que no verificaste no recibe nada que sirva contra un agente real. Lo que queda es que el agente no está autenticado ante ti: quien responda puede leer lo que esa sesión le envía. Prefiere una 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

Un cliente v1.17.3 no puede autenticarse ante un agente más antiguo. Envía solo la prueba ligada a la conexión, y los agentes anteriores a v1.17.3 no reconocen esa forma: responden invalid or missing agent secret aunque tu secreto sea correcto. Actualízalos primero:
$ swarmexec init --force
Mientras la flota esté mezclada, 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.

No se requiere Docker local. El cliente nunca ejecuta el binario 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_HOST apuntando a un manager remoto por tcp:// (mTLS) — en ese caso no hay Docker instalado en tu estación de trabajo en absoluto;
  • un contexto ssh:// — necesita el cliente ssh (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.
La propia CLI de 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.

$ docker context create swarm --docker host=ssh://ops@manager.example.com $ swarmexec --context swarm exec web -- sh

8. Flags globales

Estos flags persistentes se aplican a todos los comandos:

FlagPor defectoDescripción
--configruta del archivo de configuración (por defecto ~/.config/swarmexec/config.yaml)
--contextcontexto de docker para la API del manager; admite ssh:// (también $DOCKER_CONTEXT)
--port9443puerto del agente
--addr-modehostnamedirección para marcar al nodo: hostname | ip
--cacertificado CA para verificar al agente (mTLS)
--certcertificado de cliente (mTLS; el CN es la identidad de operador)
--keyclave privada del cliente (mTLS)
--server-namesobrescribe el nombre de servidor TLS para la verificación del agente
--agent-secretsecreto compartido para un agente autofirmado
--agent-secret-filearchivo del que leer el secreto compartido
--insecurefalseomite la verificación del certificado de servidor del agente
--operatorusuario del SOidentidad de operador reportada para la auditoría
--log-levelinfoverbosidad del log: debug | info | warn | error | off (off desactiva por completo el logging)
--log-fileruta del archivo de log (por defecto swarmexec.log junto al archivo de configuración)
--infomuestra la versión, la licencia y los datos de contacto
--versionimprime la versión del cliente y del protocolo
Logging. swarmexec registra en formato estructurado (Go 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.

swarmexec init
FlagPor defectoDescripción
--imagedocker.io/logleio/swarmexec-agent:latestimagen del agente a desplegar
--secretaleatoriosecreto compartido a usar (por defecto: genera uno aleatorio)
--service-nameswarmexec_agentnombre del servicio del agente
--port9443puerto de host que publica el agente
--forcefalseactualiza el servicio si ya existe
--save-configtrueescribe la configuración de cliente
--registry-authtruepasa las credenciales locales de registro para que los nodos puedan descargar una imagen privada
--waittrueespera a que los agentes arranquen e informa del progreso
--rollout-timeout90scuánto esperar a que arranquen los agentes
La imagen de agente por defecto es pública en Docker Hub, así que no hace falta iniciar sesión en ningún registro. Vuelve a ejecutarlo con --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.

swarmexec down
FlagPor defectoDescripción
--service-nameswarmexec_agentnombre del servicio del agente a eliminar
--keep-secretfalseno eliminar el secreto de Docker con el secreto compartido
-y, --yesfalseno 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.

swarmexec doctor
FlagPor defectoDescripción
--connect-timeout10stiempo de espera de conexión por nodo
--jsonfalseemite 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.

swarmexec security report -o security.md

Cuatro comprobaciones se ejecutan a nivel de clúster, y ninguna de ellas puede responderla la especificación de un servicio:

ComprobaciónSeveridadQué señala
network-unencrypted — «el tráfico overlay no está cifrado»mediumuna 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»mediumel 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-unreachablehigh / medium / lowel 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-configlowun 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.

FlagPor defectoDescripción
-o, --outputstdoutescribir en este fichero en lugar de stdout
--connect-timeout10stiempo de conexión por nodo al comprobar los agentes
--skip-agentsfalseno 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.

swarmexec stack ls swarmexec stack export postgres -o postgres.yml swarmexec stack diff postgres.yml postgres

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.

OrdenDescripción
stack lslos 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.

swarmexec stack deploy web.yml # pregunta si encuentra algo swarmexec stack deploy web.yml --check # solo comprobar, nunca desplegar swarmexec stack deploy web.yml --force # desplegar pese a los hallazgos, a conciencia

En la TUI, EDeploy 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.

FlagDescripción
--checkcomprueba y para. Termina con 1 si encontró algo por encima de lo informativo, así que sirve de control en una pipeline
-y, --yesno preguntar. Despliega si las comprobaciones están limpias; se niega si no
--forcedesplegar pese a los hallazgos, sin preguntar
--pruneelimina 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.

$ swarmexec init --force

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.

No pases --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 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.

$ swarmexec init --force --save-config=false

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.

$ swarmexec doctor

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.

swarmexec ps [service]
FlagPor defectoDescripción
--jsonfalseemite 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.

swarmexec exec [flags] <target> [-- <cmd> [args...]]
FlagPor defectoDescripción
-i, --stdintruemantiene stdin abierto
-t, --ttyautoasigna una TTY (auto: verdadero solo si stdin es una terminal y no hay comando)
-u, --usernombre de usuario o UID (p. ej. 1000:1000)
-w, --workdirdirectorio de trabajo dentro del contenedor
-e, --envdefine variables de entorno (KEY=VALUE, repetible)
--nodepista/anulación de nodo para destinos de tipo container-id
--connect-timeout10stiempo de espera para conectar con el agente
$ swarmexec exec web -- sh $ swarmexec exec web.2 -- cat /etc/hostname # una réplica concreta $ swarmexec exec -u root -w /app web -- ls -la $ swarmexec exec -e FOO=bar web -- env

logs — transmitir logs

Transmite los logs de un contenedor desde cualquier lugar del swarm.

swarmexec logs [flags] <target>
FlagPor defectoDescripción
-f, --followfalsesigue transmitiendo las nuevas líneas de log
--tail0líneas desde el final con las que empezar (0 = todas)
-t, --timestampsfalseantepone una marca de tiempo a cada línea
--since0solo logs más nuevos que esto (p. ej. 10m, 1h)
--log-formatclassicanaliza las líneas como classic | json | logfmt | gelf | raw (por defecto desde la configuración, si no classic)
--min-levelsolo muestra este nivel y superiores: trace | debug | info | warn | error | fatal
--grepsolo muestra las líneas cuyo mensaje (analizado) coincide con esta expresión regular de Go
--nodepista/anulación de nodo para destinos de tipo container-id
--connect-timeout10stiempo 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.

Las líneas sin nivel detectable siempre pasan el filtro --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.

La detección vive en el cliente: aprende la topología del manager de swarm al que ya está conectado — los agentes por nodo son locales a su nodo y no pueden ver los reemplazos a nivel de swarm.
$ swarmexec logs -f --tail 100 web $ swarmexec logs --since 15m -t web $ swarmexec logs --log-format json --min-level warn web $ swarmexec logs --grep 'timeout|refused' -f web
De cara al futuro: la selección de formato está diseñada para admitir más adelante la detección automática de formato por servicio a través de un servicio en línea (aún no implementado).

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.

swarmexec port-forward [flags] <target> [local:]remote
FlagPor defectoDescripción
--address127.0.0.1dirección local a enlazar (loopback mantiene el puerto fuera de tu red)
--nodepista/anulación de nodo para destinos de tipo container-id
--connect-timeout10stiempo de espera para conectar con el agente
$ swarmexec port-forward db 5432 # localhost:5432 → contenedor:5432 $ swarmexec pf web 9090:8080 # localhost:9090 → contenedor:8080

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).

swarmexec volume ls [name-filter]
FlagPor defectoDescripción
--sizefalsecalcula también el tamaño en disco de cada volumen (más lento: du por volumen)
--sortnameordena por: name | nodes | used | age | size (size implica --size)
--reversefalseinvierte la dirección de ordenación
--connect-timeout10stiempo de espera de conexión por nodo
--jsonfalseemite 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.

swarmexec volume rm <name> (--all | --node N …)
FlagPor defectoDescripción
--allfalseelimina en cada nodo que contiene el volumen
--nodeelimina solo en estos nodos (repetible)
--forcefalsepasa el flag force de docker
-y, --yesfalseno pedir confirmación
--connect-timeout10stiempo 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.

swarmexec ui [service]
FlagPor defectoDescripción
--connect-timeout10stiempo 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.

swarmexec config show [--json]

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):

swarmexec context create <name> --docker-host <endpoint>
FlagPor defectoDescripción
--docker-hostobligatorio — endpoint del daemon de docker: ssh:// | tcp:// | unix:// | npipe://
--descriptiondescripción opcional
--ssh-jumphost(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
--usefalseademás, conviértelo en el contexto actual

context ls (alias list) — lista los contextos; columnas NAME  CURRENT  DOCKER ENDPOINT (el activo marcado con *):

swarmexec context ls [--json]

context use <name> — fija el contexto actual:

swarmexec context use <name>

context rm <name> [name...] (alias remove) — elimina uno o más contextos:

swarmexec context rm <name> [name...]
FlagPor defectoDescripción
-f, --forcefalseobligatorio para eliminar el contexto actual (su selección se restablece a default)
$ swarmexec context create prod --docker-host ssh://ops@manager --use $ swarmexec context ls $ swarmexec --context prod ps

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.

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.

Cambio: los contextos eran la pestaña 6 y ahora son la barra lateral (c). Nodes y Configs pasan a 6 y 7.
TeclaAcció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
Tabpasa a la siguiente pestaña
18salta a Stacks/Services / Volumes / Forwards / Networks / Secrets / Contexts / Nodes / Configs
j kmueve abajo / arriba (también )
rrefresca la pestaña activa y el resumen del clúster
ycopia la lista actual al portapapeles (OSC52)
malterna 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)
qsalir

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.

TeclaAcción
/abre la barra de búsqueda (filtra por servicio / contenedor / nodo)
h lcolapsa / 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
Entersobre un contenedor: abre el menú de acciones; sobre un servicio o un stack: lo colapsa / expande
salterna la agrupación por stack — árbol agrupado ⟷ lista plana de servicios (ver más abajo)
Llogs (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)
preenvía el puerto de la tarea bajo el cursor
iinspecciona 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
Xelimina 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:

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.

De dónde sale el veredicto — y qué cuesta. Nada añadido: viaja con las lecturas de recursos de más abajo, el mismo agente, la misma llamada, ninguna petición extra a la API. La lista de contenedores que el agente ya pide en cada pasada de muestreo trae el veredicto en su línea de estado (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 palabrascpu 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.

De dónde salen las cifras — y por qué tardan un poco. El uso no está en la API del manager: las estadísticas de contenedor son locales a cada nodo. Vienen de un RPC del agente local al nodo (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.
¿No ves ningún marcador? Revisa la versión del agente. El uso es complementario y se degrada en silencio: un nodo cuyo agente no es alcanzable —o más antiguo que esta release y por tanto ajeno al RPC 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ónSeveridadQué señala
docker-socket — «Docker socket mounted in»highel 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 / mediumuna 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»highel 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»highel 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»highla spec fija el contenedor a root explícitamenteUser=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»highuna 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»lowno 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»lowla 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»lowla 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.

Nunca se muestra el valor de un secreto. De las ocho comprobaciones, solo 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.

El escaneo es un registro extensible de analizadores, no una lista fija: una comprobación nueva aparece automáticamente tanto en el marcador del árbol como en el overlay. El propio texto del overlay sale también de ese registro: el número de comprobaciones de su cabecera y la lista «Checked:» se generan a partir de los propios analizadores, así que no pueden desviarse de lo que realmente se ejecutó, como sí le pasaría a una lista fija en el código. La tecla se puede remapear como 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):

RESOURCE USAGE measured on each node, refreshed with the tree CONTAINER NODE HEALTH CPU MEMORY gl_gitlab.1 docker3v2 healthy 0.07 / 4.00 cores (2%) 6.823GiB / 8GiB (85%) limits are the container's own where it has one, otherwise the node's capacity

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 13 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.

TeclaAcción
j kmueve la selección entre líneas de datos (se omiten encabezados/blancos)
ycopia la línea seleccionada al portapapeles (OSC52) — siempre
Enteren 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 3selecciona 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
trota 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
amenú 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 Aedita 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 icierra 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:

- dbnet vip 10.0.1.137/24 (2 dns names) gl_gitlab tasks.gl_gitlab - 1 container gl_gitlab.1 10.0.1.54/24 docker3v2 - ingress vip 10.0.0.250/24 (routing mesh) published 2222 -> 22/tcp - 1 container gl_gitlab.1 10.0.0.64/24 docker3v2

Copiar dentro de NETWORKS da la dirección sin su máscara10.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:

TeclaAcción
ddiff — 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
Rrevertir 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
sescalar — pide un nuevo recuento de réplicas y lo aplica (solo servicios replicados; un servicio global informa de que no se puede escalar)
fforzar 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)
Xelimina 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
Ddiagnostica 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
pedita 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
ledita etiquetas — el mismo editor de lista por etapas sobre entradas key=value
eedita 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 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)
nedita 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.)
Sedita 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
vedita 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
redita 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
Pedita 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.
  • Restricciones — las restricciones de colocación duras del servicio (node.role, node.hostname, node.id, node.platform.os/arch, node.labels.<k>, engine.labels.<k>, cada una con == o !=, p. ej. node.role==manager, node.labels.zone!=eu). La entrada de añadir/editar autocompleta candidatos completamente formados construidos a partir del clúster actual — el rol, hostname, plataforma y etiquetas de nodo/engine de cada nodo— para que normalmente puedas elegir una restricción en lugar de escribirla (se sugiere la forma ==; escribe != a mano para una negación). El conjunto de nodos resultante también alimenta la salvaguarda de bind mount del editor de montajes.
  • Preferencias de distribución — las estrategias blandas --placement-pref spread=…: una lista por etapas y ordenada de atributos de nodo simples (p. ej. node.labels.zone, node.hostname) sobre los que Swarm distribuye las tareas de forma uniforme. La entrada autocompleta las claves de etiqueta de nodo/engine del clúster y los atributos de nodo (un descriptor es solo el atributo — sin operador ni valor).
Aplicar cualquiera de los dos reemplaza esa parte de la colocación en un 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

TeclaAcción
/búsqueda — filtra la lista por nombre de volumen, driver o nodo
ncrea 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
spaceselecciona / deselecciona el volumen (marcado ) para un borrado masivo
aselecciona / deselecciona todos los volúmenes mostrados actualmente
delimina 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)
Pprune: elimina cada volumen que ningún contenedor en ejecución monta y ningún servicio declara, tras una confirmación
Entermuestra 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)
imuestra qué servicios/contenedores lo usan
Aadjunta 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 Scicla 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

TeclaAcción
Entermuestra el detalle completo del reenvío (incluido cualquier error)
ddetiene el reenvío seleccionado
ocopia 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):

TeclaAcción
ncrea 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 / imuestra 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)
Enteren 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
Aen 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)
aen 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
den 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.

TeclaAcción
Entermuestra los metadatos del secreto y los servicios/contenedores que lo usan
aen 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
den 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.

TeclaAcción
cenfocar la barra lateral desde cualquier pestaña; c otra vez o Esc devuelve el teclado
idetalles del contexto — endpoint y jump hosts, para los que la columna no tiene sitio
u / Enteractiva 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
ncrea 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
delimina 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).

TeclaAcción
Enter / idetalles 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
ledita 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>)
afija 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
Precupera 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.

Son reservas, no uso. Las cifras son la suma de las 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».

Solo contenedores. Aquí se cuentan los contenedores del nodo — el kernel, el demonio de docker y cualquier cosa que corra fuera de docker no entran en la cifra. No es la carga del nodo, y el propio bloque lo dice.

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:

EntradaQué elimina
Untagged leftoverslas 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 imageelimina 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
No leas la segunda entrada como «lo mismo, pero más». «Every unused image» elimina imágenes que pertenecen a algo que sencillamente no está corriendo en este instante: un servicio escalado a cero, una tarea que se reinicia, un stack que paraste ayer. Todos pierden su imagen y tienen que volver a descargarla para regresar, y si el registro no es alcanzable, no volverán a levantarse. Cada una de las dos entradas pasa por su propio diálogo de confirmación, que enuncia exactamente esa consecuencia y nombra los bytes que liberaría; son textos separados precisamente para que ninguno pueda acabar describiendo al otro.

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.

De dónde salen las cifras. Se leen de la misma fuente de la que informa 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).

TeclaAcción
Enter / iabre 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 Gen 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:

TeclaAcción
falterna el seguimiento
Fcicla el formato de log (classic → json → logfmt → gelf → raw)
lcicla el filtro de nivel mínimo (off → trace → … → fatal → off)
/introduce una expresión regular grep sobre el mensaje (vacía la borra)
malterna la captura del ratón (off = selección/copia propia de tu terminal)
desplaza
Esc / qcierra

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.

Capacidad de respuesta. La lista de contenedores se refresca fuera del hilo de la UI (sus dos llamadas a la API del manager antes se ejecutaban en línea y podían congelar brevemente la TUI), y un watchdog en segundo plano registra una advertencia cuando el bucle de eventos se atasca — útil para diagnosticar lentitud desde el visor de logs.

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 18 y los alias vim j/k/g/G — son fijas. El pie siempre muestra tus teclas reales, y el overlay ? las lista todas.

# ~/.config/swarmexec/keys.yaml — omite cualquier línea para conservar su valor por defecto quit: q refresh: r copy: y toggle_mouse: m search: / fold: h unfold: l forward: p container_inspect: i logs: L security_risks: "!" # "!" debe ir entre comillas — YAML stack_group: s volume_select: space volume_select_all: a volume_delete: d volume_prune: P volume_used_by: i volume_attach: A volume_sort: s volume_sort_reverse: S volume_new: n network_attached: i network_new: n context_use: u context_new: n context_delete: d node_edit_labels: l node_availability: a node_prune_images: P secret_delete: d secret_new: n forward_stop: d forward_copy_url: o

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ódigoSignificado
0éxito
2error de uso / flag / configuración, o una selección interactiva abortada
125fallo de transporte — manager o agente inaccesible, error de dial/TLS, error de stream, confirmación abortada (refleja el 125 de Docker)
Npara exec, el propio código de salida distinto de cero del comando remoto, propagado tal cual