Ενότητα 9 · 9.3
Docker compose και το firewall
Ενότητα 9: Docker και docker compose · Χρόνος ανάγνωσης: 12 λεπτά
Στόχος μαθήματος: Μετά από αυτό το μάθημα μπορείτε να περιγράφετε υπηρεσίες σε ένα αρχείο compose, να τις ξεκινάτε, να τις ενημερώνετε και να τις σταματάτε. Μπορείτε επίσης να εξηγήσετε γιατί οι δημοσιευμένες θύρες του Docker παρακάμπτουν το ufw και πώς το αποτρέπετε.
Βασικές έννοιες: compose.yaml, services, image, ports, volumes, environment, .env, restart, networks, depends_on, healthcheck, docker compose up -d, pull, logs, down, καταργημένο version:, Docker και ufw, DOCKER-USER, δημοσίευση σε 127.0.0.1, μυστικά, docker.sock
Γιατί compose;
Με το docker run (μάθημα 9.2) ξεκινάτε ένα container με μια μεγάλη γραμμή επιλογών. Μια πραγματική εφαρμογή όμως αποτελείται συχνά από πολλά containers: ένα πρόγραμμα, μια βάση δεδομένων, ένα reverse proxy. Αν πρέπει να θυμάστε αυτές τις γραμμές και να τις πληκτρολογείτε ξανά μετά από επανεκκίνηση ή ενημέρωση, κάποια στιγμή θα γίνει λάθος.
Το Docker Compose περιγράφει όλα τα containers μιας εφαρμογής σε ένα αρχείο: compose.yaml. Τοποθετείτε αυτό το αρχείο σε δικό του φάκελο, για παράδειγμα /srv/status/compose.yaml, και διαχειρίζεστε τα πάντα με το docker compose. Το αρχείο αποτελεί ταυτόχρονα και την τεκμηρίωσή σας: τι εκτελείται, ποιο image, ποιες θύρες, πού βρίσκονται τα δεδομένα. Συμπεριλάβετε αυτόν τον φάκελο στο αντίγραφο ασφαλείας σας (μάθημα 8.1) και θα μπορείτε να επαναφέρετε την εφαρμογή σε έναν νέο server μέσα σε λίγα λεπτά.
Το plug-in ονομάζεται docker compose (με κενό, έκδοση 2). Το παλιό αυτόνομο πρόγραμμα docker-compose (με παύλα) έχει αντικατασταθεί· θα το βλέπετε ακόμη σε παλιούς οδηγούς. Και το πεδίο version: "3.8" στην αρχή παλιών αρχείων έχει καταργηθεί: το Compose χρησιμοποιεί πάντα το νεότερο σχήμα και για το version: εμφανίζει μόνο μια προειδοποίηση.
Ανάγνωση ενός αρχείου compose
Ένα παράδειγμα με ένα πρόγραμμα και μια βάση δεδομένων 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:Βήμα προς βήμα:
- services: κάθε υπηρεσία γίνεται ένα container. Το όνομα (
app,db) είναι επίσης το hostname με το οποίο τα containers βρίσκουν το ένα το άλλο. Το Compose δημιουργεί ένα δικό του δίκτυο για την εφαρμογή, οπότε τοappσυνδέεται με τη βάση δεδομένων στοdb:5432. - image: πάντα με ετικέτα έκδοσης (μάθημα 9.1).
- restart: τι κάνει το Docker όταν το container σταματήσει ή ο server επανεκκινηθεί. Το
unless-stoppedτο ξεκινά ξανά, εκτός αν το σταματήσατε εσείς σκόπιμα. Χωρίς πολιτική restart η εφαρμογή σας δεν επανέρχεται μετά από επανεκκίνηση (μάθημα 4.2). - ports: μόνο η εφαρμογή δημοσιεύει μια θύρα, και μόνο στο
127.0.0.1. Η βάση δεδομένων δεν δημοσιεύει τίποτα: η εφαρμογή τη φτάνει μέσω του εσωτερικού δικτύου. Μια βάση δεδομένων μεports: - "5432:5432"είναι ένα από τα πιο συνηθισμένα λάθη (δείτε παρακάτω). - environment: ρυθμίσεις για το πρόγραμμα. Ο κωδικός πρόσβασης δεν γράφεται αυτούσιος, αλλά ως
${DB_PASSWORD}. - volumes: ο ονομασμένος τόμος
db-dataδιατηρεί τη βάση δεδομένων όταν το container αντικαθίσταται. - depends_on με healthcheck: η εφαρμογή ξεκινά μόνο όταν η βάση δεδομένων είναι πραγματικά έτοιμη, όχι απλώς όταν έχει ξεκινήσει.
Μυστικά σε ένα αρχείο .env
Το Compose διαβάζει μεταβλητές όπως το ${DB_PASSWORD} από ένα αρχείο .env στον ίδιο φάκελο:
# /srv/notities/.env
DB_PASSWORD=Kies-hier-een-lang-willekeurig-wachtwoordanna@web1:/srv/notities$ sudo chmod 600 .envΈτσι ο κωδικός πρόσβασης δεν βρίσκεται στο αρχείο compose, το οποίο ίσως αποθηκεύετε στο Git ή επικολλάτε σε ένα φόρουμ για να ζητήσετε βοήθεια. Προσθέστε το .env στο .gitignore. Δημιουργήστε τον κωδικό πρόσβασης με έναν διαχειριστή κωδικών πρόσβασης ή με το openssl rand -base64 24.
Καθημερινή χρήση
Όλες τις εντολές τις εκτελείτε στον φάκελο που περιέχει το αρχείο compose:
anna@web1:/srv/notities$ sudo docker compose config # έλεγχος αρχείου, με συμπληρωμένες μεταβλητές
anna@web1:/srv/notities$ sudo docker compose up -d # εκκίνηση όλων (ή ενημέρωση σύμφωνα με το αρχείο)
anna@web1:/srv/notities$ sudo docker compose ps # κατάσταση ανά υπηρεσία
anna@web1:/srv/notities$ sudo docker compose logs -f app
anna@web1:/srv/notities$ sudo docker compose pull # λήψη νέων images
anna@web1:/srv/notities$ sudo docker compose up -d # αντικατάσταση των containers με τα νέα images
anna@web1:/srv/notities$ sudo docker compose down # διακοπή και αφαίρεση των containersΤο down αφήνει τους τόμους σας ανέπαφους. Το down -v διαγράφει επίσης τους τόμους της εφαρμογής, και μαζί τους τη βάση δεδομένων σας. Πληκτρολογήστε το μόνο αν θέλετε πραγματικά να ξεκινήσετε από την αρχή.
Το docker compose config είναι το αντίστοιχο των nginx -t και sshd -t: δείχνει πώς κατανοεί το Compose το αρχείο σας, με όλες τις μεταβλητές συμπληρωμένες, πριν ξεκινήσετε οτιδήποτε. Προσέξτε να μην επικολλήσετε την έξοδο κάπου, γιατί περιέχει τους κωδικούς πρόσβασης.
Το Docker παρακάμπτει το ufw
Ερχόμαστε τώρα στο σημαντικότερο θέμα ασφαλείας αυτής της ενότητας. Στο μάθημα 5.2 ρυθμίσατε το ufw σε "απόρριψη όλων, εκτός από SSH, 80 και 443". Θα περιμένατε λοιπόν ότι ένα container με ports: - "5432:5432" δεν είναι προσβάσιμο. Δεν ισχύει αυτό.
Η τεκμηρίωση του Docker το λέει ρητά: το Docker και το ufw χρησιμοποιούν κανόνες firewall με τρόπο που δεν είναι συμβατός μεταξύ τους. Όταν δημοσιεύετε μια θύρα, το Docker προσθέτει μόνο του κανόνες στο firewall του kernel. Αυτοί οι κανόνες προωθούν την κίνηση στο container (μετάφραση διευθύνσεων, DNAT) πριν εφαρμοστούν οι κανόνες του ufw. Το πακέτο λοιπόν περνά γύρω από το ufw. Το ufw status δεν δείχνει τίποτα ασυνήθιστο, ενώ η θύρα είναι προσβάσιμη από ολόκληρο το internet. Το ίδιο ισχύει και για το firewalld.
Επιπλέον, χωρίς διεύθυνση το Docker δημοσιεύει στο 0.0.0.0 και στο [::], δηλαδή σε IPv4 και σε IPv6 (μάθημα 9.1).
Τρεις τρόποι να το κάνετε σωστά, από τον απλό στον πιο σύνθετο:
- Μη δημοσιεύετε τίποτα που δεν χρειάζεται να είναι δημόσιο. Οι βάσεις δεδομένων, οι caches και οι εσωτερικές υπηρεσίες δεν παίρνουν
ports:. Τα containers στην ίδια εφαρμογή compose επικοινωνούν μεταξύ τους μέσω του εσωτερικού δικτύου. - Δημοσιεύστε στο loopback.
"127.0.0.1:8081:8080". Τότε μόνο ο ίδιος ο server έχει πρόσβαση, και τοποθετείτε μπροστά ένα reverse proxy (nginx ή Caddy, ενότητα 7) που ακούει στη θύρα 443. Μπορείτε επίσης να το ορίσετε ως προεπιλογή στο Docker: μια ρύθμιση του daemon κάνει όλες τις δημοσιευμένες θύρες χωρίς διεύθυνση να καταλήγουν στο127.0.0.1. - Κανόνες στην αλυσίδα DOCKER-USER. Το Docker δημιουργεί μια κενή αλυσίδα
DOCKER-USERπου διατρέχεται πριν από τους δικούς του κανόνες. Εκεί μπορείτε να προσθέσετε δικούς σας κανόνες, για παράδειγμα "μόνο αυτή η διεύθυνση IP επιτρέπεται προς τις δημοσιευμένες θύρες". Πρόκειται για προχωρημένη εργασία με iptables ή nftables, όπου τα λάθη γίνονται εύκολα. Για τους περισσότερους μικρούς servers αρκούν οι τρόποι 1 και 2.
Ελέγχετε πάντα από έξω, όπως στο μάθημα 5.1: nc -vz -w 3 <ip> 5432 από τον φορητό σας υπολογιστή, ή με τον έλεγχο του μαθήματος στις ασκήσεις Z5.1 και Z10.4.
Δύο ακόμη πράγματα που δεν κάνετε
- Μην προσαρτάτε το
/var/run/docker.sockσε ένα container, εκτός αν ξέρετε ακριβώς γιατί. Ένα container με πρόσβαση στο socket του Docker μπορεί να ξεκινά μόνο του containers, ακόμη και ένα με ολόκληρο τον δίσκο του host (μάθημα 9.2). Ορισμένα βοηθητικά προγράμματα (dashboards, αυτόματοι ενημερωτές) το ζητούν· στην πράξη αυτό σημαίνει ότι δίνετε root σε αυτό το πρόγραμμα. - Μη χρησιμοποιείτε
privileged: trueήnetwork_mode: hostμόνο επειδή το λέει ένας οδηγός. Και τα δύο αφαιρούν σε μεγάλο βαθμό την απομόνωση του container. Χρησιμοποιήστε τα μόνο αν ο δημιουργός εξηγεί γιατί είναι απαραίτητα.
Σύνοψη
- Ένα αρχείο compose (
compose.yaml) περιγράφει όλα τα containers μιας εφαρμογής· αποτελεί ταυτόχρονα τεκμηρίωση και ανήκει στο αντίγραφο ασφαλείας σας. - Χρησιμοποιήστε ετικέτες έκδοσης, πολιτική restart, ονομασμένους τόμους, healthcheck όπου είναι δυνατόν, και μυστικά σε ένα
.envμε δικαιώματα 600. docker compose config,up -d,ps,logs,pull,down· τοdown -vδιαγράφει επίσης τους τόμους.- Οι δημοσιευμένες θύρες παρακάμπτουν το ufw και το firewalld. Δημοσιεύστε μόνο ό,τι πρέπει να είναι δημόσιο, και κατά τα άλλα στο
127.0.0.1με ένα reverse proxy μπροστά. - Όχι docker.sock,
privilegedήnetwork_mode: hostχωρίς σοβαρό λόγο.
Κουίζ
Άσκηση
Άσκηση Z9.3: Έλεγχος ενός αρχείου compose. Στο αρχείο παραδείγματος compose-nakijken.txt βρίσκεται (ως αρχείο κειμένου) το αρχείο compose ενός μικρού ηλεκτρονικού καταστήματος με μια εφαρμογή, μια βάση δεδομένων, μια cache και ένα εργαλείο διαχείρισης. Περιέχει έξι λάθη που εξετάστηκαν σε αυτό το μάθημα ή στα μαθήματα 9.1 και 9.2. Για κάθε αριθμημένο μπλοκ γραμμών δηλώστε αν είναι σωστό ή λάθος, και για κάθε λάθος επιλέξτε τη σωστή διόρθωση. Το πρόγραμμα συγκρίνει τις απαντήσεις σας με το σταθερό κλειδί. Αν έχετε δικό σας αρχείο compose, ελέγξτε το στη συνέχεια με βάση τα ίδια σημεία.
Για περαιτέρω μελέτη
- Τεκμηρίωση Docker, How Compose works (compose.yaml): https://docs.docker.com/compose/intro/compose-application-model/
- Τεκμηρίωση Docker, Version and name top-level elements: https://docs.docker.com/reference/compose-file/version-and-name/
- Τεκμηρίωση Docker, Packet filtering and firewalls (Docker and ufw): https://docs.docker.com/engine/network/packet-filtering-firewalls/
- Τεκμηρίωση Docker, Port publishing and mapping: https://docs.docker.com/engine/network/port-publishing/
- Τεκμηρίωση Docker, Interpolation (μεταβλητές και .env): https://docs.docker.com/reference/compose-file/interpolation/
