Docker compose i zapora sieciowa
Moduł 9: Docker i docker compose · Czas czytania: 12 min
Cel lekcji: Po tej lekcji potrafią Państwo opisać usługi w pliku compose, uruchamiać je, aktualizować i zatrzymywać, a także wyjaśnić, dlaczego opublikowane porty Dockera omijają ufw i jak temu zapobiec.
Kluczowe pojęcia: compose.yaml, services, image, ports, volumes, environment, .env, restart, networks, depends_on, healthcheck, docker compose up -d, pull, logs, down, przestarzałe version:, Docker i ufw, DOCKER-USER, publikowanie na 127.0.0.1, sekrety, docker.sock
Dlaczego compose?
Za pomocą docker run (lekcja 9.2) uruchamiają Państwo jeden kontener z długą linią opcji. Prawdziwa aplikacja składa się jednak często z kilku kontenerów: programu, bazy danych, reverse proxy. Zapamiętywanie tych poleceń i wpisywanie ich ponownie po restarcie lub aktualizacji prędzej czy później kończy się błędem.
Docker Compose opisuje wszystkie kontenery aplikacji w jednym pliku: compose.yaml. Plik ten umieszczają Państwo we własnym katalogu, na przykład /srv/status/compose.yaml, i zarządzają wszystkim za pomocą docker compose. Plik jest jednocześnie Państwa dokumentacją: co działa, jaki obraz, jakie porty, gdzie są dane. Wystarczy dodać ten katalog do kopii zapasowej (lekcja 8.1), a aplikację można odtworzyć na nowym serwerze w kilka minut.
Wtyczka nazywa się docker compose (ze spacją, wersja 2). Stary, osobny program docker-compose (z łącznikiem) został zastąpiony; można go jeszcze spotkać w starych poradnikach. Przestarzałe jest również pole version: "3.8" na początku starych plików: Compose zawsze używa najnowszego schematu, a przy version: wyświetla tylko ostrzeżenie.
Czytanie pliku compose
Przykład z programem i bazą danych 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:Krok po kroku:
- services: każda usługa staje się kontenerem. Nazwa (
app,db) jest także nazwą hosta, po której kontenery się odnajdują. Compose tworzy dla aplikacji własną sieć, dlategoappłączy się z bazą danych pod adresemdb:5432. - image: zawsze z tagiem wersji (lekcja 9.1).
- restart: co Docker robi, gdy kontener się zatrzyma lub serwer zostanie zrestartowany.
unless-stoppeduruchamia go ponownie, chyba że zatrzymali go Państwo celowo. Bez polityki restartu aplikacja po restarcie serwera (lekcja 4.2) nie wróci. - ports: tylko aplikacja publikuje port i tylko na
127.0.0.1. Baza danych nie publikuje niczego: aplikacja dociera do niej przez sieć wewnętrzną. Baza danych zports: - "5432:5432"to jeden z najczęściej popełnianych błędów (patrz niżej). - environment: ustawienia programu. Hasło nie jest w nim zapisane dosłownie, tylko jako
${DB_PASSWORD}. - volumes: nazwany wolumen
db-dataprzechowuje bazę danych, gdy kontener zostanie wymieniony. - depends_on z healthcheck: aplikacja startuje dopiero wtedy, gdy baza danych jest naprawdę gotowa, a nie tylko uruchomiona.
Sekrety w pliku .env
Compose odczytuje zmienne takie jak ${DB_PASSWORD} z pliku .env w tym samym katalogu:
# /srv/notities/.env
DB_PASSWORD=Kies-hier-een-lang-willekeurig-wachtwoordanna@web1:/srv/notities$ sudo chmod 600 .envDzięki temu hasła nie ma w pliku compose, który być może przechowują Państwo w Git lub wklejają na forum, prosząc o pomoc. Prosimy dodać .env do .gitignore. Hasło warto wygenerować menedżerem haseł lub poleceniem openssl rand -base64 24.
Codzienne użycie
Wszystkie polecenia wykonują Państwo w katalogu z plikiem compose:
anna@web1:/srv/notities$ sudo docker compose config # sprawdź plik, zmienne podstawione
anna@web1:/srv/notities$ sudo docker compose up -d # uruchom wszystko (lub zaktualizuj zgodnie z plikiem)
anna@web1:/srv/notities$ sudo docker compose ps # status każdej usługi
anna@web1:/srv/notities$ sudo docker compose logs -f app
anna@web1:/srv/notities$ sudo docker compose pull # pobierz nowe obrazy
anna@web1:/srv/notities$ sudo docker compose up -d # zastąp kontenery nowymi obrazami
anna@web1:/srv/notities$ sudo docker compose down # zatrzymaj i usuń kontenerydown pozostawia wolumeny. down -v usuwa także wolumeny aplikacji, a wraz z nimi bazę danych. Prosimy wpisywać to tylko wtedy, gdy naprawdę chcą Państwo zacząć od nowa.
docker compose config jest odpowiednikiem nginx -t i sshd -t: pokazuje, jak Compose rozumie Państwa plik, ze wszystkimi wstawionymi zmiennymi, zanim cokolwiek zostanie uruchomione. Prosimy nie wklejać nigdzie tego wyniku, ponieważ zawiera hasła.
Docker omija ufw
Teraz najważniejsza kwestia bezpieczeństwa w tym module. W lekcji 5.2 ustawili Państwo ufw na "odrzucaj wszystko oprócz SSH, 80 i 443". Można by się spodziewać, że kontener z ports: - "5432:5432" nie będzie wtedy osiągalny. Tak nie jest.
Dokumentacja Dockera mówi to wprost: Docker i ufw używają reguł zapory w sposób, który się ze sobą nie godzi. Gdy publikują Państwo port, Docker sam dodaje reguły do zapory jądra. Reguły te przekierowują ruch do kontenera (translacja adresów, DNAT), zanim zostaną zastosowane reguły ufw. Pakiet omija więc ufw. ufw status nie pokazuje niczego szczególnego, a port jest osiągalny z całego internetu. To samo dotyczy firewalld.
Ponadto Docker bez podanego adresu publikuje na 0.0.0.0 i [::], czyli zarówno na IPv4, jak i na IPv6 (lekcja 9.1).
Trzy sposoby, aby zrobić to dobrze, od prostego do złożonego:
- Nie publikować niczego, co nie musi być publiczne. Bazy danych, cache i usługi wewnętrzne nie dostają
ports:. Kontenery w tej samej aplikacji compose docierają do siebie przez sieć wewnętrzną. - Publikować na loopback.
"127.0.0.1:8081:8080". Wtedy dostęp ma tylko sam serwer, a przed usługą stawiają Państwo reverse proxy (nginx lub Caddy, moduł 7), który nasłuchuje na 443. Docker może też mieć to jako ustawienie domyślne: opcja demona sprawia, że wszystkie opublikowane porty bez adresu trafiają na127.0.0.1. - Reguły w łańcuchu DOCKER-USER. Docker tworzy pusty łańcuch
DOCKER-USER, który jest przetwarzany przed jego własnymi regułami. Mogą Państwo dodać tam własne reguły, na przykład "tylko ten adres IP ma dostęp do opublikowanych portów". To zaawansowana praca z iptables lub nftables, w której łatwo o błąd. Dla większości małych serwerów wystarczą sposoby 1 i 2.
Zawsze prosimy sprawdzać z zewnątrz, jak w lekcji 5.1: nc -vz -w 3 <ip> 5432 z Państwa laptopa lub kontrolą kursu z zadania Z5.1 i Z10.4.
Jeszcze dwie rzeczy, których się nie robi
- Nie montować
/var/run/docker.sockw kontenerze, chyba że dokładnie wiedzą Państwo dlaczego. Kontener z dostępem do gniazda Dockera może sam uruchamiać kontenery, także taki z całym dyskiem hosta (lekcja 9.2). Niektóre narzędzia (panele, automatyczne aktualizatory) tego wymagają; w praktyce oznacza to przyznanie temu programowi uprawnień root. - Nie używać
privileged: trueaninetwork_mode: hosttylko dlatego, że tak każe poradnik. Obie opcje w dużej mierze usuwają izolację kontenera. Należy ich używać tylko wtedy, gdy twórca wyjaśnia, dlaczego są potrzebne.
Podsumowanie
- Plik compose (
compose.yaml) opisuje wszystkie kontenery aplikacji; jest jednocześnie dokumentacją i powinien znaleźć się w kopii zapasowej. - Prosimy używać tagów wersji, polityki restartu, nazwanych wolumenów, healthcheck tam, gdzie to możliwe, oraz sekretów w
.envz uprawnieniami 600. docker compose config,up -d,ps,logs,pull,down;down -vusuwa także wolumeny.- Opublikowane porty omijają ufw i firewalld. Publikować należy tylko to, co musi być publiczne, a w pozostałych przypadkach na
127.0.0.1z reverse proxy przed usługą. - Bez docker.sock,
privilegedczynetwork_mode: hostbez ważnego powodu.
Quiz
Zadanie
Zadanie Z9.3: Sprawdzanie pliku compose. W przykładowym pliku compose-nakijken.txt znajduje się (jako plik tekstowy) plik compose małego sklepu internetowego z aplikacją, bazą danych, cache i narzędziem administracyjnym. Zawiera on sześć błędów omówionych w tej lekcji lub w lekcjach 9.1 i 9.2. Prosimy wskazać dla każdego ponumerowanego bloku linii, czy jest poprawny, czy błędny, a przy każdym błędzie wybrać właściwą poprawkę. Program porównuje odpowiedzi ze stałym kluczem. Jeśli mają Państwo własny plik compose, prosimy następnie sprawdzić go pod kątem tych samych punktów.
Przeczytaj więcej
- Dokumentacja Dockera, How Compose works (compose.yaml): https://docs.docker.com/compose/intro/compose-application-model/
- Dokumentacja Dockera, Version and name top-level elements: https://docs.docker.com/reference/compose-file/version-and-name/
- Dokumentacja Dockera, Packet filtering and firewalls (Docker and ufw): https://docs.docker.com/engine/network/packet-filtering-firewalls/
- Dokumentacja Dockera, Port publishing and mapping: https://docs.docker.com/engine/network/port-publishing/
- Dokumentacja Dockera, Interpolation (zmienne i .env): https://docs.docker.com/reference/compose-file/interpolation/
