Modulul 9 · 9.3
Docker compose și firewall-ul
Modulul 9: Docker și docker compose · Timp de citire: 12 minute
Obiectivul lecției: După această lecție veți putea descrie servicii într-un fișier compose, le veți putea porni, actualiza și opri și veți putea explica de ce porturile publicate de Docker ocolesc ufw și cum puteți preveni acest lucru.
Noțiuni cheie: compose.yaml, services, image, ports, volumes, environment, .env, restart, networks, depends_on, healthcheck, docker compose up -d, pull, logs, down, câmpul învechit version:, Docker și ufw, DOCKER-USER, publicarea pe 127.0.0.1, secrete, docker.sock
De ce compose?
Cu docker run (lecția 9.2) porniți un singur container cu o linie lungă de opțiuni. O aplicație reală constă adesea din mai multe containere: un program, o bază de date, un reverse proxy. Dacă trebuie să rețineți aceste linii și să le tastați din nou după o repornire sau o actualizare, la un moment dat ceva va merge prost.
Docker Compose descrie toate containerele unei aplicații într-un singur fișier: compose.yaml. Puneți acest fișier într-un director propriu, de exemplu /srv/status/compose.yaml, și gestionați totul cu docker compose. Fișierul este în același timp documentația dumneavoastră: ce rulează, ce imagine, ce porturi, unde se află datele. Includeți acest director în backup (lecția 8.1) și veți putea restaura aplicația pe un server nou în câteva minute.
Pluginul se numește docker compose (cu spațiu, versiunea 2). Vechiul program separat docker-compose (cu cratimă) a fost înlocuit; îl mai întâlniți în ghidurile vechi. Și câmpul version: "3.8" din partea de sus a fișierelor vechi este învechit: Compose folosește întotdeauna cea mai nouă schemă și la version: afișează doar un avertisment.
Citirea unui fișier compose
Un exemplu cu un program și o bază de date PostgreSQL:
# /srv/notities/compose.yaml
services:
app:
image: ghcr.io/example/notities:1.4
restart: unless-stopped
ports:
- "127.0.0.1:8081:8080"
environment:
DB_HOST: db
DB_USER: notities
DB_PASSWORD: ${DB_PASSWORD}
depends_on:
db:
condition: service_healthy
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_USER: notities
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U notities"]
interval: 10s
retries: 5
volumes:
db-data:Pas cu pas:
- services: fiecare serviciu devine un container. Numele (
app,db) este și numele de gazdă prin care containerele se găsesc între ele. Compose creează o rețea proprie pentru aplicație, așa căappse conectează la baza de date pedb:5432. - image: întotdeauna cu o etichetă de versiune (lecția 9.1).
- restart: ce face Docker când containerul se oprește sau serverul repornește.
unless-stoppedîl pornește din nou, cu excepția cazului în care l-ați oprit intenționat. Fără o politică de restart, aplicația dumneavoastră nu mai revine după o repornire (lecția 4.2). - ports: doar aplicația publică un port, și doar pe
127.0.0.1. Baza de date nu publică nimic: aplicația ajunge la ea prin rețeaua internă. O bază de date cuports: - "5432:5432"este una dintre cele mai frecvente greșeli (vedeți mai jos). - environment: setări pentru program. Parola nu apare literal aici, ci ca
${DB_PASSWORD}. - volumes: volumul cu nume
db-datapăstrează baza de date atunci când containerul este înlocuit. - depends_on cu healthcheck: aplicația pornește abia când baza de date este cu adevărat pregătită, nu doar pornită.
Secrete într-un fișier .env
Compose citește variabile precum ${DB_PASSWORD} dintr-un fișier .env aflat în același director:
# /srv/notities/.env
DB_PASSWORD=Kies-hier-een-lang-willekeurig-wachtwoordanna@web1:/srv/notities$ sudo chmod 600 .envAstfel, parola nu apare în fișierul compose pe care poate îl păstrați în Git sau îl lipiți pe un forum când cereți ajutor. Adăugați .env în .gitignore. Generați parola cu un manager de parole sau cu openssl rand -base64 24.
Utilizarea zilnică
Toate comenzile le executați în directorul care conține fișierul compose:
anna@web1:/srv/notities$ sudo docker compose config # verificarea fișierului, cu variabilele completate
anna@web1:/srv/notities$ sudo docker compose up -d # pornire completă (sau actualizare conform fișierului)
anna@web1:/srv/notities$ sudo docker compose ps # starea fiecărui serviciu
anna@web1:/srv/notities$ sudo docker compose logs -f app
anna@web1:/srv/notities$ sudo docker compose pull # descărcarea imaginilor noi
anna@web1:/srv/notities$ sudo docker compose up -d # înlocuirea containerelor cu imaginile noi
anna@web1:/srv/notities$ sudo docker compose down # oprire și ștergerea containerelordown vă lasă volumele neatinse. down -v șterge și volumele aplicației, deci și baza de date. Tastați această comandă doar dacă doriți cu adevărat să o luați de la capăt.
docker compose config este echivalentul lui nginx -t și sshd -t: arată cum înțelege Compose fișierul dumneavoastră, cu toate variabilele completate, înainte să porniți ceva. Aveți grijă să nu lipiți apoi rezultatul undeva, deoarece conține parolele.
Docker ocolește ufw
Acum, cel mai important punct de securitate al acestui modul. În lecția 5.2 ați configurat ufw pe „refuză tot, cu excepția SSH, 80 și 443”. V-ați aștepta ca un container cu ports: - "5432:5432" să nu fie accesibil. Nu este așa.
Documentația Docker o spune explicit: Docker și ufw folosesc regulile de firewall într-un mod incompatibil. Când publicați un port, Docker adaugă singur reguli în firewall-ul kernelului. Aceste reguli redirecționează traficul către container (translatare de adrese, DNAT) înainte ca regulile ufw să fie aplicate. Pachetul ocolește deci ufw. ufw status nu arată nimic deosebit, în timp ce portul este accesibil de pe întregul internet. Același lucru este valabil pentru firewalld.
În plus, fără o adresă, Docker publică pe 0.0.0.0 și [::], deci atât pe IPv4, cât și pe IPv6 (lecția 9.1).
Trei moduri de a face lucrurile corect, de la simplu la complicat:
- Nu publicați nimic din ce nu trebuie să fie public. Bazele de date, cache-urile și serviciile interne nu primesc
ports:. Containerele din aceeași aplicație compose comunică între ele prin rețeaua internă. - Publicați pe loopback.
"127.0.0.1:8081:8080". Astfel doar serverul însuși are acces, iar în fața lui puneți un reverse proxy (nginx sau Caddy, modulul 7) care ascultă pe 443. Docker poate avea și acest comportament implicit: o setare a daemonului face ca toate porturile publicate fără adresă să ajungă pe127.0.0.1. - Reguli în lanțul DOCKER-USER. Docker creează un lanț gol
DOCKER-USERcare este parcurs înaintea propriilor sale reguli. Acolo puteți adăuga reguli proprii, de exemplu „doar această adresă IP are acces la porturile publicate”. Aceasta este o muncă avansată cu iptables sau nftables, în care se greșește ușor. Pentru majoritatea serverelor mici, variantele 1 și 2 sunt suficiente.
Verificați întotdeauna din exterior, ca în lecția 5.1: nc -vz -w 3 <ip> 5432 de pe laptopul dumneavoastră sau verificarea din curs a exercițiilor Z5.1 și Z10.4.
Încă două lucruri pe care nu le faceți
- Nu montați
/var/run/docker.sockîntr-un container, decât dacă știți exact de ce. Un container cu acces la socketul Docker poate porni singur containere, inclusiv unul cu întregul disc al gazdei (lecția 9.2). Unele utilitare (dashboarduri, programe de actualizare automată) cer acest lucru; practic, îi dați acelui program drepturi de root. - Nu folosiți
privileged: truesaunetwork_mode: hostdoar pentru că așa scrie într-un ghid. Ambele elimină în mare parte izolarea containerului. Folosiți-le doar dacă autorul explică de ce sunt necesare.
Rezumat
- Un fișier compose (
compose.yaml) descrie toate containerele unei aplicații; este totodată documentație și trebuie inclus în backup. - Folosiți etichete de versiune, o politică de restart, volume cu nume, un healthcheck unde este posibil și secrete într-un
.envcu permisiunile 600. docker compose config,up -d,ps,logs,pull,down;down -vșterge și volumele.- Porturile publicate ocolesc ufw și firewalld. Publicați doar ce trebuie să fie public, iar în rest pe
127.0.0.1, cu un reverse proxy în față. - Fără docker.sock,
privilegedsaunetwork_mode: hostfără un motiv întemeiat.
Test (quiz)
Exercițiu
Exercițiul Z9.3: Verificarea unui fișier compose. În fișierul de exemplu compose-nakijken.txt se află (ca fișier text) fișierul compose al unui mic magazin online cu o aplicație, o bază de date, un cache și un instrument de administrare. Conține șase greșeli care au fost tratate în această lecție sau în lecțiile 9.1 și 9.2. Indicați pentru fiecare bloc de linii numerotat dacă este corect sau greșit și alegeți pentru fiecare greșeală corectura potrivită. Programul compară răspunsurile cu cheia fixă. Dacă aveți un fișier compose propriu, verificați-l apoi după aceleași puncte.
Lecturi suplimentare
- Documentația Docker, How Compose works (compose.yaml): https://docs.docker.com/compose/intro/compose-application-model/
- Documentația Docker, Version and name top-level elements: https://docs.docker.com/reference/compose-file/version-and-name/
- Documentația Docker, Packet filtering and firewalls (Docker and ufw): https://docs.docker.com/engine/network/packet-filtering-firewalls/
- Documentația Docker, Port publishing and mapping: https://docs.docker.com/engine/network/port-publishing/
- Documentația Docker, Interpolation (variabile și .env): https://docs.docker.com/reference/compose-file/interpolation/
