Перейти к основному содержимому

Как работает bridge-сеть в Docker

·1149 слов·6 минут
DevOps • Networks • Security • Infrastructure
Автор
DevOps • Networks • Security • Infrastructure
DevOps/Network/Infra Engineer, CyberSecurity Expert

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

В мире контейнеризации Docker сеть — не просто способ подключения к интернету. Это механизм, который определяет, как контейнеры видят друг друга, хост и внешние сети. Одним из базовых, но ключевых элементов этой архитектуры является bridge-сеть — программный сетевой мост, который по умолчанию связывает контейнеры на одном Docker-хосте.

Bridge-сеть обеспечивает:

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

В этой статье мы подробно разберём, как устроена bridge-сеть в Docker, как пакеты проходят через неё и что происходит “под капотом”, когда контейнер общается с внешним миром или с другим контейнером на том же хосте.

Общая архитектура bridge-сети
#

Текущие настройки сети на сервере с Docker
#

# Просмотр запущенные контейнеры
~# docker ps
CONTAINER ID  IMAGE                  PORTS                                    NAMES
31941b774406  nginx                  0.0.0.0:8082->80/tcp, [::]:8082->80/tcp  test-nginx
cf2a84c799b4  docker/getting-started 0.0.0.0:80->80/tcp, [::]:8080->80/tcp    test-tutorial

# Просмотр полной конфигурации контейнера
~# docker inspect test-nginx | jq .

# Просмотр только сетевой конфигурации контейнера 
~# docker inspect --format='{{json .NetworkSettings}}' test-nginx | jq .

# Просмотр veth-интерфейса контейнера 
~# docker inspect --format='{{.NetworkSettings.SandboxKey}}' test-nginx | xargs basename

# Просмотр сетевых интерфейсов внутри контейнера
~# nsenter -t $(docker inspect --format='{{.State.Pid}}' test-nginx) -n ip addr

# Просмотр сетевых интерфейсов на сервере
~# ip -br a
lo               UNKNOWN        127.0.0.1/8 ::1/128 
eth0             UP             10.130.0.5/24 metric 100 fe80::d20d:1dff:fe15:a32f/64 
docker0          UP             172.17.0.1/16 fe80::80f8:64ff:fe5b:40d9/64 
veth883b6aa@if2  UP             fe80::9cda:1dff:fe14:4d9/64
veth8027757@if2  UP             fe80::dc1a:d4ff:fe20:d925/64 

# Просмотр bridge-интерфейсов с подключенными veth
~# brctl show
bridge name   bridge id		      STP enabled	  interfaces
docker0		    8000.82f8645b40d9	no		        veth883b6aa
                                                    veth8027757
# Просмотр какие сети доступы в Docker
~# docker network ls
NETWORK ID     NAME        DRIVER    SCOPE
03478b020492   bridge      bridge    local
081f803a9ecb   host        host      local
58f54b28e459   none        null      local

# Просмотр подключенных контейнеров к мосту bridge и имя моста системе (docker0) 
~# docker network inspect bridge | jq .
[
    {
        "Name": "bridge",
        "Id": "143c484b8cbb717475dbd1f113f6da0e21d29561c2520b288b99bc69d3e5fbdb",
        "Created": "2026-02-22T17:17:52.117740329Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv4": true,
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "31941b774406e219a23464c09df814f6c835d7d559ccd85d57d79694caac09d1": {
                "Name": "test-nginx",
                "EndpointID": "5cf9d13c0c334ad68b2e4950725c300590ac3f8f8b98ca86a6f65a70dcec4478",
                "MacAddress": "8e:29:57:b7:a2:84",
                "IPv4Address": "172.17.0.6/16",
                "IPv6Address": ""
            },
            "cf2a84c799b46ed1a8287f589b4bdfe3fceeeb8f8b5f6d4ad76df8b982c9c23c": {
                "Name": "test-tutorial",
                "EndpointID": "a4654cd43299cc7b3b96b75bffc3f7450182ca98370fe4c475a9493c26297332",
                "MacAddress": "a6:3d:2b:1c:60:84",
                "IPv4Address": "172.17.0.5/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

# Просмотр какой veth-интерфейс на хосту к какому контейнеру относится
for container in $(docker ps -q); do
    echo "Контейнер: $(docker inspect --format='{{.Name}}' $container | cut -c2-)"
    echo "IP: $(docker inspect --format='{{.NetworkSettings.IPAddress}}' $container)"
    echo "veth: $(docker inspect --format='{{.NetworkSettings.SandboxKey}}' $container | xargs basename 2>/dev/null || echo 'N/A')"
    echo "---"
done

Путешествие пакета: как трафик проходит через Docker bridge
#

Исходящий трафик (контейнер → Интернет):
172.17.0.5:42495 -(SNAT)-> 10.130.0.5:42495 -(SNAT)-> 158.160.151.151:42495 -(Интернет)-> 137.74.42.42:80

Входящий трафик (Интернет → контейнер):
137.74.42.42:45448 -(Интернет)-> 158.160.151.151:80 -(DNAT)-> 10.130.0.5:80 -(DNAT)-> 172.17.0.5:80

Трафик из контейнера в Интернет
#

Генерируем запрос через утилиту netcat: nc -lv 80 и nc -vz 137.74.42.42 80
Источник: 172.17.0.5:42495 (контейнер)
Назначение: 137.74.42.42:80 (внешний сервер)

Путь исходящего пакета

┌─────────────┐   1. SYN    ┌─────────────┐   2. SYN    ┌─────────────┐
│  Контейнер  │ ──────────> │  veth пара  │ ──────────> │  docker0    │
│ 172.17.0.5  │             │ veth883b6aa │             │   bridge    │
└─────────────┘             └─────────────┘             └──────┬──────┘
                                                         3. SYN│
                                                         (SNAT)│
┌─────────────┐   5. SYN    ┌─────────────┐   4. SYN    ┌─────────────┐
│ Внешний     │ <────────── │    eth0     │ <────────── │    NAT      │
│ сервер      │             │ 10.130.0.5  │             │  iptables   │
│ 137.74.42.42│             └─────────────┘             └─────────────┘
└─────────────┘

Реальный дамп трафика (исходящее соединение)

# TCP SYN (SNAT) 
# Three-Way Handshake
veth883b6aa P   IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [S]
docker0 In  IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [S]
eth0  Out IP 10.130.0.5.42495 > 137.74.42.42.80: Flags [S]
# TCP SYN-ACK (DNAT)
eth0  In  IP 137.74.42.42.80 > 10.130.0.5.42495: Flags [S.]
docker0 Out IP 137.74.42.42.80 > 172.17.0.5.42495: Flags [S.]
veth883b6aa Out IP 137.74.42.42.80 > 172.17.0.5.42495: Flags [S.]
# TCP ACK (SNAT)
veth883b6aa P   IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [.]
docker0 In  IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [.]
eth0  Out IP 10.130.0.5.42495 > 137.74.42.42.80: Flags [.]
# TCP FIN-ACK (SNAT) 
# Four-Way Handshake
veth883b6aa P   IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [F.]
docker0 In  IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [F.]
eth0  Out IP 10.130.0.5.42495 > 137.74.42.42.80: Flags [F.]
# TCP ACK + FIN-ACK (DNAT)
eth0  In  IP 137.74.42.42.80 > 10.130.0.5.42495: Flags [.]
docker0 Out IP 137.74.42.42.80 > 172.17.0.5.42495: Flags [.]
veth883b6aa Out IP 137.74.42.42.80 > 172.17.0.5.42495: Flags [.]
eth0  In  IP 137.74.42.42.80 > 10.130.0.5.42495: Flags [F.]
docker0 Out IP 137.74.42.42.80 > 172.17.0.5.42495: Flags [F.]
veth883b6aa Out IP 137.74.42.42.80 > 172.17.0.5.42495: Flags [F.]
# TCP ACK (SNAT) 
veth883b6aa P   IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [.]
docker0 In  IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [.]
eth0  Out IP 10.130.0.5.42495 > 137.74.42.42.80: Flags [.]

Трафик из Интернета в контейнер
#

Генерируем запрос через утилиту netcat: nc -lv 80 и nc -vz 158.160.151.151 80
Источник: 137.74.42.42.45448 (внешний сервер)
Назначение: 172.17.0.5:80:80 (контейнер) nc -lv 80

Путь входящего пакета

┌─────────────┐   1. SYN    ┌─────────────┐   2. SYN    ┌─────────────┐
│  Внешний    │ ──────────> │    eth0     │ ──────────> │    NAT      │
│  клиент     │             │ 10.130.0.5  │             │  iptables   │
│ 137.74.42.42│             └─────────────┘             └──────┬──────┘
└─────────────┘                                          3. SYN│
                                                         (DNAT)│
┌─────────────┐  5. SYN     ┌─────────────┐   4. SYN    ┌─────────────┐
│  Контейнер  │ <────────── │  veth пара  │ <────────── │  docker0    │
│ 172.17.0.5  │             │ veth883b6aa │             │   bridge    │
└─────────────┘             └─────────────┘             └─────────────┘

Реальный дамп трафика (входящее соединение)

# TCP SYN (DNAT) 
# Three-Way Handshake
eth0  In  IP 137.74.42.42.45448 > 10.130.0.5.80: Flags [S]
docker0 Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [S]
veth883b6aa Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [S]
# TCP SYN-ACK (SNAT)
veth883b6aa P   IP 172.17.0.5.80 > 137.74.42.42.45448: Flags [S.]
docker0 In  IP 172.17.0.5.80 > 137.74.42.42.45448: Flags [S.]
eth0  Out IP 10.130.0.5.80 > 137.74.42.42.45448: Flags [S.]
# TCP ACK (DNAT)
eth0  In  IP 137.74.42.42.45448 > 10.130.0.5.80: Flags [.]
docker0 Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [.]
veth883b6aa Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [.]
# TCP FIN-ACK (DNAT) 
# Four-Way Handshake
eth0  In  IP 137.74.42.42.45448 > 10.130.0.5.80: Flags [F.]
docker0 Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [F.]
veth883b6aa Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [F.]
# TCP FIN-ACK (SNAT)
veth883b6aa P   IP 172.17.0.5.80 > 137.74.42.42.45448: Flags [F.]
docker0 In  IP 172.17.0.5.80 > 137.74.42.42.45448: Flags [F.]
eth0  Out IP 10.130.0.5.80 > 137.74.42.42.45448: Flags [F.]
# TCP ACK (DNAT) 
eth0  In  IP 137.74.42.42.45448 > 10.130.0.5.80: Flags [.]
docker0 Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [.]
veth883b6aa Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [.]

Трафик из контейнера в контейнер
#

# ICMP echo request
veth883b6aa P   IP 172.17.0.5 > 172.17.0.6: ICMP echo request
veth8027757 Out IP 172.17.0.5 > 172.17.0.6: ICMP echo request
# ICMP echo reply
veth8027757 P   IP 172.17.0.6 > 172.17.0.5: ICMP echo reply
veth883b6aa Out IP 172.17.0.6 > 172.17.0.5: ICMP echo reply

Полезная статья 1 (оригинал)
Полезная статья 1 (перевод)
Полезная статья 2

Related

Lazydocker для Docker
·89 слов·1 минута
Установка Docker на Ubuntu
·263 слов·2 минут
Настройка Policy-based и Route-based VPN через strongSwan с swanctl
·834 слов·4 минут