Здесь показаны различия между двумя версиями данной страницы.
| Предыдущая версия справа и слева Предыдущая версия | |||
|
linux:docker_network_conflict [2026/08/13 08:02] werwolf |
linux:docker_network_conflict [2026/08/13 08:03] (текущий) werwolf |
||
|---|---|---|---|
| Строка 1: | Строка 1: | ||
| ====== Конфликт сети Docker с корпоративной / VPN-сетью ====== | ====== Конфликт сети Docker с корпоративной / VPN-сетью ====== | ||
| - | **Страница:** ''dev:docker_network_conflict'' **Когда смотреть:** внутренний сервис не открывается, DNS «в порядке», VPN подключён, недавно поднимали Docker. | + | <note tip> |
| + | **Страница:** ''dev:docker_network_conflict'' | ||
| + | **Когда смотреть:** внутренний сервис не открывается, DNS «в порядке», VPN подключён, недавно поднимали Docker. | ||
| + | </note> | ||
| ===== Симптомы ===== | ===== Симптомы ===== | ||
| - | Не открывается внутренний сервис (Jira, LDAP, ''change-pass.iep.loc'', git и т.п.) — ни в браузере, ни в ''curl'' | + | * Не открывается внутренний сервис (Jira, LDAP, ''change-pass.iep.loc'', git и т.п.) — ни в браузере, ни в ''curl'' |
| - | Ошибка: ''Connection refused'' или ''Failed to connect'' на портах 80/443 | + | * Ошибка: ''Connection refused'' или ''Failed to connect'' на портах 80/443 |
| - | ''getent hosts hostname'' возвращает ожидаемый IP | + | * ''getent hosts hostname'' возвращает **ожидаемый** IP |
| - | Проблема часто появляется после ''docker compose up'' или когда на машине много compose-проектов | + | * Проблема часто появляется после ''docker compose up'' или когда на машине много compose-проектов |
| - | VPN подключён (''tun0'' / ''tun1'' есть), но сервис недоступен | + | * VPN подключён (''tun0'' / ''tun1'' есть), но сервис недоступен |
| ===== Суть проблемы ===== | ===== Суть проблемы ===== | ||
| Docker создаёт bridge-интерфейсы (''br-xxxxxxxxxxxx'') с собственными подсетями: | Docker создаёт bridge-интерфейсы (''br-xxxxxxxxxxxx'') с собственными подсетями: | ||
| - | ''172.17.0.0/16'' — default bridge | + | * ''172.17.0.0/16'' — default bridge |
| - | ''172.22.0.0/16'' — сеть проекта ''rpcserver_default'' | + | * ''172.22.0.0/16'' — сеть проекта ''rpcserver_default'' |
| - | ''172.18.0.0/16'' — ''status_app-network'' | + | * ''172.18.0.0/16'' — ''status_app-network'' |
| - | Если подсеть Docker пересекается с корпоративной (часто диапазон ''172.16.0.0/12'', в IEP — ''172.22.0.0/16''), Linux направляет пакеты в Docker-bridge, а не в VPN. | + | |
| + | Если подсеть Docker **пересекается** с корпоративной (часто диапазон ''172.16.0.0/12'', в IEP — ''172.22.0.0/16''), Linux направляет пакеты **в Docker-bridge**, а не в VPN. | ||
| + | |||
| + | <note important> | ||
| + | Это **не** проблема DNS и **не** ''resolv.conf''. Имя резолвится верно; ломается **маршрутизация** на уровне ядра Linux. | ||
| + | </note> | ||
| - | Это **не** проблема DNS и **не** ''resolv.conf''. Имя резолвится верно; ломается **маршрутизация** на уровне ядра. | ||
| ===== Один IP — два разных случая ===== | ===== Один IP — два разных случая ===== | ||
| - | Типичная ловушка: DNS и маршрут говорят разное про один хост. Кажется, что «имя резолвится — значит всё ок», а ''curl'' и браузер URL не открывают. | + | Типичная ловушка: **DNS и маршрут говорят разное** про один и тот же хост. Поэтому кажется, что «имя резолвится — значит всё ок», а ''curl''/браузер всё равно не открывают URL. |
| + | |||
| + | ==== Пример с change-pass.iep.loc ==== | ||
| - | ==== Пример: change-pass.iep.loc ==== | + | Выполняем подряд: |
| + | <code bash> | ||
| grep -n change-pass /etc/hosts /etc/resolv.conf 2>/dev/null | grep -n change-pass /etc/hosts /etc/resolv.conf 2>/dev/null | ||
| getent hosts change-pass.iep.loc | getent hosts change-pass.iep.loc | ||
| ip route get 172.22.224.153 | ip route get 172.22.224.153 | ||
| - | При конфликте с Docker вывод такой: | + | </code> |
| + | Вывод **при конфликте с Docker**: | ||
| + | |||
| + | <code> | ||
| 172.22.224.153 change-pass.iep.loc | 172.22.224.153 change-pass.iep.loc | ||
| 172.22.224.153 dev br-355c08cd95dd src 172.22.0.1 uid 1891243316 | 172.22.224.153 dev br-355c08cd95dd src 172.22.0.1 uid 1891243316 | ||
| cache | cache | ||
| - | Разбор: | + | </code> |
| - | ^ Строка / команда ^ Что кажется ^ Что на самом деле ^ | ''getent hosts'' → ''172.22.224.153 change-pass.iep.loc'' | DNS/hosts работает | Имя → IP верно. ''resolv.conf'' тут ни при чём | | ''ip route get'' → ''dev br-355c08cd95dd src 172.22.0.1'' | — | Пакеты уходят в Docker-bridge, не в VPN/IEP | | ''grep … /etc/hosts'' — пусто | — | Имя резолвится через DNS, без записи в ''/etc/hosts'' | | + | Разбор по строкам: |
| - | Одна подсеть ''172.22.0.0/16'' — два назначения: | + | ^ Команда / строка ^ Что кажется ^ Что на самом деле ^ |
| + | | ''getent hosts'' → ''172.22.224.153 change-pass.iep.loc'' | DNS/hosts работает, IP «правильный» | Да, имя → IP определено верно. Это **не** проблема ''resolv.conf'' | | ||
| + | | ''ip route get'' → ''dev br-355c08cd95dd src 172.22.0.1'' | — | Пакеты к ''172.22.224.153'' уходят в **Docker-bridge**, а не в VPN/IEP | | ||
| + | | ''grep … /etc/hosts'' — пусто | Запись только в DNS | Имя может резолвиться и без ''/etc/hosts'' | | ||
| - | Корпоративная IEP — сервер ''change-pass.iep.loc'' (''172.22.224.153''), доступ через VPN (''tun0'') | + | <note important> |
| - | Docker ''rpcserver_default'' — bridge ''br-355c08cd95dd'', адрес ''172.22.0.1/16'' | + | **Два случая использования одной подсети ''172.22.0.0/16'':** |
| - | Ядро не различает «корпоративный'' и «docker'' ''172.22.x.x''. Срабатывает маршрут ''172.22.0.0/16 dev br-...'' → трафик не доходит до IEP → ''Connection refused''. | + | |
| - | ==== DNS vs маршрут — сводка ==== | + | - **Корпоративная сеть IEP** — реальный сервер ''change-pass.iep.loc'' (''172.22.224.153''), доступ через VPN (''tun0'') |
| + | - **Docker-сеть ''rpcserver_default''** — локальный bridge ''br-355c08cd95dd'' с адресом ''172.22.0.1/16'' | ||
| - | ^ Проверка ^ Норма ^ Конфликт с Docker ^ | ''getent hosts HOST'' | IP из корпсети | То же — IP верный | | ''ip route get IP'' | ''via 10.x.x.x dev tun0'' | ''dev br-... src 172.22.0.1'' | | ''curl https://HOST/'' | HTTP 200 | ''Connection refused'' | | + | Ядро Linux не различает «корпоративный'' и «docker'' ''172.22.x.x'' — срабатывает маршрут ''172.22.0.0/16 dev br-...''. Трафик **не доходит** до сервера в IEP → ''Connection refused''. |
| + | </note> | ||
| + | |||
| + | ==== Как отличить: DNS vs маршрут ==== | ||
| + | |||
| + | ^ Проверка ^ Норма (конфликта нет) ^ Конфликт с Docker ^ | ||
| + | | ''getent hosts HOST'' | IP из корпсети (напр. ''172.22.224.153'') | То же — IP верный | | ||
| + | | ''ip route get IP'' | ''via 10.x.x.x dev tun0'' (VPN) | ''dev br-xxxxxxxxxxxx src 172.22.0.1'' | | ||
| + | | ''curl https://HOST/'' | HTTP 200 / редирект | ''Connection refused'' | | ||
| + | |||
| + | <note tip> | ||
| + | Если ''getent'' и ''curl'' расходятся по смыслу — **сначала смотрите ''ip route get''**, не ''resolv.conf''. | ||
| + | </note> | ||
| + | |||
| + | ==== После устранения конфликта ==== | ||
| + | |||
| + | <code bash> | ||
| + | docker compose down # в проекте с сетью 172.22.0.0/16 | ||
| + | ip route get 172.22.224.153 | ||
| + | </code> | ||
| - | Если ''getent'' «работает», а ''curl'' — нет, **сначала ''ip route get''**, не ''resolv.conf''. | + | Ожидаемый маршрут: |
| - | После устранения конфликта тот же IP, другой маршрут: | + | |
| + | <code> | ||
| 172.22.224.153 via 10.5.192.1 dev tun0 src 10.5.193.211 | 172.22.224.153 via 10.5.192.1 dev tun0 src 10.5.193.211 | ||
| cache | cache | ||
| + | </code> | ||
| + | |||
| + | Тот же IP ''172.22.224.153'', но теперь трафик идёт **через VPN**, а не в Docker-bridge. | ||
| + | |||
| ===== Быстрая диагностика ===== | ===== Быстрая диагностика ===== | ||
| ==== 1. DNS ==== | ==== 1. DNS ==== | ||
| + | <code bash> | ||
| HOST=change-pass.iep.loc | HOST=change-pass.iep.loc | ||
| getent hosts "$HOST" | getent hosts "$HOST" | ||
| - | IP получен — резолв в порядке (DNS или ''/etc/hosts''). | + | </code> |
| + | |||
| + | Если IP получен — резолв в порядке (DNS или ''/etc/hosts''). | ||
| ==== 2. Маршрут (главная проверка) ==== | ==== 2. Маршрут (главная проверка) ==== | ||
| + | <code bash> | ||
| ip route get $(getent hosts "$HOST" | awk '{print $1; exit}') | ip route get $(getent hosts "$HOST" | awk '{print $1; exit}') | ||
| - | Плохо: | + | </code> |
| + | |||
| + | **Плохо** — трафик в Docker: | ||
| + | <code> | ||
| 172.22.224.153 dev br-355c08cd95dd src 172.22.0.1 | 172.22.224.153 dev br-355c08cd95dd src 172.22.0.1 | ||
| - | Хорошо: | + | </code> |
| + | **Хорошо** — трафик через VPN: | ||
| + | |||
| + | <code> | ||
| 172.22.224.153 via 10.5.192.1 dev tun0 src 10.5.193.211 | 172.22.224.153 via 10.5.192.1 dev tun0 src 10.5.193.211 | ||
| + | </code> | ||
| + | |||
| ==== 3. Порты и HTTP ==== | ==== 3. Порты и HTTP ==== | ||
| + | <code bash> | ||
| IP=$(getent hosts "$HOST" | awk '{print $1; exit}') | IP=$(getent hosts "$HOST" | awk '{print $1; exit}') | ||
| nc -vz "$IP" 80 | nc -vz "$IP" 80 | ||
| nc -vz "$IP" 443 | nc -vz "$IP" 443 | ||
| curl -vk --connect-timeout 5 "https://$HOST/" | curl -vk --connect-timeout 5 "https://$HOST/" | ||
| - | При конфликте с Docker — ''Connection refused'' до VPN, даже если ''tun0'' поднят. | + | </code> |
| + | |||
| + | При конфликте с Docker — ''Connection refused'' **до** VPN, даже если ''tun0'' поднят. | ||
| - | ==== 4. Конфликтующая Docker-сеть ==== | + | ==== 4. Найти конфликтующую Docker-сеть ==== |
| + | <code bash> | ||
| ip route | grep 172.22 | ip route | grep 172.22 | ||
| docker network ls | docker network ls | ||
| docker network inspect $(docker network ls -q) | grep -E '"Name"|Subnet|Gateway' | docker network inspect $(docker network ls -q) | grep -E '"Name"|Subnet|Gateway' | ||
| - | Ищите ''Subnet'', совпадающий с корпоративным диапазоном. | + | </code> |
| - | Кейс 2026-08-13: | + | Ищите сеть, у которой ''Subnet'' совпадает с диапазоном корпоративных адресов. |
| + | |||
| + | **Кейс 2026-08-13:** | ||
| + | |||
| + | * Сеть: ''rpcserver_default'' | ||
| + | * Subnet: ''172.22.0.0/16'' | ||
| + | * Bridge: ''br-355c08cd95dd'' | ||
| + | * Конфликт с: ''change-pass.iep.loc'' (''172.22.224.153'') | ||
| - | Сеть: ''rpcserver_default'' | ||
| - | Subnet: ''172.22.0.0/16'' | ||
| - | Bridge: ''br-355c08cd95dd'' | ||
| - | Конфликт с: ''change-pass.iep.loc'' (''172.22.224.153'') | ||
| ===== Скрипт проверки ===== | ===== Скрипт проверки ===== | ||
| - | #!/usr/bin/env bash set -u | + | Сохраните как ''check-docker-network-conflict.sh'': |
| + | |||
| + | <file bash check-docker-network-conflict.sh> | ||
| + | #!/usr/bin/env bash | ||
| + | set -u | ||
| HOST="${1:-change-pass.iep.loc}" | HOST="${1:-change-pass.iep.loc}" | ||
| - | echo "=== DNS ===" getent hosts "$HOST" || { echo "DNS fail"; exit 1; } IP=$(getent hosts "$HOST" | awk '{print $1; exit}') echo "IP=$IP" | + | echo "=== DNS ===" |
| + | getent hosts "$HOST" || { echo "DNS fail"; exit 1; } | ||
| + | IP=$(getent hosts "$HOST" | awk '{print $1; exit}') | ||
| + | echo "IP=$IP" | ||
| + | |||
| + | echo | ||
| + | echo "=== Route ===" | ||
| + | ip route get "$IP" | ||
| - | echo echo "=== Route ===" ip route get "$IP" | + | echo |
| + | echo "=== TCP 80/443 ===" | ||
| + | nc -vz "$IP" 80 2>&1 || true | ||
| + | nc -vz "$IP" 443 2>&1 || true | ||
| - | echo echo "=== TCP 80/443 ===" nc -vz "$IP" 80 2>&1 || true nc -vz "$IP" 443 2>&1 || true | + | echo |
| + | echo "=== Docker routes (172.16/12) ===" | ||
| + | ip route | grep -E '172\.(1[6-9]|2[0-9]|3[0-1])\.' || echo "(none)" | ||
| - | echo echo "=== Docker routes (172.16/12) ===" ip route | grep -E '172.(1[6-9]|2[0-9]|3[0-1]).' || echo "(none)" | + | echo |
| + | echo "=== Docker networks ===" | ||
| + | docker network inspect $(docker network ls -q) 2>/dev/null \ | ||
| + | | grep -E '"Name"|Subnet|Gateway' || echo "docker not available" | ||
| + | </file> | ||
| - | echo echo "=== Docker networks ===" docker network inspect $(docker network ls -q) 2>/dev/null | + | Запуск: |
| - | | grep -E '"Name"|Subnet|Gateway' || echo "docker not available" | + | |
| + | <code bash> | ||
| chmod +x check-docker-network-conflict.sh | chmod +x check-docker-network-conflict.sh | ||
| ./check-docker-network-conflict.sh change-pass.iep.loc | ./check-docker-network-conflict.sh change-pass.iep.loc | ||
| + | </code> | ||
| + | |||
| ===== Решение ===== | ===== Решение ===== | ||
| ==== Сразу восстановить доступ ==== | ==== Сразу восстановить доступ ==== | ||
| + | <code bash> | ||
| cd ~/projects/rpc.server | cd ~/projects/rpc.server | ||
| docker compose down | docker compose down | ||
| + | </code> | ||
| + | |||
| Проверка: | Проверка: | ||
| + | <code bash> | ||
| ip route get 172.22.224.153 | ip route get 172.22.224.153 | ||
| curl -vk https://change-pass.iep.loc/ | curl -vk https://change-pass.iep.loc/ | ||
| - | Маршрут — через ''tun0'', не ''br-...''. ''curl'' — HTTP 200. | + | </code> |
| + | |||
| + | Маршрут должен идти через ''tun0'', не через ''br-...''. ''curl'' — HTTP 200. | ||
| ==== Навсегда: другой subnet в compose ==== | ==== Навсегда: другой subnet в compose ==== | ||
| + | В ''docker-compose.yml'' проекта задайте подсеть **вне** корпоративной: | ||
| + | |||
| + | <code yaml> | ||
| networks: | networks: | ||
| default: | default: | ||
| Строка 127: | Строка 219: | ||
| config: | config: | ||
| - subnet: 192.168.220.0/24 | - subnet: 192.168.220.0/24 | ||
| - | Берите диапазоны **вне** ''172.16.0.0/12'', если VPN/IEP использует ''172.18–172.31''. Примеры: ''192.168.200.0/24'', ''192.168.220.0/24''. | + | </code> |
| - | ==== Глобально: пулы для новых сетей Docker ==== | + | |
| + | <note tip> | ||
| + | Выбирайте диапазоны вне ''172.16.0.0/12'' (172.16–172.31), если VPN/IEP использует ''172.22.x'', ''172.18.x'' и т.п. Удобные варианты: ''192.168.200.0/24'', ''192.168.220.0/24''. | ||
| + | </note> | ||
| + | |||
| + | ==== Глобально: пулы для новых Docker-сетей ==== | ||
| ''/etc/docker/daemon.json'': | ''/etc/docker/daemon.json'': | ||
| + | <code json> | ||
| { | { | ||
| "default-address-pools": [ | "default-address-pools": [ | ||
| Строка 137: | Строка 235: | ||
| ] | ] | ||
| } | } | ||
| + | </code> | ||
| + | |||
| + | Применить: | ||
| + | |||
| + | <code bash> | ||
| sudo systemctl restart docker | sudo systemctl restart docker | ||
| - | После смены пулов пересоздайте compose-проекты (''docker compose down && docker compose up -d''). Старые сети с ''172.22.0.0/16'' останутся, пока их не удалите. | + | </code> |
| + | |||
| + | <note warning> | ||
| + | После смены пулов **пересоздайте** существующие compose-проекты (''docker compose down && docker compose up -d''). Старые сети с ''172.22.0.0/16'' останутся, пока их не удалите. | ||
| + | </note> | ||
| ===== Таблица: что проверять ===== | ===== Таблица: что проверять ===== | ||
| - | ^ Симптом ^ Причина ^ Действие ^ | DNS не резолвит | ''resolv.conf'', VPN DNS, ''/etc/hosts'' | ''getent hosts'' | | DNS OK, route через ''br-...'' | Конфликт subnet Docker | ''ip route get'', ''docker network inspect'' | | Route через ''tun0'', timeout | VPN без маршрута в сеть | ''ip route'', админы VPN | | Route OK, Connection refused | Сервис не слушает порт | ''nc -vz'' на сервере | | TLS verify error | Корпоративный CA | ''curl -k'' / установка CA | | + | ^ Симптом ^ Вероятная причина ^ Что смотреть ^ |
| + | | DNS не резолвит | ''resolv.conf'', VPN DNS, ''/etc/hosts'' | ''getent hosts'' | | ||
| + | | DNS OK, route через ''br-...'' | Конфликт subnet Docker | ''ip route get'', ''docker network inspect'' | | ||
| + | | Route через ''tun0'', но timeout | VPN без маршрута в сеть | ''ip route'', админы VPN | | ||
| + | | Route OK, Connection refused | Сервис не слушает порт | ''nc -vz IP 443'' на сервере | | ||
| + | | TLS verify error | Корпоративный CA | ''curl -k'' / установка CA в систему | | ||
| ===== Профилактика ===== | ===== Профилактика ===== | ||
| - | Не использовать ''172.16.0.0/12'' для Docker при работе с IEP/RTLabs VPN | + | - Не использовать ''172.16.0.0/12'' для Docker, если работаете с IEP/RTLabs VPN |
| - | Перед ''docker compose up'': ''docker network inspect … | grep Subnet'' | + | - Перед ''docker compose up'' нового проекта: ''docker network inspect … | grep Subnet'' |
| - | После смены VPN/машины: ''ip route get <корп-хост>'' | + | - При смене машины / VPN — один раз: ''ip route get <корп-хост>'' |
| - | Subnet compose-проектов — в README | + | - Subnet каждого локального compose — в README проекта |
| ===== Полезные команды ===== | ===== Полезные команды ===== | ||
| + | <code bash> | ||
| + | # все маршруты Docker-bridge в 172.16/12 | ||
| ip route | grep '^172\.' | ip route | grep '^172\.' | ||
| + | |||
| + | # какой bridge за какой сетью | ||
| docker network ls | docker network ls | ||
| docker network inspect rpcserver_default | docker network inspect rpcserver_default | ||
| - | docker network rm rpcserver_default # если compose уже down | + | |
| + | # удалить «висячую» сеть (если compose уже down) | ||
| + | docker network rm rpcserver_default | ||
| + | </code> | ||
| ===== История кейса ===== | ===== История кейса ===== | ||
| - | Дата: 2026-08-13 | + | * **Дата:** 2026-08-13 |
| - | Хост: ''change-pass.iep.loc'' → ''172.22.224.153'' | + | * **Хост:** ''change-pass.iep.loc'' → ''172.22.224.153'' |
| - | Причина: ''rpcserver_default'' (''172.22.0.0/16'') перехватывала маршрут | + | * **Причина:** Docker-сеть ''rpcserver_default'' (''172.22.0.0/16'') перехватывала маршрут |
| - | Решение: ''docker compose down'' + subnet ''192.168.220.0/24'' в compose | + | * **Решение:** ''docker compose down'' в ''rpc.server'' + subnet ''192.168.220.0/24'' в compose |
| - | Результат: route via ''tun0'', ''curl'' → HTTP 200 | + | * **Результат:** ''ip route get'' → ''via 10.5.192.1 dev tun0'', ''curl'' → HTTP 200 |
| - | Структура: симптомы → суть → пример с двумя случаями → пошаговая диагностика → скрипт → решение → таблица → профилактика → кейс. Блок про ''getent'' vs ''ip route get'' стоит сразу после «Суть проблемы», перед пошаговыми командами — логика «сначала понять ловушку, потом чинить». | + | |