Preskočiť na obsah
← Späť na blog

Ten istý compose súbor na notebooku a na swarme

· Štefan Hudáč

Predchádzajúci článok rozdelil compose súbor na zdieľané služby, ktoré môže používať akýkoľvek stack. Tento je o samotnom súbore. Compose súbor napísaný pre docker compose up nie je ten istý artefakt ako ten, ktorý dostane docker stack deploy. Syntax vyzerá rovnako, správanie nie: Swarm nemá poradie štartu, nečíta .env, nepozná cesty špecifické pre hostiteľa ani krok buildu. Väčšina týchto rozdielov zlyháva potichu, a práve preto sa oplatí poznať ich skôr, než sa súbor dostane na klaster.

Dva príkazy, dve pravidlá

docker compose up spustí kontajnery na uzle, kde ho spustíte. Na manažérovi to povie aj nahlas: „Compose nepoužíva režim swarm na nasadenie služieb na viac uzlov.“

docker stack deploy odovzdá ten istý súbor orchestrátoru, ktorý vytvorí služby, rozmiestni repliky, postaví overlay sieť a nechá každý uzol stiahnuť image z registra. Z toho hneď vyplývajú tri dôsledky:

  • Neexistuje lokálny build. build: sa ignoruje, takže image musí skončiť v registri.
  • Používa starší formát compose verzie 3, nie Compose špecifikáciu.
  • Interpolácia prebieha v shelli, z ktorého nasadzujete, nie v adresári projektu.

Čo súbor nesmie obsahovať

Kľúč name: na najvyššej úrovni je odmietnutý hneď s „additional property ‘name’ is not allowed”. Názov stacku prichádza z príkazového riadka.

Neexistuje prepínač --env-file a .env sa nikdy nečíta. Každá ${VARIABLE} sa nahrádza z prostredia shellu, ktorý deploy spúšťa. Ak ju necháte nenastavenú, stane sa z nej prázdny reťazec, nie chyba — a pri odkaze na image z toho vznikne holé dvojbodkové zakončenie a invalid reference format.

env_file: funguje, ale vyhodnocuje sa na klientovi: súbor musí ležať vedľa compose súboru na stroji, ktorý deploy spúšťa, nie na uzloch.

Čo Swarm potichu zahodí

Docker vypíše „Ignoring unsupported options: …“ a ide ďalej:

  • build, restart, links, network_mode, privileged, devices
  • shm_size, security_opt, ipc, pid, mac_address
  • cgroup_parent, cgroupns_mode, domainname, external_links, userns_mode

restart je ten, ktorý ľudí prekvapí. Na swarme žije politika reštartu pod deploy.restart_policy, a ak chýba, predvolená hodnota je condition: any — kontajner, ktorý padá v slučke, bude reštartovaný donekonečna. links nahradzujú siete: služby na tej istej overlay sieti sa nájdu podľa názvu. container_name je deprecated a ignorovaný, pretože Swarm generuje názvy taskov.

depends_on nemá ekvivalent

Na swarme neexistuje poradie štartu ani čakanie na healthcheck. depends_on zlyháva dvoma spôsobmi podľa toho, ako je napísaný.

Krátka podoba ako zoznam sa načíta a potom bez varovania zmizne. Dlhá podoba — tá s condition: service_healthy — neprejde validáciou vôbec:

services.n8n.depends_on: must be a list

Tak či tak sa aplikácia musí vyrovnať s tým, že databáza ešte nebeží, čo v praxi znamená opakovať pripojenie namiesto spoliehania sa na naštartovanú závislosť.

Skutočný súbor s poznámkami

Compose súbor n8n je dobrý exemplár, pretože používa takmer všetky tieto voľby:

volumes:
  db_storage:
  n8n_storage:

services:
  postgres:
    image: postgres:18
    restart: always # ignored by swarm
    environment:
      - POSTGRES_USER # read from the shell at deploy time
      - POSTGRES_PASSWORD
      - POSTGRES_DB
      - POSTGRES_NON_ROOT_USER
      - POSTGRES_NON_ROOT_PASSWORD
    volumes:
      - db_storage:/var/lib/postgresql/data
      - ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}",
        ]

  n8n:
    image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
    restart: always # ignored by swarm
    ports:
      - 5678:5678
    links:
      - postgres # ignored by swarm
    volumes:
      - n8n_storage:/home/node/.n8n
    depends_on: # rejected by the v3 schema: must be a list
      postgres:
        condition: service_healthy

Dva riadky v ňom zlyhávajú bez toho, aby to povedali. ${POSTGRES_USER} nahrádza Compose, nie shell vnútri kontajnera; s nenastavenou premennou sa kontrola zmení na pg_isready -h localhost -U -d a nikdy neprejde. Potrebuje $$POSTGRES_USER, aby ju rozbalil shell kontajnera. A passthrough environment: - POSTGRES_USER sa vyhodnotí raz, pri nasadení, z vášho shellu — nie z .env, ktoré mal projekt na notebooku.

Porty, cesty a volumy

Publikované porty fungujú, s jedným rozdielom: IP adresa hostiteľa sa zahodí. 127.0.0.1:8000:8000 vyprodukuje varovanie — „ignoring IP-address (127.0.0.1:8000:8000/tcp) service will listen on ‘0.0.0.0’“ — a služba je dostupná cez routing mesh na každom uzle. Keď potrebujete port na jednom konkrétnom uzle, použite dlhú syntax s mode: host.

Cesty sú druhý tichý rozdiel. Bind mount sa vyhodnocuje na tom uzle, kde pristane task, a Docker tam vytvorí prázdny adresár namiesto toho, aby zlyhal. Pomenovaný volume s predvoleným lokálnym driverom nie je o nič lepší: žije len na jednom uzle, takže presunutá databáza začne s prázdnym volume a vyzerá to ako strata dát. Buď službu pripnite cez deploy.placement.constraints, alebo dajte volume na zdieľané úložisko. Sprievodný článok o zdieľaných službách ukazuje verziu s NFS.

Skontrolujte pred nasadením

docker stack config -c paperless.yml

Zlúči a nahrádza premenné v súbore bez toho, aby sa dotkol swarmu, čo odchytí chýbajúcu premennú pred nasadením. Nepovie vám však, ktoré kľúče sa práve ignorujú — to vypíše len docker stack deploy. Prečítať si výstup nasadenia raz, na čerstvom stacku, je cennejšie než akákoľvek kontrola schémy.

Pravidlo, ktoré to celé pokrýva: na swarme compose súbor popisuje služby, nie stroj. Čokoľvek, čo predpokladalo jedného hostiteľa — poradie procesov, cesty na hostiteľovi, lokálny Docker build — treba nahradiť niečím, čomu orchestrátor rozumie.