Перейти к основному содержимому
  1. Posts/
  2. Self-Hosting/

Docker упёрся в лимит сетей: как я это диагностировал и починил через default-address-pools

··976 слов·5 минут· loading · loading · ·
Stilicho2011
Автор
Stilicho2011
Пишу о homelab, self-hosting, автоматизации и open-source решениях
Оглавление
Self-Hosting - This article is part of a series.
Part : This Article

С чем столкнулся
#

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

Потом я перенёс Traefik из Docker в отдельный LXC-контейнер, где он работает уже не как контейнер, а как обычный бинарник (подробно расписано в серии статей про установку Traefik в LXC: часть 2 про финальный конфиг, часть 3 про CrowdSec рядом с ним и часть про firewall про вынос всего этого в отдельный VLAN). И тут схема с общей сетью перестала работать в принципе. Traefik больше не Docker-контейнер, а значит он физически не может подключиться к docker-сети через external: true, как раньше, - у него просто нет доступа внутрь докеровской сетевой изоляции. Пришлось перекраивать всё заново. Хотя более правильно сказать оптимизировать. Общую сеть с прокси я убрал, а каждый docker-compose стэк получил свою собственную отдельную сеть.

Note

Побочным эффектом это дало и классическую изоляцию: контейнеры одного стэка видят друг друга, а контейнеры разных стэков - вообще не видят друг друга напрямую, ведь общей сети с Traefik больше не существует как класса.

Пока я по очереди пересоздавал стэки под новую схему, в какой-то момент docker compose up перестал подниматься с ошибкой:

Error response from daemon: could not find an available, non-overlapping IPv4 address pool among the defaults to assign to the network

Иногда было то же самое но несколько видоизмененном виде:

failed to create network <имя>_default: Error response from daemon: could not find an available, non-overlapping IPv4 address pool among the defaults to assign to the network

То есть Docker физически не может создать ещё одну сеть - у него закончились свободные диапазоны адресов.

Ко всему прочему еще и другие docker-сети переставали подниматься с той же самой ошибкой - то есть проблема была не в одном конкретном стэке, а системная, во всём пуле адресов сразу.

Почему это происходит
#

Когда сеть создаётся без явно указанной подсети - subnet, Docker берёт её из внутреннего пула - так называемого default-address-pools. По умолчанию это диапазон 172.17.0.0/12 (то есть весь блок 172.16.0.0–172.31.255.255), из которого каждой новой сети нарезается часть размером /20.

Кусок /20 - это 4096 адресов на одну сеть. Даже если в стэке два контейнера, Docker всё равно выделит ему такой блок. При таком размере куска из /12 помещается всего около 15–16 сетей - и это без учёта того, что часть диапазона может пересекаться с адресами, которые уже заняты в системе (например VPN, другие интерфейсы и т.д.).

Warning

Именно поэтому переход на схему «своя сеть каждому стэку» - это ровно тот сценарий, где лимит наступает очень и очень быстро. Один общий bridge на всё - одна сеть. С десяток стэков с отдельными сетями - десяток сетей, и лимит по умолчанию оказывается где-то совсем рядом.

Диагностика
#

Первым делом стоит убедиться, что дело действительно в исчерпании пула, а не в разовом конфликте с конкретной подсетью. Смотрю, сколько сетей уже создано и какие диапазоны они занимают:

docker network ls
docker network inspect $(docker network ls -q) -f '{{.Name}}: {{range .IPAM.Config}}{{.Subnet}}{{end}}'

Если в выводе видно, что уже занята большая часть диапазона 172.16.0.0/12 кусками по /20, а новых сетей становится всё больше - это оно.

Также стоит проверить, не мешает ли что-то ещё в сети хоста (VPN-клиент, дополнительный интерфейс), поднимающее маршруты в том же диапазоне - тогда пересечение начнётся ещё раньше, чем реально закончится пул адресов. У меня причиной послужило именно количество сетей.

Решение
#

Пул адресов и размер куска на одну сеть настраиваются в /etc/docker/daemon.json через ключ default-address-pools. Я задал следующее значение:

{
  "default-address-pools": [
    { "base": "172.20.0.0/12", "size": 24 }
  ]
}

Смысл в двух деталях:

  1. Кусок размером /24 вместо дефолтного /20 - это 256 адресов на сеть вместо 4096. Для обычного docker-compose стэка этого более чем достаточно.
  2. Пул /12, нарезанный на /24, даёт порядка 4096 доступных сетей вместо изначальных полутора десятков.

После правки конфига нужен рестарт демона:

sudo systemctl restart docker
Caution

Рестарт демона Docker не трогает уже запущенные контейнеры напрямую, но пересоздаёт сетевой стек - на практике я делал это в окно с минимальным трафиком. Говоря человеческим языком - ночью. После проверял, что все контейнеры и их сети поднялись штатно. Если у вас продакшн-нагрузка - планируйте рестарт как отдельный шаг, а не «на ходу».

После рестарта новые сети уже нарезаются из нового пула, а docker compose up для оставшихся стэков прошёл без ошибок.

На что обратить внимание, если планируете то же самое
#

  • Если вы переходите с общей сети на отдельную сеть под каждый стэк - сразу закладывайте default-address-pools с меньшим размером куска (/24 или даже /28 для совсем мелких сетей), а не ждите, пока упрётесь в лимит на середине миграции.
  • base можно выбрать любой приватный диапазон, не пересекающийся с вашей физической сетью и VLAN’ами - если у вас уже используется часть 172.x.x.x под что-то ещё (например, под VLAN’ы, как у меня), проверьте, что диапазон в daemon.json с ними не пересекается.
  • Изменение default-address-pools действует только на новые сети - уже существующие сети сохраняют свои старые (более широкие) подсети, пересоздавать их не обязательно.
  • Удалённые, но не подключённые ни к одному контейнеру сети (docker network prune) освобождают место в пуле - если лимит подкрался предательски внезапно, для начала стоит проверить, не накопился ли мусор из старых сетей.

Итог
#

Ограничение по числу Docker-сетей - это не какой-то отдельный «лимит», а прямое следствие того, как Docker по умолчанию нарезает адресный пул: большими кусками из небольшого диапазона. Как только сетей становится больше десятка, дефолтных настроек перестает хватать. Решение простое, делается один раз - сузить размер выделяемой каждой сети подсети через default-address-pools в daemon.json, и лимит отодвигается на тысячи сетей вперёд. Для домашней лабы это уже фактически означает отсутствие лимита.

Мой вариант, по сути, побочный эффект от более крупной миграции - переноса Traefik из Docker-контейнера в отдельный LXC как systemd-бинарник. Более подробно об этом:

Self-Hosting - This article is part of a series.
Part : This Article

Related

Подключаем приложения к общей PostgreSQL и учимся её обслуживать. Часть 2

·1712 слов·9 минут· loading · loading
Вторая часть серии про единую PostgreSQL для домашнего сервера. Создаём в pgAdmin отдельного пользователя и базу данных с минимальными правами под конкретное приложение, подключаем Authentik к общей БД вместо его собственного контейнера, и разбираем базовое обслуживание - VACUUM, ANALYZE, REINDEX и когда это вообще нужно делать руками.

Переезжаем с Portainer на Komodo: полная установка и настройка

··5397 слов·26 минут· loading · loading
Пошаговая инструкция по установке Komodo как альтернативы Portainer: docker-compose, MongoDB, bind mount вместо volumes, вход через Authentik (OIDC) и подключение дополнительных хостов через Periphery.