Устранение неполадок в сети Docker: эффективное решение проблем с подключением

Исправьте проблемы с сетью Docker, связанные с DNS контейнеров, пользовательскими сетями, публикацией портов, доступом к хосту, DNS и брандмауэрами.

Устранение неполадок в сети Docker: эффективное решение проблем с подключением

Проблемы с сетью Docker решаются гораздо проще, если указать направление неудачного соединения. «Контейнер не может подключиться» — слишком расплывчато. Хост пытается подключиться к контейнеру? Один контейнер пытается подключиться к другому? Контейнер пытается выйти в интернет? Трафик поступает с другой машины? Каждый путь использует разное поведение Docker.

Начните с записи пути на простом языке:

браузер на хосте -> localhost:8080 -> порт контейнера 80
контейнер api -> контейнер db -> порт 5432
контейнер worker -> публичный интернет -> api.example.com:443
удаленный ноутбук -> публичный IP сервера -> опубликованный порт контейнера

Как только вы знаете путь, вы можете проверить каждый переход, вместо того чтобы менять сети наугад.

Знайте основные режимы сети Docker

Большинство одноконтейнерных установок Docker используют мостовую сеть. Docker создает виртуальную сеть на хосте, присваивает контейнерам частные IP-адреса и может публиковать выбранные порты контейнера на хосте.

Сеть bridge по умолчанию работает, но пользовательские мостовые сети лучше подходят для приложений, поскольку они обеспечивают встроенный DNS по имени контейнера. Это означает, что контейнер api может подключиться к контейнеру db по адресу db:5432, если оба подключены к одной пользовательской сети.

Создайте такую сеть следующим образом:

docker network create appnet
docker run -d --name db --network appnet postgres:16
docker run -d --name api --network appnet my-api

Существуют и другие режимы. Сеть host разделяет пространство имен сети хоста и отключает обычное поведение публикации портов; это полезно в некоторых случаях на Linux, но снижает изоляцию. none не предоставляет контейнеру сети. overlay предназначен для многоконтейнерной сети Docker Swarm. Compose автоматически создает пользовательские сети для проектов, если не настроено иное.

«Сеть не найдена»

Эта ошибка обычно означает, что имя сети указано неверно или сеть существует в другом контексте, чем ожидает команда.

Проверьте доступные сети:

docker network ls

Проверьте сеть, которую вы собираетесь использовать:

docker network inspect appnet

Если она не существует, создайте ее:

docker network create appnet

В Compose фактическое имя сети может иметь префикс имени проекта. Сеть с именем backend в compose.yml может отображаться как myproject_backend. Используйте:

docker compose ps
docker network ls

Если вы объявили внешнюю сеть Compose, Compose не будет создавать ее за вас:

networks:
  appnet:
    external: true

В этом случае создайте ее вручную или удалите external: true, если Compose должен управлять ею.

Связь между контейнерами не работает

Чтобы два контейнера могли общаться по имени, они обычно должны находиться в одной пользовательской сети. Сначала подтвердите это:

docker network inspect appnet

Найдите оба контейнера в разделе Containers.

Затем протестируйте из контейнера, в котором есть базовые инструменты. Ваш образ приложения может не включать curl, dig или ping, и это нормально. Используйте временный отладочный контейнер в той же сети:

docker run --rm -it --network appnet nicolaka/netshoot

Изнутри:

dig db
curl -v http://api:8080/health
nc -vz db 5432

Если DNS не работает, контейнеры, вероятно, не находятся в одной пользовательской сети, или вы используете сеть bridge по умолчанию, ожидая разрешения имен, которое она не предоставляет таким же образом. Если DNS работает, но соединение не устанавливается, проверьте, прослушивает ли целевая служба ожидаемый порт.

Внутри целевого контейнера:

docker exec -it api sh
ss -ltnp || netstat -ltnp

Распространенная ошибка — привязка приложения к 127.0.0.1 внутри контейнера. Это приводит к прослушиванию только на loopback-интерфейсе внутри этого контейнера. Другие контейнеры не могут к нему подключиться. Настройте приложение на прослушивание на 0.0.0.0.

Также убедитесь, что вы используете порт контейнера, а не опубликованный на хосте порт, для трафика между контейнерами. Если база данных прослушивает порт 5432 в контейнере, другие контейнеры должны использовать db:5432, а не localhost:15432 или опубликованный порт хоста.

Хост не может подключиться к контейнеру

Чтобы хост мог подключиться к службе в контейнере с мостовой сетью, обычно требуется опубликованный порт:

docker run -d --name web -p 8080:80 nginx

Это сопоставляет порт хоста 8080 с портом контейнера 80. Проверьте с хоста:

curl -v http://localhost:8080

Проверьте, что опубликовал Docker:

docker port web
docker ps --format 'table {{.Names}}	{{.Ports}}'

Если сопоставления портов нет, EXPOSE в Dockerfile не публикует порт. EXPOSE — это документация и метаданные. Вам все равно нужен -p или ports: в Compose.

Если порт опубликован, но соединение не устанавливается, проверьте четыре вещи:

  1. Приложение прослушивает внутри контейнера порт контейнера.
  2. Приложение прослушивает на 0.0.0.0, а не только на 127.0.0.1.
  3. Брандмауэр хоста не блокирует порт хоста.
  4. Никакой другой процесс не занимает порт хоста.

Найдите конфликты портов хоста:

sudo lsof -i :8080
# или
sudo ss -ltnp 'sport = :8080'

Для Compose запомните синтаксис:

ports:
  - "8080:80"

Левая сторона — порт хоста. Правая сторона — порт контейнера.

Контейнер не может выйти в интернет

Проверьте IP-связь и DNS отдельно:

docker exec -it app sh
ping -c 2 1.1.1.1
ping -c 2 example.com

Некоторые образы не включают ping. Используйте curl, если он доступен:

curl -I https://example.com

Если IP работает, но имена не разрешаются, это проблема DNS. Проверьте:

cat /etc/resolv.conf

Docker обычно внедряет настройки резолвера. Корпоративные VPN, пользовательские DNS и сеть Docker Desktop могут усложнить ситуацию. Вы можете настроить DNS на уровне демона в настройках Docker или передать DNS для конкретного контейнера:

docker run --dns 1.1.1.1 ...

Не используйте публичные DNS вслепую в корпоративных средах, где должны разрешаться внутренние имена. Используйте DNS-серверы, подходящие для сети.

Если ни IP, ни DNS не работают, проверьте, находится ли контейнер в сети --network none, не нарушены ли правила брандмауэра/NAT хоста, есть ли у демона Docker пользовательские настройки сети и есть ли у самого хоста доступ в интернет.

Контейнеру необходимо подключиться к службе на хосте

Из контейнера localhost означает сам контейнер, а не хост. Это один из самых распространенных сюрпризов в сети Docker.

В Docker Desktop host.docker.internal обычно разрешается в хост. В современном Docker Engine для Linux вы можете добавить запись шлюза хоста:

docker run --add-host=host.docker.internal:host-gateway ...

Затем контейнер может вызвать:

curl http://host.docker.internal:3000

Убедитесь, что служба хоста прослушивает адрес, доступный из Docker, а не только привязку loopback, к которой Docker не может получить доступ в вашей среде. Если локальный сервер разработки привязан только к 127.0.0.1, вам может потребоваться привязать его к 0.0.0.0 или интерфейсу хоста, в зависимости от ОС и требований безопасности.

Удаленные машины не могут подключиться к контейнеру

Если хост может подключиться к localhost:8080, но другая машина не может подключиться к server-ip:8080, с Docker все может быть в порядке. Проверьте брандмауэр хоста, группу безопасности облака, маршрутизатор/NAT и то, не опубликовал ли Docker только на loopback.

Это публикует на всех интерфейсах хоста:

docker run -p 8080:80 nginx

Это публикует только на localhost:

docker run -p 127.0.0.1:8080:80 nginx

Публикация только на loopback часто желательна для локальной разработки или настройки обратного прокси, но она по определению блокирует удаленный доступ.

На облачных серверах также проверьте брандмауэры провайдера. Открытие ufw на виртуальной машине не поможет, если группа безопасности облака все еще блокирует порт.

Ошибки сети в Compose

Compose присваивает каждой службе DNS-имя на основе имени службы. Если ваша служба называется db, другие службы обычно должны подключаться к db, а не к localhost.

Пример:

services:
  api:
    build: .
    environment:
      DATABASE_URL: postgres://postgres:postgres@db:5432/app
    depends_on:
      - db
  db:
    image: postgres:16

depends_on управляет порядком запуска, а не готовностью. Контейнер базы данных может запуститься до того, как Postgres будет готов принимать соединения. Ваше приложение должно повторять попытки подключения или использовать шаблон запуска с проверкой состояния.

Также различайте ports и expose. ports публикует на хосте. expose документирует или открывает порты для связанных служб, но не делает их доступными с хоста таким же образом.

Отладка на уровне пакетов

Когда обычные проверки не объясняют проблему, используйте образ для отладки сети:

docker run --rm -it --network container:<target-container> nicolaka/netshoot

Это присоединяется к пространству имен сети целевого контейнера, что позволяет вам проверять сеть с той же точки зрения без установки инструментов в образ приложения.

Полезные команды включают:

ip addr
ip route
cat /etc/resolv.conf
dig service-name
curl -v http://service-name:port
tcpdump -nn -i any port 8080

Используйте tcpdump, когда вам нужно узнать, прибывают ли пакеты вообще. Если пакеты никогда не прибывают, ищите проблему до контейнера: публикация, брандмауэр, маршрутизация, балансировщик нагрузки. Если пакеты прибывают, а ответ не уходит, ищите проблему внутри контейнера или приложения.

Краткий порядок устранения неполадок

Используйте этот порядок для большинства проблем с сетью Docker:

  1. Определите точный путь: хост к контейнеру, контейнер к контейнеру, контейнер к интернету или удаленный к хосту к контейнеру.
  2. Проверьте подключение к сети с помощью docker network inspect.
  3. Проверьте разрешение имен со стороны источника.
  4. Проверьте, прослушивает ли целевой процесс правильный интерфейс и порт.
  5. Проверьте публикацию портов только для трафика, который пересекает границу от хоста к контейнеру.
  6. Проверьте брандмауэр, VPN, прокси, DNS и правила безопасности облака вне Docker.

Большинство проблем с сетью Docker — это не глубокие ошибки Docker. Обычно это неправильные имена, неправильные порты, привязка к loopback, отсутствие опубликованных портов, предположения о DNS или тестирование трафика с неправильной стороны границы.