Модул 9 · 9.3
Docker compose и защитната стена
Модул 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) стартирате един контейнер с дълъг ред опции. Едно истинско приложение обаче често се състои от няколко контейнера: програма, база данни, reverse proxy. Ако трябва да помните тези редове и да ги въвеждате отново след рестарт или актуализация, рано или късно ще сгрешите.
Docker Compose описва всички контейнери на едно приложение в един файл: compose.yaml. Поставяте този файл в отделна папка, например /srv/status/compose.yaml, и управлявате всичко с docker compose. Файлът е едновременно и Вашата документация: какво работи, кой image, кои портове, къде се намират данните. Включете тази папка в резервното си копие (урок 8.1) и ще можете да възстановите приложението на нов сървър за няколко минути.
Плъгинът се казва 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: всяка услуга става контейнер. Името (
app,db) е и името на хоста, по което контейнерите се намират един друг. Compose създава отделна мрежа за приложението, така чеappсе свързва с базата данни наdb:5432. - image: винаги с таг за версия (урок 9.1).
- restart: какво прави Docker, когато контейнерът спре или сървърът се рестартира.
unless-stoppedго стартира отново, освен ако Вие съзнателно не сте го спрели. Без политика за restart Вашето приложение няма да се върне след рестарт (урок 4.2). - ports: само приложението публикува порт, и то само на
127.0.0.1. Базата данни не публикува нищо: приложението я достига през вътрешната мрежа. База данни сports: - "5432:5432"е една от най-честите грешки (вижте по-долу). - environment: настройки за програмата. Паролата не е записана буквално, а като
${DB_PASSWORD}. - volumes: именуваният volume
db-dataзапазва базата данни, когато контейнерът бъде заменен. - 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 # замяна на контейнерите с новите images
anna@web1:/srv/notities$ sudo docker compose down # спиране и премахване на контейнеритеdown оставя Вашите volumes. down -v изтрива и volumes на приложението, а с тях и Вашата база данни. Въвеждайте това само ако наистина искате да започнете отначало.
docker compose config е еквивалентът на nginx -t и sshd -t: показва как Compose разбира Вашия файл, с попълнени всички променливи, преди да стартирате каквото и да е. Внимавайте да не поставяте този изход никъде, защото паролите са в него.
Docker заобикаля ufw
Сега най-важният въпрос за сигурността в този модул. В урок 5.2 настроихте ufw на "отказ на всичко, освен SSH, 80 и 443". Бихте очаквали контейнер с ports: - "5432:5432" тогава да е недостъпен. Това не е така.
Документацията на Docker го казва дословно: Docker и ufw използват правилата на защитната стена по начин, който не е съвместим помежду си. Когато публикувате порт, Docker сам добавя правила в защитната стена на ядрото. Тези правила пренасочват трафика към контейнера (транслация на адреси, DNAT), преди да се приложат правилата на ufw. Пакетът значи заобикаля ufw. ufw status не показва нищо особено, докато портът е достъпен от целия интернет. Същото важи и за firewalld.
Освен това Docker без адрес публикува на 0.0.0.0 и [::], т.е. едновременно по IPv4 и IPv6 (урок 9.1).
Три начина да го направите правилно, от простия към сложния:
- Не публикувайте нищо, което не трябва да е публично. Бази данни, кешове и вътрешни услуги не получават
ports:. Контейнерите в едно и също compose приложение се достигат един друг през вътрешната мрежа. - Публикувайте на loopback.
"127.0.0.1:8081:8080". Тогава достъп има само самият сървър, а пред него поставяте reverse proxy (nginx или Caddy, модул 7), който слуша на 443. Docker може да получи това и като стандарт: настройка на daemon кара всички публикувани портове без адрес да попадат на127.0.0.1. - Правила във веригата DOCKER-USER. Docker създава празна верига
DOCKER-USER, която се обхожда преди неговите собствени правила. Там можете сами да добавите правила, например "само този IP адрес има достъп до публикуваните портове". Това е работа за напреднали с iptables или nftables и лесно може да се сбърка. За повечето малки сървъри 1 и 2 са достатъчни.
Винаги проверявайте отвън, както в урок 5.1: nc -vz -w 3 <ip> 5432 от Вашия лаптоп или чрез проверката от курса в задача Z5.1 и Z10.4.
Още две неща, които не правите
- Не монтирайте
/var/run/docker.sockв контейнер, освен ако не знаете точно защо. Контейнер с достъп до Docker сокета може сам да стартира контейнери, включително такъв с целия диск на хоста (урок 9.2). Някои помощни програми (табла за управление, автоматични обновители) го изискват; на практика това означава да дадете root на тази програма. - Не използвайте
privileged: trueилиnetwork_mode: hostсамо защото така пише в някое ръководство. И двете в голяма степен премахват изолацията на контейнера. Използвайте ги само ако създателят обяснява защо е необходимо.
Обобщение
- Compose файлът (
compose.yaml) описва всички контейнери на едно приложение; той е и документация и трябва да е в резервното Ви копие. - Използвайте тагове за версия, политика за restart, именувани volumes, healthcheck, където е възможно, и тайни в
.envс права 600. docker compose config,up -d,ps,logs,pull,down;down -vизтрива и volumes.- Публикуваните портове заобикалят ufw и firewalld. Публикувайте само това, което трябва да е публично, а всичко друго на
127.0.0.1с reverse proxy пред него. - Без docker.sock,
privilegedилиnetwork_mode: hostбез основателна причина.
Тест (quiz)
Задача
Задача Z9.3: Проверка на compose файл. В примерния файл compose-nakijken.txt се намира (като текстов файл) compose файлът на малък онлайн магазин с приложение, база данни, кеш и инструмент за администриране. В него има шест грешки, разгледани в този урок или в уроци 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/
