← swarm-exec.cloud-surfers.net

swarmexec_ docs

Utiliser le client — commandes, options, config & la TUI.

swarmexec vous donne docker exec -it, les logs, la redirection de ports et la gestion des volumes sur n'importe quel conteneur d'un Docker Swarm, depuis un seul terminal. Un petit agent par nœud (un service Swarm global) fait le travail sur son nœud ; le client demande au manager du Swarm quel nœud exécute votre cible, puis se connecte directement à l'agent de ce nœud via mTLS. Cette page documente le client.

1. Fonctionnement

Deux binaires, un seul protocole réseau :

terminal opérateur ──gRPC/mTLS──> agent(nœudN) ──docker.sock──> conteneur └── API du manager Docker (résout le nœud + l'id du conteneur)

La connexion au manager utilise votre contexte Docker CLI (elle respecte donc --context, les bastions ssh://, mTLS, etc.). La connexion à l'agent est authentifiée par TLS mutuel, ou par un secret partagé avec un agent auto-signé — voir Authentification.

2. Installation

Nécessite Docker Engine 19.03 ou plus récent (API 1.40) sur le manager et sur chaque nœud. swarmexec refuse un démon plus ancien dès la connexion, en nommant les deux versions, plutôt que de se connecter puis d'échouer vue après vue. Deux fonctionnalités demandent un peu plus au démon du nœud : l'utilisation des ressources en direct et la vue des images par nœud veulent Docker 23.0 (API 1.41/1.42) ; en dessous elles restent vides et le disent, au lieu d'échouer.

Le client est un unique binaire statique. Récupérez le build de la dernière version pour votre plateforme :

$ 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. L'image de l'agent est publique sur Docker Hub sous logleio/swarmexec-agent.

3. Démarrage rapide

Pointez votre contexte Docker sur un manager Swarm, puis provisionnez les agents une seule fois. init crée un secret partagé, déploie l'agent sur chaque nœud en mode auto-signé et écrit une configuration client correspondante — de sorte que les autres commandes fonctionnent immédiatement.

$ swarmexec --context my-swarm init # déployer les agents sur chaque nœud (une fois) $ swarmexec ps # lister les tâches et le nœud sur lequel chacune s'exécute $ swarmexec exec web -- sh # ouvrir un shell dans le service « web », n'importe quel nœud $ swarmexec logs -f web # suivre les logs d'un service $ swarmexec port-forward db 5432 # localhost:5432 → conteneur:5432 $ swarmexec ui # TUI interactive

Retirez à nouveau les agents avec swarmexec down (cela laisse votre configuration client intacte).

4. Sélecteurs de cible

exec, logs et port-forward prennent une cible comme premier argument. Elle est résolue dans cet ordre :

FormeExempleSignification
servicewebLa tâche en cours d'exécution du service. S'il a plus d'une réplique, c'est ambigu — voir ci-dessous.
service.slotweb.2Un emplacement de réplique précis. Pendant une mise à jour progressive, la tâche la plus récente de l'emplacement l'emporte.
task-idxxh8k1…Un ID de tâche Swarm.
container-id3f9a2b…Un préfixe d'ID de conteneur. Nécessite un nœud : passez --node, ou il est trouvé en parcourant les tâches en cours d'exécution.

Ambiguïté. Un simple nom de service avec plusieurs répliques ne peut pas être résolu vers une seule tâche. exec vous invite à choisir dans une liste numérotée lorsqu'il est exécuté de façon interactive ; logs et port-forward ne demandent jamais — ils affichent la liste des candidats et se terminent. Levez l'ambiguïté avec un emplacement (web.0, web.1, …).

--node n'est consulté que pour les cibles de type container-id et peut être un nom d'hôte, une IP ou un ID de nœud. Les cibles service / emplacement / tâche sont localisées via l'API du manager, elles n'en ont donc jamais besoin.

5. Configuration

Les paramètres proviennent de quatre couches, chacune l'emportant sur la précédente :

valeurs par défaut intégrées fichier de config environnement options en ligne de commande

Une option ne l'emporte que lorsque vous la passez effectivement, de sorte qu'une valeur du fichier ou de l'environnement subsiste sauf si elle est explicitement remplacée.

Fichier de configuration

Le chemin par défaut est le premier de ceux-ci qui est défini :

  1. $SWARMEXEC_CONFIG
  2. $XDG_CONFIG_HOME/swarmexec/config.yaml
  3. ~/.config/swarmexec/config.yaml

Remplacez-le avec --config <path>. Le fichier est en YAML ; un fichier manquant ne pose pas de problème, un fichier mal formé est une erreur. Il est écrit avec les droits 0600 (il peut contenir le secret partagé). Le fichier de log se trouve à côté par défaut — ~/.config/swarmexec/swarmexec.log — sauf si vous définissez --log-file. Clés :

CléTypeSignification
castringcertificat CA qui vérifie le certificat serveur de l'agent (mTLS)
certstringcertificat client — son CN est votre identité d'opérateur
keystringclé privée du client
portintport de l'agent (par défaut 9443)
addr_modestringhostname (par défaut) ou ip — comment joindre un nœud
server_namestringremplace le nom de serveur TLS utilisé pour vérifier l'agent
agent_secretstringsecret partagé pour un agent auto-signé
agent_secret_filestringlit le secret depuis ce fichier (prioritaire sur agent_secret)
insecureboolignore la vérification du certificat serveur de l'agent (agents auto-signés)
legacy_secretboolenvoie en plus le secret brut, pour les agents antérieurs à v1.17.3 — désactivé par défaut ; voir ci-dessous
operatorstringidentité d'audit lorsqu'aucun certificat client n'est utilisé (par défaut : nom d'utilisateur du système)
logs.formatstringformat de log par défaut pour logs et la TUI : classic | json | logfmt | gelf | raw (vide = classic)
logs.min_levelstringniveau minimum par défaut : trace..fatal (omettez pour aucun filtre de niveau)
ui.dimfloatà quel point l'arrière-plan derrière un overlay ouvert est assombri, une fraction 01 (par défaut 0.6 ; 0 = aucun assombrissement)

La section logs: définit les valeurs par défaut de l'analyse et du filtrage des logs tenant compte du format ; les options --log-format / --min-level de la commande logs les remplacent :

logs: format: json # classic | json | logfmt | gelf | raw min_level: warn # trace..fatal ; omettez pour aucun filtre de niveau

Inspectez la configuration effective et fusionnée (secret masqué) avec :

$ swarmexec config show # ou : --json

addr-mode

hostname (par défaut) joint le nom d'hôte signalé par le nœud ; ip joint son adresse annoncée. Utilisez ip lorsque les noms d'hôte des nœuds ne sont pas résolvables depuis votre poste de travail — init écrit addr_mode: ip dans la configuration générée précisément pour cette raison. Le leader du swarm signale 0.0.0.0 pour lui-même ; le client récupère automatiquement sa véritable adresse depuis la liste des pairs raft.

Variables d'environnement

VariableDéfinit
SWARMEXEC_CONFIGchemin du fichier de configuration
SWARMEXEC_CA / _CERT / _KEYmatériel mTLS
SWARMEXEC_PORTport de l'agent
SWARMEXEC_ADDR_MODEhostname / ip
SWARMEXEC_SERVER_NAMEnom de serveur TLS
SWARMEXEC_AGENT_SECRET / _FILEsecret partagé / fichier de secret
SWARMEXEC_INSECUREignore la vérification (1/true/yes/on)
SWARMEXEC_OPERATORidentité d'audit
SWARMEXEC_UI_DIMassombrissement du fond de l'overlay (ui.dim)
SWARMEXEC_KEYSchemin du fichier de raccourcis de la TUI (par défaut keys.yaml à côté de la configuration)
SWARMEXEC_SSH_MULTIPLEX0/off/false/no désactive le partage des connexions ssh (voir Partage des connexions ssh)
DOCKER_CONTEXTcontexte Docker pour l'API du manager
XDG_CONFIG_HOMEbase du chemin de configuration par défaut

6. Authentification

Le client s'authentifie auprès de l'agent selon l'un de deux modes.

Mode A — TLS mutuel (par défaut)

Définissez ca, cert et key (les trois requis). Le client vérifie l'agent par rapport à votre CA et présente son certificat ; l'agent vous autorise et vous audite par le CN du certificat. C'est le mode par défaut et la posture recommandée.

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

Mode B — agent auto-signé + secret partagé

Plus simple à exploiter (un secret, pas de PKI) — c'est ce que met en place init. Définissez agent_secret (ou agent_secret_file), et soit un ca pour vérifier l'agent soit insecure: true pour ignorer la vérification. Un certificat client est facultatif (mais cert et key doivent être définis ensemble ou tous deux vides). Votre identité d'audit est la valeur operator (par défaut : votre nom d'utilisateur système).

$ swarmexec --agent-secret "$SECRET" --insecure ps
insecure ignore la vérification du certificat serveur de l'agent. Cela ne met pas le secret en danger : le client ne l'envoie jamais, seulement une preuve calculée à partir du secret et du certificat de la connexion qu'elle emprunte — un serveur que vous n'avez pas vérifié ne reçoit donc rien qui fonctionne contre un vrai agent. Il reste que l'agent n'est pas authentifié auprès de vous : celui qui répond peut lire ce que cette session lui envoie. Préférez un ca sur tout réseau auquel vous ne faites pas confiance, et gardez le secret hors de l'historique de votre shell (utilisez agent_secret_file ou le fichier de configuration).

Passage à v1.17.3 — mettez d'abord les agents à jour

Un client v1.17.3 ne peut pas s'authentifier auprès d'un agent plus ancien. Il n'envoie que la preuve liée à la connexion, et les agents antérieurs à v1.17.3 ne connaissent pas cette forme : ils répondent invalid or missing agent secret alors même que votre secret est correct. Mettez-les à jour d'abord :
$ swarmexec init --force
Tant que le parc est mixte, legacy_secret: true dans la configuration du client envoie aussi le secret brut, avec l'exposition décrite ci-dessus. Retirez-le dès que les agents sont à jour, et ajoutez -allow-legacy-secret=false à l'agent pour qu'aucun client ne puisse mettre l'identifiant sur le câble par accident.

7. Contexte Docker & SSH

--context sélectionne le contexte Docker CLI utilisé pour l'API du manager. Ordre de résolution : --context$DOCKER_CONTEXT$DOCKER_HOST → le contexte actif dans ~/.docker/config.json → le socket local unix:///var/run/docker.sock.

Aucun Docker local requis. Le client n'exécute jamais le binaire docker — il intègre le SDK Go de Docker et dialogue directement avec l'API du manager, lisant lui-même toute métadonnée de contexte depuis les fichiers. Tout ce dont il a besoin est un point d'accès manager Swarm joignable (les appels de résolution de nœud ne fonctionnent que contre un manager) :
  • $DOCKER_HOST sur un manager tcp:// distant (mTLS) — dans ce cas aucun Docker n'est installé sur votre poste de travail ;
  • un contexte ssh:// — nécessite le client ssh (pas docker) ; le trafic du manager et de l'agent est tunnelisé au travers ;
  • le socket local unix:///var/run/docker.sock — uniquement le repli par défaut, et la seule option qui implique un démon local.
La CLI docker elle-même n'est nécessaire que pour créer des contextes nommés (docker context create) — ou créez-les avec swarmexec context create <name> --docker-host …, de sorte que la CLI docker n'est plus nécessaire même pour cela ; utilisez $DOCKER_HOST pour éviter entièrement les contextes nommés. (init ne lit les identifiants du docker login local que pour une image d'agent privée — pas pour l'image publique par défaut.)

Bastion SSH. Si l'hôte du contexte est un point d'accès ssh://, l'API du manager est tunnelisée par SSH — tout comme la connexion à l'agent : comme les points d'accès node:9443 des nœuds ne sont généralement pas routables depuis votre poste de travail, le client tunnelise automatiquement le trafic gRPC de l'agent par le même hôte SSH. Aucune option supplémentaire nécessaire.

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

8. Options globales

Ces options persistantes s'appliquent à chaque commande :

OptionDéfautDescription
--configchemin du fichier de configuration (par défaut ~/.config/swarmexec/config.yaml)
--contextcontexte docker pour l'API du manager ; prend en charge ssh:// (aussi $DOCKER_CONTEXT)
--port9443port de l'agent
--addr-modehostnameadresse de connexion au nœud : hostname | ip
--cacertificat CA pour vérifier l'agent (mTLS)
--certcertificat client (mTLS ; le CN est l'identité d'opérateur)
--keyclé privée du client (mTLS)
--server-nameremplace le nom de serveur TLS pour la vérification de l'agent
--agent-secretsecret partagé pour un agent auto-signé
--agent-secret-filefichier depuis lequel lire le secret partagé
--insecurefalseignore la vérification du certificat serveur de l'agent
--operatornom d'utilisateur du systèmeidentité d'opérateur signalée pour l'audit
--log-levelinfoverbosité des logs : debug | info | warn | error | off (off désactive entièrement la journalisation)
--log-filechemin du fichier de log (par défaut swarmexec.log à côté du fichier de configuration)
--infoaffiche la version, la licence et les informations de contact
--versionaffiche la version du client et du protocole
Journalisation. swarmexec journalise sous forme structurée (Go slog, format texte) dans le fichier de log et dans un tampon circulaire en mémoire. Dans la TUI, les logs ne sont jamais écrits dans le terminal (cela corromprait l'écran) — le tampon circulaire alimente à la place un visualiseur en direct dans l'application (voir La TUI). --log-level off désactive entièrement la journalisation ; un fichier de log qui ne peut pas être ouvert n'est pas fatal (il se rabat uniquement sur le tampon circulaire).

9. Commandes

init — provisionner les agents

Déploie l'agent comme service Swarm global via l'API du manager : crée un secret Docker de secret partagé, exécute l'agent en mode auto-signé sur chaque nœud (port hôte 9443) et écrit la configuration client correspondante. À exécuter une fois par swarm.

swarmexec init
OptionDéfautDescription
--imagedocker.io/logleio/swarmexec-agent:latestimage de l'agent à déployer
--secretaléatoiresecret partagé à utiliser (par défaut : en générer un aléatoire)
--service-nameswarmexec_agentnom du service de l'agent
--port9443port hôte que l'agent publie
--forcefalsemet à jour le service s'il existe déjà
--save-configtrueécrit la configuration client
--registry-authtruetransmet les identifiants de registre locaux pour que les nœuds puissent récupérer une image privée
--waittrueattend que les agents démarrent et signale la progression
--rollout-timeout90sdurée d'attente du démarrage des agents
L'image d'agent par défaut est publique sur Docker Hub, aucune connexion à un registre n'est donc nécessaire. Relancez avec --force pour déployer une nouvelle version de l'agent — voir redéployer les agents sans changer le secret.

down — retirer les agents

L'inverse de init : retire le service d'agent global et, par défaut, le secret Docker de secret partagé. Il ne touche pas à votre configuration client.

swarmexec down
OptionDéfautDescription
--service-nameswarmexec_agentnom du service d'agent à retirer
--keep-secretfalsene pas retirer le secret Docker de secret partagé
-y, --yesfalsene pas demander de confirmation

doctor — diagnostiquer le swarm

Vérifie la connexion au manager, si le service d'agent est déployé, et sonde l'agent de chaque nœud prêt pour vérifier sa joignabilité et l'écart de version. Affiche un tableau par nœud (NODE  AGENT  VERSION  PROTO) ; se termine avec un code non nul si le manager est injoignable ou si un agent est en mauvaise santé. Un nœud signalé too old (init --force) exécute un agent plus ancien que votre client — voir redéployer les agents sans changer le secret.

swarmexec doctor
OptionDéfautDescription
--connect-timeout10sdélai de connexion par nœud
--jsonfalseaffiche du JSON au lieu d'un tableau

security report — un rapport Markdown sur tout le cluster

La surcouche des risques de sécurité analyse les spécifications des services et affiche le résultat à l'écran. Cette commande écrit la même analyse — plus les contrôles qui relèvent du cluster et d'aucun service en particulier — au format Markdown, pour la relire loin du terminal, la joindre à un ticket, ou la committer à côté de vos fichiers de stack et la comparer d'une version à l'autre. La sortie va sur stdout sauf si -o est donné : elle se redirige aussi facilement qu'elle s'enregistre.

swarmexec security report -o security.md

Quatre contrôles s'exécutent au niveau du cluster, et aucun ne peut être répondu par la spécification d'un service :

ContrôleGravitéCe qu'il signale
network-unencrypted — « le trafic overlay n'est pas chiffré »mediumun réseau overlay transportant du trafic de services sans chiffrement du plan de données. Swarm tunnellise le trafic entre nœuds sur VXLAN en clair à moins que le réseau n'ait été créé avec --opt encrypted, et le service en fonctionnement n'a pas l'air différent dans un cas ou dans l'autre. Les réseaux sans rien d'attaché ne sont pas signalés — ils ne transportent rien — ni ingress, qui ne peut pas être chiffré du tout : un constat le concernant ne pourrait jamais être levé
autolock-disabled — « les managers ne sont pas autolockés »mediumle magasin raft des managers n'est pas chiffré au repos. Il contient chaque secret, chaque config et la clé de l'autorité de certification du cluster, et sa clé se trouve sur le même disque : qui emporte le disque d'un manager emporte le tout. Activez-le avec docker swarm update --autolock=true et conservez la clé de déverrouillage : un manager redémarré la demandera. Si la configuration du swarm est illisible, le rapport indique autolock-unknown plutôt que de deviner — supposer « désactivé » inventerait un constat, et supposer « activé » serait un faux tout-va-bien
agent-proto-mismatch / agent-version-skew / agent-too-old / agent-unreachablehigh / medium / lowl'agent est ce qui applique l'autorisation à chaque exec, chaque log et chaque port-forward : un agent plus ancien que votre client peut ne pas appliquer une règle que le client tient pour acquise. Un désaccord de protocole est high et prime sur un écart de version : les deux extrémités ne s'accordent pas sur le contrat lui-même, et pas seulement sur le build qui l'implémente. Un agent qui n'a pas répondu est signalé comme une lacune, pas comme un succès. Aucun écart n'est signalé pour un client non publié (dev), qui diffère par construction de tout agent publié — la même exception que fait doctor
unused-secret / unused-configlowun secret ou une config qu'aucun service ne référence. Il continue d'être distribué par le magasin raft et reste lisible par tout ce qui atteint un manager — le plus souvent un identifiant que quelqu'un a fait tourner sans jamais le supprimer, si bien que l'ancienne valeur est toujours vivante dans le cluster longtemps après que tout le monde la croit partie

Ce que le rapport dit de lui-même. Il est lu loin du terminal, par quelqu'un qui n'était pas là quand il a tourné : il porte donc son propre contexte — quel cluster, quand, avec quel build. Il comporte aussi une section Not covered — agents injoignables, configuration du swarm illisible, liste de nœuds vide — parce que le silence dans un rapport de sécurité se lit comme un tout-va-bien sur un terrain qu'il n'a jamais foulé. L'ordre ne dépend que des constats : deux rapports d'un cluster inchangé ne diffèrent que par l'horodatage et peuvent être comparés l'un à l'autre — ce qui ne vaut quelque chose que parce qu'un constat absent signifie non trouvé et jamais non cherché.

Le fichier est écrit en 0600, les répertoires parents créés en 0700. Il nomme chaque service, chaque réseau et chaque secret du cluster avec chaque faiblesse trouvée : c'est une carte des points d'attaque, et elle n'a rien à faire en lecture pour tous les comptes de la machine.

OptionDéfautDescription
-o, --outputstdoutécrire dans ce fichier au lieu de stdout
--connect-timeout10sdélai de connexion par nœud lors de la vérification des agents
--skip-agentsfalsene pas contacter les agents des nœuds. Plus rapide, et l'écart des agents figure alors dans Not covered au lieu d'être omis en silence

Le même rapport s'écrit depuis la TUI : appuyez sur w dans la surcouche des risques de sécurité. C'est une collecte neuve à l'échelle du cluster, pas un vidage de ce que la surcouche affiche, et elle tourne hors de la goroutine de l'interface — celle-ci reste réactive pendant qu'elle interroge chaque nœud.

stack export / stack diff — une stack déployée sous forme de fichier

stack export relit une stack déployée depuis le cluster et l'écrit en YAML de forme compose. stack diff compare un fichier de stack à ce qui tourne réellement et affiche un diff unifié : une ligne + est ce que déployer le fichier ajouterait, une - ce qu'il retirerait. Comme git diff --exit-code, la commande sort en 1 dès qu'il y a une différence : utilisable en CI.

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

Dans la TUI, E sur l'onglet Stacks/Services propose les deux pour la stack sous le curseur — la ligne de la stack, un de ses services, ou un de ses conteneurs.

Pourquoi pas une comparaison de texte. Une stack déployée porte ce que le fichier n'a jamais eu : l'empreinte de l'image que le démon a résolue au déploiement, ses propres valeurs par défaut de politique de redémarrage et de mise à jour, et l'espace de noms de la stack préfixé à chaque réseau, secret et volume. Comparer les deux comme du texte signale des dizaines de différences pour une stack pourtant exactement à jour — ce qui est pire que pas d'outil du tout, puisque cela apprend à ignorer la sortie. À la place, le fichier passe par le chargeur et le convertisseur compose de docker eux-mêmes, le code qu'utilise docker stack deploy, puis les deux côtés sont réduits par la même fonction. La question à laquelle il est répondu est « déployer ce fichier changerait-il quelque chose ? », pas « ces deux fichiers sont-ils écrits pareil ? ». Les valeurs par défaut du démon sont retirées des deux côtés ; une valeur qui n'est pas la valeur par défaut s'affiche toujours, donc rien de réel n'est masqué.

Un export est une description, pas une sauvegarde. La valeur d'un secret est en écriture seule dans l'API du moteur — elle ne peut jamais être relue — et le contenu d'un volume vit sur les nœuds. Les deux sont donc déclarés external, et le fichier exporté le dit dans son propre en-tête : recréer la stack ailleurs suppose de créer d'abord les secrets. Les champs de spécification que ce rendu ne porte pas (tty, ulimits, réglages seccomp et AppArmor, et quelques autres) sont nommés au même endroit, car l'échec dangereux d'un outil de comparaison n'est pas une réponse fausse mais un silence assuré : « aucune différence » arrive toujours avec ses réserves.

Les variables sont interpolées depuis votre environnement, exactement comme le fait docker stack deploy. Un fichier contenant ${TAG} décrit donc une stack différente pour un TAG différent, et le diff dépend de l'environnement où il tourne — ce qui est correct : prétendre le contraire signalerait « aucun changement » pour un déploiement qui changerait l'image.

CommandeDescription
stack lsles stacks déployées sur le cluster
stack export <stack>écrit la stack en YAML compose ; avec -o dans un fichier, sinon sur stdout
stack diff <fichier> [stack]compare un fichier à la stack déployée. Le nom de la stack vaut par défaut celui du fichier sans extension ; --stack ou un second argument le remplace. Sort en 1 à la moindre différence

stack deploy — appliquer un fichier de stack, après vérification

Charge un fichier de stack, exécute les contrôles de sécurité sur ce qu'il déploierait réellement, puis l'applique. Ce qui en fait l'intérêt face à docker stack deploy, c'est le garde-fou : le fichier est vérifié avant que quoi que ce soit ne soit créé, et si quelque chose au-dessus de l'informatif est trouvé, le déploiement s'arrête et demande.

swarmexec stack deploy web.yml # demande si quelque chose est trouvé swarmexec stack deploy web.yml --check # vérifier seulement, jamais déployer swarmexec stack deploy web.yml --force # déployer malgré les constats, délibérément

Dans la TUI, EDeploy a file fait de même : d'abord les constats, d poursuit, Esc abandonne. À ce moment rien n'a été créé, ce qui est précisément l'objet de cette pause.

Les contrôles ne sont pas un second jeu de règles. Le fichier est converti exactement en les valeurs ServiceSpec que le déploiement soumettrait, et les huit analyseurs existants tournent dessus — les mêmes contrôles qui posent le bouclier dans l'arbre et remplissent la surcouche ! ainsi que le rapport de sécurité. Deux jeux dériveraient : une règle durcie d'un côté et pas de l'autre signifie que le garde-fou laisse passer un fichier que l'arbre signale dès qu'il tourne — et à qui on a dit « rien trouvé », on a dit quelque chose de faux. Cela fait aussi que les contrôles voient ce que le cluster fera, pas ce que le fichier dit ; les valeurs par défaut et raccourcis de compose sont entre les deux.

--yes ne veut pas dire « ignore les constats ». Cela veut dire « ne demande pas », et une exécution qui trouve quelque chose au-dessus de l'informatif refuse tout de même et sort avec un code non nul. Autrement, le garde-fou deviendrait une formalité dès la première mise en CI. Pour déployer malgré les constats il faut écrire --force : autre chose à taper, autre chose à expliquer ensuite. Une exécution non interactive sans réponse sur stdin compte comme non.

OptionDescription
--checkvérifie et s'arrête. Sort en 1 si quelque chose au-dessus de l'informatif a été trouvé : utilisable comme garde-fou de pipeline
-y, --yesne pas demander. Déploie si les contrôles sont propres ; refuse sinon
--forcedéployer malgré les constats, sans demander
--prunesupprime les services de la stack que le fichier ne déclare plus. Désactivé par défaut, comme dans docker : un fichier qui n'est qu'une partie de la stack est bien plus souvent un oubli qu'un ordre de suppression

Ce que fait un déploiement, dans cet ordre : réseaux, puis secrets, puis configs, puis services — un service qui référence quelque chose qui n'existe pas encore échoue, et la stack reste à moitié appliquée. Les réseaux externes sont vérifiés en premier, car un réseau inexistant est la façon la plus courante pour un déploiement de s'arrêter à mi-chemin. Un secret existant n'est jamais écrasé : sa valeur est immuable dans swarm, le changer signifie en créer un nouveau sous un autre nom. Si un déploiement échoue malgré tout en cours de route, ce qui a déjà été appliqué est rapporté avec l'erreur plutôt que laissé à deviner.

Redéployer les agents sans changer le secret

Tôt ou tard, vous devrez redéployer les agents sur un swarm déjà provisionné : un agent se bloque, doctor affiche un nœud en too old (init --force), une commande échoue avec « agent is older than this client (missing RPC) — update it with swarmexec init --force », ou un nœud a été réinstallé et son agent n'est jamais revenu. La solution consiste à relancer init avec --force, dont c'est exactement le rôle : mettre à jour le service s'il existe déjà. Sans cette option, lancer init sur un service existant est une erreur d'utilisation qui vous demande d'ajouter --force ; rien n'est modifié.

$ swarmexec init --force

L'agent est alors redéployé sur chaque nœud et le secret partagé reste inchangé. Les secrets Docker sont immuables : init retrouve le swarmexec_agent_secret existant, le signale comme reusing existing et conserve la valeur déjà présente dans le cluster. Tout client qui fonctionnait avant continue de fonctionner — il n'y a rien à redistribuer.

Ne passez pas --secret lors d'un redéploiement. Si le secret existe déjà, il ne peut pas être écrasé : votre valeur n'est pas appliquée au cluster, mais elle est tout de même écrite dans votre configuration client. À moins qu'elle ne corresponde à la valeur réelle du secret existant, les agents vous rejetteront à partir de ce moment, et la panne n'apparaît que plus tard, sous les traits d'un agent cassé plutôt que d'une configuration locale erronée. init avertit précisément dans ce cas ; prenez cet avertissement au sérieux. Pour changer réellement le secret du cluster, vous devez d'abord supprimer le secret (aucun service ne doit le référencer) puis relancer init.

--save-config vaut true par défaut : un redéploiement réécrit donc aussi ~/.config/swarmexec/config.yaml. Passez --save-config=false dès que la configuration client locale doit rester intacte — par exemple lorsque vous redéployez les agents depuis une machine dont la configuration est déjà correcte, ou depuis la CI.

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

Vérifiez ensuite. doctor indique un état par nœud ; ok sur tous les nœuds signifie que client, agent et secret sont de nouveau cohérents.

$ swarmexec doctor

ps — lister les tâches

Liste les tâches/conteneurs candidats et le nœud sur lequel chacun s'exécute. Prend un filtre de service facultatif. Colonnes : SERVICE  SLOT  CONTAINER  NODE  IP  UPTIME. Cela dialogue uniquement avec l'API du manager — cela fonctionne même avant que les agents soient joignables.

swarmexec ps [service]
OptionDéfautDescription
--jsonfalseaffiche du JSON au lieu d'un tableau

exec — exécuter une commande / ouvrir un shell

Exécute dans un conteneur situé n'importe où dans le swarm. Sans commande, il ouvre /bin/sh. Un TTY est alloué automatiquement lorsque stdin est un terminal et que vous n'avez donné aucune commande ; forcez-le avec -t. Le code de sortie propre de la commande distante est propagé tel quel.

swarmexec exec [flags] <target> [-- <cmd> [args...]]
OptionDéfautDescription
-i, --stdintruegarde stdin ouvert
-t, --ttyautoalloue un TTY (auto : vrai si et seulement si stdin est un terminal et sans commande)
-u, --usernom d'utilisateur ou UID (p. ex. 1000:1000)
-w, --workdirrépertoire de travail dans le conteneur
-e, --envdéfinit des variables d'environnement (KEY=VALUE, répétable)
--nodeindice/remplacement de nœud pour les cibles container-id
--connect-timeout10sdélai de connexion à l'agent
$ swarmexec exec web -- sh $ swarmexec exec web.2 -- cat /etc/hostname # une réplique précise $ swarmexec exec -u root -w /app web -- ls -la $ swarmexec exec -e FOO=bar web -- env

logs — diffuser les logs

Diffuse les logs d'un conteneur situé n'importe où dans le swarm.

swarmexec logs [flags] <target>
OptionDéfautDescription
-f, --followfalsecontinue à diffuser les nouvelles lignes de log
--tail0lignes depuis la fin pour commencer (0 = toutes)
-t, --timestampsfalsepréfixe chaque ligne d'un horodatage
--since0uniquement les logs plus récents que ceci (p. ex. 10m, 1h)
--log-formatclassicanalyse les lignes comme classic | json | logfmt | gelf | raw (par défaut depuis la config, sinon classic)
--min-leveln'affiche que ce niveau et au-dessus : trace | debug | info | warn | error | fatal
--grepn'affiche que les lignes dont le message (analysé) correspond à cette regexp Go
--nodeindice/remplacement de nœud pour les cibles container-id
--connect-timeout10sdélai de connexion à l'agent

Analyse & filtrage tenant compte du format. --log-format indique au client comment lire chaque ligne pour qu'il puisse en extraire un niveau et un message : classic extrait un niveau d'une ligne de texte brut, json analyse du JSON de style logstash (champs level/message), logfmt analyse le style clé=valeur utilisé par de nombreuses applications Go, le démon Docker et les outils HashiCorp (msg/message, level/lvl/severity, ts/time), gelf analyse du JSON GELF Graylog (niveau syslog numérique), et raw laisse passer les lignes inchangées. --min-level écarte alors tout ce qui est en dessous du niveau choisi, et --grep ne conserve que les lignes dont le message analysé correspond à la regexp.

Les lignes sans niveau détectable passent toujours le filtre --min-level, de sorte que les traces de pile multilignes ne sont pas perdues. Les mêmes valeurs par défaut peuvent être fixées une fois sous la section de config logs: (les options l'emportent).

Suivi au travers du remplacement de conteneur. Avec -f sur une cible service ou service.slot, logs continue de suivre lorsque le conteneur qu'il diffuse est remplacé par une mise à jour progressive, un redémarrage ou une replanification : il ré-résout le conteneur en cours d'exécution du service (le même emplacement, ou le même nœud pour un service global), se reconnecte automatiquement — comme docker service logs -f — et affiche une ligne d'avis atténuée (container replaced; reconnected to <id> on <node>). Il attend jusqu'à ~30 s qu'un remplaçant soit planifié avant d'abandonner, et s'arrête proprement si le service est retiré. Une cible container-id simple n'a pas de successeur, elle s'arrête donc simplement comme avant.

La détection réside dans le client : il apprend la topologie depuis le manager du swarm auquel il est déjà connecté — les agents par nœud sont locaux à leur nœud et ne peuvent pas voir les remplacements à l'échelle du 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
Perspective : la sélection du format est conçue pour prendre en charge plus tard la détection automatique du format par service via un service en ligne (pas encore implémenté).

port-forward (alias pf) — rediriger un port local

Lie un port TCP local et le redirige vers un port à l'intérieur d'un conteneur, sans publier ce port sur le cluster. Le port local prend par défaut la valeur du port distant. Cibler un service redirige vers exactement une de ses tâches (celle vers laquelle la cible se résout), pas à travers les répliques. Lié à 127.0.0.1 par défaut. Appuyez sur Ctrl-C pour arrêter.

swarmexec port-forward [flags] <target> [local:]remote
OptionDéfautDescription
--address127.0.0.1adresse locale à lier (le loopback garde le port hors de votre réseau)
--nodeindice/remplacement de nœud pour les cibles container-id
--connect-timeout10sdélai de connexion à l'agent
$ swarmexec port-forward db 5432 # localhost:5432 → conteneur:5432 $ swarmexec pf web 9090:8080 # localhost:9090 → conteneur:8080

volume ls — lister les volumes

Liste les volumes sur tous les nœuds et quels nœuds détiennent chacun. Les volumes Swarm sont locaux au nœud, le client interroge donc chaque nœud et agrège. Filtre de nom par sous-chaîne facultatif. Colonnes : VOLUME  DRIVER  NODES  USED BY  AGE (plus SIZE avec --size).

swarmexec volume ls [name-filter]
OptionDéfautDescription
--sizefalsecalcule aussi la taille sur disque de chaque volume (plus lent : du par volume)
--sortnametrie par : name | nodes | used | age | size (size implique --size)
--reversefalseinverse le sens du tri
--connect-timeout10sdélai de connexion par nœud
--jsonfalseaffiche du JSON au lieu d'un tableau

volume rm — supprimer un volume

Supprime un volume sur chaque nœud qui le détient (--all) ou sur des nœuds précis (--node, répétable). L'un des deux est requis.

swarmexec volume rm <name> (--all | --node N …)
OptionDéfautDescription
--allfalsesupprime sur chaque nœud qui détient le volume
--nodesupprime uniquement sur ces nœuds (répétable)
--forcefalsepasse l'option force de docker
-y, --yesfalsene pas demander de confirmation
--connect-timeout10sdélai de connexion par nœud

ui — TUI interactive

Une vue interactive des conteneurs et des volumes, avec exec, logs et redirection de ports intégrés. Nécessite un terminal interactif. Voir La TUI pour les touches.

swarmexec ui [service]
OptionDéfautDescription
--connect-timeout10sdélai de connexion à un agent

config show — inspecter la config

Affiche la configuration client effective et fusionnée avec le secret masqué.

swarmexec config show [--json]

context (alias ctx) — gérer les contextes Docker

Crée et gère les contextes Docker que --context (et $DOCKER_CONTEXT) résolvent pour l'API du manager. swarmexec écrit dans le magasin sur disque propre à docker, donc les contextes créés ici sont interchangeables avec la CLI docker — et vous n'avez plus besoin que docker soit installé, même pour en créer un. Le contexte default intégré ne peut pas être retiré.

context create <name> — crée un contexte pointant sur un hôte manager (prend exactement un argument de nom) :

swarmexec context create <name> --docker-host <endpoint>
OptionDéfautDescription
--docker-hostrequis — point d'accès du démon docker : ssh:// | tcp:// | unix:// | npipe://
--descriptiondescription facultative
--ssh-jumphôte(s) de rebond SSH pour un contexte ssh://, séparés par des virgules (ProxyJump / -J multi-sauts) ; injectés à la fois dans la connexion à l'API Docker et dans le tunnel vers l'agent
--usefalseen fait aussi le contexte courant

context ls (alias list) — liste les contextes ; colonnes NAME  CURRENT  DOCKER ENDPOINT (l'actif marqué *) :

swarmexec context ls [--json]

context use <name> — définit le contexte courant :

swarmexec context use <name>

context rm <name> [name...] (alias remove) — retire un ou plusieurs contextes :

swarmexec context rm <name> [name...]
OptionDéfautDescription
-f, --forcefalserequis pour retirer le contexte courant (sa sélection est réinitialisée à default)
$ swarmexec context create prod --docker-host ssh://ops@manager --use $ swarmexec context ls $ swarmexec --context prod ps

Partage des connexions ssh

Avec un contexte ssh://, swarmexec atteint le cluster deux fois par ssh : l'API du manager Docker via l'assistant de connexion d'ssh, et chaque agent de nœud via son propre tunnel vers le même hôte. Chaque exec, chaque flux de logs, chaque redirection de port, chaque relevé de statistiques et chaque cycle de rafraîchissement ouvrait jusqu'ici une connexion ssh neuve — une poignée de main TCP, un échange de clés et une authentification par appel, et autant par hôte de rebond — vers un bastion qui était connecté un instant plus tôt.

swarmexec leur fait désormais partager un seul transport, à l'aide du ControlMaster d'OpenSSH : la première connexion vers une destination l'ouvre, chacune des suivantes devient un canal dessus. Mesuré sur un cluster de trois nœuds derrière un hôte de rebond, swarmexec doctor est passé de 8 authentifications et 3,6 s à 2 et 1,0 s. Le trafic lui-même ne change pas, et le gain croît avec ce que l'on fait dans une session.

10. La TUI

swarmexec ui possède sept onglets — Stacks/Services (1), Volumes (2), Forwards (3), Networks (4), Secrets (5), Nodes (6), Configs (7) — une barre latérale des contextes sur le bord droit, et un pied de page sur deux lignes : les rappels de touches par onglet en haut, puis une ligne d'état avec le contexte docker actif (ctx <name>, pour qu'il soit toujours clair sur quel cluster vous êtes), un résumé en direct du cluster, le nombre de redirections et — sur l'onglet Volumes — combien de volumes vous avez sélectionnés. Ces touches fonctionnent sur chaque onglet :

La barre d'onglets est adaptative : sur un terminal plus étroit elle bascule sur des libellés courts — St/Sv, Vol, Fwd, Net, Sec, Node, Cfg — pour que les sept onglets restent visibles au lieu que les derniers soient tronqués. (Cfg, c'est Configs.) Les chiffres de raccourci et les zones cliquables à la souris ne changent pas.

Changement : les contextes étaient l'onglet 6 ; ils sont désormais la barre latérale (c). Nodes et Configs passent à 6 et 7.
ToucheAction
?ouvre la surcouche des raccourcis — la référence complète et vivante des touches (elle est générée à partir de votre keymap, donc les touches réaffectées s'affichent correctement) ; le pied de page d'une seule ligne n'a de place que pour les touches les plus utilisées
Tabpasse à l'onglet suivant
18saute à Stacks/Services / Volumes / Forwards / Networks / Secrets / Contexts / Nodes / Configs
j kdescend / monte (aussi )
rrafraîchit l'onglet actif et le résumé du cluster
ycopie la liste courante dans le presse-papiers (OSC52)
mbascule la capture de la souris (off = la sélection/copie propre de votre terminal)
`ouvre / ferme le visualiseur de logs en direct (voir ci-dessous)
qquitte

Onglet Stacks/Services

L'onglet 1, autrefois intitulé Containers. C'est un seul arbre, stack → service → conteneur, et c'est là que vivent exec, les logs, la redirection de ports, l'inspection et les éditeurs de service.

Vérification de version :latest. Swarm épingle :latest à un digest au moment du déploiement, donc un service étiqueté :latest exécute en réalité une image fixe. swarmexec résout cela auprès du registre (en utilisant vos identifiants docker locaux) et annote la ligne du service juste après l'URI de l'image : la véritable version entre parenthèses — lue depuis le label org.opencontainers.image.version de l'image — et une flèche vers le haut ↑ lorsque le :latest actuel du registre est un digest plus récent que celui sur lequel le service est épinglé (p. ex. nginx:latest (1.4.0) ↑). Au mieux et mis en cache : les erreurs de registre laissent simplement la ligne non annotée. La section IMAGE de la surcouche d'inspection affiche la même version et, lorsqu'une image plus récente existe, une ligne sélectionnable nouvelle version disponible — déplacez-vous dessus et appuyez sur u (ou Enter) pour mettre à jour le service.

Définir une version avec u — pas seulement quand une mise à jour existe. u ne dépend plus qu'une mise à niveau soit proposée. La touche est disponible dans toute inspection de service dont swarmexec a pu lire les tags d'image auprès du registre — aussi bien pour un service épinglé à une version que pour un service sur :latest — et depuis n'importe quelle ligne de la surcouche, si bien que vous n'avez jamais à chercher l'indication. Redéfinir la même version, épingler ce qui tourne actuellement ou revenir à un tag plus ancien sont des usages tout à fait normaux ; inutile d'attendre qu'une mise à jour existe. Le libellé du pied de page vous dit dans quelle situation vous êtes : u update version lorsqu'une version plus récente a été trouvée (la ligne nouvelle version disponible est alors là aussi, avec une cible concrète), et u set version lorsqu'il n'y en a pas.

Ce que u ouvre dépend de ce que connaît le registre. Sur un service épinglé à une version pour lequel une version plus récente existe, c'est le sélecteur de version : un champ dont l'autocomplétion propose les tags plus récents de la même famille, le plus élevé en premier et prérempli avec celui-ci. Sur un service déjà sur le tag le plus récent, c'est le même sélecteur, à ceci près que les suggestions se rabattent sur tous les tags que liste le dépôt (prérempli avec le tag en cours d'exécution) — c'est ainsi que l'on épingle ou que l'on revient en arrière. Un service sur :latest obtient lui aussi le sélecteur, avec tous les tags connus : y choisir une version concrète est la façon de sortir un service :latest du tag flottant pour l'épingler — précisément ce que réclame le constat de risque unpinned-image (voir la surcouche des risques de sécurité plus bas). La seule exception est un service :latest dont une mise à jour de digest est en attente : celui-là reste la simple confirmation sur le nouveau digest du registre qu'il a toujours été, car un sélecteur inviterait à y saisir latest, qui se résout au tag nu et abandonnerait silencieusement l'épinglage par digest que la mise à jour est justement là pour rafraîchir.

Dans le sélecteur, la liste de suggestions est plafonnée à 25 entrées (un dépôt très actif peut en lister des centaines) et le champ reste en texte libre : vous pouvez saisir n'importe quel tag existant, y compris un plus ancien pour l'épingler ou revenir en arrière vers une version éprouvée. Un tag saisi est validé contre les tags du dépôt (un tag qu'il ne liste pas est rejeté), et choisir un tag plus ancien affiche un avertissement de rétrogradation avant de l'appliquer. Dans tous les cas, la modification est un seul ServiceUpdate / mise à jour progressive, derrière une confirmation. Si le registre n'a renvoyé aucun tag et qu'il n'y a pas non plus de cible concrète — registre privé ou injoignable, ou service épinglé par digest — vous obtenez un avis explicatif et rien n'est modifié.

ToucheAction
/ouvre la barre de recherche (filtre par service / conteneur / nœud)
h lreplie / déplie sur les trois niveaux. h replie la ligne sous le curseur — un stack replie tout le groupe, un service déplié replie ses conteneurs — et là où il n'y a plus rien à replier, elle remonte vers le parent, si bien que des appuis successifs parcourent conteneur → service → stack. l déplie la ligne sous le curseur, ou descend vers son premier enfant si elle est déjà ouverte
Entersur un conteneur : ouvre le menu d'actions ; sur un service ou un stack : le replie / déplie
sbascule le regroupement par stack — arbre groupé ⟷ liste plate de services (voir ci-dessous)
Llogs (L majuscule) — sur un conteneur ses propres logs, sur un service les logs agrégés de toutes ses tâches
!ouvre la surcouche des risques de sécurité pour toute la liste des services (voir ci-dessous)
predirige le port de la tâche sous le curseur
iinspecte le nœud sous le curseur — une surcouche navigable avec un résumé tabulaire ; déplacez la sélection avec / ou j/k, y/Enter copie la ligne sélectionnée, une barre d'onglets en haut nomme les trois vues et 1/2/3 sélectionnent directement tableau / stats / JSON brut du démon, tandis que t les fait défiler (Esc/q/i ferme). Sur un service la surcouche l'édite aussi : s mise à l'échelle, f mise à jour forcée, p ports, l labels, e env, n réseaux, S secrets, v montages, A alias ; D diagnostique pourquoi il ne s'exécute pas partout ; X le supprime
Xsupprime un service directement depuis l'arbre (X majuscule, c.-à-d. Shift+x) — sans détour par la surcouche d'inspection. Ce sur quoi elle agit dépend de la ligne sous le curseur : sur une ligne de service elle supprime ce service ; sur une ligne de conteneur elle supprime le service propriétaire du conteneur, car une tâche seule ne peut pas être supprimée — Swarm la replanifierait aussitôt ; sur une ligne de stack elle ne supprime rien et signale brièvement dans le pied de page que les services d'un stack se suppriment individuellement, ou avec docker stack rm <stack>. Elle passe par la même confirmation que le X de l'inspection ci-dessous — elle supprime définitivement le service et arrête toutes ses tâches ; irréversible. Touche majuscule fixe, pas une action de keymap réaffectable ; le pied de page l'indique en rouge comme X remove et la surcouche ? la liste aussi. Le X à l'intérieur de la surcouche d'inspection est inchangé

Chaque service est affiché (même un mis à l'échelle à zéro), chaque ligne rendue comme une ligne docker service ls — nom, mode, nombre en cours d'exécution/souhaité, image et ports publiés — et colorée selon son état réel : le nombre en cours d'exécution/souhaité donne la couleur de base (aqua = toutes les tâches actives, orange = partiel, rouge = arrêté, gris = mis à l'échelle à zéro), et un healthcheck en échec la remplace (voir plus bas). Un marqueur / indique si un service est replié ou déplié ; ses conteneurs en cours d'exécution s'imbriquent en dessous. Un service en cours de mise à jour progressive porte un badge coloré sur sa ligne d'arbre — ⟳ updating ou ↺ rolling back — et le même statut apparaît dans l'overlay d'inspection du service ; il disparaît dès que la mise à jour est terminée.

Colorée par le healthcheck, et pas seulement par le nombre de réplicas. La couleur d'une ligne de service venait du seul rapport en cours d'exécution / souhaité : un service dont tous les conteneurs échouaient à leur healthcheck s'affichait donc toujours en un paisible 3/3 aqua. Le compte était juste et la ligne trompeuse. L'état de tâche de Swarm n'aide pas davantage : une tâche est running pendant que son conteneur rate chaque sonde — le verdict doit donc venir du nœud. Une sonde en échec est désormais traitée comme une dégradation de même nature qu'un réplica manquant :

Les marqueurs. Une ligne de service porte ✖ N unhealthy en rouge, ou ◌ N starting en jaune tant que les sondes ne sont pas encore passées ; une feuille de conteneur porte ✖ unhealthy / ◌ starting. Unhealthy prime sur starting : c'est celui sur lequel il faut agir. Une ligne de stack cumule le même marqueur sur tout ce qu'elle contient (voir Regroupé par stack plus bas). Un conteneur sain n'obtient aucun marqueur, ni d'ailleurs un conteneur sans healthcheck — ainsi le marqueur continue de vouloir dire quelque chose. Ils côtoient les marqueurs de ressources cpu/mem ci-dessous, et la santé vient en premier dans la ligne : c'est elle qui contredit le compte juste à côté.

D'où vient le verdict — et ce qu'il coûte. Rien de plus : il voyage avec les relevés de ressources ci-dessous — même agent, même appel, aucune requête d'API supplémentaire. La liste de conteneurs que l'agent récupère déjà à chaque passe d'échantillonnage porte le verdict dans sa ligne de statut (Up 3 days (healthy)), la chaîne même qu'imprime docker ps. L'analyse est volontairement stricte : tout ce qu'elle ne reconnaît pas devient pas de verdict plutôt qu'une supposition, de sorte que si le démon reformule un jour cette ligne, swarmexec cesse de prétendre savoir au lieu d'annoncer sain un conteneur en échec. Même prérequis que pour les chiffres d'utilisation : il faut des agents de cette version ou plus récents, un cluster qui n'a pas été redéployé n'affiche donc aucun marqueur de santé — déployez-les avec swarmexec init --force.

Marqueurs CPU et mémoire en direct. À côté de ces badges, une ligne peut aussi porter ce qu'elle consomme réellement à l'instant : jusqu'ici swarmexec ne pouvait montrer que ce que l'ordonnanceur avait réservé (voir l'onglet Nœuds plus bas). Un conteneur — ou un service — dont l'utilisation dépasse 70 % reçoit un marqueur orange, et 90 % un rouge, en fin de ligne : la ressource nommée en toutes lettres (cpu pour le CPU, mem pour la mémoire) suivie du pourcentage (cpu 94 %, mem 91 %). Si les deux chauffent, les deux apparaissent. Des mots plutôt que des symboles, à largeur égale : un marqueur dont il faut aller chercher le sens ne fait pas son travail, et des lettres ne peuvent pas échouer à s'afficher dans un terminal. En dessous de 70 % rien n'est dessiné, pour que les marqueurs restent un signal et non du papier peint. Une ligne de service prend la pire de ses réplicas, pas la moyenne — une moyenne masque précisément le conteneur qui est sur le point de mourir, celui qu'il faut voir. (La mémoire absolue d'une ligne de service est la somme sur les réplicas ; le pourcentage est le pic.)

De quoi ces pourcentages sont-ils le pourcentage ? Chacun est mesuré par rapport à ce que ce conteneur a réellement le droit d'utiliser : sa propre limite lorsqu'il en a une, la capacité du nœud sinon. C'est là tout l'enjeu — 91 % d'une limite de 256 Mo signifie qu'un OOM kill est proche, 91 % d'un nœud de 64 Go est une tout autre conversation. La vue stats de la surcouche d'inspection affiche les deux côtés de cette division par conteneur et nomme la base sous le tableau.

D'où viennent les chiffres — et pourquoi ils arrivent en retard. L'utilisation n'est pas dans l'API du manager : les statistiques de conteneur sont locales au nœud. Elles proviennent d'un RPC de l'agent local au nœud (Stats) ; chaque agent échantillonne ses propres conteneurs en arrière-plan et répond depuis sa mémoire, si bien que le client l'interroge simplement sur le cycle de rafraîchissement qu'il a déjà. Deux conséquences. Un pourcentage de CPU est un delta entre deux relevés : il n'existe donc pas tant que l'agent n'en a pas pris deux (quelques secondes) — pendant ce temps les vues affichent , jamais 0 % ; la mémoire n'a besoin d'aucun delta et apparaît dès le premier relevé. Et un agent n'échantillonne que tant que quelqu'un regarde : il s'arrête environ une minute après la dernière requête et oublie ses relevés plutôt que d'en servir de périmés. C'est pourquoi les tout premiers chiffres après l'ouverture de l'interface mettent un instant à se remplir. C'est délibéré — un agent que personne ne regarde ne doit rien coûter.
Aucun marqueur nulle part ? Vérifiez la version de l'agent. L'utilisation est un supplément, et elle se dégrade sans bruit : un nœud dont l'agent est injoignable — ou plus ancien que cette version et donc ignorant du RPC Stats — ne fournit tout simplement aucun relevé. Les marqueurs restent éteints, le bloc du nœud reste absent et tout le reste est intact ; le client met de plus ce nœud de côté un moment au lieu de le sonder à chaque rafraîchissement. Sur un cluster qui n'a pas encore été redéployé, il n'y a donc aucun chiffre d'utilisation ni aucun marqueur de santé tant que les agents ne sont pas à jour — déployez-les avec swarmexec init --force. C'est de loin la raison la plus probable de ne rien voir.

Regroupé par stack. docker stack deploy étiquette chaque service qu'il crée avec com.docker.stack.namespace. swarmexec lit ce label dans la spec du service que le manager a déjà renvoyée — Swarm n'a aucun objet « stack », ce label est le seul lien — et imbrique l'arbre sur trois niveaux : stack → service → conteneur. Une ligne de stack affiche le nom du stack, combien de services en dépendent et le nombre de tâches en cours d'exécution/souhaité cumulé ((3 svc · 7/8)), coloré selon exactement la même règle qu'une ligne de service : le total en cours d'exécution / souhaité, avec le même remplacement par le healthcheck par-dessus. Les compteurs de synthèse la suivent lorsqu'ils ne sont pas nuls : ⟳ n — les services de ce stack en cours de mise à jour progressive —, 🛡 n — les services présentant un constat de sécurité qui mérite une action, le même bouclier que portent les lignes de service — puis le marqueur de santé, ✖ N unhealthy ou ◌ N starting, cumulé sur tous les conteneurs de tous les services du stack. Même replié, un stack indique donc si quelque chose à l'intérieur demande votre attention. Et c'est ici que la remontée de la santé compte le plus : la ligne de stack est le niveau qu'un opérateur parcourt en premier, un « tout est en ligne » trompeur y est donc pire qu'un étage plus bas, et non moins grave.

Les services créés avec docker service create ne portent pas de label de stack ; ils sont regroupés sous (no stack), qui est toujours trié en dernier et ne repousse donc jamais les vrais stacks vers le bas. Rien n'est masqué : chaque service apparaît toujours exactement une fois. Les stacks sont triés par nom et démarrent dépliés, de sorte que l'arbre groupé montre les mêmes services que l'arbre plat ; les replis que vous faites survivent au rafraîchissement automatique.

Le regroupement est actif par défaut, mais il ne prend effet que lorsqu'au moins un service du cluster porte réellement un label de stack — sur un cluster sans stacks, l'arbre est exactement tel qu'il a toujours été, sans parent (no stack) inutile. s bascule entre l'arbre groupé et la liste plate de services (le pied de page l'indique comme s stacks) ; s'il n'y a aucun label de stack, il le dit plutôt que de redessiner un arbre identique. La touche est réaffectable via stack_group dans keys.yaml.

La recherche (/) ne change pas : le filtre porte toujours sur le service, le conteneur et le nœud. Un stack que le filtre vide disparaît de l'arbre, et les compteurs de chaque ligne de stack décrivent ce qui s'y trouve réellement — ils suivent donc le filtre au lieu d'annoncer des services que vous avez filtrés.

Le menu d'actions propose Logs, Bash, Sh, Shell as user… (demande un utilisateur/UID, comme docker exec -u, pour les images dont l'utilisateur par défaut n'a pas les outils ou les droits nécessaires) et Port forward (les shells indisponibles sont grisés après un sondage). Dans la barre de recherche, Enter conserve le filtre et revient à la liste ; Esc efface le filtre et ferme la barre.

Risques de sécurité (!). À chaque récupération de la liste des services, swarmexec exécute un ensemble de petits analyseurs statiques sur la spec de chaque service — la même spec que le manager a déjà renvoyée : il n'y a donc aucun appel d'API supplémentaire, l'agent n'intervient pas et rien n'est exécuté à l'intérieur de vos conteneurs. Un service porteur d'un constat qui mérite une action est marqué d'un 🛡 en tête de sa ligne dans l'arbre des services, dans un emplacement de largeur fixe devant le nom, de sorte que les boucliers forment une colonne verticale de lecture. Appuyez sur ! pour ouvrir la surcouche des risques de sécurité, et sur w à l'intérieur pour écrire un rapport Markdown sur tout le cluster — qui couvre davantage que la surcouche, puisqu'il ajoute les contrôles relevant du cluster et non d'un service.

Les huit contrôles livrés à ce jour. D'abord ceux qui peuvent marquer un service, puis, en fin de liste, les purement informatifs :

ContrôleSévéritéCe qu'il signale
docker-socket — « Docker socket mounted in »highle constat le plus grave de tous. Un bind mount dont la source est la socket du démon Docker — /var/run/docker.sock, /run/docker.sock ou tout chemin source se terminant par /docker.sock. Tout ce qui tourne dans ce conteneur peut parler à la socket, et qui peut parler à la socket peut démarrer un conteneur privilégié sur ce nœud — c'est donc, de fait, root sur l'hôte, quel que soit l'utilisateur sous lequel tourne le conteneur lui-même. Le montage en lecture seule n'y change rien : la socket est une API, pas un fichier dont le contenu compte — le constat le précise lui-même lorsque le montage est en lecture seule. Il nomme le chemin cible où la socket est montée
added-capability — « capability NOM added »high / mediumune capability Linux que le service ajoute à son conteneur. Swarm n'a pas de --privileged : les capabilities sont donc la façon dont un service réclame davantage de pouvoir sur l'hôte — d'où l'intérêt de lire l'ensemble ajouté. Huit sont traitées comme high, chacune avec la raison que porte le constat : ALL (accorde toutes les capabilities), SYS_ADMIN (quasi root : contrôle des montages, des namespaces et des cgroups), SYS_MODULE (peut charger des modules noyau), SYS_PTRACE (peut inspecter et contrôler d'autres processus), SYS_RAWIO (accès d'E/S brut aux périphériques), DAC_READ_SEARCH (contourne les contrôles de permission en lecture), NET_ADMIN (contrôle total du réseau du nœud), NET_RAW (peut forger et capturer des paquets bruts). Toute autre capability ajoutée est medium — un privilège au-delà de l'ensemble par défaut. Un constat par capability ajoutée ; les deux écritures sont reconnues, quelle que soit la casse (CAP_SYS_ADMIN et SYS_ADMIN)
host-network — « runs on the host network »highle service est rattaché au réseau nommé host : le conteneur partage la pile réseau du nœud, il n'y a donc plus d'isolation réseau et la publication de ports de swarm ne s'applique plus. Il atteint tout ce que le nœud atteint — y compris les services écoutant sur localhost, que l'on suppose d'ordinaire hors de portée d'un conteneur
unconfined — « seccomp disabled » / « AppArmor disabled »highle bac à sable au niveau du noyau a été désactivé pour le conteneur : seccomp réglé sur unconfined — le conteneur peut effectuer n'importe quel appel système, ce qui supprime la principale barrière face aux exploits noyau — ou confinement AppArmor désactivé. Les deux sont signalés séparément : un service qui coupe les deux obtient deux constats
root-user — « runs as root »highla spec fixe le conteneur sur root explicitementUser=root ou l'UID 0 (les formes numériques comme 00 ou +0 sont aussi détectées ; seule la partie utilisateur de user:group est examinée)
secret-in-env — « secret in environment variable »highune clé d'environnement qui ressemble à un identifiant contient une valeur littérale dans la spec du service, lisible par quiconque peut lire la spec. Utilisez plutôt un secret Docker ou la convention *_FILE
root-user — « no user set »lowaucun utilisateur n'est défini : le conteneur s'exécute donc sous l'utilisateur par défaut de l'image, souvent root. Purement informatif, et ne marque pas le service : un utilisateur non défini est la valeur par défaut de Swarm sur presque tous les services, et savoir s'il s'agit réellement de root dépend du USER de l'image, que la spec du manager ne révèle pas
no-resource-limits — « no resource limits »lowla tâche ne fixe ni limite CPU ni limite mémoire : un conteneur qui s'emballe peut consommer le nœud entier et affamer tout ce qui s'y trouve. Purement informatif — voir ci-dessous
unpinned-image — « image not pinned »lowl'image est épinglée sur :latest, ou ne porte aucun tag (ce qui revient à :latest) — la version qui tourne peut changer sans modification de la spec, un déploiement n'est donc pas reproductible. Une image épinglée par digest (…@sha256:…) est exactement reproductible et n'est jamais signalée, et un deux-points appartenant au port de l'hôte du registre (registry:5000/img) n'est pas pris pour un tag. Purement informatif — voir ci-dessous

Pourquoi les contrôles informatifs ne marquent jamais un service. Seul un constat au-dessus de low — high ou medium — rend un service actionnable et pose le 🛡 sur sa ligne dans l'arbre. Les constats low (no-resource-limits, unpinned-image et le « no user set » de root-user) sont vrais de presque tous les services d'un cluster réel : les badger mettrait un bouclier sur presque chaque ligne et détruirait précisément le signal en un coup d'œil pour lequel le marqueur existe. Ils ne sont pas pour autant escamotés — dès qu'un service est marqué pour autre chose, ses constats low sont listés avec les autres dans la surcouche, c'est-à-dire là où ils valent vraiment la peine d'être lus.

Aucune valeur de secret n'est jamais affichée. Des huit contrôles, seul secret-in-env regarde les variables d'environnement, et son constat ne nomme que la clé de la variable d'environnement ; sa valeur n'est jamais lue dans le constat, jamais rendue et jamais copiée. Le contrôle est également conçu pour rester discret : il compare des jetons entiers délimités par _ (PASSWORD, PASSWD, PASS, PASSPHRASE, SECRET, TOKEN, APIKEY, CREDENTIAL(S), PRIVATEKEY, ainsi que les paires API_KEY, ACCESS_KEY, PRIVATE_KEY, SECRET_KEY, CLIENT_SECRET, AUTH_TOKEN), si bien que COMPASS, PASSENGER_PORT ou BYPASS_AUTH ne sont pas signalés. Les clés qui ne font que référencer un identifiant sont exemptées (_FILE, _PATH, _URL, _URI, _NAME, _ID, _TYPE, _ENABLED, _REQUIRED, _LENGTH, _TIMEOUT, _TTL, _EXPIRY, _ALGORITHM), tout comme les valeurs qui ne peuvent manifestement pas être un identifiant : un chemin absolu, un booléen, un nombre.

La surcouche liste chaque service marqué (« N of M service(s) flagged · 8 checks »), groupé par service, chaque constat sur sa propre ligne avec une pastille de sévérité — ● high, ● medium, ● low — un titre court et une explication d'une ligne. Les constats sont triés du plus grave au moins grave. Dès qu'un service est marqué pour quelque chose d'actionnable, ses constats informatifs de niveau low sont listés eux aussi. Si le service sous le curseur en fait partie, il est présélectionné et amené à l'écran. Quand rien n'est marqué, la surcouche le dit explicitement et nomme chaque contrôle qui a tourné (« 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 sorte que le feu vert dit aussi ce qu'il couvre ; si la liste des services n'est pas encore chargée, elle indique que rien n'a encore été analysé plutôt que de donner un faux feu vert. j/k font défiler, Esc (ou q, ou ! à nouveau) ferme.

L'analyse est un registre extensible d'analyseurs, pas une liste figée : un nouveau contrôle apparaît automatiquement à la fois dans le marqueur de l'arbre et dans la surcouche. Le texte même de la surcouche vient aussi de ce registre : le nombre de contrôles dans son en-tête et la liste « Checked: » sont générés à partir des analyseurs eux-mêmes et ne peuvent donc pas diverger de ce qui a réellement tourné, contrairement à une liste écrite en dur. La touche est réaffectable sous le nom security_risks dans keys.yaml.

i ouvre une surcouche d'inspection pour le nœud sous le curseur — sur un service elle affiche docker service inspect, et sur une feuille conteneur l'inspection de tâche swarm (la vue du manager de cette instance : état, emplacement, nœud, id du conteneur, spécification du conteneur, ressources et historique d'état). Les deux proviennent du manager du swarm. La surcouche s'ouvre sur un résumé tabulaire, orienté opérateur avec des sections ordonnées par pertinence opérationnelle — réseaux, labels, volumes/montages et secrets (et configs) d'abord, puis ports, image, mode, env, ressources, placement et politique de mise à jour (plus l'état, le nœud et l'id du conteneur pour un conteneur/tâche), avec les ids et horodatages en dernier. La première ligne de la surcouche, à l'intérieur de la bordure et fixée au-dessus du contenu qui défile, est une barre d'onglets qui nomme ses trois vues — table 1   stats 2   raw json 3 — la vue courante dans la couleur d'accentuation ; elle a la même forme que la barre d'onglets de la fenêtre principale : un libellé suivi du chiffre qui le sélectionne. 1, 2 et 3 sautent directement au tableau, aux stats et au JSON brut du démon, et t fait toujours défiler tableau → stats → JSON brut puis revenir. L'indication du pied de page se résume à 1-3/t view, et le titre de la bordure ne répète plus le nom de la vue courante — il dit simplement inspect service foo, puisque la barre indique déjà où vous êtes. Notez que la vue tabulaire est la vue de tâche du manager, pas d'un docker container inspect complet et local au nœud du conteneur en cours d'exécution — une véritable inspection de conteneur via l'agent est prévue.

La vue stats. Celle du milieu montre l'utilisation des ressources en direct — les mêmes relevés que ceux qui marquent l'arbre : l'ouvrir ne coûte aucun appel supplémentaire et les chiffres continuent de se rafraîchir tant qu'elle est ouverte. C'est un tableau, une ligne par conteneur du service (ou l'unique conteneur d'une inspection de tâche) :

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

Un tableau, parce qu'un pourcentage nu n'est pas lisible si l'on ne sait pas déjà comment l'outil mesure — 94 % de quoi ? Chaque cellule porte ses propres unités : 0.07 / 4.00 cores n'a besoin d'aucune légende, et le pourcentage se tient à côté du chiffre dont il est le pourcentage ; et plusieurs réplicas se comparent en parcourant une colonne plutôt qu'en confrontant des blocs empilés. La colonne HEALTH formule le verdict en toutes lettres : healthy, unhealthy, starting, ou none pour un conteneur qui ne déclare aucun healthcheck. none et healthy sont délibérément deux mots distincts, et non un mot et un vide : « aucun healthcheck configuré » et « la sonde passe » sont deux faits différents, et un vide se lirait comme le second. Un conteneur qu'aucun agent n'a rapporté — nœud injoignable, agent trop ancien pour le RPC Stats, ou conteneur démarré depuis le dernier relevé — affiche no reading dans les deux colonnes de ressources au lieu d'un zéro, qui se lirait comme « au repos ».

La note sous le tableau nomme le dénominateur : la limite propre du conteneur lorsqu'il en a une, la capacité du nœud sinon. C'est cela qui donne un sens à un pourcentage — 85 % d'une limite de 8 Gio et 85 % d'un nœud de 64 Gio sont deux conversations différentes. La mémoire est en unités binaires (MiB/GiB), les mêmes que le détail de nœud et celles que docker rapporte lui-même : le même chiffre ne se lit jamais différemment à deux endroits. Avec plus d'un conteneur mesuré, une synthèse s'ajoute sous le tableau : CPU et mémoire de la pire réplica — pas une moyenne, qui masque justement le conteneur sur le point d'être tué par l'OOM — plus la mémoire totale sur l'ensemble. L'entrée t de la surcouche d'aide ? indique en conséquence cycle table / stats / raw JSON.

La surcouche est navigable et sélectionnable, pas seulement un simple défilement : les lignes de données sont individuellement sélectionnables et vous déplacez le curseur ligne par ligne avec / ou j/k (les en-têtes de section et les lignes vides sont ignorés). y copie toujours la ligne sélectionnée dans votre presse-papiers via OSC52 — le même mécanisme de copie utilisé ailleurs — pour que vous puissiez saisir un seul id, montage ou adresse sans sélectionner de texte à la main. Enter copie aussi, sauf sur une ligne NETWORKS repliable, où il déplie/replie plutôt cette ligne (voir ci-dessous). Les trois vues (le résumé tabulaire, la vue stats et le JSON brut, atteints par 13 ou en faisant défiler avec t) sont sélectionnables et copiables ligne par ligne. Un pied de page liste toujours les touches disponibles, de sorte que ce que vous pouvez faire est visible d'un coup d'œil ; pour un service, le pied de page affiche aussi les touches d'édition ci-dessous.

ToucheAction
j kdéplace la sélection entre les lignes de données (en-têtes/vides ignorés)
ycopie la ligne sélectionnée dans le presse-papiers (OSC52) — toujours
Entersur une ligne NETWORKS repliable, déplie/replie ce niveau — les lignes s'imbriquent sur deux niveaux, un réseau et son détail N containers ; sur toute autre ligne, la copie (OSC52)
1 2 3sélectionne une vue directement — 1 résumé tabulaire, 2 stats (CPU / mémoire en direct, voir ci-dessous), 3 JSON brut du démon ; ce sont les chiffres qu'affiche la barre d'onglets
tfait défiler les trois vues — résumé tabulaire → stats (CPU / mémoire en direct, voir ci-dessous) → JSON brut du démon → retour ; les trois sont sélectionnables, et la barre d'onglets en haut marque celle où vous êtes
amenu d'actions — tous les éditeurs et actions du service en une seule liste, sans Maj (services uniquement ; voir ci-dessous)
s f p l e n S v Aédite le service — mise à l'échelle / mise à jour forcée / ports / labels / env / réseaux / secrets / montages / alias (service uniquement ; S est en majuscule, Maj+s, pour le distinguer de s mise à l'échelle ; A est aussi en majuscule ; voir ci-dessous)
Esc q iferme la surcouche

La section NETWORKS est repliable, et elle répond à « à quelle adresse est-ce joint ? » avant même de rien déplier : chaque réseau attaché apparaît comme une ligne repliée + <network> 🔒 vip 10.0.5.2/24 (2 dns names). Une icône de cadenas 🔒 après le nom du réseau signale un réseau overlay chiffré (chiffrement du plan de données, --opt encrypted) ; son absence signifie non chiffré. Sur l'inspection d'un service, l'adresse est étiquetée vip — l'IP virtuelle du service sur ce réseau, l'adresse à laquelle répond le répartiteur de charge du swarm, tirée du ServiceInspect du manager (Endpoint.VirtualIPs). Sur celle d'un conteneur/tâche, elle est étiquetée addr : l'adresse propre de ce conteneur sur ce réseau. Un service en mode d'endpoint dnsrr n'a aucune VIP — c'est voulu, pas une donnée manquante — donc au lieu d'un blanc, le réseau déplié porte la ligne no vip — dnsrr endpoint mode, the DNS name resolves to the containers. Le réseau ingress est lui aussi listé ici, bien qu'aucune spec ne le mentionne — le swarm y attache le service de lui-même (voir plus bas). Il est ajouté après les réseaux que déclare la spec, jamais intercalé entre eux, et son en-tête se lit + ingress vip 10.0.0.250/24 (routing mesh) : la mention (routing mesh) prend la place d'un décompte de noms DNS qui ne pourrait jamais dire que (0 dns names).

Déplacez-vous sur une ligne de réseau et appuyez sur Enter pour la déplier ou la replier. Dépliée, elle liste les noms DNS qui se résolvent vers le service/conteneur sur ce réseau — le nom du service, tasks.<service> et tout alias personnalisé — pour que vous puissiez voir sous quel nom DNS il est joint et quels alias il porte, par réseau. En dessous se trouve une seconde ligne repliable, + 2 containers (au singulier, + 1 container) ; Enter sur celle-ci ouvre le niveau le plus profond : une ligne par conteneur, alignée en colonnes, web.1 10.0.1.5/24 host-a — nom de la tâche, son adresse sur ce réseau, et le nœud sur lequel elle tourne (les tâches répliquées sont nommées <service>.<slot>, les globales <service>.<node>, faute de slot).

Le filtre porte sur l'état souhaité (desired state) de la tâche, pas sur son état courant : toute tâche que le manager veut encore voir tourner est listée, y compris celles qui ne sont que preparing, assigned ou starting — elles détiennent déjà leur adresse, et pendant une mise à jour progressive c'est le cas de la plupart ; filtrer sur l'état courant viderait donc le détail précisément quand il est le plus intéressant. Une tâche que le manager a abandonnée — état souhaité shutdown, c'est-à-dire remplacée par une mise à jour progressive, supprimée par une mise à l'échelle ou en échec — n'est pas listée : son adresse a été libérée et peut déjà appartenir à un autre conteneur, l'afficher serait donc activement faux. Une tâche qui ne sert pas encore de trafic porte son état entre parenthèses en fin de ligne, web.1 10.0.1.5/24 host-a (starting) ; une tâche en cours d'exécution n'a pas de suffixe. Les lignes viennent d'un TaskList de l'API du manager filtré sur le service, au mieux (best-effort) : une erreur d'API laisse simplement le détail vide. Les deux niveaux se replient indépendamment ; replier le réseau masque avec lui le niveau des conteneurs.

La ligne ingress est celle qui ne figure dans aucune spec : le swarm rattache un service au réseau ingress de lui-même dès qu'il publie un port en mode ingress, et cet attachement n'apparaît nulle part dans la spec du service — pourtant sa vip est l'adresse à laquelle le routing mesh répond réellement, ce que l'on veut savoir en premier quand un port publié se comporte mal. Elle obtient donc une ligne à elle, ajoutée après les réseaux déclarés par la spec et de forme légèrement différente : aucun nom DNS de service ne se résout sur ingress, aussi, là où un autre réseau compte ses noms, cet en-tête porte la mention (routing mesh). Dépliée, la ligne liste à la place les ports publiés via ingress qui y ont mené le service — la raison d'être de la ligne —, un par ligne : published 2222 -> 22/tcp. Les ports publiés en mode host contournent le routing mesh et n'y sont délibérément pas listés. En dessous, elle descend jusqu'aux conteneurs exactement comme toute autre ligne de réseau : le même niveau + 1 container, avec l'adresse propre de chaque conteneur sur le réseau ingress. Un service qui ne publie aucun port en ingress n'y a pas de VIP — et donc pas de telle ligne du tout.

Un service GitLab publiant SSH sur 2222, les deux niveaux dépliés :

- 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

Copier dans NETWORKS donne l'adresse sans son masque10.0.5.2, la forme que vous collez dans un curl ou un ping : sur une ligne de réseau son vip/addr, sur une ligne de conteneur l'adresse de ce conteneur. Une ligne de réseau sans adresse (dnsrr) copie le nom du réseau, comme avant. Sur les deux types de lignes repliables Enter bascule ; sur toute autre ligne il copie (et y copie toujours).

Lorsque vous inspectez un service (pas une feuille conteneur/tâche, dont la surcouche reste en lecture seule), le pied de page de la surcouche expose aussi plusieurs touches d'édition, chacune un ServiceUpdate de l'API du manager qui déclenche une mise à jour progressive / réconciliation des tâches du service.

Commencez par a — le menu d'actions. Sur une inspection de service (et là seulement), a ouvre une liste unique de tous les éditeurs et actions que propose la surcouche, pour ne pas avoir à retenir ~14 touches sensibles à la casse (d/D, s/f, p/l/e, n/S/v, r/P, R, X). C'est le chemin découvrable, sans Maj — et non un chemin réduit : choisir une entrée ferme le menu et ouvre exactement ce qu'ouvre la touche directe ; les deux sont donc équivalents, pas des variantes au comportement différent. Les entrées, dans l'ordre : Update image version… (Set image version… quand il n'y en a pas de plus récente — épingler une version de votre choix ou y revenir est tout aussi valable), 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 destructive, signalée en rouge — et Cancel. On se déplace avec j/k (g/G sautent à la première/dernière entrée), Enter sélectionne ; Esc, ou l'entrée Cancel, ferme le menu sans rien faire. Le sélecteur de version d'image vient en premier, parce que fixer une version est le changement de service le plus ordinaire qui soit ; sa touche directe u fonctionne toujours depuis n'importe quelle ligne de l'inspection. Si l'image est de celles pour lesquelles aucune version ne peut être choisie — une référence sans tag, ou un registre qui refuse de lister les tags du dépôt, typiquement un registre privé sans identifiants —, l'entrée reste dans la liste et le dit au lieu de disparaître en silence, et renvoie vers le docker service update --image qui, lui, fonctionne. La touche directe de chaque entrée :

ToucheAction
ddiff — affiche un diff unifié de la spécification actuelle du service par rapport à sa précédente (ce que la dernière mise à jour progressive a changé), en utilisant le PreviousSpec de Swarm. Les champs modifiés sont listés groupés par champ avec des lignes - supprimé / + ajouté (image, mode/répliques, env, labels, réseaux, alias, secrets, montages, ports, contraintes de placement, préférences de répartition, limites/réservations de ressources) ; les champs inchangés sont omis. Si le service n'a jamais été mis à jour (pas de spécification précédente) il l'indique. Surcouche en lecture seule — j/k défilent, Esc ferme
Rrevient à la version précédente (R majuscule, c.-à-d. Maj+r, pour le distinguer de r limites de ressources) — la contrepartie du diff d ci-dessus : il annule la dernière mise à jour progressive. La boîte de confirmation affiche ce même diff inversé sous « This will undo: », car c'est bien ce que le retour arrière va changer — une ligne que la mise à jour a ajoutée apparaît comme - supprimé, et une qu'elle a supprimée revient comme + ajouté. L'aperçu est plafonné à 12 entrées, suivies d'une note « … and N more » qui renvoie à d pour le diff complet (si seules les métadonnées ont changé, il indique qu'il n'y a aucune différence au niveau des champs). Confirmer déclenche un retour arrière côté serveur — un seul ServiceUpdate de l'API du manager avec ServiceUpdateOptions{Rollback: "previous"}, pas une réapplication du PreviousSpec côté client — c'est donc le manager qui l'exécute, en respectant la configuration de retour arrière propre au service (parallélisme, délai, action en cas d'échec), et il rapporte la progression via l'état de mise à jour rollback_started : exactement le badge ↺ rolling back décrit plus haut, sur la ligne de l'arbre et dans cette surcouche. Un service qui n'a jamais été mis à jour n'a pas de spécification précédente — vous obtenez un message explicatif, pas une erreur. Également accessible depuis le menu d'actions a. Comme d, D et X, c'est une touche fixe de l'inspection, pas une action remappable du keymap
smise à l'échelle — demande un nouveau nombre de répliques et l'applique (services répliqués uniquement ; un service global signale qu'il ne peut pas être mis à l'échelle)
fmise à jour forcée — redéploie le service sans changer sa spécification (l'équivalent de docker service update --force : incrémente TaskTemplate.ForceUpdate), après une confirmation. Chaque tâche est redémarrée / replanifiée, c'est ainsi que vous débloquez un service coincé dans un état incomplet (p. ex. 1/2 répliques). Un seul ServiceUpdate (mise à jour progressive)
Xsupprime le service (X majuscule) — le supprime définitivement et arrête toutes ses tâches, derrière une confirmation ; irréversible. Un seul ServiceRemove de l'API du manager ; en cas de succès la surcouche d'inspection se ferme et l'arbre des services se rafraîchit
Ddiagnostique le placement (D majuscule) — répond à pourquoi un service ne s'exécute pas partout où vous l'attendez en une seule vue, au lieu de le pourchasser à travers plusieurs commandes docker. Pour un service global il liste chaque nœud avec ✓ en cours d'exécution ou ✗ et la raison pour laquelle il est exclu (disponibilité drain/pause, un état down, une contrainte de placement non satisfaite, ou une plateforme non concordante) — c'est pourquoi un cluster de 3 nœuds peut légitimement afficher 2/2 (le troisième nœud n'est pas éligible). Pour un service répliqué il liste chaque tâche qui n'est pas en cours d'exécution avec le message propre au planificateur (contraintes, ressources insuffisantes, erreurs de récupération d'image, …). Les contraintes qui ne peuvent pas être vérifiées côté client (engine.labels.*) sont signalées, pas devinées. Surcouche en lecture seule
pédite les ports publiés — ouvre un éditeur de liste étagé des ports du service, chacun sous la forme PUBLISHED:TARGET[/proto] (proto tcp|udp|sctp, par défaut tcp), p. ex. 8080:80/tcp
lédite les labels — le même éditeur de liste étagé sur des entrées key=value
eédite les variables d'environnement — le même éditeur de liste étagé sur l'env du ContainerSpec, chacune saisie comme KEY=VALUE (p. ex. LOG_LEVEL=debug) ; la clé est requise, la valeur peut être vide et peut elle-même contenir =. Comme ports/labels/montages, cet éditeur autorise l'édition e, pour que vous puissiez ajuster une valeur sur place. Contrairement aux autres éditeurs, e ouvre ici l'entrée dans une zone de texte multiligne défilable (plutôt qu'un champ sur une ligne) pour que les longues valeurs comme GITLAB_OMNIBUS_CONFIG soient confortables à éditer — tapez librement (Enter insère un saut de ligne), Ctrl-S enregistre, Esc annule. Appliquer remplace l'env du service en un seul ServiceUpdate (mise à jour progressive)
nédite les réseaux — un éditeur de liste étagé sur les attachements réseau du service ; comme une entrée n'est qu'un nom c'est ajout/retrait seulement (pas d'édition e). Le champ d'ajout complète automatiquement les noms de réseau (suggérant les réseaux auxquels le service n'est pas déjà attaché, ou tapez-en un vous-même). Appliquer remplace les attachements en un seul ServiceUpdate. Les alias DNS s'éditent aussi d'ici : sélectionnez un réseau dans cet éditeur et appuyez sur A pour ouvrir une liste étagée des alias DNS du service sur ce réseau (a ajouter / e éditer / d supprimer / y copier / u annuler / w appliquer / Esc annuler) ; appliquer remplace les alias de ce réseau en un seul ServiceUpdate. Fonctionne même quand le service n'a pas encore d'alias. (Vous devez d'abord appliquer un réseau nouvellement ajouté avant de pouvoir définir ses alias.)
Sédite les secrets (S majuscule, c.-à-d. Maj+s, pour le distinguer de s mise à l'échelle) — le même éditeur de liste étagé que les réseaux sur les secrets que le service référence ; une entrée n'est qu'un nom, donc c'est ajout/retrait seulement (pas d'édition e). Le champ d'ajout complète automatiquement les noms de secret (suggérant les secrets que le service n'utilise pas déjà, ou tapez-en un vous-même). Appliquer remplace les références de secret du service en un seul ServiceUpdate (mise à jour progressive) ; chaque secret est monté dans /run/secrets/<name>. Fonctionne même quand le service n'a actuellement aucun secret
védite les montages — un éditeur de liste étagé sur les volumes et bind mounts du service. Ajouter (a) ou éditer (e) ouvre un petit formulaire au lieu d'un unique champ de texte : une case bind mount, une source (quand la case est décochée c'est un nom de volume avec autocomplétion des volumes existants du cluster ; quand elle est cochée c'est un chemin hôte), un chemin conteneur, et une case read-only. (En interne chaque entrée reste volume:NAME:TARGET[:ro] / bind:/host/path:TARGET[:ro].) Les sources bind et toutes les cibles doivent être des chemins absolus. Appliquer remplace les montages en un seul ServiceUpdate (mise à jour progressive). Garde-fou bind-mount (souple) : si la liste étagée contient des bind mounts, appliquer affiche d'abord un avertissement listant les nœuds sur lesquels le service pourrait être planifié (calculés à partir des contraintes de placement plus le rôle/labels/disponibilité des nœuds) et vous rappelle que chaque source bind doit déjà exister sur tous — swarmexec ne peut pas vérifier les chemins hôte (l'agent n'a pas d'accès au système de fichiers de l'hôte), donc les chemins bind ne sont ni autocomplétés ni vérifiés pour leur existence ; c'est purement indicatif
rédite les limites de ressources — un petit formulaire pour définir, changer ou effacer les limites CPU et mémoire du service (et, en option, les réservations). Le CPU est donné en cœurs (p. ex. 0.5, 2) ; la mémoire comme une taille lisible (p. ex. 512m, 2g, 1.5GiB). Laisser un champ vide efface cette limite (Swarm la traite alors comme illimitée). Une réservation ne peut pas dépasser sa limite. Appliquer effectue un seul ServiceUpdate (mise à jour progressive) ; les limites de Pids existantes et les réservations device/génériques sont préservées
Pédite le placement (P majuscule, c.-à-d. Maj+p, pour le distinguer de p ports) — ouvre un petit menu avec deux éditeurs étagés : Contraintes et Préférences de répartition.
  • Contraintes — les contraintes de placement strictes du service (node.role, node.hostname, node.id, node.platform.os/arch, node.labels.<k>, engine.labels.<k>, chacune avec == ou !=, p. ex. node.role==manager, node.labels.zone!=eu). Le champ d'ajout/édition autocomplète des candidats entièrement formés construits à partir du cluster actuel — rôle, nom d'hôte, plateforme et labels node/engine de chaque nœud — pour que vous puissiez généralement choisir une contrainte au lieu de la taper (la forme == est suggérée ; tapez != à la main pour une négation). L'ensemble de nœuds résultant alimente aussi le garde-fou bind-mount de l'éditeur de montages.
  • Préférences de répartition — les stratégies souples --placement-pref spread=… : une liste étagée et ordonnée de simples attributs de nœud (p. ex. node.labels.zone, node.hostname) sur lesquels Swarm répartit les tâches de façon homogène. Le champ autocomplète les clés de label node/engine du cluster et les attributs de nœud (un descripteur n'est que l'attribut — pas d'opérateur ni de valeur).
Appliquer l'un ou l'autre remplace cette partie du placement en un seul ServiceUpdate (mise à jour progressive) ; l'autre partie est préservée.

Ces éditeurs sont étagés : a ajoute une entrée, d supprime celle sous le curseur, y copie l'entrée sélectionnée dans votre presse-papiers via OSC52 (pour que vous puissiez saisir p. ex. une seule variable d'environnement en la consultant), u annule le dernier changement étagé (ajout / édition / suppression) tant que vous n'avez pas encore appliqué — appuyez-y de façon répétée pour remonter dans l'historique étagé — et w applique tous les changements d'un coup en un seul ServiceUpdate (donc ajouter, disons, plusieurs volumes puis appuyer sur w déclenche une mise à jour progressive, pas une par entrée), derrière une confirmation. Si vous appuyez sur Esc alors que des changements sont encore étagés, l'éditeur ne les abandonne pas en silence — il demande s'il faut appliquer, abandonner, ou continuer l'édition. Tant que les changements sont encore étagés, ils sont mis en évidence pour que vous voyiez exactement ce qui va changer : les entrées ajoutées ou éditées apparaissent en vert (nouveau) et les entrées supprimées subsistent comme des lignes atténuées rouge « supprimé » — la mise en évidence s'efface une fois que vous appliquez. Les éditeurs ports, labels, montages, env et alias (alias atteints depuis l'éditeur de réseaux via A) ont aussi e pour éditer l'entrée sous le curseur ; les éditeurs réseaux et secrets n'ont pas de e — une entrée de réseau ou de secret n'est qu'un nom, donc c'est ajout (a) / retrait (d) seulement. Le e de l'éditeur env ouvre une zone de texte multiligne défilable (Ctrl-S enregistrer / Esc annuler) pour les longues valeurs ; les autres éditeurs conservent le champ sur une seule ligne avec Enter pour confirmer.

L'arbre se rafraîchit automatiquement toutes les ~10 s, de sorte qu'un conteneur remplacé ou un service mis à l'échelle en arrière-plan apparaît de lui-même — le curseur et tout service déplié sont préservés. r force toujours un rafraîchissement immédiat.

Onglet Volumes

ToucheAction
/recherche — filtre la liste par nom de volume, driver ou nœud
ncrée un volume — un formulaire avec nom, driver (par défaut local), labels (k=v,k=v) et un nœud cible (autocomplète les noms de nœud ; laissez vide pour créer sur chaque nœud, car les volumes sont locaux au nœud). Le crée sur l'agent de chaque nœud cible et signale le succès/échec par nœud. Tab se déplace entre les champs, Esc annule
spacesélectionne / désélectionne le volume (marqué ) pour une suppression en masse
asélectionne / désélectionne tous les volumes actuellement affichés
dsupprime les volumes sélectionnés — ou celui sous le curseur — sur chaque nœud qui les détient, derrière une confirmation (avec une surcouche de progression ; les suppressions s'exécutent avec un parallélisme borné)
Ppurge : supprime tout volume qu'aucun conteneur en cours d'exécution ne monte et qu'aucun service ne déclare, derrière une confirmation
Enteraffiche quels nœuds détiennent le volume ; les labels du volume sont listés en lecture seule au-dessus des nœuds (Docker n'a pas d'API de mise à jour de volume, donc les labels ne peuvent pas être édités après création — définissez-les à la création du volume)
iaffiche quels services/conteneurs l'utilisent
Aattache le volume à un service : choisissez un service (autocomplétion du nom) → saisissez le chemin cible du conteneur (absolu) → choisissez Attach ou Attach read-only. Ajoute le montage via un seul ServiceUpdate (mise à jour progressive) — la contrepartie volume des actions d'attachement réseau/secret ; l'inverse se trouve dans l'éditeur de montages v de l'inspection de service
s Sfait défiler le champ de tri (name → nodes → used → age → size) / inverse

La suppression tient compte des nœuds : un volume est supprimé sur chaque nœud qui le détient. La purge épargne les volumes qu'un service déclare même si aucune tâche n'est en cours d'exécution, elle n'effacera donc pas les données d'une stack arrêtée. Pour un contrôle plus fin, Enter ouvre la liste par nœud où space sélectionne des nœuds, d supprime la copie du nœud en surbrillance et a supprime sur tous les nœuds — chacun derrière une confirmation.

Onglet Forwards

ToucheAction
Enteraffiche le détail complet de la redirection (y compris toute erreur)
darrête la redirection sélectionnée
ocopie l'URL de la redirection (http://127.0.0.1:<port>)

Un conteneur en cours d'exécution est annoté dans l'arbre par local→remote (p. ex. 9090→8080). Les redirections continuent de fonctionner quand leur surcouche se ferme et quand vous changez de cluster ; elles s'arrêtent sur d ou quand vous quittez l'UI. La colonne CLUSTER nomme le contexte auquel chacune appartient, estompée pour celui que vous regardez — une redirection sur un autre cluster reste la vôtre et continue d'écouter, mais ses colonnes CONTAINER et NODE désignent des choses invisibles d'ici. L'annotation dans l'arbre est par cluster pour la même raison : un identifiant de conteneur n'est unique qu'au sein de son propre démon.

De ce fait, un port local que vous utilisez déjà est refusé d'emblée, en nommant la redirection qui le détient et le cluster auquel elle appartient — le message reste ouvert pour que vous en choisissiez un autre. Le « address already in use » du système reste pour le cas où il a raison : un port détenu par un autre programme, que swarmexec ne peut pas nommer.

Onglet Networks

Une liste des réseaux du swarm — nom, driver, portée, type (ingress / internal / attachable), si le réseau est chiffré et combien de services s'attachent à chacun. La colonne TYPE affiche overlay ou le nom du driver (p. ex. bridge) pour les réseaux ordinaires plutôt qu'un tiret. La colonne ENC affiche 🔒 yes quand le réseau overlay a le chiffrement du plan de données activé (créé avec --opt encrypted), sinon un tiret. Le nom de chaque réseau et sa cellule TYPE sont codés par couleur selon le genre (priorité la plus haute d'abord) :

ToucheAction
ncrée un réseau — ouvre un formulaire (driver par défaut overlay) avec les options swarm courantes : attachable (laisser des conteneurs autonomes rejoindre), encrypted (chiffrement du plan de données overlay), internal (pas de routage externe), IPv6, un MTU facultatif, un subnet/gateway facultatif (IPAM) et des labels (k=v,k=v). Tab se déplace entre les champs, Enter sur Create confirme, Esc annule. Le crée via un seul NetworkCreate de l'API du manager, puis rafraîchit l'onglet
Enter / iaffiche les services attachés, chacun avec ses conteneurs en cours d'exécution imbriqués en dessous. Les labels propres au réseau sont listés en lecture seule en haut (Docker n'a pas d'API de mise à jour de réseau, donc les labels de réseau ne peuvent pas être édités après création — définissez-les à la création du réseau). Chaque en-tête de service affiche aussi combien d'alias DNS il a sur ce réseau (ou aucun alias)
Enterdans la vue des membres : déplie / replie les alias DNS du service sélectionné (gardés repliés par défaut pour que la liste reste compacte) ; les alias apparaissent indentés sous l'en-tête du service
Adans la vue des membres : ajoute / édite les alias DNS du service sélectionné sur ce réseau — ouvre le même éditeur d'alias étagé que la vue d'inspection (a ajouter / e éditer / d supprimer / w appliquer / Esc annuler) ; appliquer remplace les alias de ce réseau en un seul ServiceUpdate et rafraîchit la vue. Fonctionne même quand le service n'a pas encore d'alias. (A est en majuscule, Maj+a, pour le distinguer de a attacher ; les conteneurs autonomes hors swarm ne sont pas éditables — les alias sont un ServiceUpdate par service)
adans la vue des membres : attache un service à ce réseau — un champ d'autocomplétion (suggérant les services pas déjà attachés ; vous pouvez aussi taper n'importe quel nom), puis une confirmation de mise à jour progressive
ddans la vue des membres : détache un service de ce réseau — un champ d'autocomplétion (suggérant les services actuellement attachés ; vous pouvez aussi taper n'importe quel nom), puis une confirmation de mise à jour progressive

Attacher ou détacher effectue une mise à jour de service qui ajoute ou retire le réseau dans la spécification du service, puis rafraîchit l'onglet Networks. C'est une opération de l'API du manager (ServiceUpdate) — le même canal que le client utilise pour la topologie — et elle déclenche une mise à jour progressive qui redémarre les tâches du service.

Onglet Secrets

Une liste en lecture seule des secrets du swarm — nom, combien de services utilisent chacun, âge, dernière mise à jour et nombre de labels. Les valeurs des secrets ne sont jamais affichées : l'API Docker ne les expose pas.

ToucheAction
Enteraffiche les métadonnées du secret et les services/conteneurs qui l'utilisent
adans la vue de détail : attache le secret à un service — un champ d'autocomplétion (suggérant les services qui ne l'utilisent pas déjà ; vous pouvez aussi taper n'importe quel nom), puis une confirmation de mise à jour progressive
ddans la vue de détail : détache le secret d'un service — un champ d'autocomplétion (suggérant les services qui l'utilisent actuellement ; vous pouvez aussi taper n'importe quel nom), puis une confirmation de mise à jour progressive

Attacher ajoute le secret au service, monté dans /run/secrets/<name> (comme docker service update --secret-add) ; détacher retire la référence de secret. Puis l'onglet Secrets se rafraîchit. Comme l'attachement/détachement réseau, chacun est un ServiceUpdate de l'API du manager qui déclenche une mise à jour progressive redémarrant les tâches du service.

Barre latérale des contextes

Les clusters occupent une colonne sur le bord droit, pas un onglet. Changer de cluster est quelque chose que l'on fait depuis l'endroit où l'on est — et la liste des clusters est précisément ce qui indique où c'est. Elle a donc sa place à l'écran, pas sur une page où il faut se rendre.

c lui donne le clavier depuis n'importe quel onglet ; c à nouveau ou Esc le rend à l'onglet où vous étiez. Le cluster actif est marqué — le nom que le pied de page affiche. Un cluster déjà visité pendant cette session porte un · vert : il est toujours connecté, donc y basculer est instantané. Un cluster qui a refusé la connexion porte un rouge. Le contexte default intégré est protégé.

La barre latérale s'ajuste au nom de contexte le plus long et s'efface sur un terminal étroit : en dessous d'environ 92 colonnes elle se masque plutôt que de tronquer l'arbre, et revient tant que c la retient. Les points d'accès ne figurent pas dans la colonne — il n'y a pas la place, et i en montre le détail complet.

Changer de cluster conserve votre session. L'UI ne se reconstruit pas : la position du curseur, les stacks et services dépliés et le filtre / sont mémorisés par cluster, si bien qu'en revenant vous retrouvez exactement l'endroit que vous aviez quitté. Les redirections de port continuent de tourner ; l'onglet Forwards gagne une colonne CLUSTER pour voir d'un coup d'œil lesquelles appartiennent au cluster affiché et lesquelles non.

Seul le cluster que vous regardez est interrogé : en basculant ailleurs, l'autre cesse de se rafraîchir et sa connexion ssh expire d'elle-même peu après. Le premier passage vers un cluster prend le temps qu'il faut pour l'atteindre, car la connexion est établie avant que quoi que ce soit change à l'écran — si elle échoue, on vous dit de quel cluster il s'agit et vous restez sur celui qui fonctionne. Chaque retour ultérieur est immédiat.

ToucheAction
cdonner le clavier à la barre latérale depuis n'importe quel onglet ; c à nouveau ou Esc le rend
idétails du contexte — point d'accès et hôtes de rebond, pour lesquels la colonne n'a pas la place
u / Enteractive le contexte sélectionné — en fait le contexte courant (y compris le contexte courant stocké par docker) et bascule l'UI vers ce cluster, en conservant votre position, vos filtres et vos redirections de port
ncrée un contexte — un formulaire guidé. Une case « Connect to Docker over SSH » décide du point d'accès : quand elle est cochée, vous renseignez utilisateur / hôte / port SSH et une seconde case « Use a jump host » révèle un champ jump host(s) (séparés par des virgules, ProxyJump multi-saut) — swarmexec les stocke sur le contexte et injecte -J dans les deux connexions ssh de l'API Docker et du tunnel de l'agent, pour que les bastions fonctionnent sans éditer ~/.ssh/config. Quand elle est décochée, vous saisissez un hôte tcp:// / unix:// simple. Le formulaire assemble l'hôte docker pour vous, et Test teste le point d'accès assemblé avant que vous n'enregistriez. Aussi en CLI : swarmexec context create <name> --docker-host ssh://ops@mgr --ssh-jump bastion1,bastion2
dretire le contexte sélectionné (derrière une confirmation) ; retirer le contexte courant réinitialise la sélection à default

Onglet Nodes

Liste les nœuds du swarm avec des informations générales et quelques agrégations — NODE (nom d'hôte ; managers en aqua, le leader marqué ), ROLE (manager / worker), AVAIL (active / pause / drain, codé par couleur), STATE (ready / down), version ENGINE, TASKS (tâches en cours d'exécution planifiées sur le nœud), VOLS (volumes que le nœud détient — locaux au nœud, donc cela se remplit un instant après le reste) et LABELS (nombre).

ToucheAction
Enter / idétails du nœud — une surcouche en lecture seule avec nom d'hôte, id, rôle/leader, disponibilité, état, adresse, moteur, plateforme, CPU, mémoire, nombre de tâches en cours d'exécution, les blocs ressources réservées, utilisation réelle et images sur ce nœud (voir ci-dessous), nombre de volumes et la liste complète des labels
lédite les labels du nœud sélectionné — un éditeur de liste étagé (a ajouter / e éditer / d supprimer / w appliquer / Esc annuler) sur des entrées key=value. Appliquer effectue un seul NodeUpdate — contrairement à un service, une mise à jour de nœud s'applique immédiatement (pas de mise à jour progressive). Les labels de nœud sont couramment utilisés comme cibles de contraintes de placement (node.labels.<k>)
adéfinit la disponibilité du nœud — un menu Active / Pause / Drain qui signale l'état dans lequel le nœud se trouve actuellement. Active y planifie les tâches normalement ; Pause conserve les tâches en cours d'exécution et empêche seulement les nouveaux placements ; Drain déplace toutes les tâches hors du nœud. Active et Pause s'appliquent aussitôt ; Drain passe par une confirmation qui indique combien de tâches en cours d'exécution le swarm va arrêter et replanifier ailleurs (les services dont les tâches ne peuvent être placées nulle part ailleurs deviennent unschedulable). Comme un changement de labels, c'est un seul NodeUpdate (lecture-modification-écriture sur la spec du nœud) et cela s'applique immédiatement — les nœuds n'ont pas de mise à jour progressive. Remappable via node_availability
Précupère de l'espace disque occupé par les images sur le nœud (P majuscule, c.-à-d. Shift+p — le même geste que celui de l'onglet Volumes pour son prune). Ouvre un petit menu avec les deux modes décrits ci-dessous, chacun derrière sa propre confirmation ; mieux vaut lire lequel est lequel avant de choisir. Le pied de page se termine par P reclaim images et la surcouche ? l'annonce comme reclaim image disk space on the node. Remappable via node_prune_images

Ressources réservées. Les détails du nœud comportent un bloc reserved by tasks (scheduler's view) : pour le CPU et la mémoire, une barre plus réservé / capacité et ce qui reste libre. La barre est verte, passe au jaune à partir de 75 % et au rouge à 100 % ou au-delà ; le surengagement est signalé explicitement, et « libre » ne devient jamais négatif. Un nœud qui n'annonce aucune capacité le dit au lieu de dessiner une barre. Le bloc ne coûte aucun appel d'API supplémentaire — la liste des nœuds récupère déjà la liste des tâches.

Ce sont des réservations, pas de l'utilisation. Les chiffres sont la somme des Reservations déclarées par les tâches — exactement le calcul que fait l'ordonnanceur du swarm lui-même lorsqu'il décide si une tâche tient sur un nœud, et la raison habituelle pour laquelle un service reste unschedulable. La consommation réelle de CPU/RAM est locale au nœud et n'est pas ce qui est affiché ici : elle a son propre bloc, juste en dessous. Les tâches qui ne déclarent aucune réservation sont comptées et signalées séparément : elles ne réservent rien ici mais peuvent tout de même consommer le nœud entier, donc un faible pourcentage ne signifie pas que le nœud est inactif — et dans la plupart des clusters, peu de services définissent des réservations. Une tâche conserve sa réservation depuis son affectation jusqu'à ce qu'elle atteigne un état terminal : les tâches en preparing ou starting comptent donc, tandis que shutdown / failed / complete / rejected ne comptent pas (le swarm les garde dans la liste des tâches à titre d'historique).

Utilisé par les conteneurs. En dessous vient un second bloc — in use by containers (measured on the node) — dessiné avec les mêmes barres proportionnelles : ce que les conteneurs du nœud consomment réellement à cet instant, le CPU en cœurs et la mémoire, chacun face à la capacité du nœud. Les deux blocs sont délibérément séparés, car ils répondent à des questions différentes et sont régulièrement très éloignés l'un de l'autre : un nœud peut être entièrement réservé et inactif, ou à peine réservé et en feu. Les chiffres viennent de l'agent de ce nœud, la même source que les marqueurs d'utilisation de l'arbre des services — tant que le delta CPU n'est pas prêt, la ligne affiche measuring…, et un nœud dont l'agent est injoignable ou trop ancien n'obtient aucun bloc plutôt qu'une rangée de zéros qui se lirait comme « inactif ».

Les conteneurs seulement. Ce sont les conteneurs du nœud qui sont comptés — le noyau, le démon docker et tout ce qui tourne en dehors de docker ne sont pas dans ce chiffre. Ce n'est pas la charge du nœud, et le bloc le dit lui-même.

Images sur ce nœud. En dernier, les détails portent un bloc images on this node : ce que pèse le magasin de couches du nœud et en combien d'images, puis ce qui en est récupérable en restes non étiquetés (un nœud qui n'a rien de non étiqueté le dit, plutôt que de proposer un zéro). Lorsqu'il y en a, une ligne supplémentaire indique combien d'espace est en outre étiqueté mais inutilisé — formulé comme un coût, l'enlever oblige à retélécharger ces images, et non comme une offre. Ce bloc existe parce que les images sont la seule ressource locale au nœud que le côté cluster ne voit pas du tout : l'API du manager n'a aucune vue sur les images, contrairement aux volumes, dont elle sait au moins qu'ils sont montés. Les volumes se géraient déjà depuis swarmexec à l'échelle du cluster ; un nœud dont le disque se remplissait silencieusement de vieilles couches n'était même pas visible. Le bloc se charge de son côté, en parallèle de la liste des nœuds, et apparaît donc un instant après le reste de la surcouche.

Récupérer l'espace — P sur l'onglet Nodes. Le menu ouvert comporte deux entrées, délibérément pas une action avec une case à cocher, car ce sont deux actes différents et une case invite à tomber par accident sur le destructeur :

EntréeCe qu'elle supprime
Untagged leftoversles couches laissées derrière eux par les rebuilds, dont rien ne peut démarrer. Sans risque : cela ne peut ni arrêter un service ni forcer un téléchargement
Every unused imagesupprime en plus les images étiquetées qu'aucun conteneur n'exécute à cet instant. Sur un nœud swarm, cela comprend tout service actuellement mis à zéro réplica et toute tâche entre deux redémarrages : chacune devra retélécharger son image
Ne lisez pas la seconde entrée comme « la même chose, en plus large ». « Every unused image » supprime des images qui appartiennent à quelque chose qui, simplement, ne tourne pas à cet instant : un service mis à zéro, une tâche qui redémarre, une stack arrêtée hier. Toutes perdent leur image et doivent la retélécharger pour revenir — et si le registre est injoignable, elles ne remonteront pas. Chacune des deux entrées passe par sa propre boîte de confirmation, qui énonce exactement cette conséquence et nomme les octets qu'elle libérerait ; ce sont deux textes séparés précisément pour qu'aucun ne puisse se mettre à décrire l'autre.

Deux modes, deux autorisations. L'agent les autorise comme deux actions distinctes — image.prune pour les restes non étiquetés et image.prune.all pour le balayage — de sorte qu'une politique peut autoriser la récupération des couches non étiquetées sans autoriser la destructrice. Chaque prune est inscrit dans le journal d'audit de l'agent avec le mode utilisé, l'espace récupéré et le nombre d'images parties.

D'où viennent les chiffres. Ils proviennent de la source même dont docker system df tire ses rapports, et coïncident exactement avec lui — vérifié sur un nœud réel à 56 images, 32,70 Go sur le disque, 22,99 Go récupérables. En particulier, swarmexec n'additionne pas les tailles des images prises une à une : la taille d'une image inclut toutes les couches dont elle est bâtie, et les couches sont partagées — cette somme surestime donc largement ; pendant le développement, elle annonçait 32,7 Go là où le démon disait 22,2 Go. Une réserve qu'il faut énoncer : le chiffre non étiquetés seulement est une borne inférieure — une couche partagée par deux images non étiquetées n'appartient à la taille propre d'aucune des deux — si bien qu'un prune des non étiquetées peut libérer un peu plus qu'annoncé, jamais moins.

Même prérequis que les chiffres d'utilisation et de santé ci-dessus : il faut des agents de cette version ou plus récents. Un nœud dont l'agent est plus ancien n'affiche tout simplement aucun bloc images, et signale qu'il n'a pas répondu si vous appuyez sur P — déployez-les avec swarmexec init --force.

Onglet Configs

Onglet 8 — le jumeau de l'onglet Secrets pour l'autre objet que le swarm distribue aux conteneurs, avec une différence décisive : le contenu d'un config est lisible. L'onglet Secrets ne peut jamais montrer une valeur (l'API Docker ne la renvoie pas) ; le détail d'un config affiche le contenu réel, si bien que vous voyez ce qu'un service reçoit vraiment sans passer par docker config inspect. C'est tout l'intérêt de cet onglet.

La liste est en lecture seule : CONFIG (nom), USED BY (combien de services le montent), SIZE, AGE, UPDATED et LABELS (nombre).

ToucheAction
Enter / iouvre la vue de détail : nom, id, taille, création, mise à jour, les services qui le montent, ses labels — et le contenu du config
j k / g Gdans la vue de détail : faire défiler · aller au début / à la fin (Esc ferme)

Le contenu est récupéré à la demande, à l'ouverture du détail — pas avec la liste — car un config peut être un nginx.conf entier ; en attendant, la vue affiche loading…, et un config vide indique (empty). Ce qui est rendu est un aperçu, plafonné à la fois à 64 Kio et à 500 lignes. Les deux plafonds sont nécessaires : ne borner que les octets laisse encore un fichier fait de lignes très courtes devenir des dizaines de milliers de lignes rendues — chacune indentée —, et c'est précisément ce qui fait ramer la vue. Une troncature est toujours annoncée (« … truncated at 500 lines », ou à la limite d'octets), jamais une coupe silencieuse, et le contenu binaire est signalé, pas déversé (« (binary content, N — not shown) », détecté en cherchant des octets NUL en tête des données). Le config complet est à un docker config inspect de là.

USED BY est dérivé exactement comme dans l'onglet Secrets : le swarm ne tient aucun index inverse d'un config vers ses consommateurs, donc swarmexec lit les spécifications des services (un ServiceList) et reconnaît une référence par l'ID ou le nom du config — une spec peut porter l'une ou l'autre forme, les deux sont donc consultées et fusionnées. Au mieux : si la liste des services ne peut pas être lue, les configs s'affichent quand même, simplement sans cette correspondance.

Shell & logs intégrés

Dans un shell intégré, Ctrl-] détache (sans terminer le processus). La vue des logs — un seul conteneur ou les logs agrégés d'un service — prend en charge la même analyse et le même filtrage tenant compte du format que la commande logs :

ToucheAction
fbascule le suivi
Ffait défiler le format de log (classic → json → logfmt → gelf → raw)
lfait défiler le filtre de niveau minimum (off → trace → … → fatal → off)
/saisit une regexp grep sur le message (vide l'efface)
mbascule la capture de la souris (off = la sélection/copie propre de votre terminal)
défile
Esc / qferme

La vue rerend les lignes mises en tampon en direct quand vous changez de format, de niveau ou de grep, et la barre de titre affiche l'état actif fmt:… lvl:… grep:….

Lors du suivi, la vue des logs se reconnecte au travers du remplacement de conteneur de la même façon que la commande logs — à la fois pour les logs d'un seul conteneur et les logs agrégés d'un service (chaque réplique suivie par emplacement). Les avis de reconnexion apparaissent en ligne dans la vue des logs.

Visualiseur de logs

Appuyez sur ` (accent grave) depuis n'importe quel onglet pour basculer une surcouche de logs en direct. Elle affiche les enregistrements les plus récents du tampon circulaire en mémoire — les plus récents en bas — se rafraîchissant tant qu'elle est ouverte et codée par couleur selon le niveau (rouge = error, jaune = warn, gris = debug). / défilent ; Esc, q ou ` la ferment. C'est la fenêtre de la TUI sur les mêmes logs qui vont dans le fichier de log, puisque le terminal lui-même ne peut pas les afficher pendant que l'UI dessine.

Réactivité. La liste des conteneurs se rafraîchit en dehors du thread de l'UI (ses deux appels à l'API du manager s'exécutaient auparavant en ligne et pouvaient brièvement figer la TUI), et un chien de garde en arrière-plan journalise un avertissement quand la boucle d'événements se cale — pratique pour diagnostiquer une lenteur depuis le visualiseur de logs.

Touches personnalisées

Les touches de raccourci sont remappables dans ~/.config/swarmexec/keys.yaml (à côté de config.yaml ; remplacez le chemin avec $SWARMEXEC_KEYS). Chaque valeur est une seule touche, ou le mot space. Les touches structurelles — Enter, Esc, Tab, les flèches, les touches de numéro d'onglet 18 et les alias vim j/k/g/G — sont fixes. Le pied de page affiche toujours vos touches réelles, et la surcouche ? les liste toutes.

# ~/.config/swarmexec/keys.yaml — omit any line to keep its default quit: q refresh: r copy: y toggle_mouse: m search: / fold: h unfold: l forward: p container_inspect: i logs: L security_risks: "!" # "!" doit être entre guillemets — 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

Le chargement n'échoue jamais : une touche invalide ou réservée, une action inconnue, ou deux actions liées à la même touche sur un onglet se rabattent chacune sur le défaut, et l'UI affiche les avertissements une fois au démarrage.

11. Codes de sortie

CodeSignification
0succès
2erreur d'usage / d'option / de configuration, ou une sélection interactive abandonnée
125échec de transport — manager ou agent injoignable, erreur de connexion/TLS, erreur de flux, confirmation abandonnée (reflète le 125 de Docker)
Npour exec, le code de sortie non nul propre à la commande distante, propagé tel quel