Главная/Docker Networking/Урок

8.1. Основы Docker networking

Цели

После этого материала вы сможете:

  • перечислить, что именно изолирует сетевой namespace;
  • объяснить, как veth pair соединяет container с host;
  • найти на host интерфейс, парный к eth0 конкретного container'а;
  • прочитать таблицу маршрутизации внутри container и объяснить каждую строку;
  • проследить путь пакета от процесса в container до внешней сети и обратно;
  • выбрать тип сети под задачу.

Предварительные знания

Ключевые термины

ТерминОбъяснение
network namespaceИзолированный сетевой стек: интерфейсы, маршруты, правила
veth pairПара виртуальных интерфейсов; всё, что вошло в один, выходит из другого
bridgeВиртуальный коммутатор второго уровня внутри ядра
docker0Bridge сети по умолчанию
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 — пара виртуальных интерфейсов, работающих как труба: пакет, отправленный в один конец, выходит из другого.

text
   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 (физический)       │
                                        └──────────────┬───────────────┘
                                                       ▼
                                                   внешняя сеть

Три элемента конструкции:

  1. eth0 внутри container'а — один конец veth pair, перенесённый в его namespace.
  2. vethXXXX на host — второй конец, подключённый к bridge.
  3. docker0 — bridge, играющий роль коммутатора: пересылает кадры между всеми подключёнными veth и наружу.

Bridge имеет собственный IP (172.17.0.1), и он же является шлюзом по умолчанию для container'ов.

Путь пакета наружу

Запрос из container'а к внешнему адресу:

ШагЧто происходит
1Процесс пишет в сокет; ядро выбирает маршрут в таблице namespace container'а
2Маршрут по умолчанию ведёт на 172.17.0.1 через eth0
3Пакет проходит veth pair и появляется на host
4Bridge docker0 передаёт его в маршрутизацию host
5Правило MASQUERADE подменяет адрес источника на адрес host
6Пакет уходит через физический интерфейс
7Ответ возвращается, conntrack восстанавливает исходный адрес, путь разворачивается

Шаг 5 объясняет, почему внешний сервер видит адрес host, а не 172.17.0.2: адрес container'а маршрутизируется только внутри host.

Путь пакета внутрь

Обращение с host к опубликованному порту:

ШагЧто происходит
1curl localhost:8080 попадает в цепочку OUTPUT/PREROUTING netfilter host
2Правило DNAT заменяет адрес назначения на 172.17.0.2:8000
3Маршрутизация host направляет пакет на docker0
4Bridge передаёт его в 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Значение по умолчанию. Почти всегда
hostNamespace host, без изоляцииМаксимум производительности, много портов
noneNamespace без интерфейсов, кроме loЗадачи без сети
container:<имя>Общий namespace с другим container'омSidecar, отладка
overlayСеть поверх нескольких hostSwarm, кластер
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:

json
{
  "default-address-pools": [
    {"base": "10.201.0.0/16", "size": 24}
  ]
}

Внутренний механизм

Как найти парный интерфейс

Каждый конец veth pair знает номер второго. Внутри container'а:

bash
cat /sys/class/net/eth0/iflink

Полученное число — индекс парного интерфейса в namespace host. По нему находится имя:

bash
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:

bash
sudo nsenter -t "$(docker inspect -f '{{.State.Pid}}' <container>)" -n ip addr

Это даёт полный набор сетевых инструментов host внутри namespace container'а — без установки чего-либо в образ (урок 8.6).


Команды и примеры

Что видит container

bash
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

Ожидаемый вывод:

text
═══ интерфейсы внутри 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

bash
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]}'

Ожидаемый вывод:

text
═══ индекс парного интерфейса (изнутри container) ═══
  iflink: 42
═══ имя этого интерфейса на host ═══
  veth:   veth8f3a1c2
═══ к какому bridge он подключён ═══
master docker0
═══ все интерфейсы в bridge docker0 ═══
  veth8f3a1c2

Цепочка замкнулась: eth0 внутри container'а — это veth8f3a1c2 на host, подключённый к docker0.

Практическая ценность в диагностике: зная имя veth, можно смотреть трафик конкретного container'а с host, не заходя внутрь:

bash
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

Ожидаемый вывод:

text
соединение установлено
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 происходит позже, при выходе через физический интерфейс.

Где происходит подмена адреса

bash
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

Ожидаемый вывод:

text
═══ адрес, который видит внешний сервер ═══
  из 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'а в одной сети

bash
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 не разрешается"'

Ожидаемый вывод:

text
  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хороший признак. Он означает, что пакет дошёл до хоста и тот ответил отказом. Недоступность выглядит иначе — как таймаут.

bash
docker rm -f net1 net2 > /dev/null

Namespace действительно изолирован

bash
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

Ожидаемый вывод:

text
═══ порт 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

bash
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
'

Ожидаемый вывод:

text
═══ container без сети ═══
  интерфейсы: lo:
  маршрутов:  0

Только loopback и ни одного маршрута. Namespace создан, но veth pair не подключён — сети нет вовсе.


Практическое упражнение

Задание. Постройте полную карту сетевого пути одного container'а, используя только команды, а не документацию.

Требуется установить и подтвердить:

  1. IP-адрес и маску container'а.
  2. Шлюз по умолчанию и то, что это адрес bridge.
  3. Имя парного veth-интерфейса на host.
  4. Bridge, к которому он подключён, и его адрес.
  5. Правило MASQUERADE, отвечающее за исходящий трафик этой сети.
  6. Что порт, слушаемый внутри, не занят на 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.

Решение

Показать решение
bash
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

Ожидаемый вывод:

text
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).

Проверка результата

bash
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 конфликтует с корпоративной сетьюУмолчание Dockerdefault-address-pools в daemon.json
Считают таймаут и Connection refused одним и тем жеОба «не работает»Refused означает, что хост ответил

Контрольные вопросы

На понимание:

  1. Что именно изолирует сетевой namespace? Назовите пять вещей.
  2. Как veth pair соединяет container с host?
  3. Почему внешний сервер видит адрес host, а не container'а?
  4. Почему процесс должен слушать 0.0.0.0, а не 127.0.0.1?
  5. Чем Connection refused отличается от таймаута при диагностике?

На применение:

  1. Как найти veth-интерфейс конкретного container'а на host?
  2. Как посмотреть трафик одного container'а, не заходя в него?
  3. Как выполнить ss -tlnp в namespace container'а, где нет ss?

На диагностику:

  1. Container не может обратиться к корпоративному сервису 172.17.5.10. Версия?
  2. Два container'а видят друг друга по IP, но не по имени. Причина?

Краткое резюме

  1. Сетевой namespace — отдельный экземпляр стека: интерфейсы, маршруты, правила, порты, свой localhost.
  2. Container соединён с host через veth pair; один конец — eth0 внутри, второй — vethXXXX снаружи.
  3. Второй конец подключён к bridge, который служит шлюзом по умолчанию.
  4. Исходящий трафик проходит MASQUERADE, поэтому внешний сервер видит адрес host.
  5. Входящий трафик проходит DNAT: публикация порта — правило трансляции, а не «открытие».
  6. Пакет приходит на eth0, поэтому процесс обязан слушать 0.0.0.0.
  7. Индекс парного интерфейса лежит в /sys/class/net/eth0/iflink.
  8. ip netns list container'ов не показывает; namespace доступен через /proc/<pid>/ns/net.
  9. nsenter -t <pid> -n даёт инструменты host внутри namespace container'а.
  10. Порты принадлежат namespace: один и тот же номер свободен на host и занят внутри.
  11. Default bridge и user-defined bridge — один механизм с разным поведением DNS.
  12. Пулы адресов настраиваются через default-address-pools при конфликте с сетью организации.

Официальные источники

ИсточникСсылкаЧто подтверждает
Docker: networking overviewhttps://docs.docker.com/engine/network/Типы сетей, модель
Docker: bridge driverhttps://docs.docker.com/engine/network/drivers/bridge/docker0, veth, адресация
Docker: packet filtering and firewallshttps://docs.docker.com/engine/network/packet-filtering-firewalls/Правила MASQUERADE и DNAT
Docker: daemon configurationhttps://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
Главное оглавление

Markdown на GitHub ↗