← swarm-exec.cloud-surfers.net

swarmexec_ dokumentacja

Obsługa klienta — polecenia, flagi, konfiguracja & TUI.

swarmexec daje ci docker exec -it, logi, przekierowanie portów i zarządzanie wolumenami dla dowolnego kontenera w Docker Swarm, z jednego terminala. Mały agent na każdym węźle (globalna usługa Swarma) wykonuje pracę na swoim węźle; klient pyta menedżera Swarma, który węzeł uruchamia twój cel, a potem łączy się bezpośrednio z agentem na tym węźle przez mTLS. Ta strona dokumentuje klienta.

1. Jak to działa

Dwa pliki binarne, jeden protokół transmisji:

operator terminal ──gRPC/mTLS──> agent(nodeN) ──docker.sock──> container └── Docker manager API (resolve node + container id)

Połączenie z menedżerem korzysta z twojego kontekstu Docker CLI (respektuje więc --context, bastiony ssh://, mTLS itd.). Połączenie z agentem jest uwierzytelniane wzajemnym TLS albo współdzielonym sekretem wobec agenta z certyfikatem samopodpisanym — zobacz Uwierzytelnianie.

2. Instalacja

Wymaga Docker Engine 19.03 lub nowszego (API 1.40) na menedżerze i na każdym węźle. swarmexec odrzuca starszego demona już przy połączeniu, podając obie wersje, zamiast łączyć się i potem zawodzić widok po widoku. Dwie funkcje wymagają nieco więcej od demona węzła: bieżące zużycie zasobów i widok obrazów per węzeł potrzebują Dockera 23.0 (API 1.41/1.42) — poniżej pozostają puste i mówią o tym, zamiast zawodzić.

Klient to pojedynczy statyczny plik binarny. Pobierz najnowszą wersję dla swojej platformy:

$ 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

Kompilacje: linux-amd64, linux-arm64, darwin-arm64, darwin-amd64, windows-amd64. Obraz agenta jest publicznie dostępny na Docker Hubie jako logleio/swarmexec-agent.

3. Szybki start

Ustaw swój kontekst Dockera na menedżera Swarma, a potem raz zaprowizjonuj agentów. init tworzy współdzielony sekret, wdraża agenta na każdym węźle w trybie z certyfikatem samopodpisanym i zapisuje pasującą konfigurację klienta — dzięki temu pozostałe polecenia działają od razu.

$ swarmexec --context my-swarm init # wdróż agentów na każdym węźle (raz) $ swarmexec ps # wypisz zadania i węzeł, na którym działa każde z nich $ swarmexec exec web -- sh # shell w usłudze "web", dowolny węzeł $ swarmexec logs -f web # śledź logi usługi $ swarmexec port-forward db 5432 # localhost:5432 → container:5432 $ swarmexec ui # interaktywna TUI

Agentów usuniesz z powrotem poleceniem swarmexec down (twoja konfiguracja klienta pozostaje nietknięta).

4. Selektory celu

exec, logs i port-forward przyjmują cel jako pierwszy argument. Jest on rozwiązywany w tej kolejności:

PostaćPrzykładZnaczenie
servicewebDziałające zadanie usługi. Jeśli ma więcej niż jedną replikę, jest to niejednoznaczne — zobacz niżej.
service.slotweb.2Konkretny slot repliki. Podczas aktualizacji kroczącej wygrywa najnowsze zadanie w danym slocie.
task-idxxh8k1…ID zadania Swarma.
container-id3f9a2b…Prefiks ID kontenera. Wymaga węzła: podaj --node albo zostanie znaleziony przez przeskanowanie działających zadań.

Niejednoznaczność. Sama nazwa usługi z kilkoma replikami nie da się sprowadzić do jednego zadania. exec uruchomiony interaktywnie prosi cię o wybór z numerowanej listy; logs i port-forward nigdy nie pytają — wypisują listę kandydatów i kończą działanie. Doprecyzuj cel slotem (web.0, web.1, …).

--node jest brane pod uwagę tylko dla celów typu container-id i może być nazwą hosta, adresem IP albo ID węzła. Cele typu usługa / slot / zadanie są lokalizowane przez API menedżera, więc nigdy go nie potrzebują.

5. Konfiguracja

Ustawienia pochodzą z czterech warstw, z których każda nadpisuje poprzednią:

wbudowane domyślne plik konfiguracyjny środowisko flagi wiersza poleceń

Flaga nadpisuje wartość tylko wtedy, gdy faktycznie ją podasz, więc wartość z pliku albo ze środowiska zostaje zachowana, o ile nie nadpiszesz jej jawnie.

Plik konfiguracyjny

Domyślna ścieżka to pierwsza z tych, która jest ustawiona:

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

Nadpiszesz ją przez --config <path>. Plik jest w formacie YAML; brakujący plik jest w porządku, źle sformułowany to błąd. Zapisywany jest z prawami 0600 (może zawierać współdzielony sekret). Plik logu leży domyślnie obok niego — ~/.config/swarmexec/swarmexec.log — chyba że ustawisz --log-file. Klucze:

KluczTypZnaczenie
castringcertyfikat CA weryfikujący certyfikat serwera agenta (mTLS)
certstringcertyfikat klienta — jego CN jest twoją tożsamością operatora
keystringklucz prywatny klienta
portintport agenta (domyślnie 9443)
addr_modestringhostname (domyślnie) albo ip — jak łączyć się z węzłem
server_namestringnadpisuje nazwę serwera TLS używaną do weryfikacji agenta
agent_secretstringwspółdzielony sekret dla agenta z certyfikatem samopodpisanym
agent_secret_filestringwczytaj sekret z tego pliku (ma pierwszeństwo przed agent_secret)
insecureboolpomiń weryfikację certyfikatu serwera agenta (agenci z certyfikatem samopodpisanym)
legacy_secretboolwysyła dodatkowo surowy sekret, dla agentów starszych niż v1.17.3 — domyślnie wyłączone; patrz niżej
operatorstringtożsamość w audycie, gdy nie używasz certyfikatu klienta (domyślnie: nazwa użytkownika systemu)
logs.formatstringdomyślny format logów dla logs i TUI: classic | json | logfmt | gelf | raw (puste = classic)
logs.min_levelstringdomyślny minimalny poziom: trace..fatal (pomiń, żeby nie filtrować po poziomie)
ui.dimfloatjak mocno przyciemniane jest tło pod otwartą nakładką, ułamek 01 (domyślnie 0.6; 0 = bez przyciemniania)

Sekcja logs: ustawia domyślne wartości dla parsowania i filtrowania logów świadomego formatu; flagi --log-format / --min-level polecenia logs je nadpisują:

logs: format: json # classic | json | logfmt | gelf | raw min_level: warn # trace..fatal; pomiń, żeby nie filtrować po poziomie

Obowiązującą, scaloną konfigurację (z zamaskowanym sekretem) obejrzysz przez:

$ swarmexec config show # albo: --json

addr-mode

hostname (domyślnie) łączy się z nazwą hosta zgłoszoną przez węzeł; ip łączy się z jego rozgłaszanym adresem. Użyj ip, gdy nazwy hostów węzłów nie są rozwiązywalne z twojej stacji roboczej — właśnie dlatego init wpisuje addr_mode: ip do wygenerowanej konfiguracji. Lider swarma zgłasza dla siebie 0.0.0.0; klient automatycznie odzyskuje jego prawdziwy adres z listy peerów raft.

Zmienne środowiskowe

ZmiennaUstawia
SWARMEXEC_CONFIGścieżkę pliku konfiguracyjnego
SWARMEXEC_CA / _CERT / _KEYmateriał mTLS
SWARMEXEC_PORTport agenta
SWARMEXEC_ADDR_MODEhostname / ip
SWARMEXEC_SERVER_NAMEnazwę serwera TLS
SWARMEXEC_AGENT_SECRET / _FILEwspółdzielony sekret / plik z sekretem
SWARMEXEC_INSECUREpominięcie weryfikacji (1/true/yes/on)
SWARMEXEC_OPERATORtożsamość w audycie
SWARMEXEC_UI_DIMprzyciemnienie tła pod nakładką (ui.dim)
SWARMEXEC_KEYSścieżkę do pliku mapowania klawiszy TUI (domyślnie keys.yaml obok konfiguracji)
SWARMEXEC_SSH_MULTIPLEX0/off/false/no wyłącza współdzielone połączenia ssh (zob. Współdzielone połączenia ssh)
DOCKER_CONTEXTkontekst Dockera dla API menedżera
XDG_CONFIG_HOMEbazę domyślnej ścieżki konfiguracji

6. Uwierzytelnianie

Klient uwierzytelnia się wobec agenta w jednym z dwóch trybów.

Tryb A — wzajemny TLS (domyślny)

Ustaw ca, cert i key (wszystkie trzy są wymagane). Klient weryfikuje agenta wobec twojego CA i przedstawia swój certyfikat; agent autoryzuje cię i zapisuje w audycie po CN z certyfikatu. To jest ustawienie domyślne i zalecane.

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

Tryb B — agent z certyfikatem samopodpisanym + współdzielony sekret

Prostszy w uruchomieniu (jeden sekret, żadnego PKI) — to właśnie konfiguruje init. Ustaw agent_secret (albo agent_secret_file) oraz albo ca do weryfikacji agenta, albo insecure: true, żeby pominąć weryfikację. Certyfikat klienta jest opcjonalny (ale cert i key muszą być ustawione razem albo oba puste). Twoją tożsamością w audycie jest wartość operator (domyślnie: twoja nazwa użytkownika systemu).

$ swarmexec --agent-secret "$SECRET" --insecure ps
insecure pomija sprawdzenie certyfikatu serwera agenta. Sekret nie jest przez to zagrożony: klient nigdy go nie wysyła, tylko dowód wyliczony z sekretu i certyfikatu połączenia, którym podróżuje — serwer, którego nie zweryfikowałeś, nie dostaje więc niczego, co zadziała wobec prawdziwego agenta. Pozostaje to, że agent nie jest uwierzytelniony wobec ciebie: ten, kto odpowie, przeczyta to, co ta sesja mu wyśle. W sieci, której nie ufasz, wybierz ca i trzymaj sekret poza historią powłoki (użyj agent_secret_file albo pliku konfiguracyjnego).

Przejście na v1.17.3 — najpierw zaktualizuj agentów

Klient v1.17.3 nie uwierzytelni się wobec starszego agenta. Wysyła wyłącznie dowód związany z połączeniem, a agenci sprzed v1.17.3 nie znają tej formy — odpowiadają invalid or missing agent secret, mimo że twój sekret jest poprawny. Zaktualizuj ich najpierw:
$ swarmexec init --force
Dopóki flota jest mieszana, legacy_secret: true w konfiguracji klienta wysyła też surowy sekret, z ekspozycją opisaną wyżej. Usuń to, gdy agenci będą aktualni, i dodaj agentowi -allow-legacy-secret=false, aby żaden klient nie położył przypadkiem poświadczenia na kablu.

7. Docker context & SSH

--context wybiera kontekst Docker CLI używany dla API menedżera. Kolejność rozwiązywania: --context$DOCKER_CONTEXT$DOCKER_HOST → aktywny kontekst w ~/.docker/config.json → lokalne gniazdo unix:///var/run/docker.sock.

Lokalny Docker nie jest potrzebny. Klient nigdy nie uruchamia binarki docker — linkuje SDK Dockera dla Go i mówi bezpośrednio do API menedżera, sam czytając metadane kontekstów z plików. Wszystko, czego potrzebuje, to osiągalny punkt końcowy menedżera Swarma (wywołania rozwiązujące węzeł działają tylko wobec menedżera):
  • $DOCKER_HOST wskazujący na zdalnego menedżera po tcp:// (mTLS) — wtedy na twojej stacji roboczej nie ma w ogóle zainstalowanego Dockera;
  • kontekst ssh:// — wymaga klienta ssh (nie dockera); ruch do menedżera i do agenta tuneluje się przez niego;
  • lokalne gniazdo unix:///var/run/docker.sock — tylko domyślny wariant awaryjny i jedyna opcja, która zakłada lokalnego demona.
Samo CLI docker jest potrzebne wyłącznie do tworzenia nazwanych kontekstów (docker context create) — albo utwórz je przez swarmexec context create <name> --docker-host …, dzięki czemu CLI dockera nie jest potrzebne nawet do tego; użyj $DOCKER_HOST, żeby całkowicie pominąć nazwane konteksty. (init czyta lokalne poświadczenia docker login tylko dla prywatnego obrazu agenta — nie dla publicznego, domyślnego.)

Bastion SSH. Jeśli host kontekstu jest punktem końcowym ssh://, API menedżera jest tunelowane przez SSH — i tak samo połączenie z agentem: ponieważ punkty końcowe węzłów node:9443 zwykle nie są routowalne z twojej stacji roboczej, klient automatycznie tuneluje ruch gRPC agenta przez ten sam host SSH. Żadne dodatkowe flagi nie są potrzebne.

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

8. Flagi globalne

Te trwałe flagi dotyczą każdego polecenia:

FlagaDomyślnieOpis
--configścieżka pliku konfiguracyjnego (domyślnie ~/.config/swarmexec/config.yaml)
--contextkontekst dockera dla API menedżera; obsługuje ssh:// (także $DOCKER_CONTEXT)
--port9443port agenta
--addr-modehostnameadres, pod którym łączymy się z węzłem: hostname | ip
--cacertyfikat CA do weryfikacji agenta (mTLS)
--certcertyfikat klienta (mTLS; CN jest tożsamością operatora)
--keyklucz prywatny klienta (mTLS)
--server-namenadpisuje nazwę serwera TLS przy weryfikacji agenta
--agent-secretwspółdzielony sekret dla agenta z certyfikatem samopodpisanym
--agent-secret-fileplik, z którego wczytać współdzielony sekret
--insecurefalsepomiń weryfikację certyfikatu serwera agenta
--operatornazwa użytkownika systemutożsamość operatora raportowana na potrzeby audytu
--log-levelinfoszczegółowość logów: debug | info | warn | error | off (off całkowicie wyłącza logowanie)
--log-fileścieżka pliku logu (domyślnie swarmexec.log obok pliku konfiguracyjnego)
--infopokaż wersję, licencję i dane kontaktowe
--versionwypisz wersję klienta i protokołu
Logowanie. swarmexec loguje w formie strukturalnej (Go slog, format tekstowy) do pliku logu oraz do bufora cyklicznego w pamięci. W TUI logi nigdy nie trafiają na terminal (zepsułyby ekran) — zamiast tego bufor cykliczny zasila wbudowaną podglądarkę na żywo (zobacz TUI). --log-level off wyłącza logowanie całkowicie; plik logu, którego nie da się otworzyć, nie jest błędem krytycznym (zostaje wtedy sam bufor cykliczny).

9. Polecenia

init — zaprowizjonuj agentów

Wdraża agenta jako globalną usługę Swarma przez API menedżera: tworzy sekret Dockera ze współdzielonym sekretem, uruchamia agenta w trybie z certyfikatem samopodpisanym na każdym węźle (port hosta 9443) i zapisuje pasującą konfigurację klienta. Uruchom to raz na swarm.

swarmexec init
FlagaDomyślnieOpis
--imagedocker.io/logleio/swarmexec-agent:latestobraz agenta do wdrożenia
--secretlosowywspółdzielony sekret do użycia (domyślnie: wygeneruj losowy). Uwaga: jeśli sekret agenta już istnieje, zostaje zachowany bez zmian (sekrety Dockera są niezmienne), więc --secret nie jest stosowany do klastra — trafia tylko do twojej konfiguracji klienta, a init ostrzega o ryzyku niezgodności. Żeby zmienić sekret klastra, usuń go najpierw (żadna usługa nie może się do niego odwoływać), a potem uruchom init ponownie
--service-nameswarmexec_agentnazwa usługi agenta
--port9443port hosta publikowany przez agenta
--forcefalsezaktualizuj usługę, jeśli już istnieje
--save-configtruezapisz konfigurację klienta
--registry-authtrueprzekaż lokalne poświadczenia rejestru, żeby węzły mogły pobrać prywatny obraz
--waittruepoczekaj, aż agenci wstaną, i raportuj postęp
--rollout-timeout90sjak długo czekać na start agentów
Domyślny obraz agenta jest publiczny na Docker Hubie, więc logowanie do rejestru nie jest potrzebne. Uruchom ponownie z --force, żeby wdrożyć nową wersję agenta — zobacz ponowne wdrożenie agentów bez zmiany sekretu.

down — usuń agentów

Odwrotność init: usuwa globalną usługę agenta oraz, domyślnie, sekret Dockera ze współdzielonym sekretem. Nie rusza twojej konfiguracji klienta.

swarmexec down
FlagaDomyślnieOpis
--service-nameswarmexec_agentnazwa usługi agenta do usunięcia
--keep-secretfalsenie usuwaj sekretu Dockera ze współdzielonym sekretem
-y, --yesfalsenie pytaj o potwierdzenie

doctor — zdiagnozuj swarm

Sprawdza połączenie z menedżerem, to czy usługa agenta jest wdrożona, oraz odpytuje agenta na każdym gotowym węźle pod kątem osiągalności i rozjazdu wersji. Wypisuje tabelę per węzeł (NODE  AGENT  VERSION  PROTO); kończy się kodem niezerowym, jeśli menedżer jest nieosiągalny albo któryś agent jest niezdrowy. Węzeł raportowany jako too old (init --force) uruchamia agenta starszego niż twój klient — zobacz ponowne wdrożenie agentów bez zmiany sekretu.

swarmexec doctor
FlagaDomyślnieOpis
--connect-timeout10slimit czasu połączenia na węzeł
--jsonfalsewypisz JSON zamiast tabeli

security report — raport Markdown o całym klastrze

Nakładka z ryzykami bezpieczeństwa analizuje specyfikacje usług i pokazuje wynik na ekranie. To polecenie zapisuje tę samą analizę — plus kontrole, które należą do klastra, a nie do żadnej pojedynczej usługi — w formacie Markdown, żeby dało się ją przejrzeć z dala od terminala, dołączyć do zgłoszenia albo trzymać obok plików stacka i porównywać wydanie po wydaniu. Wynik idzie na stdout, o ile nie podano -o, więc równie łatwo go przekierować, jak zapisać.

swarmexec security report -o security.md

Na poziomie klastra działają cztery kontrole i na żadną z nich specyfikacja usługi nie potrafi odpowiedzieć:

KontrolaWagaCo zgłasza
network-unencrypted — „ruch overlay nie jest szyfrowany”mediumsieć overlay przenosząca ruch usług bez szyfrowania warstwy danych. Swarm tuneluje ruch między węzłami przez VXLAN jawnym tekstem, o ile sieci nie utworzono z --opt encrypted — a działająca usługa w obu przypadkach wygląda tak samo. Sieci, do których nic nie jest podłączone, nie są zgłaszane (nic nie przenoszą), podobnie jak ingress, której w ogóle nie da się zaszyfrować: takiego wpisu nikt nigdy by nie zamknął
autolock-disabled — „menedżery nie mają autolocka”mediummagazyn raft menedżerów nie jest szyfrowany w spoczynku. Zawiera każdy sekret, każdy config i klucz CA klastra, a jego klucz leży na tym samym dysku — kto zabierze dysk menedżera, zabierze wszystko. Włącz przez docker swarm update --autolock=true i zachowaj klucz odblokowujący: zrestartowany menedżer o niego poprosi. Jeśli konfiguracji swarma nie da się odczytać, raport podaje autolock-unknown zamiast zgadywać — przyjęcie „wyłączone” wymyśliłoby wpis, a przyjęcie „włączone” byłoby fałszywym uspokojeniem
agent-proto-mismatch / agent-version-skew / agent-too-old / agent-unreachablehigh / medium / lowto agent egzekwuje autoryzację przy każdym execu, każdym logu i każdym port-forwardzie, więc starszy od twojego klienta może nie egzekwować reguły, którą klient uznaje za obowiązującą. Niezgodność protokołu ma wagę high i przeważa nad różnicą wersji: obie strony nie zgadzają się co do samego kontraktu, a nie tylko co do tego, który build go realizuje. Agent, który nie odpowiedział, jest zgłaszany jako luka, a nie jako wynik pozytywny. Dla niewydanego klienta (dev) rozjazd nie jest zgłaszany — z założenia różni się od każdego wydanego agenta; ten sam wyjątek stosuje doctor
unused-secret / unused-configlowsekret lub config, do którego nie odwołuje się żadna usługa. Nadal jest rozprowadzany przez magazyn raft i nadal czytelny dla wszystkiego, co dosięgnie menedżera — zwykle poświadczenie, które ktoś zrotował i nigdy nie usunął, więc stara wartość wciąż żyje w klastrze długo po tym, jak wszyscy uznali ją za zniknioną

Co raport mówi sam o sobie. Czyta go ktoś z dala od terminala, kogo nie było przy jego powstaniu — niesie więc własny kontekst: który klaster, kiedy i jakim buildem. Ma też sekcję Not covered — nieosiągalne agenty, nieczytelna konfiguracja swarma, pusta lista węzłów — bo milczenie w raporcie bezpieczeństwa czyta się jak uspokojenie co do terenu, na którym nigdy nie postał. Kolejność zależy wyłącznie od znalezisk, więc dwa raporty z niezmienionego klastra różnią się tylko znacznikiem czasu i dają się ze sobą porównać — a to jest coś warte tylko dlatego, że brak wpisu znaczy nie znaleziono, a nigdy nie sprawdzono.

Plik zapisywany jest z prawami 0600, a tworzone katalogi nadrzędne z 0700. Wymienia każdą usługę, każdą sieć i każdy sekret w klastrze wraz z każdą znalezioną słabością — to mapa miejsc do ataku i nie ma powodu, by była czytelna dla każdego konta na maszynie.

FlagaDomyślnieOpis
-o, --outputstdoutzapisz do tego pliku zamiast na stdout
--connect-timeout10slimit czasu połączenia na węzeł przy sprawdzaniu agentów
--skip-agentsfalsenie kontaktuj się z agentami węzłów. Szybciej, a rozjazd agentów trafia wtedy do Not covered, zamiast zniknąć po cichu

Ten sam raport zapiszesz z TUI: naciśnij w w nakładce z ryzykami bezpieczeństwa. To świeże zebranie danych z całego klastra, a nie zrzut tego, co pokazuje nakładka, i działa poza goroutine interfejsu — ten pozostaje responsywny, gdy raport odpytuje kolejne węzły.

stack export / stack diff — wdrożony stack jako plik

stack export odczytuje wdrożony stack z klastra i zapisuje go jako YAML w formie compose. stack diff porównuje plik stacka z tym, co naprawdę działa, i wypisuje diff zunifikowany: linia + to coś, co wdrożenie pliku by dodało, a - to coś, co by usunęło. Jak git diff --exit-code, polecenie kończy się kodem 1, gdy cokolwiek się różni — nadaje się więc do CI.

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

W TUI E na zakładce Stacki/Usługi proponuje oba działania dla stacka pod kursorem — wiersza stacka, jego usługi albo jednego z jej kontenerów.

Dlaczego nie porównanie tekstu. Wdrożony stack niesie rzeczy, których plik nigdy nie miał: skrót obrazu rozwiązany przez demona przy wdrożeniu, jego własne wartości domyślne polityki restartu i aktualizacji oraz przestrzeń nazw stacka doklejoną do każdej sieci, sekretu i wolumenu. Porównanie obu jako tekstu zgłasza dziesiątki różnic dla stacka, który jest dokładnie zsynchronizowany — a to gorsze niż brak narzędzia, bo uczy ignorowania wyniku. Zamiast tego plik przechodzi przez własny loader i konwerter compose dockera, ten sam kod, którego używa docker stack deploy, a potem obie strony redukuje ta sama funkcja. Odpowiadamy na pytanie „czy wdrożenie tego pliku coś by zmieniło?”, a nie „czy te dwa pliki są tak samo zapisane?”. Wartości domyślne demona są pomijane po obu stronach; wartość, która nie jest domyślna, nadal się pokazuje, więc nic prawdziwego nie zostaje ukryte.

Eksport to opis, nie kopia zapasowa. Wartość sekretu jest w API silnika tylko do zapisu — nigdy nie da się jej odczytać — a zawartość wolumenu leży na węzłach. Oba są więc deklarowane jako external, a wyeksportowany plik mówi to we własnym nagłówku: odtworzenie stacka gdzie indziej oznacza najpierw utworzenie sekretów. Pola specyfikacji, których to odwzorowanie nie niesie (tty, ulimits, ustawienia seccomp i AppArmor oraz kilka innych), są wymienione w tym samym miejscu, bo groźną porażką narzędzia porównującego nie jest zła odpowiedź, lecz pewne siebie milczenie: „brak różnic” przychodzi zawsze wraz z zastrzeżeniami.

Zmienne są interpolowane z twojego środowiska, dokładnie tak jak robi to docker stack deploy. Plik zawierający ${TAG} opisuje zatem inny stack dla innego TAG, a diff zależy od środowiska, w którym działa — i tak jest poprawnie: udawanie inaczej zgłosiłoby „brak zmian” dla wdrożenia, które podmieniłoby obraz.

PolecenieOpis
stack lsstacki wdrożone na klastrze
stack export <stack>zapisuje stack jako YAML compose; z -o do pliku, inaczej na stdout
stack diff <plik> [stack]porównuje plik z wdrożonym stackiem. Nazwa stacka domyślnie pochodzi od nazwy pliku bez rozszerzenia; --stack albo drugi argument ją nadpisuje. Kończy się kodem 1 przy każdej różnicy

stack deploy — wdrożenie pliku stacka, po sprawdzeniu

Wczytuje plik stacka, uruchamia kontrole bezpieczeństwa na tym, co naprawdę zostałoby wdrożone, i stosuje go. To, co czyni go wartościowym wobec docker stack deploy, to bramka: plik jest sprawdzany, zanim cokolwiek powstanie, a jeśli znajdzie się coś powyżej informacyjnego, wdrożenie zatrzymuje się i pyta.

swarmexec stack deploy web.yml # pyta, jeśli coś znaleziono swarmexec stack deploy web.yml --check # tylko sprawdź, nigdy nie wdrażaj swarmexec stack deploy web.yml --force # wdroż mimo znalezisk, świadomie

W TUI EDeploy a file robi to samo: najpierw znaleziska, d kontynuuje, Esc odchodzi. W tym momencie nic jeszcze nie powstało — i właśnie po to jest ta pauza.

Kontrole nie są drugim zestawem reguł. Plik jest przekształcany dokładnie w te wartości ServiceSpec, które wysłałoby wdrożenie, i na nich działa osiem istniejących analizatorów — te same kontrole, które stawiają tarczę w drzewie i wypełniają nakładkę ! oraz raport bezpieczeństwa. Dwa zestawy by się rozjechały: reguła zaostrzona w jednym miejscu, a nie w drugim, oznacza, że bramka przepuszcza plik, który drzewo oznacza w chwili, gdy ten zaczyna działać — a komu powiedziano „nic nie znaleziono”, temu powiedziano nieprawdę. Dzięki temu kontrole widzą też, co klaster zrobi, a nie co plik mówi; wartości domyślne i skróty compose leżą pomiędzy.

--yes nie znaczy „zignoruj znaleziska”. Znaczy „nie pytaj”, a uruchomienie, które znajdzie coś powyżej informacyjnego, i tak odmawia i kończy się kodem niezerowym. Inaczej bramka stałaby się formalnością przy pierwszym wstawieniu do CI. Aby wdrożyć mimo znalezisk, trzeba napisać --force — co innego się wpisuje i co innego potem tłumaczy. Uruchomienie nieinteraktywne bez odpowiedzi na stdin liczy się jako nie.

FlagaOpis
--checksprawdza i zatrzymuje się. Kończy kodem 1, jeśli znaleziono coś powyżej informacyjnego — nadaje się jako bramka w pipeline
-y, --yesnie pytaj. Wdraża, gdy kontrole są czyste; odmawia, gdy nie są
--forcewdroż mimo znalezisk, bez pytania
--pruneusuwa usługi stacka, których plik już nie deklaruje. Domyślnie wyłączone, jak w dockerze: plik będący podzbiorem stacka to znacznie częściej przeoczenie niż polecenie usunięcia

Co robi wdrożenie, w tej kolejności: sieci, potem sekrety, potem configi, potem usługi — usługa odwołująca się do czegoś, czego jeszcze nie ma, kończy się błędem, a stack zostaje wdrożony połowicznie. Sieci zewnętrzne są sprawdzane najpierw, bo nieistniejąca sieć to najczęstszy powód zatrzymania wdrożenia w połowie. Istniejący sekret nigdy nie jest nadpisywany: jego wartość jest w swarmie niezmienna, więc zmiana oznacza utworzenie nowego pod nową nazwą. Jeśli wdrożenie mimo to zawiedzie w trakcie, to, co już zastosowano, jest raportowane wraz z błędem, a nie pozostawione do odgadnięcia.

Ponowne wdrożenie agentów bez zmiany sekretu

Prędzej czy później musisz wdrożyć agentów ponownie w swarmie, który jest już zaprowizjonowany: agent się zawiesza, doctor pokazuje węzeł jako too old (init --force), komenda kończy się błędem „agent is older than this client (missing RPC) — update it with swarmexec init --force albo węzeł został postawiony od nowa i jego agent nigdy nie wrócił. Rozwiązaniem jest ponowne uruchomienie init z --force — flaga oznacza dokładnie zaktualizuj usługę, jeśli już istnieje. Bez niej uruchomienie init na istniejącej usłudze jest błędem użycia, który każe ci dodać --force; nic nie zostaje zmienione.

$ swarmexec init --force

To wdraża agenta ponownie na każdym węźle i nie rusza współdzielonego sekretu. Sekrety Dockera są niezmienne, więc init znajduje istniejący swarmexec_agent_secret, raportuje go jako reusing existing i zachowuje wartość, która już jest w klastrze. Każdy klient, który działał wcześniej, działa dalej — nie trzeba niczego rozdystrybuowywać ponownie.

Nie przekazuj --secret przy ponownym wdrożeniu. Jeśli sekret już istnieje, nie da się go nadpisać, więc twoja wartość nie zostaje zastosowana w klastrze — ale mimo to zostaje zapisana do twojej konfiguracji klienta. Jeśli nie jest akurat prawdziwą wartością istniejącego sekretu, agenci od tego momentu będą cię odrzucać, a awaria ujawni się dopiero później i wygląda jak zepsuty agent, a nie jak zła lokalna konfiguracja. init ostrzega dokładnie w tym przypadku; potraktuj to ostrzeżenie poważnie. Żeby naprawdę zmienić sekret klastra, musisz najpierw usunąć sekret (żadna usługa nie może się do niego odwoływać) i uruchomić init ponownie.

--save-config domyślnie ma wartość true, więc ponowne wdrożenie przepisuje też ~/.config/swarmexec/config.yaml. Przekaż --save-config=false, kiedy lokalna konfiguracja klienta ma pozostać nietknięta — na przykład gdy wdrażasz agentów ponownie z maszyny, której konfiguracja jest już poprawna, albo z CI.

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

Potem sprawdź wynik. doctor raportuje status per węzeł; ok na każdym węźle oznacza, że klient, agent i sekret znów się zgadzają.

$ swarmexec doctor

ps — wypisz zadania

Wypisuje kandydujące zadania/kontenery i węzeł, na którym każde działa. Przyjmuje opcjonalny filtr usługi. Kolumny: SERVICE  SLOT  CONTAINER  NODE  IP  UPTIME. Rozmawia wyłącznie z API menedżera — działa nawet zanim agenci staną się osiągalni.

swarmexec ps [service]
FlagaDomyślnieOpis
--jsonfalsewypisz JSON zamiast tabeli

exec — uruchom polecenie / otwórz shell

Wejdź przez exec do kontenera działającego gdziekolwiek w swarmie. Bez podanego polecenia otwiera /bin/sh. TTY jest przydzielane automatycznie, gdy stdin jest terminalem i nie podałeś polecenia; wymusisz je przez -t. Kod wyjścia zdalnego polecenia jest przekazywany dosłownie.

swarmexec exec [flags] <target> [-- <cmd> [args...]]
FlagaDomyślnieOpis
-i, --stdintruetrzymaj stdin otwarte
-t, --ttyautoprzydziel TTY (auto: prawda wtedy i tylko wtedy, gdy stdin jest terminalem i nie ma polecenia)
-u, --usernazwa użytkownika albo UID (np. 1000:1000)
-w, --workdirkatalog roboczy wewnątrz kontenera
-e, --envustaw zmienne środowiskowe (KEY=VALUE, można powtarzać)
--nodepodpowiedź/nadpisanie węzła dla celów typu container-id
--connect-timeout10slimit czasu połączenia z agentem
$ swarmexec exec web -- sh $ swarmexec exec web.2 -- cat /etc/hostname # konkretna replika $ swarmexec exec -u root -w /app web -- ls -la $ swarmexec exec -e FOO=bar web -- env

logs — strumieniuj logi

Strumieniuj logi kontenera z dowolnego miejsca w swarmie.

swarmexec logs [flags] <target>
FlagaDomyślnieOpis
-f, --followfalsestrumieniuj dalej nowe linie logu
--tail0ile linii od końca pokazać na początek (0 = wszystkie)
-t, --timestampsfalsepoprzedź każdą linię znacznikiem czasu
--since0tylko logi nowsze niż to (np. 10m, 1h)
--log-formatclassicparsuj linie jako classic | json | logfmt | gelf | raw (domyślnie z konfiguracji, inaczej classic)
--min-levelpokaż tylko ten poziom i wyższe: trace | debug | info | warn | error | fatal
--greppokaż tylko linie, których (sparsowany) komunikat pasuje do tego wyrażenia regularnego Go
--nodepodpowiedź/nadpisanie węzła dla celów typu container-id
--connect-timeout10slimit czasu połączenia z agentem

Parsowanie i filtrowanie świadome formatu. --log-format mówi klientowi, jak czytać każdą linię, żeby mógł wyciągnąć z niej poziom i komunikat: classic wyciąga poziom ze zwykłej linii tekstu, json parsuje JSON w stylu logstash (pola level/message), logfmt parsuje styl key=value używany przez wiele aplikacji w Go, demona Dockera i narzędzia HashiCorp (msg/message, level/lvl/severity, ts/time), gelf parsuje JSON GELF z Grayloga (numeryczny poziom syslog), a raw przepuszcza linie bez zmian. --min-level odrzuca potem wszystko poniżej wybranego poziomu, a --grep zostawia tylko linie, których sparsowany komunikat pasuje do wyrażenia regularnego.

Linie bez wykrywalnego poziomu zawsze przechodzą przez filtr --min-level, więc wielolinijkowe ślady stosu nie giną. Te same wartości domyślne można ustawić raz w sekcji konfiguracji logs: (flagi mają pierwszeństwo).

Śledzenie mimo wymiany kontenera. Z -f na celu typu usługa albo service.slot logs śledzi dalej, gdy kontener, z którego strumieniuje, zostanie zastąpiony przez aktualizację kroczącą, restart albo przeplanowanie: ponownie rozwiązuje bieżący działający kontener usługi (ten sam slot albo ten sam węzeł dla usługi global), łączy się automatycznie ponownie — jak docker service logs -f — i wypisuje przygaszoną linię z powiadomieniem (container replaced; reconnected to <id> on <node>). Czeka na zaplanowanie następcy do ok. 30 s, zanim się podda, i kończy czysto, gdy usługa zostanie usunięta. Sam cel typu container-id nie ma następcy, więc po prostu zatrzymuje się jak dawniej.

Wykrywanie żyje w kliencie: uczy się topologii od menedżera swarma, z którym i tak jest już połączony — agenci na węzłach są lokalni dla swojego węzła i nie widzą wymian na poziomie swarma.
$ 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
Na przyszłość: wybór formatu został zaprojektowany tak, by później obsłużyć automatyczne wykrywanie formatu per usługa przez usługę online (jeszcze niezaimplementowane).

port-forward (alias pf) — przekieruj lokalny port

Zajmij lokalny port TCP i przekieruj go na port wewnątrz kontenera, bez publikowania tego portu w klastrze. Port lokalny domyślnie równa się zdalnemu. Wskazanie usługi przekierowuje do dokładnie jednego jej zadania (tego, do którego rozwiązuje się cel), a nie do wszystkich replik. Domyślnie wiąże się z 127.0.0.1. Naciśnij Ctrl-C, żeby zatrzymać.

swarmexec port-forward [flags] <target> [local:]remote
FlagaDomyślnieOpis
--address127.0.0.1lokalny adres do zbindowania (pętla zwrotna trzyma port poza twoją siecią)
--nodepodpowiedź/nadpisanie węzła dla celów typu container-id
--connect-timeout10slimit czasu połączenia z agentem
$ swarmexec port-forward db 5432 # localhost:5432 → container:5432 $ swarmexec pf web 9090:8080 # localhost:9090 → container:8080

volume ls — wypisz wolumeny

Wypisuje wolumeny ze wszystkich węzłów i to, które węzły trzymają każdy z nich. Wolumeny w Swarmie są lokalne dla węzła, więc klient odpytuje każdy węzeł i agreguje wyniki. Opcjonalny filtr nazwy po fragmencie. Kolumny: VOLUME  DRIVER  NODES  USED BY  AGE (plus SIZE z --size).

swarmexec volume ls [name-filter]
FlagaDomyślnieOpis
--sizefalsepolicz też rozmiar każdego wolumenu na dysku (wolniejsze: du na wolumen)
--sortnamesortuj po: name | nodes | used | age | size (size włącza --size)
--reversefalseodwróć kierunek sortowania
--connect-timeout10slimit czasu połączenia na węzeł
--jsonfalsewypisz JSON zamiast tabeli

volume rm — usuń wolumen

Usuń wolumen na każdym węźle, który go trzyma (--all), albo na wskazanych węzłach (--node, można powtarzać). Jedno z dwóch jest wymagane.

swarmexec volume rm <name> (--all | --node N …)
FlagaDomyślnieOpis
--allfalseusuń na każdym węźle, który trzyma wolumen
--nodeusuń tylko na tych węzłach (można powtarzać)
--forcefalseprzekaż flagę force dockera
-y, --yesfalsenie pytaj o potwierdzenie
--connect-timeout10slimit czasu połączenia na węzeł

ui — interaktywna TUI

Interaktywny widok kontenerów i wolumenów, z wbudowanym exekiem, logami i przekierowaniem portów. Wymaga interaktywnego terminala. Klawisze znajdziesz w rozdziale TUI.

swarmexec ui [service]
FlagaDomyślnieOpis
--connect-timeout10slimit czasu połączenia z agentem

config show — obejrzyj konfigurację

Wypisuje obowiązującą, scaloną konfigurację klienta z zamaskowanym sekretem.

swarmexec config show [--json]

context (alias ctx) — zarządzaj kontekstami Dockera

Twórz konteksty Dockera, które --context (i $DOCKER_CONTEXT) rozwiązują dla API menedżera, i zarządzaj nimi. swarmexec zapisuje do własnego magazynu dockera na dysku, więc konteksty utworzone tutaj są wymienne z tymi z CLI docker — i nie potrzebujesz już zainstalowanego dockera nawet po to, żeby jakiś utworzyć. Wbudowanego kontekstu default nie da się usunąć.

context create <name> — utwórz kontekst wskazujący na host menedżera (przyjmuje dokładnie jeden argument z nazwą):

swarmexec context create <name> --docker-host <endpoint>
FlagaDomyślnieOpis
--docker-hostwymagane — punkt końcowy demona dockera: ssh:// | tcp:// | unix:// | npipe://
--descriptionopcjonalny opis
--ssh-jumphost(y) pośredniczące ssh dla kontekstu ssh://, rozdzielone przecinkami (wieloetapowy ProxyJump / -J); wstrzykiwane zarówno do połączenia z API Dockera, jak i do tunelu agenta
--usefalseustaw go od razu jako bieżący kontekst

context ls (alias list) — wypisz konteksty; kolumny NAME  CURRENT  DOCKER ENDPOINT (aktywny oznaczony *):

swarmexec context ls [--json]

context use <name> — ustaw bieżący kontekst:

swarmexec context use <name>

context rm <name> [name...] (alias remove) — usuń jeden lub więcej kontekstów:

swarmexec context rm <name> [name...]
FlagaDomyślnieOpis
-f, --forcefalsewymagane, żeby usunąć bieżący kontekst (wybór wraca wtedy na default)
$ swarmexec context create prod --docker-host ssh://ops@manager --use $ swarmexec context ls $ swarmexec --context prod ps

Współdzielone połączenia ssh

Przy kontekście ssh:// swarmexec sięga do klastra przez ssh dwukrotnie: do API managera Dockera przez pomocnika połączeń ssh, a do każdego agenta węzła własnym tunelem do tego samego hosta. Każdy exec, każdy strumień logów, każde przekierowanie portu, każde odpytanie o statystyki i każdy cykl odświeżania otwierał dotąd świeże połączenie ssh — uzgodnienie TCP, wymianę kluczy i uwierzytelnienie na każde wywołanie, i tyle samo na każdy host pośredniczący — do bastionu, który chwilę wcześniej był już połączony.

Teraz swarmexec pozwala im współdzielić jeden transport, korzystając z ControlMaster samego OpenSSH: pierwsze połączenie do danego celu je otwiera, a każde kolejne staje się kanałem na nim. Zmierzone na klastrze trzech węzłów za hostem pośredniczącym: swarmexec doctor przeszedł z 8 uwierzytelnień i 3,6 s do 2 i 1,0 s. Sam ruch się nie zmienia, a efekt rośnie wraz z tym, ile robi się w jednej sesji.

10. TUI

swarmexec ui ma siedem zakładek — Stacks/Services (1), Volumes (2), Forwards (3), Networks (4), Secrets (5), Nodes (6), Configs (7)panel boczny kontekstów przy prawej krawędzi oraz dwuliniową stopkę: na górze podpowiedzi klawiszy dla danej zakładki, a pod nimi linia statusu z aktywnym kontekstem dockera (ctx <name>, żeby zawsze było jasne, na którym klastrze jesteś), podsumowaniem klastra na żywo, liczbą przekierowań oraz — na zakładce Volumes — liczbą zaznaczonych przez ciebie wolumenów. Te klawisze działają na każdej zakładce:

Pasek zakładek jest responsywny: na węższym terminalu przełącza się na krótkie etykiety — St/Sv, Vol, Fwd, Net, Sec, Node, Cfg — dzięki czemu wszystkie siedem zakładek pozostaje widocznych zamiast obcinania ostatnich. (Cfg to Configs.) Cyfry skrótów i obszary klikalne myszą pozostają bez zmian.

Zmiana: konteksty były zakładką 6, a teraz są panelem bocznym (c). Nodes i Configs przesuwają się na 6 i 7.
KlawiszAkcja
?otwórz nakładkę ze skrótami klawiszowymi — kompletny, aktualny spis klawiszy (generowany z twojego mapowania, więc przemapowane klawisze pokazują się poprawnie); jednoliniowa stopka mieści tylko najczęściej używane klawisze
Tabprzejdź do następnej zakładki
18przeskocz do Stacks/Services / Volumes / Forwards / Networks / Secrets / Contexts / Nodes / Configs
j kw dół / w górę (także )
rodśwież aktywną zakładkę i podsumowanie klastra
yskopiuj bieżącą listę do schowka (OSC52)
mprzełącz przechwytywanie myszy (wyłączone = własne zaznaczanie/kopiowanie twojego terminala)
`otwórz / zamknij podgląd logów na żywo (zobacz niżej)
qwyjdź

Zakładka Stacks/Services

Zakładka 1, wcześniej nazywana Containers. To jedno drzewo, stack → usługa → kontener, i to w nim mieszkają exec, logi, przekierowanie portów, inspekcja i edytory usług.

Sprawdzanie wersji dla :latest. Swarm przypina :latest do skrótu (digest) w chwili wdrożenia, więc usługa otagowana :latest naprawdę uruchamia ustalony obraz. swarmexec rozwiązuje to wobec rejestru (używając twoich lokalnych poświadczeń dockera) i opisuje wiersz usługi zaraz za URI obrazu: podaje prawdziwą wersję w nawiasach — odczytaną z etykiety obrazu org.opencontainers.image.version — oraz strzałkę w górę ↑, gdy bieżący :latest w rejestrze ma nowszy digest niż ten, do którego usługa jest przypięta (np. nginx:latest (1.4.0) ↑). Działa najlepiej jak się da i jest buforowane: błędy rejestru po prostu zostawiają wiersz bez adnotacji. Sekcja IMAGE nakładki inspekcji pokazuje tę samą wersję, a gdy istnieje nowszy obraz — wybieralny wiersz newer version available; najedź na niego i naciśnij u (albo Enter), żeby zaktualizować usługę.

Ustawianie wersji klawiszem u — nie tylko wtedy, gdy jest aktualizacja. u nie zależy już od tego, czy oferowana jest aktualizacja. Działa w każdej inspekcji usługi, której tagi obrazu swarmexec zdołał odczytać z rejestru — zarówno dla usług przypiętych do konkretnej wersji, jak i dla tych na :latest — i to z dowolnego wiersza nakładki, więc nigdy nie musisz szukać podpowiedzi. Ustawienie tej samej wersji jeszcze raz, przypięcie tego, co aktualnie działa, albo powrót do starszego tagu to zupełnie normalne zastosowania; nie musisz czekać, aż pojawi się aktualizacja. Napis w stopce mówi ci, w której sytuacji jesteś: u update version, gdy znaleziono nowszą wersję (jest wtedy również wiersz newer version available z konkretnym celem), oraz u set version, gdy jej nie ma.

To, co otworzy u, zależy od tego, co wie rejestr. Dla usługi przypiętej do konkretnej wersji, dla której istnieje nowsza wersja, jest to wybierak tagów: pole, którego autouzupełnianie podpowiada nowsze tagi z tej samej rodziny — najwyższy pierwszy i wstępnie wpisany. Dla usługi, która jest już na najnowszym tagu, jest to ten sam wybierak, tyle że podpowiedzi sięgają po wszystkie tagi wymienione przez repozytorium (wstępnie wpisany jest wtedy działający tag) — tak właśnie się przypina albo cofa. Usługa na :latest również dostaje wybierak, ze wszystkimi znanymi tagami: wybranie tam konkretnej wersji to sposób, żeby zdjąć usługę :latest z ruchomego tagu i ją przypiąć — dokładnie o to prosi ustalenie ryzyka unpinned-image (patrz nakładka ryzyk bezpieczeństwa niżej). Jedyny wyjątek to usługa na :latest z oczekującą aktualizacją digestu: ta pozostaje pojedynczym potwierdzeniem na nowy digest z rejestru, którym była zawsze — wybierak kusiłby tam, żeby wpisać latest, co rozwiązuje się do gołego tagu i po cichu porzuciłoby właśnie to przypięcie po digeście, które ta aktualizacja ma odświeżyć.

W wybieraku lista podpowiedzi jest ograniczona do 25 pozycji (ruchliwe repozytorium potrafi wymieniać setki), a samo pole pozostaje polem tekstowym, więc możesz wpisać dowolny istniejący tag — w tym starszy, żeby przypiąć się do sprawdzonego wydania albo się do niego cofnąć. Wpisany tag jest walidowany wobec repozytorium (tag, którego ono nie wymienia, zostaje odrzucony), a wybranie starszego tagu pokazuje ostrzeżenie o downgrade przed zastosowaniem. W każdym przypadku zmiana to jeden ServiceUpdate / jedna aktualizacja krocząca, za potwierdzeniem. Jeśli rejestr nie zwrócił żadnych tagów i nie ma też konkretnego celu, na który można by się cofnąć — prywatny albo nieosiągalny rejestr czy usługa przypięta po digeście — dostajesz wyjaśniający komunikat i nic nie zostaje zmienione.

KlawiszAkcja
/otwórz pasek wyszukiwania (filtruje po usłudze / kontenerze / węźle)
h lzwiń / rozwiń, przechodząc przez wszystkie trzy poziomy. h zwija wiersz pod kursorem — stack zwija całą grupę, rozwinięta usługa zwija swoje kontenery — a gdy nie ma już nic do zwinięcia, wychodzi wyżej, do rodzica, więc kolejne naciśnięcia wędrują w górę kontener → usługa → stack. l rozwija wiersz pod kursorem albo schodzi do jego pierwszego dziecka, jeśli jest już otwarty
Enterna kontenerze: otwiera menu akcji; na usłudze albo stacku: rozwija / zwija go
sprzełącz grupowanie po stackach — drzewo z grupami ⟷ płaska lista usług (zobacz niżej)
Llogi (wielkie L) — na kontenerze jego własne logi, na usłudze zagregowane logi wszystkich jej zadań
!otwórz nakładkę z ryzykami bezpieczeństwa dla całej listy usług (zobacz niżej)
pprzekieruj port zadania pod kursorem
izbadaj węzeł drzewa pod kursorem — nawigowalna nakładka z tabelarycznym podsumowaniem; zaznaczenie przesuwasz przez / albo j/k, y/Enter kopiuje zaznaczoną linię, pasek zakładek u góry nazywa trzy widoki, a 1/2/3 wybierają wprost tabelę / stats / surowy JSON demona, natomiast t przełącza je po kolei (Esc/q/i zamyka). Na usłudze nakładka pozwala też ją edytować: s skalowanie, f wymuszona aktualizacja, p porty, l etykiety, e zmienne środowiskowe, n sieci, S sekrety, v montowania, A aliasy; D diagnozuje, dlaczego usługa nie działa wszędzie; X ją usuwa
Xusuń usługę prosto z drzewa (wielkie X, czyli Shift+x) — bez okrężnej drogi przez nakładkę inspekcji. To, na co działa, zależy od wiersza pod kursorem: na wierszu usługi usuwa tę usługę; na wierszu kontenera usuwa usługę właścicielską tego kontenera, bo pojedynczego zadania nie da się usunąć samego — Swarm natychmiast zaplanowałby je ponownie; na wierszu stacka nie usuwa niczego, a zamiast tego pokazuje krótką notkę w stopce, że usługi stacka trzeba usuwać pojedynczo albo poleceniem docker stack rm <stack>. Przechodzi przez to samo potwierdzenie co X w inspekcji niżej — trwale kasuje usługę i zatrzymuje wszystkie jej zadania, nie da się tego cofnąć, a potem proponuje usunięcie osieroconych sekretów. Stały klawisz z wielkiej litery, nie przemapowywalna akcja keymapy; stopka wymienia go na czerwono jako X remove, a nakładka ? również go wypisuje. X wewnątrz nakładki inspekcji pozostaje bez zmian

Pokazywana jest każda usługa (nawet przeskalowana do zera), każdy wiersz wyrenderowany jak linia docker service ls — nazwa, tryb, liczba działających/żądanych, obraz i opublikowane porty — i pokolorowany według tego, jak usłudze naprawdę idzie: liczba działających/żądanych zadań ustala kolor bazowy (turkusowy = wszystkie zadania działają, pomarańczowy = częściowo, czerwony = leży, szary = przeskalowana do zera), a nieprzechodzący healthcheck go nadpisuje (patrz niżej). Znacznik / pokazuje, czy usługa jest zwinięta, czy rozwinięta; jej działające kontenery zagnieżdżają się pod nią. Usługa w trakcie aktualizacji kroczącej nosi kolorową plakietkę na swoim wierszu drzewa — ⟳ updating albo ↺ rolling back — a ten sam status pojawia się w nakładce inspekcji usługi; znika po zakończeniu aktualizacji.

Kolor z healthchecku, nie tylko z liczby replik. Kolor wiersza usługi brał się wyłącznie z relacji działające do żądanych — więc usługa, której każdy kontener oblewał healthcheck, nadal rysowała się jako spokojne, turkusowe 3/3. Liczba była prawdziwa, a wiersz mylący. Stan zadania w Swarmie też tego nie powie: zadanie jest running, podczas gdy jego kontener oblewa każdą próbę — werdykt musi więc przyjść z węzła. Nieudana próba liczy się teraz jako pogorszenie tego samego rodzaju co brakująca replika:

Znaczniki. Wiersz usługi niesie ✖ N unhealthy na czerwono albo ◌ N starting na żółto, dopóki próby nie przeszły; liść kontenera niesie ✖ unhealthy / ◌ starting. Unhealthy bije starting — to ono wymaga reakcji. Wiersz stacka zbiera ten sam znacznik ze wszystkiego, co jest pod nim (zobacz Grupowanie po stackach niżej). Zdrowy kontener nie dostaje żadnego znacznika, tak samo jak taki bez healthchecku, dzięki czemu znacznik nadal coś znaczy. Stoją obok znaczników zasobów cpu/mem opisanych niżej, a kondycja idzie w wierszu pierwsza: to ona przeczy liczbie stojącej tuż obok.

Skąd bierze się werdykt — i ile kosztuje. Nic ponadto: jedzie razem z odczytami zużycia opisanymi niżej — ten sam agent, to samo wywołanie, żadnego dodatkowego zapytania do API. Lista kontenerów, którą agent i tak pobiera przy każdym przebiegu próbkowania, niesie werdykt w linii statusu (Up 3 days (healthy)) — dokładnie tym ciągu, który drukuje docker ps. Parser jest celowo rygorystyczny: czego nie rozpozna, staje się brakiem werdyktu, a nie domysłem. Jeśli demon kiedyś przeredaguje tę linię, swarmexec przestanie twierdzić, że wie, zamiast zgłaszać oblewający kontener jako zdrowy. Obowiązuje ten sam warunek co przy liczbach o zużyciu: potrzebne są agenty z tego wydania lub nowsze, więc klaster, który nie został przetoczony, nie pokaże żadnych znaczników kondycji — wdrożysz je przez swarmexec init --force.

Znaczniki bieżącego zużycia CPU i pamięci. Obok tych plakietek wiersz może nieść także to, co naprawdę zużywa w tej chwili — do tej pory swarmexec potrafił pokazać tylko to, co scheduler zarezerwował (zobacz zakładkę Nodes niżej). Kontener albo usługa, której zużycie przekroczy 70%, dostaje pomarańczowy znacznik, a przy 90% czerwony — na końcu wiersza: zasób nazwany słowem (cpu dla CPU, mem dla pamięci) i procent (cpu 94%, mem 91%). Jeśli gorąco jest na obu, pojawią się oba. Słowa zamiast symboli, przy tej samej szerokości: znacznik, którego znaczenie trzeba dopiero sprawdzić, nie robi swojej roboty, a litery nie mają jak się nie wyrenderować w żadnym terminalu. Poniżej 70% nie rysuje się nic, żeby znaczniki pozostały sygnałem, a nie tapetą. Wiersz usługi bierze najgorszą ze swoich replik, a nie średnią — średnia ukrywa dokładnie ten jeden kontener, który zaraz padnie, czyli ten, który warto zobaczyć. (Bezwzględna pamięć w wierszu usługi to suma po replikach; procent to wartość szczytowa.)

Czego te procenty są procentem. Każdy mierzy się względem tego, co dany kontener naprawdę może zużyć: własnego limitu, jeśli go ma, a w przeciwnym razie pojemności węzła. I to jest tu najważniejsze — 91% limitu 256 MB znaczy, że OOM kill jest blisko; 91% węzła o 64 GB to zupełnie inna rozmowa. Widok stats w nakładce inspekcji pokazuje obie strony tego dzielenia dla każdego kontenera i nazywa podstawę pod tabelą.

Skąd biorą się liczby — i dlaczego pojawiają się z opóźnieniem. Zużycia nie ma w API menedżera: statystyki kontenerów są lokalne dla węzła. Pochodzą z lokalnego dla węzła RPC agenta (Stats); każdy agent próbkuje swoje kontenery w tle i odpowiada z pamięci, więc klient odpytuje go po prostu w cyklu odświeżania, który i tak ma. Wynikają z tego dwie rzeczy. Procent CPU to różnica między dwoma odczytami, więc nie istnieje, dopóki agent nie wykona dwóch (kilka sekund) — do tego czasu widoki pokazują , nigdy 0%; pamięć nie potrzebuje różnicy i pojawia się już przy pierwszym odczycie. A agent próbkuje tylko, dopóki ktoś faktycznie patrzy: po około minucie bez zapytań przestaje i zapomina swoje odczyty, zamiast serwować nieświeże. Dlatego pierwsze liczby po otwarciu UI potrzebują chwili, żeby się wypełnić. To celowe — agent, na którego nikt nie patrzy, ma nic nie kosztować.
Nigdzie nie ma znaczników? Sprawdź wersję agenta. Zużycie jest dodatkiem i degraduje się po cichu: węzeł, którego agent jest nieosiągalny — albo starszy niż to wydanie i przez to nieświadomy RPC Stats — po prostu nie dostarcza odczytów. Znaczniki się nie pojawiają, sekcji w szczegółach węzła nie ma, a cała reszta działa bez zmian; klient dodatkowo na jakiś czas odpuszcza taki węzeł, zamiast odpytywać go przy każdym odświeżeniu. Na klastrze, który nie został jeszcze przetoczony, nie ma więc żadnych liczb o zużyciu ani żadnych znaczników kondycji, dopóki agenty nie zostaną zaktualizowane — wdrożysz je przez swarmexec init --force. To zdecydowanie najczęstszy powód, dla którego nic nie widać.

Grupowanie po stackach. docker stack deploy etykietuje każdą tworzoną usługę etykietą com.docker.stack.namespace. swarmexec odczytuje tę etykietę ze specyfikacji usługi, którą menedżer i tak już zwrócił — Swarm nie ma obiektu stack, a to jedyne powiązanie — i zagnieżdża drzewo na trzech poziomach: stack → usługa → kontener. Wiersz stacka pokazuje nazwę stacka, ile usług się pod nim znajduje oraz zsumowaną liczbę działających/żądanych zadań ((3 svc · 7/8)), pokolorowaną według dokładnie tej samej reguły co wiersz usługi: zsumowanej liczby działających/żądanych, z tym samym nadpisaniem z healthchecku na wierzchu. Za nim pojawiają się zbiorcze liczniki, gdy są niezerowe: ⟳ n — usługi w tym stacku będące właśnie w trakcie aktualizacji kroczącej🛡 n — usługi z ustaleniem bezpieczeństwa wymagającym działania, tą samą tarczą, którą noszą wiersze usług — a po nich znacznik kondycji, ✖ N unhealthy albo ◌ N starting, zsumowany po wszystkich kontenerach wszystkich usług w stacku. Dzięki temu zwinięty stack wciąż mówi ci, czy coś w środku wymaga uwagi. Przy kondycji liczy się to najbardziej: wiersz stacka to poziom, po którym operator wodzi wzrokiem najpierw, więc zwodnicze „wszystko działa” jest tu gorsze niż piętro niżej, a nie mniej groźne.

Usługi utworzone przez docker service create nie mają etykiety stacka; zbierane są pod (no stack), który zawsze sortuje się na końcu, więc nigdy nie spycha prawdziwych stacków w dół listy. Nic nie jest ukrywane — każda usługa nadal pojawia się dokładnie raz. Stacki sortują się po nazwie i startują rozwinięte, więc drzewo z grupami pokazuje te same usługi co płaskie; zwinięcia, których dokonasz, przeżywają automatyczne odświeżenie.

Grupowanie jest domyślnie włączone, ale działa tylko wtedy, gdy co najmniej jedna usługa w klastrze faktycznie nosi etykietę stacka — na klastrze bez stacków drzewo wygląda dokładnie tak, jak wyglądało zawsze, bez zbędnego rodzica (no stack). Naciśnij s, żeby przełączać się między drzewem z grupami a płaską listą usług (stopka wymienia to jako s stacks); gdy nigdzie nie ma etykiet stacków, program tak mówi, zamiast przerysowywać identyczne drzewo. Klawisz jest przemapowywalny jako stack_group w keys.yaml.

Wyszukiwanie (/) działa bez zmian: filtr wciąż dopasowuje usługę, kontener i węzeł. Stack, który filtr opróżni, znika z drzewa, a liczniki w każdym wierszu stacka opisują to, co faktycznie pod nim zostało — liczby idą więc za filtrem, zamiast reklamować usługi, które odfiltrowałeś.

Menu akcji oferuje Logs, Bash, Sh, Shell as user… (pyta o użytkownika/UID, jak docker exec -u, dla obrazów, których domyślny użytkownik nie ma potrzebnych narzędzi albo uprawnień) oraz Port forward (niedostępne powłoki są wyszarzane po sprawdzeniu). W pasku wyszukiwania Enter zachowuje filtr i wraca do listy; Esc czyści filtr i zamyka pasek.

Ryzyka bezpieczeństwa (!). Za każdym razem, gdy pobierana jest lista usług, swarmexec uruchamia zestaw małych analizatorów statycznych na specyfikacji każdej usługi — tej samej specyfikacji, którą menedżer i tak już zwrócił, więc nie ma dodatkowego wywołania API, agent nie bierze w tym udziału i nic nie jest wykonywane wewnątrz twoich kontenerów. Usługa z ustaleniem, na które warto zareagować, jest oznaczona wiodącą 🛡 w drzewie usług, w polu o stałej szerokości przed nazwą, żeby tarcze układały się w pionową kolumnę do przejrzenia. Naciśnij !, żeby otworzyć nakładkę z ryzykami bezpieczeństwa, a w niej w, żeby zapisać raport Markdown o całym klastrze — obejmuje on więcej niż nakładka, bo dokłada kontrole należące do klastra, a nie do usługi.

Osiem kontroli dostępnych dzisiaj. Najpierw te, które mogą oznaczyć usługę, na końcu te czysto informacyjne:

KontrolaWagaCo zgłasza
docker-socket — „zamontowany socket Dockera”wysokanajpoważniejsze ustalenie z całej listy. Montowanie bind, którego źródłem jest socket demona Dockera — /var/run/docker.sock, /run/docker.sock albo dowolna ścieżka źródłowa kończąca się na /docker.sock. Wszystko w takim kontenerze może rozmawiać z socketem, a kto może rozmawiać z socketem, może uruchomić na tym węźle kontener uprzywilejowany — czyli jest to w praktyce root na hoście, niezależnie od tego, jako kto działa sam kontener. Tryb tylko do odczytu tego nie łagodzi: socket to API, a nie plik, którego treść ma znaczenie — samo ustalenie mówi o tym wprost, gdy montowanie jest tylko do odczytu. Podawana jest ścieżka docelowa, pod którą socket jest zamontowany
added-capability — „dodano capability NAZWAwysoka / średniauprawnienie (capability) Linuksa, które usługa dodaje swojemu kontenerowi. Swarm nie ma --privileged, więc capabilities są tym, przez co usługa prosi o dodatkową władzę nad hostem — dlatego warto czytać, co zostało dodane. Osiem ma wagę wysoką, każde z uzasadnieniem, które niesie ustalenie: ALL (daje każde uprawnienie), SYS_ADMIN (niemal root: kontrola montowań, przestrzeni nazw i cgroup), SYS_MODULE (może ładować moduły jądra), SYS_PTRACE (może podglądać i sterować innymi procesami), SYS_RAWIO (surowy dostęp we/wy do urządzeń), DAC_READ_SEARCH (omija sprawdzanie praw do odczytu plików), NET_ADMIN (pełna kontrola nad siecią węzła), NET_RAW (może fałszować i podsłuchiwać surowe pakiety). Każde inne dodane uprawnienie ma wagę średnią — przywilej ponad domyślny zestaw. Jedno ustalenie na każde dodane uprawnienie; rozpoznawane są oba zapisy, w dowolnej wielkości liter (CAP_SYS_ADMIN i SYS_ADMIN)
host-network — „działa w sieci hosta”wysokausługa jest podpięta do sieci o nazwie host: kontener dzieli stos sieciowy węzła, więc nie ma izolacji sieciowej, a mapowanie publikowanych portów Swarma przestaje obowiązywać. Dosięgnie wszystkiego, co dosięga węzeł — łącznie z usługami nasłuchującymi tylko na localhoście, które zwykle uznaje się za nieosiągalne z kontenera
unconfined — „seccomp wyłączony” / „AppArmor wyłączony”wysokapiaskownica na poziomie jądra została dla kontenera wyłączona: seccomp ustawiony na unconfined — kontener może wykonać dowolne wywołanie systemowe, co usuwa główną barierę przed exploitami jądra — albo wyłączone ograniczenie AppArmor. Zgłaszane są osobno, więc usługa, która wyłącza oba, dostaje dwa ustalenia
root-user — „działa jako root”wysokaspecyfikacja przypina kontener do roota jawnieUser=root albo uid 0 (formy liczbowe takie jak 00 czy +0 też są wyłapywane; brana pod uwagę jest tylko część użytkownika z user:group)
secret-in-env — „sekret w zmiennej środowiskowej”wysokaklucz zmiennej środowiskowej wyglądający na poświadczenie trzyma dosłowną wartość w specyfikacji usługi, czytelną dla każdego, kto może odczytać tę specyfikację. Użyj zamiast tego sekretu Dockera albo konwencji *_FILE
root-user — „nie ustawiono użytkownika”niskażaden użytkownik nie jest w ogóle ustawiony, więc kontener działa jako domyślny użytkownik samego obrazu, którym często jest root. Tylko informacja i nie oznacza usługi: nieustawiony użytkownik to domyślny stan Swarma w niemal każdej usłudze, a to, czy naprawdę jest to root, zależy od USER obrazu, czego specyfikacja z menedżera nie ujawnia
no-resource-limits — „brak limitów zasobów”niskazadanie nie ustawia ani limitu CPU, ani limitu pamięci, więc rozbiegany kontener może zająć cały węzeł i zagłodzić wszystko inne, co na nim stoi. Tylko informacja — patrz niżej
unpinned-image — „obraz nieprzypięty”niskaobraz jest przypięty do :latest albo nie ma żadnego tagu (co i tak sprowadza się do :latest) — działająca wersja może się zmienić bez zmiany specyfikacji, więc wdrożenie nie jest powtarzalne. Obraz przypięty digestem (…@sha256:…) jest dokładnie powtarzalny i nigdy nie jest zgłaszany, a dwukropek należący do portu hosta rejestru (registry:5000/img) nie jest brany za tag. Tylko informacja — patrz niżej

Dlaczego kontrole informacyjne nigdy nie oznaczają usługi. Dopiero ustalenie powyżej wagi niskiej — wysokie albo średnie — czyni usługę wymagającą działania i stawia 🛡 przy jej wierszu w drzewie. Ustalenia o niskiej wadze (no-resource-limits, unpinned-image oraz „nie ustawiono użytkownika” z root-user) są prawdziwe dla niemal każdej usługi w prawdziwym klastrze: oznaczanie ich postawiłoby tarczę przy niemal każdym wierszu i zniszczyło dokładnie ten sygnał na pierwszy rzut oka, dla którego znacznik istnieje. Nie są jednak zamiatane pod dywan — gdy usługa zostanie oznaczona z innego powodu, jej ustalenia o niskiej wadze są wypisywane w nakładce razem z resztą, czyli tam, gdzie naprawdę warto je przeczytać.

Żadna wartość sekretu nigdy nie jest wyświetlana. Z ośmiu kontroli tylko secret-in-env w ogóle zagląda do zmiennych środowiskowych, a jej ustalenie podaje wyłącznie klucz zmiennej środowiskowej; jej wartość nigdy nie jest wczytywana do ustalenia, nigdy renderowana i nigdy kopiowana. Kontrola jest też napisana tak, żeby nie hałasować: dopasowuje całe tokeny rozdzielone znakiem _ (PASSWORD, PASSWD, PASS, PASSPHRASE, SECRET, TOKEN, APIKEY, CREDENTIAL(S), PRIVATEKEY oraz pary API_KEY, ACCESS_KEY, PRIVATE_KEY, SECRET_KEY, CLIENT_SECRET, AUTH_TOKEN), więc COMPASS, PASSENGER_PORT czy BYPASS_AUTH nie są zgłaszane. Klucze, które jedynie wskazują na poświadczenie, są wyłączone (_FILE, _PATH, _URL, _URI, _NAME, _ID, _TYPE, _ENABLED, _REQUIRED, _LENGTH, _TIMEOUT, _TTL, _EXPIRY, _ALGORITHM), podobnie jak wartości, które ewidentnie nie są poświadczeniem — ścieżka bezwzględna, wartość logiczna, liczba.

Nakładka wypisuje każdą oznaczoną usługę („N z M usług(i) oznaczonych · 8 kontroli”), pogrupowaną po usługach, każde ustalenie w osobnej linii z kropką wagi — ● wysoka, ● średnia, ● niska — krótkim tytułem i jednoliniowym wyjaśnieniem. Ustalenia są uporządkowane od najgorszych. Gdy usługa zostanie już oznaczona za coś wymagającego działania, wypisywane są też jej informacyjne ustalenia o niskiej wadze. Jeśli usługa pod kursorem jest wśród nich, zostaje wstępnie podświetlona i przewinięta do widoku. Gdy nic nie jest oznaczone, nakładka mówi to wprost i wymienia każdą kontrolę, która się wykonała („Checked: Docker socket mounted in, added Linux capabilities, host network, seccomp / AppArmor disabled, container user, secrets in environment variables, missing resource limits, unpinned image.”), więc informacja „czysto” mówi też, co obejmuje. A gdy lista usług jeszcze się nie wczytała, mówi, że nic nie zostało przeskanowane, zamiast fałszywie ogłaszać, że wszystko jest w porządku. j/k przewijają, Esc (albo q, albo ponownie !) zamyka.

Skanowanie to rozszerzalny rejestr analizatorów, a nie sztywna lista: nowa kontrola pojawia się automatycznie i w znaczniku w drzewie, i w nakładce. Sam tekst nakładki również pochodzi z tego rejestru — liczba kontroli w nagłówku i lista „Checked:” są generowane z samych analizatorów, więc nie mogą się rozjechać z tym, co faktycznie się wykonało, jak rozjechałaby się lista wpisana na sztywno. Klawisz jest przemapowywalny jako security_risks w keys.yaml.

i otwiera nakładkę inspekcji dla węzła drzewa pod kursorem — na usłudze pokazuje docker service inspect, a na liściu kontenera inspekcję zadania Swarma (spojrzenie menedżera na tę instancję: stan, slot, węzeł, id kontenera, specyfikacja kontenera, zasoby i historia statusów). Oba pochodzą od menedżera swarma. Nakładka otwiera się na tabelarycznym podsumowaniu pisanym pod operatora, z sekcjami uporządkowanymi według znaczenia operacyjnego — najpierw sieci, etykiety, wolumeny/montowania i sekrety (oraz configi), potem porty, obraz, tryb, zmienne środowiskowe, zasoby, rozmieszczenie i polityka aktualizacji (plus stan, węzeł i id kontenera dla kontenera/zadania), a identyfikatory i znaczniki czasu na końcu. Pierwsza linia nakładki — wewnątrz ramki, przypięta nad przewijaną treścią — to pasek zakładek nazywający jej trzy widoki: table 1   stats 2   raw json 3, bieżący w kolorze akcentu. Ma taki sam kształt jak pasek zakładek głównego okna — etykieta, a po niej cyfra, która ją wybiera. 1, 2 i 3 przechodzą wprost do tabeli, statystyk i surowego JSON-a demona, a t nadal przełącza po kolei tabela → stats → surowy JSON i z powrotem. Podpowiedź w stopce brzmi już tylko 1-3/t view, a tytuł na ramce nie powtarza nazwy bieżącego widoku — czyta się po prostu inspect service foo, bo pasek i tak mówi, gdzie jesteś. Pamiętaj, że widok tabelaryczny to widok zadania z perspektywy menedżera, a nie pełne lokalne docker container inspect działającego kontenera na węźle — prawdziwa inspekcja kontenera przez agenta jest planowana.

Widok stats. Środkowy z trzech to bieżące zużycie zasobów — te same odczyty, które znaczą drzewo, więc otwarcie go nie kosztuje żadnego dodatkowego wywołania, a liczby odświeżają się dalej, dopóki widok jest otwarty. To tabela, jeden wiersz na kontener usługi (albo jeden kontener przy inspekcji zadania):

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

Tabela, bo goły procent jest nieczytelny, jeśli nie wie się z góry, jak to narzędzie mierzy — 94% czego? Każda komórka niesie własne jednostki, więc 0.07 / 4.00 cores nie potrzebuje legendy, a procent stoi obok liczby, której jest procentem; kilka replik porównuje się zaś, przebiegając wzrokiem kolumnę, a nie zestawiając ułożone jeden pod drugim bloki. Kolumna HEALTH ujmuje werdykt w słowa: healthy, unhealthy, starting albo none dla kontenera, który nie deklaruje żadnego healthchecku. none i healthy to celowo dwa różne słowa, a nie słowo i pustka: „brak skonfigurowanego healthchecku” i „próba przechodzi” to różne fakty, a pustka czytałaby się jak to drugie. Kontener, którego żaden agent nie zgłosił — nieosiągalny węzeł, agent zbyt stary na RPC Stats albo kontener uruchomiony po ostatnim odczycie — pokazuje w obu kolumnach zasobów no reading zamiast zera, które czytałoby się jak „bezczynny”.

Przypis pod tabelą nazywa mianownik: własny limit kontenera, jeśli go ma, a w przeciwnym razie pojemność węzła. To dopiero nadaje procentowi sens — 85% limitu 8 GiB i 85% węzła o 64 GiB to dwie różne rozmowy. Pamięć jest w jednostkach binarnych (MiB/GiB), tych samych, których używa szczegół węzła i które raportuje sam docker, więc ta sama liczba nigdy nie czyta się inaczej w dwóch miejscach. Przy więcej niż jednym zmierzonym kontenerze pod tabelą dochodzi podsumowanie: CPU i pamięć najgorszej repliki — nie średnia, która ukrywa właśnie ten kontener, który zaraz zostanie zabity przez OOM — plus łączna pamięć po wszystkich. Wpis t w nakładce pomocy ? brzmi odpowiednio cycle table / stats / raw JSON.

Nakładka jest nawigowalna i wybieralna, a nie tylko zwykłym przewijaniem: linie z danymi są pojedynczo wybieralne, a kursor przesuwasz linia po linii przez / albo j/k (nagłówki sekcji i puste linie są pomijane). y zawsze kopiuje zaznaczoną linię do schowka przez OSC52 — tym samym mechanizmem yank, co wszędzie indziej — więc możesz wyciągnąć pojedyncze id, montowanie albo adres bez zaznaczania tekstu ręcznie. Enter też kopiuje, z wyjątkiem zwijalnego wiersza NETWORKS, gdzie zamiast tego rozwija/zwija ten wiersz (zobacz niżej). Wszystkie trzy widoki (podsumowanie tabelaryczne, widok stats i surowy JSON, dostępne przez 13 albo przełączane po kolei klawiszem t) są wybieralne i kopiowalne linia po linii. Stopka zawsze wymienia dostępne klawisze, więc od razu widać, co możesz zrobić; dla usługi stopka pokazuje też klawisze edycji opisane niżej.

KlawiszAkcja
j kprzesuwaj zaznaczenie między liniami z danymi (nagłówki/puste pomijane)
yskopiuj zaznaczoną linię do schowka (OSC52) — zawsze
Enterna zwijalnym wierszu NETWORKS rozwija/zwija ten poziom — wiersze są zagnieżdżone na dwóch poziomach, sieć i jej rozwinięcie N containers; w każdej innej linii kopiuje ją (OSC52)
1 2 3wybierz widok wprost — 1 podsumowanie tabelaryczne, 2 stats (bieżące CPU / pamięć, patrz niżej), 3 surowy JSON demona; to te same cyfry, które pokazuje pasek zakładek
tprzełączaj po kolei trzy widoki — podsumowanie tabelaryczne → stats (bieżące CPU / pamięć, patrz niżej) → surowy JSON demona → z powrotem; wszystkie trzy wybieralne, a pasek zakładek u góry zaznacza bieżący
amenu akcji — wszystkie edytory i akcje usługi na jednej liście, bez Shiftu (tylko usługi; patrz niżej)
s f p l e n S v Aedytuj usługę — skalowanie / wymuszona aktualizacja / porty / etykiety / zmienne środowiskowe / sieci / sekrety / montowania / aliasy (tylko usługa; S to wielka litera, Shift+s, żeby odróżnić ją od s skalowanie; A też jest wielkie; zobacz niżej)
Esc q izamknij nakładkę

Sekcja NETWORKS jest zwijalna i już w stanie zwiniętym odpowiada na pytanie „pod jakim adresem to jest osiągalne”: każda podłączona sieć pokazuje się jako zwinięty wiersz + <network> 🔒 vip 10.0.5.2/24 (2 dns names). Ikona kłódki 🔒 za nazwą sieci oznacza sieć overlay szyfrowaną (szyfrowanie płaszczyzny danych, --opt encrypted); jej brak oznacza brak szyfrowania. Przy badaniu usługi adres jest opisany jako vip — wirtualny IP usługi w tej sieci, adres, pod którym odpowiada load balancer swarma, wzięty z ServiceInspect menedżera (Endpoint.VirtualIPs). Przy badaniu kontenera/zadania jest opisany jako addr: własny adres tego kontenera w tej sieci. Usługa w trybie endpointu dnsrr nie ma żadnego VIP-a — tak ma być, to nie brakujące dane — więc zamiast pustego miejsca rozwinięta sieć pokazuje linię no vip — dnsrr endpoint mode, the DNS name resolves to the containers. Sieć ingress też jest tu wymieniana, choć nie pojawia się w żadnej spec — swarm podłącza do niej usługę sam z siebie (patrz niżej). Dopisywana jest za sieciami zadeklarowanymi w spec, nigdy nie jest z nimi przeplatana, a jej nagłówek brzmi + ingress vip 10.0.0.250/24 (routing mesh): adnotacja (routing mesh) stoi w miejscu licznika nazw DNS, który mógłby pokazać wyłącznie (0 dns names).

Najedź na wiersz sieci i naciśnij Enter, żeby go rozwinąć lub zwinąć. Rozwinięty wypisuje nazwy DNS, które rozwiązują się na usługę/kontener w tej sieci — nazwę usługi, tasks.<service> oraz wszelkie własne aliasy — dzięki czemu widzisz, pod jaką nazwą DNS jest osiągalny i jakie nosi aliasy, w każdej sieci z osobna. Pod nimi znajduje się drugi zwijalny wiersz, + 2 containers (w liczbie pojedynczej + 1 container); Enter na nim otwiera najgłębszy poziom: po jednym wierszu na kontener, wyrównanym w kolumnach, web.1 10.0.1.5/24 host-a — nazwa zadania, jego adres w tej sieci i węzeł, na którym działa (zadania replikowane nazywają się <service>.<slot>, globalne <service>.<node>, bo nie mają slotu).

Filtrem jest pożądany (desired) stan zadania, a nie jego bieżący: wypisywane jest każde zadanie, które menedżer nadal chce mieć uruchomione — również te, które są dopiero preparing, assigned albo starting. One już trzymają swój adres, a podczas aktualizacji kroczącej stanowią większość, więc filtrowanie po stanie bieżącym opróżniałoby to rozwinięcie dokładnie wtedy, gdy jest najciekawsze. Zadanie, z którego menedżer zrezygnował — pożądany stan shutdown, czyli zastąpione przez aktualizację kroczącą, usunięte przy skalowaniu albo nieudane — nie jest wypisywane: jego adres został zwolniony i może już należeć do innego kontenera, więc pokazanie go byłoby wprost błędne. Zadanie, które nie obsługuje jeszcze ruchu, ma swój stan w nawiasie na końcu wiersza, web.1 10.0.1.5/24 host-a (starting); działające nie ma żadnego przyrostka. Wiersze pochodzą z TaskList w API menedżera, przefiltrowanego po usłudze, i są best effort: błąd API po prostu zostawia rozwinięcie puste. Oba poziomy zwijają się niezależnie; zwinięcie sieci ukrywa razem z nią poziom kontenerów.

Wiersz ingress to ten, którego nie ma w żadnej spec: swarm podłącza usługę do sieci ingress sam z siebie, gdy tylko opublikuje ona port w trybie ingress, a to podłączenie nie pojawia się nigdzie w spec usługi — za to jego vip jest adresem, pod którym faktycznie odpowiada routing mesh, czyli pierwszą rzeczą, jakiej szukasz, gdy opublikowany port zachowuje się dziwnie. Dlatego dostaje własny wiersz, dopisany za sieciami zadeklarowanymi w spec i nieco inaczej zbudowany: w sieci ingress nie rozwiązuje się żadna nazwa DNS usługi, więc tam, gdzie inna sieć liczy swoje nazwy, ten nagłówek nosi adnotację (routing mesh). Rozwinięty wypisuje natomiast porty opublikowane przez ingress, które w ogóle umieściły tam usługę — powód istnienia tego wiersza — po jednej linii na port: published 2222 -> 22/tcp. Porty opublikowane w trybie host omijają routing mesh i celowo nie są tam wypisywane. Niżej schodzi do kontenerów dokładnie tak samo jak każdy inny wiersz sieci: ten sam poziom + 1 container, z własnym adresem każdego kontenera w sieci ingress. Usługa, która nie publikuje żadnego portu przez ingress, nie ma tam VIP-a — a więc i takiego wiersza w ogóle.

Usługa GitLab publikująca SSH na 2222, oba poziomy rozwinięte:

- 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

Kopiowanie wewnątrz NETWORKS daje adres bez maski10.0.5.2, w postaci, którą wklejasz do curl albo ping: w wierszu sieci jej vip/addr, w wierszu kontenera adres tego kontenera. Wiersz sieci bez adresu (dnsrr) kopiuje, tak jak dotąd, nazwę sieci. Na obu rodzajach zwijalnych wierszy Enter przełącza; w każdej innej linii kopiuje (a y kopiuje zawsze).

Gdy badasz usługę (a nie liść kontenera/zadania, którego nakładka pozostaje tylko do odczytu), stopka nakładki udostępnia też kilka klawiszy edycji, z których każdy jest wywołaniem ServiceUpdate w API menedżera wyzwalającym aktualizację kroczącą / uzgodnienie zadań usługi.

Zacznij od a — menu akcji. W inspekcji usługi (i tylko tam) a otwiera jedną listę wszystkich edytorów i akcji, jakie oferuje nakładka, więc nie musisz pamiętać ~14 klawiszy wrażliwych na wielkość liter (d/D, s/f, p/l/e, n/S/v, r/P, R, X). To ścieżka odkrywalna i bez Shiftu — a nie okrojona: wybranie pozycji zamyka menu i otwiera dokładnie to samo, co klawisz bezpośredni, więc oba są równoważne, a nie są wariantami o różnym działaniu. Pozycje, w kolejności: Update image version… (Set image version…, gdy nie ma nowszej — przypięcie dowolnego tagu albo powrót do niego jest równie uprawnione), 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 — ta niszcząca, dlatego oznaczona na czerwono — oraz Cancel. Poruszasz się klawiszami j/k (g/G skaczą do pierwszej/ostatniej pozycji), Enter wybiera; Esc — albo pozycja Cancel — zamyka menu, nic nie robiąc. Poza menu zostaje tylko wybór wersji obrazu jest pierwszy, bo ustawienie wersji to najzwyklejsza zmiana usługi, jaka istnieje; jego bezpośredni klawisz u nadal działa z dowolnego wiersza inspekcji. Jeśli obraz jest taki, dla którego nie da się wybrać wersji — referencja bez tagu albo rejestr, który nie wypisze tagów repozytorium, zwykle prywatny bez poświadczeń — pozycja zostaje na liście i mówi o tym, zamiast po cichu zniknąć, oraz wskazuje na docker service update --image, które zadziała. Klawisz bezpośredni dla każdej pozycji:

KlawiszAkcja
ddiff — pokazuje ujednolicony diff bieżącej specyfikacji usługi wobec poprzedniej (co zmieniła ostatnia aktualizacja krocząca), korzystając z PreviousSpec Swarma. Zmienione pola są wypisane w grupach, z liniami - usunięte / + dodane (obraz, tryb/repliki, zmienne środowiskowe, etykiety, sieci, aliasy, sekrety, montowania, porty, ograniczenia rozmieszczenia, preferencje rozproszenia, limity/rezerwacje zasobów); pola bez zmian są pomijane. Jeśli usługa nigdy nie była aktualizowana (brak poprzedniej specyfikacji), nakładka tak mówi. Nakładka tylko do odczytu — j/k przewijają, Esc zamyka
Rwycofanie do poprzedniej wersji (wielkie R, czyli Shift+r, żeby odróżnić od r limity zasobów) — odpowiednik diffa d powyżej: cofa ostatnią aktualizację kroczącą. Okno potwierdzenia pokazuje ten sam diff odwrócony pod nagłówkiem „This will undo:”, bo to właśnie zmieni wycofanie — linia, którą aktualizacja dodała, figuruje jako - usunięte, a ta, którą usunęła, wraca jako + dodane. Podgląd jest ograniczony do 12 wpisów, po których pojawia się adnotacja „… and N more” odsyłająca do d po pełny diff (jeśli zmieniły się tylko metadane, informuje, że nie ma różnic na poziomie pól). Potwierdzenie uruchamia wycofanie po stronie serwera — jeden ServiceUpdate API managera z ServiceUpdateOptions{Rollback: "previous"}, a nie ponowne zastosowanie PreviousSpec po stronie klienta — więc wykonuje je manager, respektując własną konfigurację wycofania usługi (równoległość, opóźnienie, akcja przy niepowodzeniu), i raportuje postęp jako stan aktualizacji rollback_started: dokładnie ta plakietka ↺ rolling back, opisana wyżej, na wierszu drzewa i w tej nakładce. Usługa, która nigdy nie była aktualizowana, nie ma poprzedniej specyfikacji — dostajesz wyjaśniający komunikat, nie błąd. Dostępne również z menu akcji a. Podobnie jak d, D i X jest to stały klawisz inspekcji, nie przemapowywalna akcja keymapy
sskalowanie — pyta o nową liczbę replik i ją stosuje (tylko usługi replikowane; usługa globalna zgłasza, że nie da się jej skalować)
fwymuszona aktualizacja — wdraża usługę ponownie bez zmiany jej specyfikacji (odpowiednik docker service update --force: podbija TaskTemplate.ForceUpdate), po potwierdzeniu. Każde zadanie jest restartowane / przeplanowywane, i tak właśnie odblokowuje się usługę tkwiącą w niekompletnym stanie (np. 1/2 repliki). Jeden ServiceUpdate (aktualizacja krocząca)
Xusuń usługę (wielkie X) — trwale ją kasuje i zatrzymuje wszystkie jej zadania, za potwierdzeniem; nie da się tego cofnąć. Jedno ServiceRemove w API menedżera; po powodzeniu nakładka inspekcji zamyka się, a drzewo usług się odświeża. Jeśli usługa była jedynym użytkownikiem jednego lub więcej sekretów, swarmexec pyta następnie, czy usunąć także te osierocone sekrety (sekrety wciąż używane przez inną usługę zostają nietknięte)
Dzdiagnozuj rozmieszczenie (wielkie D) — odpowiada w jednym widoku na pytanie, dlaczego usługa nie działa wszędzie tam, gdzie się tego spodziewasz, zamiast gonić za tym przez kilka poleceń docker. Dla usługi globalnej wypisuje każdy węzeł z ✓ działa albo ✗ i powodem wykluczenia (dostępność drain/pause, stan down, niespełnione ograniczenie rozmieszczenia albo niezgodność platformy) — dlatego trzywęzłowy klaster może całkiem legalnie pokazywać 2/2 (trzeci węzeł nie jest uprawniony). Dla usługi replikowanej wypisuje każde zadanie, które nie działa, wraz z własnym komunikatem schedulera (ograniczenia, za mało zasobów, błędy pobierania obrazu, …). Ograniczenia, których nie da się sprawdzić po stronie klienta (engine.labels.*), są odnotowywane, a nie zgadywane. Nakładka tylko do odczytu
pedytuj opublikowane porty — otwiera buforowany edytor listy portów usługi, każdy w postaci PUBLISHED:TARGET[/proto] (proto tcp|udp|sctp, domyślnie tcp), np. 8080:80/tcp
ledytuj etykiety — ten sam buforowany edytor listy nad wpisami key=value
eedytuj zmienne środowiskowe — ten sam buforowany edytor listy nad zmiennymi z ContainerSpec, każda wprowadzana jako KEY=VALUE (np. LOG_LEVEL=debug); klucz jest wymagany, wartość może być pusta i sama może zawierać =. Tak jak porty/etykiety/montowania ten edytor pozwala na edycję pod e, więc możesz poprawić wartość w miejscu. W odróżnieniu od pozostałych edytorów e otwiera tu wpis w przewijalnym wieloliniowym polu tekstowym (a nie w jednoliniowym), żeby długie wartości takie jak GITLAB_OMNIBUS_CONFIG dało się wygodnie edytować — pisz swobodnie (Enter wstawia nową linię), Ctrl-S zapisuje, Esc anuluje. Zastosowanie podmienia zmienne środowiskowe usługi w jednym ServiceUpdate (aktualizacja krocząca)
nedytuj sieci — buforowany edytor listy nad podłączeniami sieciowymi usługi; skoro wpis to sama nazwa, jest to tylko dodawanie/usuwanie (bez edycji pod e). Pole dodawania autouzupełnia nazwy sieci (podpowiada sieci, do których usługa nie jest jeszcze podłączona, albo wpisz własną). Zastosowanie podmienia podłączenia w jednym ServiceUpdate. Aliasy DNS edytuje się również stąd: zaznacz sieć w tym edytorze i naciśnij A, żeby otworzyć buforowaną listę aliasów DNS usługi w tej sieci (a dodaj / e edytuj / d usuń / y kopiuj / u cofnij / w zastosuj / Esc anuluj); zastosowanie podmienia aliasy tej sieci w jednym ServiceUpdate. Działa nawet wtedy, gdy usługa nie ma jeszcze żadnych aliasów. (Nowo dodaną sieć musisz najpierw zastosować, zanim ustawisz jej aliasy.)
Sedytuj sekrety (wielkie S, czyli Shift+s, żeby odróżnić od s skalowanie) — ten sam buforowany edytor listy co przy sieciach, nad sekretami, do których usługa się odwołuje; wpis to sama nazwa, więc jest to tylko dodawanie/usuwanie (bez edycji pod e). Pole dodawania autouzupełnia nazwy sekretów (podpowiada sekrety, których usługa jeszcze nie używa, albo wpisz własną). Zastosowanie podmienia odwołania usługi do sekretów w jednym ServiceUpdate (aktualizacja krocząca); każdy sekret jest montowany w /run/secrets/<name>. Działa nawet wtedy, gdy usługa nie ma obecnie żadnych sekretów
vedytuj montowania — buforowany edytor listy nad wolumenami i montowaniami bind usługi. Dodawanie (a) albo edycja (e) otwiera mały formularz zamiast pojedynczego pola tekstowego: pole wyboru bind mount, źródło (gdy pole jest odznaczone, jest to nazwa wolumenu z autouzupełnianiem istniejących wolumenów klastra; gdy zaznaczone — ścieżka na hoście), ścieżka w kontenerze oraz pole wyboru tylko do odczytu. (Pod spodem każdy wpis to nadal volume:NAME:TARGET[:ro] / bind:/host/path:TARGET[:ro].) Źródła bindów i wszystkie cele muszą być ścieżkami bezwzględnymi. Zastosowanie podmienia montowania w jednym ServiceUpdate (aktualizacja krocząca). Zabezpieczenie przed bindami (miękkie): jeśli buforowana lista zawiera jakiekolwiek montowania bind, zastosowanie pokazuje najpierw ostrzeżenie z listą węzłów, na których usługa mogłaby zostać zaplanowana (wyliczonych z ograniczeń rozmieszczenia oraz roli/etykiet/dostępności węzłów), i przypomina, że każde źródło bindu musi już istnieć na wszystkich tych węzłach — swarmexec nie potrafi zweryfikować ścieżek hosta (agent nie ma dostępu do systemu plików hosta), więc ścieżki bindów nie są autouzupełniane ani sprawdzane pod kątem istnienia; to tylko podpowiedź
redytuj limity zasobów — mały formularz do ustawienia, zmiany albo wyczyszczenia limitów CPU i pamięci usługi (oraz, opcjonalnie, rezerwacji). CPU podaje się w rdzeniach (np. 0.5, 2); pamięć jako rozmiar czytelny dla człowieka (np. 512m, 2g, 1.5GiB). Pozostawienie pola pustego czyści dany limit (Swarm traktuje go wtedy jako nieograniczony). Rezerwacja nie może przekraczać swojego limitu. Zastosowanie wykonuje jeden ServiceUpdate (aktualizacja krocząca); istniejące limity Pids oraz rezerwacje urządzeń/ogólne są zachowywane
Pedytuj rozmieszczenie (wielkie P, czyli Shift+p, żeby odróżnić od p porty) — otwiera małe menu z dwoma buforowanymi edytorami: Ograniczenia i Preferencje rozproszenia.
  • Ograniczenia — twarde ograniczenia rozmieszczenia usługi (node.role, node.hostname, node.id, node.platform.os/arch, node.labels.<k>, engine.labels.<k>, każde z == albo !=, np. node.role==manager, node.labels.zone!=eu). Pole dodawania/edycji autouzupełnia gotowe kandydatury zbudowane z bieżącego klastra — role, nazwy hostów, platformy oraz etykiety węzłów i silników wszystkich węzłów — więc zwykle możesz ograniczenie wybrać zamiast je wpisywać (podpowiadana jest forma ==; != dla negacji wpisz ręcznie). Wynikowy zbiór węzłów zasila też zabezpieczenie przed bindami w edytorze montowań.
  • Preferencje rozproszenia — miękkie strategie --placement-pref spread=…: buforowana, uporządkowana lista samych atrybutów węzła (np. node.labels.zone, node.hostname), po których Swarm równomiernie rozprasza zadania. Pole autouzupełnia klucze etykiet węzłów/silników oraz atrybuty węzłów z klastra (deskryptor to sam atrybut — bez operatora i wartości).
Zastosowanie każdego z nich podmienia tę część rozmieszczenia w jednym ServiceUpdate (aktualizacja krocząca); druga część zostaje zachowana.

Te edytory buforują zmiany: a dodaje wpis, d usuwa ten pod kursorem, y kopiuje zaznaczony wpis do schowka przez OSC52 (żebyś mógł wyciągnąć np. pojedynczą zmienną środowiskową podczas oglądania), u cofa ostatnią zbuforowaną zmianę (dodanie / edycję / usunięcie), dopóki jeszcze nie zastosowałeś zmian — naciskaj go wielokrotnie, żeby cofać się przez historię buforowanych zmian — a w stosuje wszystkie zmiany naraz w jednym ServiceUpdate (więc dodanie, powiedzmy, kilku wolumenów i naciśnięcie w wyzwala jedną aktualizację kroczącą, a nie po jednej na wpis), za potwierdzeniem. Jeśli naciśniesz Esc, gdy zmiany są jeszcze zbuforowane, edytor nie porzuci ich po cichu — zapyta, czy zastosować, odrzucić, czy edytować dalej. Dopóki zmiany są zbuforowane, są wyróżnione, żebyś dokładnie widział, co się zmieni: dodane albo zedytowane wpisy pokazują się na zielono (nowe), a usunięte wpisy zostają jako przygaszone czerwone wiersze „removed” — wyróżnienie znika po zastosowaniu. Edytory portów, etykiet, montowań, zmiennych środowiskowych i aliasów (aliasy dostępne z edytora sieci pod A) mają też e do edycji wpisu pod kursorem; edytory sieci i sekretów nie mają e — wpis sieci albo sekretu to sama nazwa, więc jest tylko dodawanie (a) / usuwanie (d). e w edytorze zmiennych środowiskowych otwiera przewijalne wieloliniowe pole tekstowe (Ctrl-S zapis / Esc anulowanie) dla długich wartości; pozostałe edytory zachowują jednoliniowe pole z Enter do zatwierdzenia.

Drzewo odświeża się automatycznie co ok. 10 s, więc kontener zastąpiony albo usługa przeskalowana w tle pojawiają się same z siebie — kursor i rozwinięte usługi są zachowywane. r nadal wymusza natychmiastowe odświeżenie.

Zakładka Volumes

KlawiszAkcja
/szukaj — filtruj listę po nazwie wolumenu, sterowniku albo węźle
nutwórz wolumen — formularz z nazwą, sterownikiem (domyślnie local), etykietami (k=v,k=v) i docelowym węzłem (autouzupełnia nazwy węzłów; zostaw puste, żeby utworzyć na każdym węźle, bo wolumeny są lokalne dla węzła). Tworzy go przez agenta na każdym docelowym węźle i raportuje sukces/porażkę per węzeł. Tab przechodzi między polami, Esc anuluje
spacezaznacz / odznacz wolumen (oznaczony ) do zbiorczego usunięcia
azaznacz / odznacz wszystkie aktualnie wyświetlane wolumeny
dusuń zaznaczone wolumeny — albo ten pod kursorem — na każdym węźle, który je trzyma, za potwierdzeniem (z nakładką postępu; usuwanie działa z ograniczoną równoległością)
Pprune: usuń każdy wolumen, którego nie montuje żaden działający kontener i którego nie deklaruje żadna usługa, za potwierdzeniem
Enterpokaż, które węzły trzymają wolumen; etykiety wolumenu są wypisane tylko do odczytu nad węzłami (Docker nie ma API aktualizacji wolumenu, więc etykiet nie da się edytować po utworzeniu — ustaw je przy tworzeniu wolumenu)
ipokaż, które usługi/kontenery go używają
Apodłącz wolumen do usługi: wybierz usługę (autouzupełnianie nazw) → podaj docelową ścieżkę w kontenerze (bezwzględną) → wybierz Attach albo Attach read-only. Dodaje montowanie jednym ServiceUpdate (aktualizacja krocząca) — wolumenowy odpowiednik akcji podłączania sieci/sekretów; operacja odwrotna mieszka w edytorze montowań v w inspekcji usługi
s Sprzełączaj pole sortowania (name → nodes → used → age → size) / odwróć

Usuwanie jest świadome węzłów: wolumen jest usuwany na każdym węźle, który go trzyma. Prune oszczędza wolumeny, które usługa deklaruje, nawet gdy żadne zadanie nie działa, więc nie wymaże danych zatrzymanego stacka. Dla większej kontroli Enter otwiera listę per węzeł, gdzie space zaznacza węzły, d usuwa kopię z podświetlonego węzła, a a usuwa na wszystkich węzłach — każde za potwierdzeniem.

Zakładka Forwards

KlawiszAkcja
Enterpokaż pełne szczegóły przekierowania (łącznie z ewentualnym błędem)
dzatrzymaj zaznaczone przekierowanie
oskopiuj URL przekierowania (http://127.0.0.1:<port>)

Działający kontener jest opisany w drzewie jako local→remote (np. 9090→8080). Przekierowania działają dalej po zamknięciu nakładki oraz po przełączeniu na inny klaster; zatrzymują się pod d albo gdy wyjdziesz z UI. Kolumna CLUSTER nazywa kontekst, do którego każde należy, przygaszona dla tego, który właśnie oglądasz — przekierowanie na innym klastrze nadal jest twoje i nadal nasłuchuje, ale jego CONTAINER i NODE nazywają rzeczy niewidoczne stąd. Opis w drzewie jest osobny dla każdego klastra z tego samego powodu: identyfikator kontenera jest unikalny tylko w obrębie własnego demona.

Dlatego lokalny port, którego już używasz, jest odrzucany z góry — z nazwą przekierowania, które go trzyma, i klastra, do którego należy; okienko pozostaje otwarte, żebyś wybrał inny port. Systemowe „address already in use" zostaje na wypadek, gdy ma rację: port zajęty przez inny program, którego swarmexec nie potrafi nazwać.

Zakładka Networks

Lista sieci swarma — nazwa, sterownik, zasięg, typ (ingress / internal / attachable), to czy sieć jest szyfrowana i ile usług jest do każdej podłączonych. Kolumna TYPE pokazuje overlay albo nazwę sterownika (np. bridge) dla zwykłych sieci, zamiast myślnika. Kolumna ENC pokazuje 🔒 yes, gdy sieć overlay ma włączone szyfrowanie płaszczyzny danych (utworzona z --opt encrypted), inaczej myślnik. Nazwa każdej sieci i jej komórka TYPE są kolorowane według rodzaju (najwyższy priorytet pierwszy):

KlawiszAkcja
nutwórz sieć — otwiera formularz (domyślny sterownik overlay) z typowymi opcjami swarma: attachable (pozwól dołączać samodzielnym kontenerom), encrypted (szyfrowanie płaszczyzny danych overlay), internal (bez routingu na zewnątrz), IPv6, opcjonalne MTU, opcjonalna podsieć/brama (IPAM) oraz etykiety (k=v,k=v). Tab przechodzi między polami, Enter na Create zatwierdza, Esc anuluje. Tworzy ją jednym NetworkCreate w API menedżera, a potem odświeża zakładkę
Enter / ipokaż podłączone usługi, każdą z jej działającymi kontenerami zagnieżdżonymi pod spodem. Etykiety samej sieci są wypisane tylko do odczytu na górze (Docker nie ma API aktualizacji sieci, więc etykiet sieci nie da się edytować po utworzeniu — ustaw je przy tworzeniu sieci). Nagłówek każdej usługi pokazuje też, ile aliasów DNS ma ona w tej sieci (albo no aliases)
Enterw widoku członków: rozwiń / zwiń aliasy DNS zaznaczonej usługi (domyślnie zwinięte, żeby lista pozostała zwarta); aliasy pojawiają się z wcięciem pod nagłówkiem usługi
Aw widoku członków: dodaj / edytuj aliasy DNS zaznaczonej usługi w tej sieci — otwiera ten sam buforowany edytor aliasów co widok inspekcji (a dodaj / e edytuj / d usuń / w zastosuj / Esc anuluj); zastosowanie podmienia aliasy tej sieci w jednym ServiceUpdate i odświeża widok. Działa nawet wtedy, gdy usługa nie ma jeszcze aliasów. (A to wielka litera, Shift+a, żeby odróżnić ją od a podłącz; samodzielne kontenery spoza swarma nie są edytowalne — aliasy to ServiceUpdate per usługa)
aw widoku członków: podłącz usługę do tej sieci — pole z autouzupełnianiem (podpowiada usługi jeszcze niepodłączone; możesz też wpisać dowolną nazwę), a potem potwierdzenie aktualizacji kroczącej
dw widoku członków: odłącz usługę od tej sieci — pole z autouzupełnianiem (podpowiada usługi obecnie podłączone; możesz też wpisać dowolną nazwę), a potem potwierdzenie aktualizacji kroczącej

Podłączenie albo odłączenie wykonuje aktualizację usługi, która dodaje lub usuwa sieć w specyfikacji usługi, a potem odświeża zakładkę Networks. To operacja API menedżera (ServiceUpdate) — tym samym kanałem klient pobiera topologię — i wyzwala aktualizację kroczącą, która restartuje zadania usługi.

Zakładka Secrets

Lista sekretów swarma tylko do odczytu — nazwa, ile usług używa każdego z nich, wiek, ostatnia aktualizacja i liczba etykiet. Wartości sekretów nigdy nie są pokazywane: API Dockera ich nie udostępnia.

KlawiszAkcja
Enterpokaż metadane sekretu oraz usługi/kontenery, które go używają
dna liście sekretów: usuń zaznaczony sekret — trwale go kasuje (jedno SecretRemove), za potwierdzeniem; nie da się tego cofnąć. Docker odmawia usunięcia sekretu, do którego wciąż odwołuje się jakaś usługa, więc potwierdzenie ostrzega, gdy sekret jest w użyciu i wypisuje te usługi (odłącz go tam najpierw). Nie mylić z d wewnątrz widoku szczegółów, które odłącza sekret od usługi
aw widoku szczegółów: podłącz sekret do usługi — pole z autouzupełnianiem (podpowiada usługi, które go jeszcze nie używają; możesz też wpisać dowolną nazwę), a potem potwierdzenie aktualizacji kroczącej
dw widoku szczegółów: odłącz sekret od usługi — pole z autouzupełnianiem (podpowiada usługi, które go obecnie używają; możesz też wpisać dowolną nazwę), a potem potwierdzenie aktualizacji kroczącej

Podłączenie dodaje sekret do usługi, montowany w /run/secrets/<name> (jak docker service update --secret-add); odłączenie usuwa odwołanie do sekretu. Potem zakładka Secrets się odświeża. Tak jak przy podłączaniu/odłączaniu sieci, każda z tych operacji to ServiceUpdate w API menedżera, które wyzwala aktualizację kroczącą restartującą zadania usługi.

Panel boczny kontekstów

Klastry zajmują kolumnę przy prawej krawędzi, a nie zakładkę. Przełączenie klastra robi się stamtąd, gdzie się właśnie jest — a lista klastrów jest dokładnie tym, co mówi, gdzie to jest. Jej miejsce jest więc na ekranie, a nie na stronie, do której trzeba się wybrać.

c oddaje jej klawiaturę z dowolnej zakładki; c ponownie albo Esc zwraca ją zakładce, na której byłeś. Aktywny klaster jest oznaczony — tą samą nazwą, którą pokazuje stopka. Klaster odwiedzony już w tej sesji ma zieloną ·: wciąż jest połączony, więc przełączenie na niego jest natychmiastowe. Ten, który odmówił połączenia, ma czerwony . Wbudowany kontekst default jest chroniony.

Panel dopasowuje szerokość do najdłuższej nazwy kontekstu i ustępuje na wąskim terminalu: poniżej mniej więcej 92 kolumn chowa się, zamiast obcinać drzewo, i wraca na czas, gdy trzyma go c. Punktów końcowych w kolumnie nie ma — nie ma na nie miejsca, a i pokazuje pełne szczegóły.

Przełączenie klastra zachowuje twoją sesję. UI nie buduje się od nowa: położenie kursora, rozwinięte stacki i usługi oraz filtr / są pamiętane osobno dla każdego klastra, więc wracając trafiasz dokładnie tam, gdzie skończyłeś. Przekierowania portów działają dalej; zakładka Forwards zyskuje kolumnę CLUSTER, żeby jednym spojrzeniem odróżnić te z oglądanego klastra od pozostałych.

Odpytywany jest tylko klaster, który oglądasz: po przełączeniu drugi przestaje się odświeżać, a jego połączenie ssh wygasa samo chwilę później. Pierwsze przejście do klastra trwa tyle, ile trwa jego osiągnięcie, bo połączenie powstaje zanim cokolwiek zmieni się na ekranie — jeśli się nie uda, dowiadujesz się o który klaster chodzi i zostajesz na tym, który działa. Każdy kolejny powrót jest natychmiastowy.

KlawiszAkcja
cprzenieś klawiaturę na panel boczny z dowolnej zakładki; c ponownie albo Esc ją zwraca
iszczegóły kontekstu — punkt końcowy i hosty pośredniczące, na które w kolumnie nie ma miejsca
u / Enteraktywuj zaznaczony kontekst — czyni go bieżącym (także dla zapisanego przez dockera bieżącego kontekstu) i przełącza UI na tamten klaster, zachowując twoje położenie, filtry i przekierowania portów
nutwórz kontekst — prowadzony formularz. Pole wyboru „Connect to Docker over SSH” decyduje o punkcie końcowym: gdy jest włączone, wypełniasz użytkownika / host / port SSH, a drugie pole wyboru „Use a jump host” odsłania pole host(y) pośredniczące (rozdzielone przecinkami, wieloetapowy ProxyJump) — swarmexec zapisuje je w kontekście i wstrzykuje -J w oba połączenia ssh: do API Dockera i do tunelu agenta, dzięki czemu bastiony działają bez edytowania ~/.ssh/config. Gdy jest wyłączone, podajesz zwykły host tcp:// / unix://. Formularz składa docker host za ciebie, a Test odpytuje złożony punkt końcowy, zanim zapiszesz. Także z CLI: swarmexec context create <name> --docker-host ssh://ops@mgr --ssh-jump bastion1,bastion2
dusuń zaznaczony kontekst (za potwierdzeniem); usunięcie bieżącego przywraca wybór na default

Zakładka Nodes

Wypisuje węzły swarma z ogólnymi informacjami i kilkoma agregacjami — NODE (nazwa hosta; menedżerowie turkusowo, lider oznaczony ), ROLE (manager / worker), AVAIL (active / pause / drain, kolorowane), STATE (ready / down), wersja ENGINE, TASKS (działające zadania zaplanowane na węźle), VOLS (wolumeny trzymane przez węzeł — lokalne dla węzła, więc uzupełnia się chwilę po reszcie) i LABELS (liczba).

KlawiszAkcja
Enter / iszczegóły węzła — nakładka tylko do odczytu z nazwą hosta, id, rolą/liderem, dostępnością, stanem, adresem, silnikiem, platformą, liczbą CPU, pamięcią, liczbą działających zadań, sekcjami zarezerwowanych zasobów, rzeczywistego zużycia i obrazów na tym węźle (patrz niżej), liczbą wolumenów i pełną listą etykiet
ledytuj etykiety zaznaczonego węzła — buforowany edytor listy (a dodaj / e edytuj / d usuń / w zastosuj / Esc anuluj) nad wpisami key=value. Zastosowanie wykonuje jeden NodeUpdate — w odróżnieniu od usługi aktualizacja węzła działa natychmiast (bez aktualizacji kroczącej). Etykiety węzłów są zwykle używane jako cele ograniczeń rozmieszczenia (node.labels.<k>)
austaw dostępność węzła — menu Active / Pause / Drain, które oznacza stan, w jakim węzeł jest obecnie. Active planuje tu zadania normalnie; Pause zachowuje działające zadania i blokuje tylko nowe rozmieszczenia; Drain przenosi wszystkie zadania z węzła. Active i Pause działają od razu; Drain przechodzi przez potwierdzenie, które podaje, ile działających zadań swarm zatrzyma i przeplanuje na innych węzłach (usługi, których zadań nie da się umieścić nigdzie indziej, staną się unschedulable). Tak jak zmiana etykiet jest to jeden NodeUpdate (odczyt-modyfikacja-zapis na specyfikacji węzła) i działa natychmiast — węzły nie mają aktualizacji kroczącej. Przemapowywalne jako node_availability
Podzyskaj miejsce na dysku zajęte przez obrazy na węźle (wielkie P, czyli Shift+p — ten sam gest, którego zakładka Volumes używa do swojego prune'a). Otwiera małe menu z dwoma trybami opisanymi niżej, każdy za własnym potwierdzeniem; warto przeczytać, który jest który, zanim się wybierze. Stopka kończy się P reclaim images, a nakładka ? wypisuje to jako reclaim image disk space on the node. Przemapowywalne jako node_prune_images

Zarezerwowane zasoby. Szczegóły węzła zawierają sekcję reserved by tasks (scheduler's view): dla CPU i pamięci pasek plus zarezerwowane / pojemność oraz ile zostaje wolne. Pasek jest zielony, od 75% robi się żółty, a przy 100% lub powyżej czerwony; nadsubskrypcja jest wprost nazwana, a „wolne” nigdy nie schodzi poniżej zera. Węzeł, który nie zgłasza pojemności, mówi o tym zamiast rysować pasek. Sekcja nie kosztuje żadnego dodatkowego wywołania API — lista węzłów i tak pobiera listę zadań.

To są rezerwacje, nie zużycie. Liczby to suma zadeklarowanych przez zadania Reservations — dokładnie ta arytmetyka, którą sam scheduler swarma wykonuje, decydując, czy zadanie mieści się na węźle, i typowy powód, dla którego usługa zostaje unschedulable. Rzeczywiste zużycie CPU/RAM jest lokalne dla węzła i nie jest tym, co tu widać — ma własną sekcję tuż poniżej. Zadania, które nie deklarują żadnej rezerwacji, są liczone i raportowane osobno: nic tu nie rezerwują, a mimo to mogą zużyć cały węzeł — niski procent nie oznacza więc, że węzeł się nudzi, a w większości klastrów mało która usługa w ogóle ustawia rezerwacje. Zadanie trzyma swoją rezerwację od momentu przypisania do węzła aż do stanu końcowego: zadania w preparing czy starting liczą się więc, a shutdown / failed / complete / rejected już nie (swarm trzyma je na liście zadań jako historię).

Rzeczywiście zużywane przez kontenery. Pod spodem jest druga sekcja — in use by containers (measured on the node) — narysowana tymi samymi proporcjonalnymi paskami: to, co kontenery węzła zużywają naprawdę w tej chwili, CPU w rdzeniach i pamięć, każde względem pojemności samego węzła. Obie sekcje celowo trzymane są osobno, bo odpowiadają na różne pytania i regularnie bywają bardzo daleko od siebie: węzeł może być w pełni zarezerwowany i się nudzić albo ledwie zarezerwowany i płonąć. Liczby pochodzą od agenta tego węzła, z tego samego źródła co znaczniki zużycia w drzewie usług — dopóki różnica CPU nie jest gotowa, w linii stoi measuring…, a węzeł, którego agent jest nieosiągalny albo zbyt stary, nie dostaje sekcji w ogóle, zamiast rzędu zer, który czytałoby się jako „nic nie robi”.

Tylko kontenery. Liczone są kontenery węzła — jądro, demon dockera i cokolwiek działa poza dockerem nie wchodzą do tej liczby. To nie jest obciążenie węzła i sama sekcja to mówi.

Obrazy na tym węźle. Na końcu szczegółów stoi sekcja images on this node: ile trzyma magazyn warstw węzła i w ilu obrazach oraz ile z tego da się odzyskać w nieotagowanych pozostałościach (węzeł, który nie ma nic nieotagowanego, mówi to wprost, zamiast oferować zero). Gdy jest coś ponadto, dochodzi jeszcze jedna linia: ile miejsca jest dodatkowo otagowane, ale nieużywane — sformułowane jako koszt, usunięcie oznacza ponowne pobranie tych obrazów, a nie jako oferta. Sekcja istnieje, bo obrazy to jedyny lokalny zasób węzła, którego strona klastra nie widzi w ogóle: API menedżera nie ma o obrazach żadnej wiedzy — inaczej niż o wolumenach, o których przynajmniej wie, że są zamontowane. Wolumenami swarmexec potrafił już zarządzać w skali klastra; węzła, któremu dysk po cichu zapełniał się starymi warstwami, nie dało się nawet zobaczyć. Sekcja ładuje się samodzielnie, równolegle z listą węzłów, więc pojawia się chwilę po reszcie nakładki.

Odzyskiwanie — P w zakładce Nodes. Menu, które się otwiera, ma dwie pozycje, celowo nie jedną akcję z checkboxem, bo to dwa różne czyny, a checkbox aż zaprasza, żeby przez przypadek trafić w ten niszczący:

PozycjaCo usuwa
Untagged leftoverswarstwy zostawione przez przebudowy, z których nic nie da się uruchomić. Bezpieczne: nie zatrzyma usługi ani nie wymusi pobrania
Every unused imageusuwa dodatkowo obrazy otagowane, których w tej chwili nie uruchamia żaden kontener. Na węźle swarma obejmuje to każdą usługę wyskalowaną do zera i każde zadanie między restartami — każde z nich będzie musiało pobrać swój obraz na nowo
Nie czytaj drugiej pozycji jako „to samo, tylko więcej”. „Every unused image” usuwa obrazy należące do czegoś, co po prostu nie działa w tej sekundzie: usługi wyskalowanej do zera, restartującego się zadania, stacka zatrzymanego wczoraj. Wszystkie tracą swój obraz i muszą pobrać go ponownie, żeby wrócić, a jeśli rejestr jest nieosiągalny, już nie wstaną. Każda z obu pozycji przechodzi przez własne okno potwierdzenia, które nazywa dokładnie tę konsekwencję i podaje bajty, jakie zwolni; to osobne teksty właśnie po to, by żaden nie zaczął opisywać drugiego.

Dwa tryby, dwa uprawnienia. Agent autoryzuje je jako dwie odrębne akcje — image.prune dla nieotagowanych pozostałości i image.prune.all dla zamiatania wszystkiego — więc polityka może pozwolić na odzyskiwanie nieotagowanych warstw bez zgody na ten niszczący. Każdy prune trafia do dziennika audytu agenta: z jakim trybem, ile odzyskano i ile obrazów zniknęło.

Skąd biorą się liczby. Pochodzą z tego samego źródła, z którego raportuje docker system df, i zgadzają się z nim dokładnie — sprawdzone na prawdziwym węźle przy 56 obrazach, 32,70 GB na dysku i 22,99 GB do odzyskania. W szczególności swarmexec nie sumuje rozmiarów poszczególnych obrazów: rozmiar obrazu obejmuje każdą warstwę, z której jest zbudowany, a warstwy są współdzielone, więc taka suma mocno zawyża — w trakcie prac twierdziła 32,7 GB tam, gdzie demon mówił 22,2 GB. Jedno uczciwe zastrzeżenie: liczba tylko nieotagowane to dolne ograniczenie — warstwa współdzielona przez dwa nieotagowane obrazy nie należy do własnego rozmiaru żadnego z nich — więc prune nieotagowanych może zwolnić odrobinę więcej, niż zapowiada, nigdy mniej.

Ten sam warunek co przy liczbach zużycia i zdrowia powyżej: potrzebne są agenty z tego wydania lub nowsze. Węzeł ze starszym agentem po prostu nie pokazuje sekcji obrazów, a po naciśnięciu P zgłasza, że nie odpowiedział — rozwiń je przez swarmexec init --force.

Zakładka Configs

Zakładka 8 — bliźniaczka zakładki Secrets dla drugiego obiektu, który swarm podaje kontenerom, z jedną decydującą różnicą: treść configu da się odczytać. Zakładka Secrets nigdy nie pokaże wartości (API Dockera jej nie zwraca); widok szczegółów configu pokazuje rzeczywistą treść, więc widzisz, co usługa naprawdę dostaje, bez schodzenia do docker config inspect. Po to właśnie jest ta zakładka.

Lista jest tylko do odczytu: CONFIG (nazwa), USED BY (ile usług go montuje), SIZE, AGE, UPDATED oraz LABELS (liczba).

KlawiszAkcja
Enter / iotwórz widok szczegółów: nazwa, id, rozmiar, utworzenie, aktualizacja, usługi, które go montują, jego etykiety — i treść configu
j k / g Gw widoku szczegółów: przewijanie · skok na początek / koniec (Esc zamyka)

Treść pobierana jest na żądanie, przy otwarciu szczegółów — nie razem z listą — bo config może być całym plikiem nginx.conf; do tego czasu widać loading…, a pusty config melduje (empty). Pokazywany jest podgląd, ograniczony jednocześnie do 64 KiB i do 500 wierszy. Oba limity są potrzebne: ograniczenie samych bajtów wciąż pozwala, by plik z bardzo krótkimi wierszami zamienił się w dziesiątki tysięcy renderowanych wierszy — każdy jeszcze wcięty — a to właśnie zamula widok. Skrócenie zawsze jest zaznaczone („… truncated at 500 lines” albo na limicie bajtów), nigdy nie jest cichym ucięciem, a treść binarna jest zgłaszana, nie wysypywana („(binary content, N — not shown)”, rozpoznawana po bajtach NUL na początku danych). Pełny config jest o jedno docker config inspect stąd.

USED BY wyprowadzane jest dokładnie tak samo jak w zakładce Secrets: swarm nie prowadzi żadnego odwrotnego indeksu od configu do jego odbiorców, więc swarmexec czyta specyfikacje usług (jedno ServiceList) i rozpoznaje odwołanie po ID albo nazwie configu — specyfikacja może nieść dowolną z tych form, więc obie są sprawdzane i scalane. Best effort: jeśli listy usług nie da się odczytać, configi i tak się wyświetlą, tylko bez tego przypisania.

Wbudowany shell & logi

We wbudowanym shellu Ctrl-] odłącza się (bez kończenia procesu). Widok logów — pojedynczego kontenera albo zagregowane logi usługi — obsługuje to samo parsowanie i filtrowanie świadome formatu co polecenie logs:

KlawiszAkcja
fprzełącz śledzenie
Fprzełączaj format logów (classic → json → logfmt → gelf → raw)
lprzełączaj filtr minimalnego poziomu (off → trace → … → fatal → off)
/wpisz wyrażenie regularne grep na komunikacie (puste je czyści)
mprzełącz przechwytywanie myszy (wyłączone = własne zaznaczanie/kopiowanie twojego terminala)
przewijaj
Esc / qzamknij

Widok renderuje zbuforowane linie na nowo w locie, gdy zmienisz format, poziom albo grep, a pasek tytułu pokazuje aktywny status fmt:… lvl:… grep:….

Podczas śledzenia widok logów łączy się ponownie po wymianie kontenera, tak samo jak robi to polecenie logs — zarówno dla logów pojedynczego kontenera, jak i dla zagregowanych logów usługi (każda replika śledzona po slocie). Powiadomienia o ponownym połączeniu pojawiają się w widoku logów.

Podgląd logów

Naciśnij ` (backtick) z dowolnej zakładki, żeby przełączyć nakładkę z logami na żywo. Pokazuje ona najnowsze wpisy z bufora cyklicznego w pamięci — najnowsze na dole — odświeżając się, gdy jest otwarta, i kolorując według poziomu (czerwony = error, żółty = warn, szary = debug). / przewijają; Esc, q albo ` ją zamykają. To okno TUI na te same logi, które trafiają do pliku logu, bo sam terminal nie może ich pokazać, gdy UI rysuje po ekranie.

Responsywność. Lista kontenerów odświeża się poza wątkiem UI (jej dwa wywołania API menedżera działały kiedyś w linii i potrafiły na moment zamrozić TUI), a działający w tle watchdog loguje ostrzeżenie, gdy pętla zdarzeń się zatnie — przydatne do diagnozowania ociężałości z poziomu podglądu logów.

Własne klawisze

Klawisze skrótów można przemapować w ~/.config/swarmexec/keys.yaml (obok config.yaml; ścieżkę nadpiszesz przez $SWARMEXEC_KEYS). Każda wartość to pojedynczy klawisz albo słowo space. Klawisze strukturalne — Enter, Esc, Tab, strzałki, cyfry zakładek 18 oraz aliasy z vima j/k/g/G — są stałe. Stopka zawsze pokazuje twoje faktyczne klawisze, a nakładka ? wypisuje je wszystkie.

# ~/.config/swarmexec/keys.yaml — pomiń dowolną linię, żeby zachować jej domyślną wartość quit: q refresh: r copy: y toggle_mouse: m search: / fold: h unfold: l forward: p container_inspect: i logs: L security_risks: "!" # "!" musi być w cudzysłowie — 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

Wczytywanie nigdy się nie wywala: nieprawidłowy albo zarezerwowany klawisz, nieznana akcja czy dwie akcje przypisane do tego samego klawisza na jednej zakładce — każde z tych ustawień wraca do wartości domyślnej, a UI pokazuje ostrzeżenia raz przy starcie.

11. Kody wyjścia

KodZnaczenie
0sukces
2błąd użycia / flagi / konfiguracji albo przerwany wybór interaktywny
125awaria transportu — nieosiągalny menedżer albo agent, błąd połączenia/TLS, błąd strumienia, przerwane potwierdzenie (odzwierciedla 125 Dockera)
Ndla exec — niezerowy kod wyjścia zdalnego polecenia, przekazany dosłownie