Акция Панель управления Ispmanager для VDS — первый месяц бесплатно
до 31.07.2026 Подробнее
Выберите продукт

IPv6 для Docker и Podman: routed /64, NDP-proxy и когда нужен NAT

Три рабочие схемы IPv6 для контейнеров на сервере: routed /64 с маршрутизацией без NAT, bridge с NDP-proxy для публичных адресов при L2-ограничениях и NAT66 как компромисс. Примеры для Docker/Podman и базовая настройка firewall.
IPv6 для Docker и Podman: routed /64, NDP-proxy и когда нужен NAT

Зачем вообще IPv6 в контейнерах (и почему тут всё сложнее, чем с IPv4)

Если просто включить IPv6 в Docker или Podman, быстро выясняется: IPv6 задумывался как «каждому узлу — публичный адрес», а контейнерные сети у многих годами жили по логике IPv4-NAT. Отсюда типовые вопросы: как раздать контейнерам реальные IPv6-адреса из вашего routed-префикса, как обеспечить входящую доступность, что делать с Neighbor Discovery (NDP) и почему публикация портов по IPv6 иногда ведёт себя не так, как ожидается.

Ниже разберём три схемы, которые чаще всего нужны на сервере/виртуалке с IPv6:

  • Маршрутизация routed /64 в контейнерные сети (рекомендуемый вариант без NAT).
  • Bridge + NDP-proxy, когда провайдер «видит» только MAC хоста (частый кейс в виртуализации).
  • NAT66 (нежелательно, но иногда единственный выход при ограничениях).

По пути затронем bridge с IPv6, нюансы публикации портов и минимально адекватный firewall для IPv6.

База: что должно быть на хосте, чтобы IPv6 для контейнеров заработал

Проверяем IPv6 на самом сервере

Сначала убедитесь, что у хоста есть глобальный IPv6-адрес и дефолтный маршрут:

ip -6 addr show
ip -6 route show
ping -6 -c 3 2606:4700:4700::1111

Если ping -6 не проходит — контейнеры ни при чём: чините IPv6 на уровне ОС/сети (адрес, маршрут, фильтры у провайдера).

Включаем форвардинг IPv6

Для схем с маршрутизацией в контейнерные сети нужен IPv6 forwarding:

sysctl -w net.ipv6.conf.all.forwarding=1
sysctl -w net.ipv6.conf.default.forwarding=1

Чтобы закрепить после перезагрузки:

printf '%s\n' 'net.ipv6.conf.all.forwarding=1' 'net.ipv6.conf.default.forwarding=1' > /etc/sysctl.d/99-ipv6-forwarding.conf
sysctl --system

Осторожно с RA на хосте

Во многих датацентрах IPv6 выдаётся как «адрес на интерфейсе + routed /64 (или /56)». В таких случаях предсказуемее опираться на статическую адресацию контейнеров/подсетей и L3-маршрутизацию, а не на SLAAC/RA внутри контейнерных сетей.

Если вы всё же планируете раздавать адреса по RA (например, через radvd), заранее проверьте, что ваш контейнерный стек и firewall не режут ICMPv6 и что вы понимаете модель маршрутизации между внешним интерфейсом и bridge.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

На практике удобнее всего начинать с сервера, где у вас полный контроль над сетевыми настройками и sysctl. Если выбираете площадку под контейнерные сервисы, посмотрите тарифы VDS и заранее уточните, выдаёт ли провайдер routed-префикс (например, /64) и как он маршрутизируется.

Проверка IPv6 маршрутов и подсети Docker bridge на хосте

Схема №1 (рекомендуется): routed /64 и маршрутизация в контейнерные сети без NAT

Идея простая: провайдер маршрутизирует на ваш сервер префикс routed /64 (например, 2001:db8:1000:2000::/64). Вы выделяете из него подсети под контейнерные сети (часто /80 или /96), назначаете подсеть на container bridge и включаете маршрутизацию между WAN-интерфейсом и bridge. NAT не нужен: каждый контейнер получает «настоящий» IPv6.

Главный плюс: end-to-end IPv6 без маскарадинга. Входящие соединения и часть прикладной логики (в т.ч. allowlist/ACL) становятся проще и прозрачнее.

План адресации: почему лучше /80 (или /96), а не весь /64 в один bridge

Технически можно отдать контейнерному bridge весь /64, но на практике удобнее дробить:

  • проще сопровождать маршруты и правила firewall;
  • легче изолировать окружения (prod/stage, разные стеки);
  • меньше шансов на «случайные» конфликты адресов и неочевидные зависимости.

Пример: routed /64 — 2001:db8:1000:2000::/64. Под Docker сеть — 2001:db8:1000:2000:10::/80, под Podman — 2001:db8:1000:2000:20::/80.

Docker: включаем IPv6 и задаём фиксированный IPv6 pool

В Docker IPv6 настраивается в /etc/docker/daemon.json. Параметр fixed-cidr-v6 должен быть внутри вашего routed-префикса:

{
  "ipv6": true,
  "fixed-cidr-v6": "2001:db8:1000:2000:10::/80",
  "ip6tables": true
}

Применяем и проверяем:

systemctl restart docker
docker network inspect bridge

Дальше проверьте IPv6 внутри контейнера:

docker run --rm -it alpine sh
ip -6 addr show
ping -6 -c 3 2606:4700:4700::1111

Podman: IPv6 в CNI/Netavark и что проверить в первую очередь

У Podman встречаются разные сетевые бэкенды. Сначала выясните текущий:

podman info --format '{{.Host.NetworkBackend}}'
podman network ls
podman network inspect podman

Для rootful Podman обычно достаточно создать сеть с IPv6-подсетью из routed /64:

podman network create --subnet 10.10.0.0/24 --ipv6 --subnet 2001:db8:1000:2000:20::/80 v6net
podman run --rm --network v6net alpine ip -6 addr

Для rootless Podman чаще используется user-space сеть (по смыслу ближе к NAT), поэтому «публичные IPv6 как у контейнеров» обычно не получают. Если нужны публичные сервисы по IPv6, выбирайте rootful-режим или заводите вход через reverse proxy на хосте.

Firewall для routed IPv6: минимум, без которого будет больно

В IPv6 нельзя «просто запретить ICMP». Для нормальной работы нужны как минимум NDP и сообщения для Path MTU Discovery (в частности Packet Too Big). Поэтому ICMPv6 лучше разрешить явно (а уже потом точечно ужесточать по типам, если понимаете последствия).

Пример минимального каркаса на nftables (адаптируйте интерфейсы и политику под себя):

nft add table inet filter
nft 'add chain inet filter input { type filter hook input priority 0; policy drop; }'
nft 'add chain inet filter forward { type filter hook forward priority 0; policy drop; }'
nft 'add chain inet filter output { type filter hook output priority 0; policy accept; }'

nft add rule inet filter input ct state established,related accept
nft add rule inet filter input iif lo accept
nft add rule inet filter input ip6 nexthdr icmpv6 accept
nft add rule inet filter input tcp dport 22 accept

nft add rule inet filter forward ct state established,related accept
nft add rule inet filter forward ip6 nexthdr icmpv6 accept

Если у вас Docker с включённым ip6tables, он добавит свои правила. Полезно понимать, как это пересекается с вашим nftables-политиками. По этой теме пригодится материал: как подружить Docker с iptables/nftables и не сломать firewall.

Публикация портов по IPv6 в routed-схеме: что реально происходит

Когда контейнер имеет глобальный IPv6-адрес, у вас обычно два рабочих подхода:

  • Давать доступ напрямую на IPv6 контейнера (без публикации порта на хосте).
  • Использовать публикацию портов (DNAT на хосте), чтобы входили на IPv6 хоста, а дальше попадали в контейнер.

На практике первый вариант часто проще (меньше «магии»): открыли порт в firewall и обращаетесь к адресу контейнера. Второй вариант удобен, если нужен единый внешний адрес и привычная схема «порт на хосте», но это уже проксирование или переназначение на уровне хоста.

Схема №2: bridge IPv6 и NDP-proxy (когда провайдер не видит контейнеры)

Эта схема нужна, когда вы назначаете контейнеру «публичный» IPv6 из routed /64, но внешний мир не может корректно разрешить соседство: провайдер видит только MAC вашего виртуального интерфейса и не принимает NDP-ответы от «чужих» MAC (контейнеров). В итоге адрес у контейнера вроде бы из вашего префикса, но снаружи он не пингуется и соединения не доходят.

Решение: NDP proxy. Хост отвечает на Neighbor Solicitation за контейнерные адреса и принимает трафик на себя, после чего отправляет его внутрь.

Где здесь чаще всего ошибаются

  • Включают proxy только на внешнем интерфейсе, забывая про настройки на стороне bridge.
  • Проксируют «весь /64» без контроля и получают мусор или конфликты в neighbor-таблицах.
  • Режут ICMPv6 в input или forward и ломают NDP.

Включаем NDP proxy на Linux

Включите proxy_ndp на внешнем интерфейсе (например, eth0) и на интерфейсе, куда смотрят контейнеры (например, docker0):

sysctl -w net.ipv6.conf.eth0.proxy_ndp=1
sysctl -w net.ipv6.conf.docker0.proxy_ndp=1

Дальше добавьте прокси-записи для конкретных IPv6 контейнеров (это обычно безопаснее, чем «разрешить всё префиксом»):

ip -6 neigh add proxy 2001:db8:1000:2000:10::10 dev eth0
ip -6 neigh add proxy 2001:db8:1000:2000:10::11 dev eth0

Теперь внешний сосед получит NDP-ответ от хоста, а дальше трафик будет доставлен до контейнера через вашу внутреннюю маршрутизацию или bridge.

Как автоматизировать NDP proxy для динамических контейнеров

Если адреса статические (вы задаёте их явно контейнеру) — держите список ip -6 neigh add proxy для нужных адресов и применяйте при старте системы.

Если адреса динамические — на практике есть три пути:

  • перейти на схему маршрутизации подсетей (Схема №1), чтобы NDP proxy не требовался;
  • повесить автоматизацию на события Docker/Podman и добавлять или удалять proxy-записи при старте или остановке контейнера;
  • сделать вход только через reverse proxy на хосте (а контейнеры держать «внутренними»).
FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Если контейнеры получают «публичные» IPv6, часто всплывает второй вопрос: как правильно терминировать TLS (особенно когда сервисов много). В таких случаях удобнее держать сертификаты централизованно и регулярно обновлять их — посмотрите SSL-сертификаты, если нужен коммерческий сертификат под прод.

Схема работы NDP-proxy для IPv6 адресов контейнеров на bridge

Firewall и NDP-proxy: критичные разрешения

Для NDP-proxy жизненно важен ICMPv6. Если у вас политика «drop всё», убедитесь, что ICMPv6 разрешён как минимум на input и forward, иначе симптомы будут максимально неприятными: «IPv6 адрес есть, но не пингуется и сервисы не открываются».

Схема №3: NAT66 для контейнеров (когда routed /64 недоступен или есть жёсткие ограничения)

NAT66 в IPv6 — компромисс. Он бывает нужен, если у вас нет routed-префикса (только один IPv6 на хосте), либо провайдер жёстко ограничивает использование дополнительных IPv6-адресов или маршрутов.

Если у вас есть routed /64 (или больше) — почти всегда лучше маршрутизация без NAT. NAT66 оставляйте как запасной план.

NAT66 на nftables: пример маскарадинга исходящих

Предположим, контейнеры живут в ULA-подсети fd00:10:10::/64. Тогда можно сделать SNAT masquerade на внешний IPv6 хоста:

nft add table ip6 nat
nft 'add chain ip6 nat postrouting { type nat hook postrouting priority 100; policy accept; }'
nft add rule ip6 nat postrouting oif eth0 ip6 saddr fd00:10:10::/64 masquerade

Входящие подключения при NAT66 обычно реализуются через DNAT (публикация портов) или через reverse proxy на хосте.

Docker/Podman и NAT66: что будет с публикацией портов

В NAT66-модели всё становится похоже на привычный IPv4-сценарий:

  • контейнеры «прячутся» за адресом хоста;
  • доступ снаружи делаете через публикацию портов на хосте.

Минус — сложнее дебажить и мониторить источники, а некоторые протоколы или приложения в IPv6-мире рассчитывают на end-to-end доступность.

Отладка: как быстро понять, где сломалось

1) Есть ли маршрут до контейнерной подсети

Проверьте маршруты на хосте:

ip -6 route show

Вы должны видеть маршрут вашей контейнерной подсети через docker0 или другой bridge-интерфейс.

2) Работает ли NDP (если используете NDP-proxy)

Посмотрите прокси-записи:

ip -6 neigh show proxy

И поймайте ICMPv6/NDP пакеты на внешнем интерфейсе:

tcpdump -n -i eth0 icmp6

Если NDP-пакеты есть, но адрес снаружи «молчит», чаще всего причина в firewall (режется ICMPv6) или в том, что не добавлены proxy-записи под реальные адреса контейнеров.

3) Проверяем, что сервис слушает на IPv6

Даже при идеальной сети приложение может слушать только IPv4. Проверяйте на хосте и в контейнере:

ss -lntup
ss -lnup

4) MTU и «странные таймауты»

В IPv6 зависания часто связаны с MTU и блокировкой ICMPv6 Packet Too Big. Если страницы грузятся частично или TLS-рукопожатие подвисает — проверьте, что ICMPv6 не режется, и при необходимости подберите MTU на контейнерном bridge.

Практические рекомендации по выбору схемы

  • Есть routed /64 и нужны публичные контейнеры: берите маршрутизацию подсетей без NAT (Схема №1).
  • Адреса из routed /64 не пингуются снаружи при bridge-схеме: вероятно, нужен NDP-proxy (Схема №2) или вы построили L2 там, где должен быть L3-route.
  • Нет routed-префикса, только один IPv6 на хосте: NAT66 как компромисс (Схема №3) плюс публикация портов или reverse proxy.

Если вы разворачиваете это на сервере под прод, полезно фиксировать адреса и подсети, хранить firewall как код и иметь стенд, где вы спокойно проверяете NDP и маршрутизацию до релиза.

Частые вопросы

Можно ли просто включить IPv6 в Docker — и всё заработает?

Если у вас корректно маршрутизируемый routed-префикс и не режется ICMPv6, часто «почти заработает». Но затем всплывают нюансы: правила firewall для ICMPv6, выбор модели доступа (напрямую на адрес контейнера или через публикацию порта) и возможная необходимость NDP-proxy при L2-ограничениях.

Почему Podman IPv6 в rootless режиме обычно не даёт публичных адресов?

Rootless чаще использует user-space сеть (например, slirp4netns), которая по смыслу ближе к NAT и не даёт простую модель «публичный IPv6 на каждый контейнер». Для end-to-end IPv6 обычно выбирают rootful Podman и bridge или маршрутизацию, либо терминируют вход на хосте.

ip6tables или nftables — что лучше?

На современных дистрибутивах чаще удобнее nftables: один язык правил и таблицы inet. Но Docker исторически активно работает с iptables/ip6tables. Если ваш основной firewall — nftables, заранее проверьте, нет ли конфликта политик и что ICMPv6 не отрезается. Ещё один полезный материал по контейнерной безопасности: когда уместны gVisor и Firecracker для изоляции контейнеров.

Итог

IPv6 в контейнерах — это не «галочка», а выбор сетевой модели. При наличии routed /64 самый устойчивый путь — маршрутизировать отдельные подсети в container bridge и жить без NAT. NDP-proxy помогает, когда вы упираетесь в L2-ограничения виртуализации. NAT66 стоит оставлять на крайний случай.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину

Ошибка mount: wrong fs type, bad option, bad superblock в Debian/Ubuntu может означать и простую опечатку в имени раздела, и пробл ...
Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление

Если XFS-раздел внезапно стал доступен только для чтения, а сервер ушёл в emergency mode, главное — не спешить. Разберём безопасны ...
Debian/Ubuntu: как исправить Failed to fetch при apt update OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Failed to fetch при apt update

Ошибка Failed to fetch при apt update в Debian и Ubuntu обычно связана не с самим APT, а с DNS, сетью, зеркалом, прокси, временем ...