Docker и Docker Compose — професионално ръководство

Част 6: Docker Networking

Контейнерите не са острови. Те трябва да комуникират помежду си, с host системата и, когато е необходимо, с външния свят. Тук разглеждам Docker Networking от основите до практическите схеми, които използвам в реална инфраструктура.

Най-важното правило при Docker Networking: не публикувай port, само защото приложението има такъв. Published port е механизъм за достъп отвън към container, а не изискване, за да могат два containers да комуникират помежду си.

1. Как мисля за Docker Networking

Docker не стартира контейнерите просто като процеси, които случайно са се оказали на една машина. Всеки container има собствен network namespace и собствена мрежова конфигурация. Docker Engine изгражда необходимата свързаност чрез network drivers, virtual interfaces и правила на host системата.

На практика това означава, че мога да имам nginx, application и database на един host, без да отварям database към локалната мрежа. Web контейнерът говори с database контейнера през Docker network, докато само nginx има публикуван port към host-а.

Client

HTTP/HTTPS

Nginx

Published port

Application

Docker network

Database

Достъпна само през вътрешната Docker network

2. Основните Docker Network Drivers

Docker поддържа няколко network drivers. Не е необходимо да използвам всички, но трябва да разбирам разликата между тях, защото изборът влияе директно върху изолацията и начина, по който контейнерите комуникират.

DriverПредназначениеПрактическа употреба
bridgeИзолирана мрежа за containers на един Docker hostНай-често срещаният избор
hostContainer използва network stack-а на host-аСпециализирани случаи
noneБез нормална мрежова свързаностСилно изолирани containers
overlayМрежа между Docker hostsDocker Swarm и разпределени среди
macvlanContainer получава собствен MAC адресСпециализирани L2 сценарии

3. Bridge Network — работният кон

За обикновена инфраструктура с няколко containers на един host bridge network е основният инструмент. Docker създава виртуална мрежа и включва контейнерите в нея чрез виртуални Ethernet интерфейси.

Има важна разлика между автоматично създадената default bridge network и собствена user-defined bridge network. За реални приложения предпочитам втория вариант.

TERMINAL — USER-DEFINED NETWORK
docker network create app_net

docker network ls

docker network inspect app_net
User-defined bridge network дава по-добър контрол и позволява containers да се намират един друг по име чрез вградения Docker DNS.

4. Комуникация между Containers

Нека създам два containers в една network. Няма нужда да знам IP адреса на първия container, за да го достигна от втория.

TERMINAL
docker network create backend

docker run -d \
  --name database \
  --network backend \
  postgres:16-alpine

docker run -it --rm \
  --network backend \
  alpine sh

От временния Alpine container мога да проверя резолюцията на името database. Docker DNS ще разреши това име към адреса на съответния container в network-а.

ВЪТРЕ В CONTAINER-А
getent hosts database
ping database
Не връзвай application към database по IP адрес. Използвай име на service или container в user-defined network. Container може да бъде унищожен и създаден отново с друг IP адрес.

5. Published Ports ≠ Container-to-Container Networking

Това е едно от най-важните разграничения в Docker.

ПРИМЕР
docker run -d \
  --name web \
  -p 8080:80 \
  nginx:alpine

8080:80 означава: port 8080 на host-а се публикува към port 80 в container-а. Това е необходимо, ако клиент извън Docker network-а трябва да достигне nginx.

Ако два containers са в една Docker network, вторият не трябва да използва host port-а, за да достигне първия.

ПРАВИЛНО
http://web:80
Моят практичен принцип: публикувам само портовете, които трябва да бъдат достъпни извън съответната Docker network. Всичко останало оставям вътрешно.

6. Network Inspect — когато нещо не работи

Когато контейнерите „трябва“ да се виждат, но не се виждат, не започвам да гадая. Първата ми работа е да проверя network configuration.

DIAGNOSTICS
docker network ls
docker network inspect backend
docker inspect database
docker exec -it database sh

В изхода на docker network inspect особено ме интересува секцията Containers. Там мога веднага да видя кои containers са включени в network-а и какви адреси са получили.

6.1. Типичен диагностичен ред

  1. Проверявам дали и двата containers са running;
  2. проверявам дали са в една и съща user-defined network;
  3. проверявам DNS резолюцията по име;
  4. проверявам дали приложението слуша на правилния port;
  5. проверявам firewall и други мрежови политики;
  6. чак тогава търся проблем в самото приложение.

7. Един Container в Няколко Networks

Docker позволява един container да бъде включен едновременно в няколко мрежи. Това е полезно за разделяне на frontend и backend комуникацията.

proxy_net

Reverse proxy ↔ application

app

Участва и в двете networks

db_net

application ↔ database

Така database container-ът може да бъде извън frontend network-а. Той приема връзки само от containers, които имат достъп до db_net.

TERMINAL
docker network create proxy_net
docker network create db_net

docker run -d --name database --network db_net postgres:16-alpine

docker run -d --name app --network db_net myapp:latest

docker network connect proxy_net app

8. Docker Compose Networking

При Docker Compose ситуацията става още по-интересна, защото Compose автоматично създава network за проекта. Services, които са част от един Compose project, могат да комуникират помежду си по service name.

compose.yaml
services:

  web:
    image: nginx:alpine
    depends_on:
      - app

  app:
    image: myapp:latest
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: secret

Application container-ът не трябва да използва localhost, за да се свърже с PostgreSQL. localhost означава текущия container.

ПРАВИЛНИЯТ DATABASE HOST
DB_HOST=db
DB_PORT=5432

При Compose service name е hostname в рамките на Compose network-а.

9. Когато Compose Network-ът трябва да е изричен

При по-голяма инфраструктура често искам да определя topology-то изрично.

compose.yaml
services:

  proxy:
    image: nginx:alpine
    networks:
      - frontend

  app:
    image: myapp:latest
    networks:
      - frontend
      - backend

  db:
    image: postgres:16-alpine
    networks:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

Тук proxy няма директна връзка с database. Application container-ът е единственият, който участва и в двете мрежи.

Това вече е архитектура, а не просто Docker команда. Когато разделям networks по предназначение, контролирам кой компонент изобщо има възможност да говори с друг компонент.

10. Най-опасната дума: localhost

Един от най-честите проблеми при Docker Networking е configuration, която изглежда напълно логична:

ГРЕШНО В CONTAINER
DB_HOST=localhost

Ако application и database са в различни containers, това няма да работи. localhost сочи към network namespace-а на application container-а, а не към Docker host-а и не към database container-а.

ПРАВИЛНО
DB_HOST=db

11. Networking и Security

Docker Networking не е само въпрос на това „да тръгне“. Мрежовата архитектура е част от security модела на приложението.

  • Не публикувам database ports без реална необходимост;
  • разделям frontend и backend traffic, когато архитектурата го изисква;
  • не приемам, че container network автоматично решава всички security проблеми;
  • проверявам реалната network topology, вместо да разчитам на предположения.
Published port означава достъп. Преди да добавя -p 5432:5432, трябва да имам конкретна причина. Ако application и PostgreSQL са в една Docker network, обикновено такъв publish не е необходим.

12. Когато Networking не работи

При проблем с комуникацията между containers не променям на случаен принцип IP адреси, ports и configuration файлове. Работя последователно.

МОЯТ БАЗОВ DIAGNOSTIC CHECK
docker ps
docker network ls
docker network inspect app_net
docker inspect app
docker exec -it app sh

# Вътре в container-а:
getent hosts db

Ако getent hosts db не върне адрес, проблемът е много вероятно в network membership или DNS. Ако името се резолвира, но връзката към port-а не работи, проверявам дали приложението слуша на правилния interface и port.

13. Практически правила, които следвам

  1. Използвам user-defined bridge networks за приложения на един host.
  2. Използвам service names, а не IP адреси между Compose services.
  3. Не използвам localhost за комуникация с друг container.
  4. Публикувам само необходимите ports.
  5. Разделям frontend и backend networks, когато архитектурата го оправдава.
  6. Проверявам network topology с docker network inspect, преди да започна да гадая.
  7. Не бъркам published port с вътрешна container комуникация.
Накратко: добрият Docker Networking не е този с най-много публикувани портове. Добър е този, при който всеки container има точно толкова мрежов достъп, колкото реално му е необходим.

Заключение

Docker Networking изглежда просто, докато не започнеш да изграждаш реална инфраструктура. Тогава разликата между host port, container port, service name, network namespace и Docker network става критична.

За мен правилният подход е да мисля първо за комуникационните зависимости, а след това за командите. Кой трябва да вижда кого? Кой port трябва да бъде достъпен отвън? Кои containers трябва да останат изолирани?

Когато тези въпроси са ясни, Docker Networking престава да бъде магия. Получава се нормална мрежова архитектура — само че реализирана върху containers.