8.1. Основы Docker networking
Цели
После этого материала вы сможете:
- перечислить, что именно изолирует сетевой namespace;
- объяснить, как veth pair соединяет container с host;
- найти на host интерфейс, парный к
eth0конкретного container'а; - прочитать таблицу маршрутизации внутри container и объяснить каждую строку;
- проследить путь пакета от процесса в container до внешней сети и обратно;
- выбрать тип сети под задачу.
Предварительные знания
- 2.3. Linux namespaces;
- базовое понимание IP, маршрутизации и NAT.
Ключевые термины
| Термин | Объяснение |
|---|---|
network namespace | Изолированный сетевой стек: интерфейсы, маршруты, правила |
veth pair | Пара виртуальных интерфейсов; всё, что вошло в один, выходит из другого |
bridge | Виртуальный коммутатор второго уровня внутри ядра |
docker0 | Bridge сети по умолчанию |
MASQUERADE | Разновидность SNAT: подмена адреса источника на адрес выходного интерфейса |
DNAT | Подмена адреса назначения — механизм публикации портов |
iflink | Номер парного интерфейса veth pair |
Теория
Что изолирует сетевой namespace
Сетевой namespace — не «фильтр», а полностью отдельный экземпляр сетевого стека ядра (урок 2.3).
| Что своё у каждого namespace | Следствие |
|---|---|
| Сетевые интерфейсы | eth0 container'а и eth0 host — разные устройства |
| Таблица маршрутизации | Свой маршрут по умолчанию |
| Правила netfilter | Свои цепочки iptables |
| Таблица ARP и соседей | Не видит соседей host |
| Порты | Порт 8000 занят в container'е — на host свободен |
localhost (127.0.0.1) | Свой loopback, не тот, что у host |
Последняя строка — источник самой частой ошибки в разделе. localhost внутри container означает сам container. Приложение, слушающее 127.0.0.1, недоступно ниоткуда, кроме этого же container'а (урок 8.3).
Как namespace соединяется с внешним миром
Изолированный стек бесполезен без канала наружу. Канал делает veth pair — пара виртуальных интерфейсов, работающих как труба: пакет, отправленный в один конец, выходит из другого.
Network namespace container'а Host (root namespace)
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ │ │ │
│ процесс (uvicorn :8000) │ │ docker0 (bridge) │
│ │ │ │ 172.17.0.1/16 │
│ ▼ │ │ ┌────┴─────┬─────────┐ │
│ eth0 172.17.0.2/16 ═════╪══════╪═══ veth1a2b veth3c4d │ │
│ │ │ veth │ │ │ │
│ ▼ │ pair │ ▼ │ │
│ lo 127.0.0.1 │ │ (другой container)│
│ (свой, не host!) │ │ │
└──────────────────────────────┘ │ eth0 (физический) │
└──────────────┬───────────────┘
▼
внешняя сеть
Три элемента конструкции:
eth0внутри container'а — один конец veth pair, перенесённый в его namespace.vethXXXXна host — второй конец, подключённый к bridge.docker0— bridge, играющий роль коммутатора: пересылает кадры между всеми подключёнными veth и наружу.
Bridge имеет собственный IP (172.17.0.1), и он же является шлюзом по умолчанию для container'ов.
Путь пакета наружу
Запрос из container'а к внешнему адресу:
| Шаг | Что происходит |
|---|---|
| 1 | Процесс пишет в сокет; ядро выбирает маршрут в таблице namespace container'а |
| 2 | Маршрут по умолчанию ведёт на 172.17.0.1 через eth0 |
| 3 | Пакет проходит veth pair и появляется на host |
| 4 | Bridge docker0 передаёт его в маршрутизацию host |
| 5 | Правило MASQUERADE подменяет адрес источника на адрес host |
| 6 | Пакет уходит через физический интерфейс |
| 7 | Ответ возвращается, conntrack восстанавливает исходный адрес, путь разворачивается |
Шаг 5 объясняет, почему внешний сервер видит адрес host, а не 172.17.0.2: адрес container'а маршрутизируется только внутри host.
Путь пакета внутрь
Обращение с host к опубликованному порту:
| Шаг | Что происходит |
|---|---|
| 1 | curl localhost:8080 попадает в цепочку OUTPUT/PREROUTING netfilter host |
| 2 | Правило DNAT заменяет адрес назначения на 172.17.0.2:8000 |
| 3 | Маршрутизация host направляет пакет на docker0 |
| 4 | Bridge передаёт его в veth pair нужного container'а |
| 5 | Пакет приходит на eth0 внутри namespace |
| 6 | Ядро доставляет его процессу, слушающему 0.0.0.0:8000 |
Шаг 6 показывает, почему процесс должен слушать 0.0.0.0, а не 127.0.0.1: пакет приходит на eth0, а не на loopback (урок 8.3).
Публикация порта — это правило трансляции адресов, а не «открытие порта» в каком-либо смысле.
Типы сетей
| Тип | Что делает | Когда применять |
|---|---|---|
bridge | Свой namespace, veth pair, NAT | Значение по умолчанию. Почти всегда |
host | Namespace host, без изоляции | Максимум производительности, много портов |
none | Namespace без интерфейсов, кроме lo | Задачи без сети |
container:<имя> | Общий namespace с другим container'ом | Sidecar, отладка |
overlay | Сеть поверх нескольких host | Swarm, кластер |
macvlan | Свой MAC и адрес в сети host | Интеграция с физической сетью |
ipvlan | Как macvlan, но общий MAC | Ограничения на число MAC в сети |
Первые четыре покрывают почти все задачи одного host; они разбираются в уроке 8.5.
Отдельно стоит различать default bridge (docker0, сеть с именем bridge) и user-defined bridge — сети, созданные командой docker network create. Это одинаковый механизм с разным поведением: в user-defined работает DNS по именам, в default — нет (урок 8.2).
Адресация
По умолчанию Docker выделяет 172.17.0.0/16 для docker0 и следующие подсети 172.x для user-defined сетей.
Типичная проблема — пересечение с корпоративной сетью: если в организации используется 172.17.0.0/16, маршруты конфликтуют, и часть внешних адресов становится недоступной из container'ов. Решение — задать пулы в /etc/docker/daemon.json:
{
"default-address-pools": [
{"base": "10.201.0.0/16", "size": 24}
]
}
Внутренний механизм
Как найти парный интерфейс
Каждый конец veth pair знает номер второго. Внутри container'а:
cat /sys/class/net/eth0/iflink
Полученное число — индекс парного интерфейса в namespace host. По нему находится имя:
ip -o link | awk -F': ' -v n="$IFLINK" '$1 == n {print $2}'
Приём полезен при диагностике: он связывает конкретный container с конкретным vethXXXX в выводе tcpdump или ip link.
Где живёт namespace
Docker не создаёт именованных namespace в /var/run/netns, поэтому команда ip netns list container'ов не показывает. Namespace существует, пока жив процесс, и доступен через /proc/<pid>/ns/net.
Отсюда способ войти в него с host:
sudo nsenter -t "$(docker inspect -f '{{.State.Pid}}' <container>)" -n ip addr
Это даёт полный набор сетевых инструментов host внутри namespace container'а — без установки чего-либо в образ (урок 8.6).
Команды и примеры
Что видит container
docker run -d --name net1 python:3.13-slim sleep 300 > /dev/null
echo "═══ интерфейсы внутри container ═══"
docker exec net1 sh -c 'cat /proc/net/dev | awk "NR>2 {print \$1}"'
echo "═══ адрес и маршруты (через /proc, без установки утилит) ═══"
docker exec net1 sh -c 'cat /proc/net/route | awk "NR>1 {print \$1, \$2, \$3}"' | head -3
echo "═══ то же самое, но читаемо — через nsenter с host ═══"
pid="$(docker inspect -f '{{.State.Pid}}' net1)"
sudo nsenter -t "$pid" -n ip -brief addr
sudo nsenter -t "$pid" -n ip route
Ожидаемый вывод:
═══ интерфейсы внутри container ═══
lo:
eth0:
═══ адрес и маршруты (через /proc, без установки утилит) ═══
Iface Destination Gateway
eth0 00000000 010011AC
eth0 000011AC 00000000
═══ то же самое, но читаемо — через nsenter с host ═══
lo UNKNOWN 127.0.0.1/8 ::1/128
eth0@if42 UP 172.17.0.2/16
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 scope link src 172.17.0.2
Два интерфейса, две строки маршрутов — весь сетевой стек container'а.
Строка default via 172.17.0.1 означает: всё, что не в 172.17.0.0/16, отправляется на bridge. Вторая строка — прямая доставка соседям по сети.
Обратите внимание на eth0@if42: суффикс @if42 и есть индекс парного интерфейса на host.
Формат /proc/net/route — шестнадцатеричный и в обратном порядке байтов: 010011AC читается как AC 11 00 01 = 172.17.0.1. Это неудобно, зато доступно в любом образе без установки пакетов.
Находим парный veth
echo "═══ индекс парного интерфейса (изнутри container) ═══"
iflink="$(docker exec net1 cat /sys/class/net/eth0/iflink)"
echo " iflink: $iflink"
echo "═══ имя этого интерфейса на host ═══"
veth="$(ip -o link | awk -F': ' -v n="$iflink" '$1+0 == n {split($2, a, "@"); print a[1]}')"
echo " veth: $veth"
echo "═══ к какому bridge он подключён ═══"
ip -o link show "$veth" | grep -o 'master [^ ]*'
echo "═══ все интерфейсы в bridge docker0 ═══"
ip -o link show master docker0 | awk -F': ' '{split($2, a, "@"); print " " a[1]}'
Ожидаемый вывод:
═══ индекс парного интерфейса (изнутри container) ═══
iflink: 42
═══ имя этого интерфейса на host ═══
veth: veth8f3a1c2
═══ к какому bridge он подключён ═══
master docker0
═══ все интерфейсы в bridge docker0 ═══
veth8f3a1c2
Цепочка замкнулась: eth0 внутри container'а — это veth8f3a1c2 на host, подключённый к docker0.
Практическая ценность в диагностике: зная имя veth, можно смотреть трафик конкретного container'а с host, не заходя внутрь:
sudo timeout 5 tcpdump -i "$veth" -nn -c 5 2>/dev/null &
sleep 1
docker exec net1 sh -c 'python -c "
import socket
s = socket.create_connection((\"1.1.1.1\", 53), timeout=3)
s.close()
print(\"соединение установлено\")
"' 2>/dev/null || echo " (нет внешней сети — это нормально в изолированной среде)"
wait
Ожидаемый вывод:
соединение установлено
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on veth8f3a1c2, link-type EN10MB (Ethernet), snapshot length 262144 bytes
13:45:02.104 IP 172.17.0.2.51234 > 1.1.1.1.53: Flags [S], seq 1234567
13:45:02.118 IP 1.1.1.1.53 > 172.17.0.2.51234: Flags [S.], seq 7654321
В трафике на veth виден исходный адрес 172.17.0.2 — подмена на адрес host происходит позже, при выходе через физический интерфейс.
Где происходит подмена адреса
echo "═══ адрес, который видит внешний сервер ═══"
docker exec net1 sh -c 'python -c "
import urllib.request
print(\" из container:\", urllib.request.urlopen(\"https://api.ipify.org\", timeout=5).read().decode())
"' 2>/dev/null || echo " (нет доступа в интернет)"
echo "═══ правило MASQUERADE ═══"
sudo iptables -t nat -S POSTROUTING | grep -i masquerade | head -3
Ожидаемый вывод:
═══ адрес, который видит внешний сервер ═══
из container: 203.0.113.45
═══ правило MASQUERADE ═══
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
Правило читается так: пакеты из сети 172.17.0.0/16, уходящие не через docker0, получают адрес источника выходного интерфейса. Именно поэтому внешний сервис видит публичный адрес host.
Условие ! -o docker0 существенно: без него подменялись бы и адреса при общении container'ов между собой, и они не смогли бы определить, кто к ним обращается.
Два container'а в одной сети
docker run -d --name net2 python:3.13-slim sleep 300 > /dev/null
ip1="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' net1)"
ip2="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' net2)"
printf ' net1: %s\n net2: %s\n' "$ip1" "$ip2"
echo "═══ связь по IP работает ═══"
docker exec net1 python -c "
import socket, sys
s = socket.socket()
s.settimeout(2)
try:
s.connect(('$ip2', 22))
print(' порт 22 открыт')
except ConnectionRefusedError:
print(' хост доступен (соединение отклонено — порт закрыт, но хост отвечает)')
except OSError as e:
print(' хост НЕдоступен:', e)
"
echo "═══ связь по имени в default bridge ═══"
docker exec net1 sh -c 'getent hosts net2 || echo " имя net2 не разрешается"'
Ожидаемый вывод:
net1: 172.17.0.2
net2: 172.17.0.3
═══ связь по IP работает ═══
хост доступен (соединение отклонено — порт закрыт, но хост отвечает)
═══ связь по имени в default bridge ═══
имя net2 не разрешается
По IP связь есть — оба container'а в одной сети 172.17.0.0/16. По имени — нет: в default bridge DNS не работает. Это одна из причин не использовать сеть по умолчанию (урок 8.2).
Обратите внимание на способ проверки: ConnectionRefused — хороший признак. Он означает, что пакет дошёл до хоста и тот ответил отказом. Недоступность выглядит иначе — как таймаут.
docker rm -f net1 net2 > /dev/null
Namespace действительно изолирован
echo "═══ порт 8000 внутри container ═══"
docker run -d --name srv -p 18000:8000 python:3.13-slim \
python -m http.server 8000 --bind 0.0.0.0 > /dev/null
sleep 3
echo " слушает ли кто-то 8000 на host:"
ss -tlnp 2>/dev/null | grep ':8000 ' | head -2 || echo " нет — порт 8000 на host свободен"
echo " а 18000:"
ss -tln 2>/dev/null | grep ':18000 ' | sed 's/^/ /'
echo "═══ можно занять 8000 на host, конфликта не будет ═══"
python3 -m http.server 8000 --bind 127.0.0.1 > /dev/null 2>&1 &
hostsrv=$!
sleep 2
printf ' host:8000 → %s\n' "$(curl -s -o /dev/null -w '%{http_code}' localhost:8000)"
printf ' container:18000 → %s\n' "$(curl -s -o /dev/null -w '%{http_code}' localhost:18000)"
kill $hostsrv 2>/dev/null
docker rm -f srv > /dev/null
Ожидаемый вывод:
═══ порт 8000 внутри container ═══
слушает ли кто-то 8000 на host:
нет — порт 8000 на host свободен
а 18000:
LISTEN 0 4096 0.0.0.0:18000 0.0.0.0:*
═══ можно занять 8000 на host, конфликта не будет ═══
host:8000 → 200
container:18000 → 200
Два разных сервера на «одном» порту 8000 — в разных namespace. На host занят только 18000, созданный публикацией.
Это наглядное подтверждение того, что порты — свойство namespace, а не машины.
Сеть none
echo "═══ container без сети ═══"
docker run --rm --network none python:3.13-slim sh -c '
echo -n " интерфейсы: "
awk "NR>2 {printf \"%s \", \$1}" /proc/net/dev
echo
echo -n " маршрутов: "
awk "NR>1" /proc/net/route | wc -l
'
Ожидаемый вывод:
═══ container без сети ═══
интерфейсы: lo:
маршрутов: 0
Только loopback и ни одного маршрута. Namespace создан, но veth pair не подключён — сети нет вовсе.
Практическое упражнение
Задание. Постройте полную карту сетевого пути одного container'а, используя только команды, а не документацию.
Требуется установить и подтвердить:
- IP-адрес и маску container'а.
- Шлюз по умолчанию и то, что это адрес bridge.
- Имя парного
veth-интерфейса на host. - Bridge, к которому он подключён, и его адрес.
- Правило
MASQUERADE, отвечающее за исходящий трафик этой сети. - Что порт, слушаемый внутри, не занят на host.
Оформите результат как отчёт, каждый пункт — с командой, которая его подтверждает.
Подсказки
Подсказка 1
Внутри python:3.13-slim нет ip и ss. Данные доступны в /proc/net/ и /sys/class/net/, а читаемо их можно получить через nsenter с host.
Подсказка 2
/proc/net/route хранит адреса в шестнадцатеричном виде с обратным порядком байтов.
Подсказка 3
Индекс парного интерфейса — в /sys/class/net/eth0/iflink.
Решение
Показать решение
mkdir -p /tmp/netmap && cd /tmp/netmap
cat > netmap.sh <<'SH'
#!/usr/bin/env bash
# Строит карту сетевого пути container'а только по данным системы.
set -uo pipefail
C="${1:?имя container}"
hex2ip() { # "010011AC" -> 172.17.0.1 (little-endian)
local h="$1"
printf '%d.%d.%d.%d' \
"0x${h:6:2}" "0x${h:4:2}" "0x${h:2:2}" "0x${h:0:2}"
}
pid="$(docker inspect -f '{{.State.Pid}}' "$C")"
# ── 1. Адрес и маска ──
echo "1. Адрес container'а"
ipaddr="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}/{{.IPPrefixLen}}{{end}}' "$C")"
echo " docker inspect: $ipaddr"
# Независимая проверка — из самого namespace
echo " nsenter ip addr: $(sudo nsenter -t "$pid" -n ip -brief addr show eth0 | awk '{print $3}')"
# ── 2. Шлюз ──
echo
echo "2. Шлюз по умолчанию"
gw_hex="$(awk '$2 == "00000000" {print $3; exit}' /proc/"$pid"/net/route)"
gw="$(hex2ip "$gw_hex")"
echo " из /proc/net/route: $gw"
echo " проверка nsenter: $(sudo nsenter -t "$pid" -n ip route | awk '/^default/ {print $3}')"
# ── 3. Парный veth ──
echo
echo "3. Парный интерфейс на host"
iflink="$(docker exec "$C" cat /sys/class/net/eth0/iflink | tr -d '\r')"
veth="$(ip -o link | awk -F': ' -v n="$iflink" '$1+0 == n {split($2, a, "@"); print a[1]}')"
echo " iflink: $iflink"
echo " имя на host: $veth"
# ── 4. Bridge ──
echo
echo "4. Bridge"
br="$(ip -o link show "$veth" | grep -o 'master [^ ]*' | awk '{print $2}')"
br_ip="$(ip -brief addr show "$br" | awk '{print $3}')"
echo " имя: $br"
echo " адрес: $br_ip"
if [ "${br_ip%%/*}" = "$gw" ]; then
echo " ✓ адрес bridge совпадает со шлюзом container'а"
else
echo " ✗ шлюз ($gw) не совпадает с адресом bridge ($br_ip)"
fi
echo " участники bridge:"
ip -o link show master "$br" | awk -F': ' '{split($2, a, "@"); print " " a[1]}'
# ── 5. MASQUERADE ──
echo
echo "5. Правило исходящего NAT"
subnet="$(docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}' 2>/dev/null)"
sudo iptables -t nat -S POSTROUTING 2>/dev/null | grep -i masquerade | \
grep -F "${subnet:-172.17.0.0/16}" | sed 's/^/ /' || \
echo " (правило не найдено — проверьте backend: nft или legacy)"
# ── 6. Порты ──
echo
echo "6. Порты"
echo " слушает внутри namespace:"
sudo nsenter -t "$pid" -n ss -tln 2>/dev/null | tail -n +2 | sed 's/^/ /'
echo " опубликовано на host:"
docker port "$C" 2>/dev/null | sed 's/^/ /' || echo " (портов не опубликовано)"
SH
chmod +x netmap.sh
docker run -d --name mapped -p 18080:8000 python:3.13-slim \
python -m http.server 8000 --bind 0.0.0.0 > /dev/null
sleep 3
./netmap.sh mapped
echo
echo "═══ проверка пункта 6: порт 8000 на host свободен ═══"
ss -tln | grep -q ':8000 ' && echo " занят" || echo " ✓ порт 8000 на host не занят"
printf ' запрос на опубликованный порт: HTTP %s\n' \
"$(curl -s -o /dev/null -w '%{http_code}' localhost:18080)"
docker rm -f mapped > /dev/null
cd /tmp && rm -rf /tmp/netmap
Ожидаемый вывод:
1. Адрес container'а
docker inspect: 172.17.0.2/16
nsenter ip addr: 172.17.0.2/16
2. Шлюз по умолчанию
из /proc/net/route: 172.17.0.1
проверка nsenter: 172.17.0.1
3. Парный интерфейс на host
iflink: 47
имя на host: veth2c9f8a1
4. Bridge
имя: docker0
адрес: 172.17.0.1/16
✓ адрес bridge совпадает со шлюзом container'а
участники bridge:
veth2c9f8a1
5. Правило исходящего NAT
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
6. Порты
слушает внутри namespace:
LISTEN 0 5 0.0.0.0:8000 0.0.0.0:*
опубликовано на host:
8000/tcp -> 0.0.0.0:18080
═══ проверка пункта 6: порт 8000 на host свободен ═══
✓ порт 8000 на host не занят
запрос на опубликованный порт: HTTP 200
Карта построена целиком из данных системы. Каждое звено подтверждено.
Три решения, определяющие качество.
Пункты 1 и 2 проверяются двумя независимыми источниками. docker inspect читает метаданные Docker, nsenter — реальное состояние namespace ядра. Совпадение подтверждает, что метаданные соответствуют действительности; расхождение указывало бы на рассинхронизацию — редкий, но реальный класс проблем после сбоя демона.
Пункт 4 не просто печатает адрес bridge, а сравнивает его со шлюзом. Печать двух чисел рядом оставляет проверку читателю; явное сравнение превращает отчёт в тест. Если сравнение не сойдётся, отчёт скажет об этом сам.
ss вызывается через nsenter, а не внутри container'а. В python:3.13-slim его нет, и типичный ход — установить iproute2 в образ. Это меняет объект исследования: вы диагностируете уже не тот container. nsenter даёт инструменты host внутри namespace, не трогая container вовсе.
Чего решение не делает. Оно не работает для сетей с драйвером host и none — там нет ни veth, ни bridge, и пункты 3–5 неприменимы. Не покрыт и случай, когда container подключён к нескольким сетям: docker inspect -f с range напечатает адреса слитно, а eth0 будет лишь одним из интерфейсов. Для многосетевых container'ов карту строят по каждой сети отдельно (урок 8.2).
Проверка результата
docker run -d --name basic-check python:3.13-slim sleep 60 > /dev/null
docker exec basic-check cat /sys/class/net/eth0/iflink
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{.Gateway}}{{end}}' basic-check
ip -brief addr show docker0
docker rm -f basic-check > /dev/null
Ожидается, что шлюз container'а совпадает с адресом docker0.
Типичные ошибки
| Ошибка | Причина | Исправление |
|---|---|---|
localhost в container = host | Привычка с одной машины | Свой loopback у каждого namespace |
Приложение слушает 127.0.0.1 | Умолчание фреймворка | Пакет приходит на eth0; слушать 0.0.0.0 |
Считают, что -p «открывает порт» | Терминология | Это правило DNAT |
Ищут container'ы через ip netns list | Логично предположить | Docker не создаёт именованных namespace |
Устанавливают iproute2 в образ ради диагностики | Инструментов нет | nsenter с host не меняет образ |
| Ожидают DNS в default bridge | В user-defined он есть | Разные сети — разное поведение |
Пул 172.17.0.0/16 конфликтует с корпоративной сетью | Умолчание Docker | default-address-pools в daemon.json |
Считают таймаут и Connection refused одним и тем же | Оба «не работает» | Refused означает, что хост ответил |
Контрольные вопросы
На понимание:
- Что именно изолирует сетевой namespace? Назовите пять вещей.
- Как veth pair соединяет container с host?
- Почему внешний сервер видит адрес host, а не container'а?
- Почему процесс должен слушать
0.0.0.0, а не127.0.0.1? - Чем
Connection refusedотличается от таймаута при диагностике?
На применение:
- Как найти
veth-интерфейс конкретного container'а на host? - Как посмотреть трафик одного container'а, не заходя в него?
- Как выполнить
ss -tlnpв namespace container'а, где нетss?
На диагностику:
- Container не может обратиться к корпоративному сервису
172.17.5.10. Версия? - Два container'а видят друг друга по IP, но не по имени. Причина?
Краткое резюме
- Сетевой namespace — отдельный экземпляр стека: интерфейсы, маршруты, правила, порты, свой
localhost. - Container соединён с host через veth pair; один конец —
eth0внутри, второй —vethXXXXснаружи. - Второй конец подключён к bridge, который служит шлюзом по умолчанию.
- Исходящий трафик проходит
MASQUERADE, поэтому внешний сервер видит адрес host. - Входящий трафик проходит
DNAT: публикация порта — правило трансляции, а не «открытие». - Пакет приходит на
eth0, поэтому процесс обязан слушать0.0.0.0. - Индекс парного интерфейса лежит в
/sys/class/net/eth0/iflink. ip netns listcontainer'ов не показывает; namespace доступен через/proc/<pid>/ns/net.nsenter -t <pid> -nдаёт инструменты host внутри namespace container'а.- Порты принадлежат namespace: один и тот же номер свободен на host и занят внутри.
- Default bridge и user-defined bridge — один механизм с разным поведением DNS.
- Пулы адресов настраиваются через
default-address-poolsпри конфликте с сетью организации.
Официальные источники
| Источник | Ссылка | Что подтверждает |
|---|---|---|
| Docker: networking overview | https://docs.docker.com/engine/network/ | Типы сетей, модель |
| Docker: bridge driver | https://docs.docker.com/engine/network/drivers/bridge/ | docker0, veth, адресация |
| Docker: packet filtering and firewalls | https://docs.docker.com/engine/network/packet-filtering-firewalls/ | Правила MASQUERADE и DNAT |
| Docker: daemon configuration | https://docs.docker.com/reference/cli/dockerd/ | default-address-pools |
Linux: network_namespaces(7) | https://man7.org/linux/man-pages/man7/network_namespaces.7.html | Что изолирует namespace |
Linux: veth(4) | https://man7.org/linux/man-pages/man4/veth.4.html | Устройство veth pair |
Linux: nsenter(1) | https://man7.org/linux/man-pages/man1/nsenter.1.html | Вход в namespace процесса |
Навигация
← Вернуться к разделу
Следующий материал → Bridge networks
Главное оглавление