Главная/Справочники/Справочник

Networking cheat sheet

Управление сетями, публикация портов, диагностика.

127.0.0.1 внутри container'а — петлевой интерфейс самого container'а, а не хоста. Половина сетевых затруднений сводится к этому (урок 8.3).

Содержание


Управление сетями

ЗадачаКоманда
Списокdocker network ls
Создать пользовательскуюdocker network create app-net
Создать без выхода наружуdocker network create --internal secure-net
Задать подсетьdocker network create --subnet 172.30.0.0/16 net
Подробности и участникиdocker network inspect app-net
Подключить работающий containerdocker network connect app-net имя
Отключитьdocker network disconnect app-net имя
Удалить неиспользуемыеdocker network prune

Режимы сети

РежимЧто даётЧего лишает
bridge (умолчание)Свой namespace, адрес в мосте
Пользовательская сетьТо же плюс разрешение имён
--network hostСеть хоста напрямую, нет NATСетевую изоляцию целиком
--network noneТолько петляЛюбую связь
--network container:имяТот же namespace, что у другого

Последний режим — основной приём отладки: подключить container с утилитами к сети исследуемого.


Публикация портов

bash
docker run -p 8080:8000 образ              # доступен из сети
docker run -p 127.0.0.1:8080:8000 образ    # только с хоста
docker run -p 8000 образ                   # случайный порт хоста
docker run -P образ                        # все EXPOSE на случайные порты
docker port имя                            # какие порты опубликованы

Синтаксис: [адрес_хоста:]порт_хоста:порт_container'а.

ЧтоЗначение
EXPOSE в DockerfileДокументация; ничего не публикует
expose: в ComposeТо же; доступ только внутри сетей проекта
ports: в ComposeНастоящая публикация

Три условия, чтобы запрос дошёл: порт опубликован, приложение слушает этот порт, приложение слушает 0.0.0.0, а не петлю. Нарушение любого даёт один и тот же симптом.


Разрешение имён

ГдеРаботает ли по именам
Сеть по умолчанию bridgeНет
Пользовательская сетьДа, по имени container'а
ComposeДа, по имени сервиса
--network hostРазрешение хоста
bash
docker run --network app-net --name db образ
docker exec api getent hosts db

Обращение к хосту изнутри

СпособЗаметка
host.docker.internalDocker Desktop; на Linux требует --add-host
--add-host=host.docker.internal:host-gatewayЯвно на Linux
Адрес шлюза сетиdocker network inspect bridge --format '{{(index .IPAM.Config 0).Gateway}}'

Диагностика изнутри

Образы slim и alpine не содержат curl, ss, netstat, dig. Python есть — им и пользуемся.

Слушает ли порт:

bash
docker exec имя python -c "
import socket
for addr in ('127.0.0.1', '0.0.0.0'):
    s = socket.socket(); s.settimeout(1)
    print(addr, 'открыт' if s.connect_ex((addr, 8000)) == 0 else 'закрыт')"

Разрешается ли имя:

bash
docker exec имя getent hosts db
docker exec имя python -c "import socket; print(socket.gethostbyname('db'))"

Доступен ли сосед:

bash
docker exec имя python -c "
import socket
s = socket.create_connection(('db', 5432), 2); s.close(); print('доступен')"

HTTP-запрос без curl:

bash
docker exec имя python -c "
import urllib.request
r = urllib.request.urlopen('http://127.0.0.1:8000/healthz')
print(r.status, r.read()[:200])"

Что видит container о сети:

bash
docker exec имя cat /etc/resolv.conf     # серверы имён
docker exec имя cat /etc/hosts           # статические записи
docker exec имя cat /proc/net/tcp        # соединения; порты в шестнадцатеричном виде

Разбор /proc/net/tcp:

bash
docker exec имя python -c "
for line in open('/proc/net/tcp').read().splitlines()[1:]:
    f = line.split()
    addr, port = f[1].split(':')
    state = f[3]
    if state == '0A':   # LISTEN
        ip = '.'.join(str(int(addr[i:i+2], 16)) for i in (6, 4, 2, 0))
        print(f'{ip}:{int(port, 16)}')"

Диагностика снаружи

Полный набор утилит без изменения образа:

bash
docker run --rm -it --network container:имя nicolaka/netshoot

Второй container подключается к тому же network namespace: ss, tcpdump, dig работают так, как если бы были внутри (урок 13.5).

То же средствами хоста:

bash
PID=$(docker inspect имя --format '{{.State.Pid}}')
sudo nsenter -n -t "$PID" ss -tlnp

Прочее:

bash
docker port имя                                        # опубликованные порты
docker inspect имя --format '{{json .NetworkSettings.Networks}}'
docker network inspect app-net --format '{{json .Containers}}'
docker network inspect app-net --format '{{.Internal}}'

От симптома к проверке

СимптомПервая проверкаВероятная причина
Порт опубликован, ответа нетСлушает ли изнутриСлушает 127.0.0.1 или другой порт
Изнутри отвечает, снаружи нетАдрес привязки127.0.0.1 вместо 0.0.0.0
Имя не разрешаетсяdocker inspect сетейСеть по умолчанию bridge
Имя разрешается, соединения нетСтрока подключенияlocalhost вместо имени сервиса
Внешние имена не разрешаютсяdocker network inspect --format '{{.Internal}}'internal: true или DNS хоста
Работает при --network host, иначе нетАдрес привязкиПриложение слушает петлю
Соединение к базе отклонено при стартеdepends_onЖдёт запуска, а не готовности
Порт занят на хостеdocker ps --format '{{.Names}} {{.Ports}}'Другой container уже опубликовал
--scale не работаетcontainer_name в конфигурацииИмя фиксировано

Разбор самого частого случая

curl localhost:8080 не отвечает при -p 8080:8000:

bash
# 1. Опубликован ли порт на самом деле
docker port имя

# 2. Слушает ли приложение внутри — и на каком адресе
docker exec имя python -c "
import socket
for addr in ('127.0.0.1', '0.0.0.0'):
    s = socket.socket(); s.settimeout(1)
    print(addr, s.connect_ex((addr, 8000)))"

# 3. Запустилось ли приложение вообще
docker logs --tail 20 имя
Результат шага 2Вывод
127.0.0.1 = 0, 0.0.0.0 ≠ 0Слушает петлю — исправить адрес привязки
оба ≠ 0Приложение не слушает этот порт
оба = 0, снаружи молчитПроблема в публикации или в правилах хоста

Навигация

Вернуться к справочникам
Docker CLI cheat sheet
Debugging checklist
Раздел 08. Networking
Главное оглавление

Markdown на GitHub ↗