Пересобирая в очередной раз контейнеры столкнулся с проблемой когда некоторые контейнеры перестали видеть в сети сервисы, расположенные в локальной сети и причина тому - конфликт IP-адресов, когда локальный докер занял тот же пул адресов.
Адрес удалённого Docker контейнера нам известен - 172.23.1.2
. Таким образом мы видим что он принадлежит подсети 172.23.0.0/16
, используемой докером по-умолчанию. Осталось проверить локальные сети, созданные им. Для этого в консоли выполним команду:
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
b37a8f1d9e0e bridge bridge local
3ddf7cc795c7 dev_dev bridge local
ce4290925aae host host local
53771bac0ba2 work_dev bridge local
Чтобы удостовериться в конфликте, нужно пройтись по каждому имени сети командой docker network inspect <name>
:
$ docker network inspect bridge
[
{
"Name": "bridge",
"Id": "b37a8f1d9e0ec568217fd07d7e3264ecb1ce2227e223cd2afd9e75171ab5dcd8",
"Created": "2023-07-12T08:34:22.064632755Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "10.66.0.0/24",
"Gateway": "10.66.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"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": {}
}
]
Здесь нас интересует ключ IPAM.Config.Subnet
. В данном случае видим что пересечений с адресом 172.23.1.2
нет, значит, проверяем дальше.
При запуске команды на MacOS или Ubuntu можно сократить вывод информации до минимума, применив
grep
:$ docker network inspect bridge | grep Subnet
Следом проверяем вторую сеть из списка с именем dev_dev
:
$ docker network inspect dev_dev
[
{
"Name": "dev_dev",
"Id": "3ddf7cc795c728e6db05d60a21d411e56537244a18ce4367f1aad1a5adfd85a3",
"Created": "2023-07-11T10:09:31.915263768Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.23.0.0/16",
"Gateway": "172.23.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {
"com.docker.compose.network": "dev",
"com.docker.compose.project": "dev",
"com.docker.compose.version": "2.19.1"
}
}
]
Вот и наша злосчастная подсеть 172.23.0.0/16
, перекрывающая внешние сервисы.
Мне удалось найти два основных решения данной проблемы:
Пересоздавать сеть до тех пор, пока она не получит не конфликтный пул IP-адресов;
Изменить диапазон используемых докером подсетей.
Пересоздание сети
Docker автоматически выбирает незанятый собой пул IP-адресов в диапазоне 172.0.0.0/8
. Таким образом, можно удалить текущую сеть и создать её заново:
$ docker network rm dev_dev
dev_dev
$ docker network create dev_dev
6e94416358eee95cf5b6765804214c67851db737e15ce37c2c50c005365eb758
# Эта команда выведет список контейнеров, из которых нужно взять значение
# колонки NAMES для подключения
$ docker ps -a
CONTAINER ID IMAGE NAMES
297cd1e629a8 erikdubbelboer/phpredisadmin:latest dev-redis-webui-1
69f0f0e3a767 redis:alpine dev-redis-1
1cc11f750fb6 dev-mysql dev-mysql-1
$ docker network connect dev_dev dev-mysql-1
$ docker network connect dev_dev dev-redis-1
$ docker network connect dev_dev dev-redis-webui-1
Если не создавать вручную сеть и не назначить её контейнерам, то получим сообщение:
Cannot start Docker Compose application. Reason: compose [start] exit status 1. Container dev-mysql-1 Starting Container dev-redis-1 Starting Error response from daemon: network 3ddf7cc795c728e6db05d60a21d411e56537244a18ce4367f1aad1a5adfd85a3 not found
При проверке выданного пула видим, что теперь адрес контейнера из локальной сети (172.23.1.2
) не конфликтует с выданным:
$ docker network inspect dev_dev
[
{
"Name": "dev_dev",
"Id": "6e94416358eee95cf5b6765804214c67851db737e15ce37c2c50c005365eb758",
"Created": "2023-07-12T09:31:13.976800597Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"1cc11f750fb63b5a7d6f8892cf09607df5ddd87cadfc9a8c0c1af19d96f7f0af": {
"Name": "dev-mysql-1",
"EndpointID": "8a8060743e9b7bcf99b16c23a2c85ac0897d6153e8fe4ad9a43e8ec942682b9d",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
},
"297cd1e629a888866009681e53357bcef7e1be03d433322b78753ca7856779f5": {
"Name": "dev-redis-webui-1",
"EndpointID": "18125f8d31f7f76e44dae64fe264fa369d82fd3a2e56ba8915bf53cbc642b56c",
"MacAddress": "02:42:ac:11:00:04",
"IPv4Address": "172.17.0.4/16",
"IPv6Address": ""
},
"69f0f0e3a76779cbb9f44cafe6ca5370992aef71fce40edbc369b535d9c6a513": {
"Name": "dev-redis-1",
"EndpointID": "06e75846ee2edd7bd0663ccaeb772a4472930bb6f10c9ef14049307174aeada9",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
Проблема этого метода в том, что при каждом возникшем конфликте требуется повторять эти действия. Особенно это касается случаев, когда контейнеры пересоздаются очень часто. Поэтому есть второй, более простой, вариант:
Ограничение пула адресов через конфигурирование Docker
Для реализации этой задумки нужно изменить файл daemon.json
. В Unix системах он находится по пути /etc/docker/daemon.json
, а под управлением Windows - %USERPROFILE%\.docker\daemon.json
.
В случае использования Docker Desktop найти файл ещё проще: открываем настройки и переходим на вкладку Docker Engine. Всё. Содержимое можно изменять.
Просто впишите в этот JSON новый ключ конфигурации:
{
"default-address-pools":[
{"base":"10.66.0.0/16","size":24},
{"base":"10.77.0.0/16","size":24}
]
}
Эта конфигурация позволит Docker выделить подсети в диапазонах 10.66.0.1-10.66.254.254 и 10.77.0.1-10.77.254.254.
И нажмите кнопку Apply & restart
:
Так как Docker назначает адреса при создании сети, то нужно удалить существующие, создать заново и привязать её к контейнерам:
$ docker network rm dev_dev
$ docker network create dev_dev
$ docker network connect dev_dev dev-mysql-1
$ docker network connect dev_dev dev-redis-1
$ docker network connect dev_dev dev-redis-webui-1
Теперь мы видим что подсеть имеет адрес из нового пула:
$ docker network inspect dev_dev
[
{
"Name": "dev_dev",
"Id": "be93ad5ca2e203d8c029d9b12966e957c042fbcab5c1e5ef2b1dcfbd1e966c41",
"Created": "2023-07-12T09:48:15.913939494Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "10.66.1.0/24",
"Gateway": "10.66.1.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
Этот метод хорош тем, что пересоздавать сети нужно всего один раз для тех, что уже были созданы, в отличие от первого метода.
Назначение адреса в docker-compose.yml
Также можно назначить адрес в файле docker-compose.yml
:
version: '3'
services:
mysql:
build: mysql
ports:
- '${FORWARD_DB_PORT:-3306}:3306'
environment:
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD:-dev}'
MYSQL_ROOT_HOST: "%"
MYSQL_DATABASE: '${DB_DATABASE:-default}'
MYSQL_USER: '${DB_USERNAME:-dev}'
MYSQL_PASSWORD: '${DB_PASSWORD:-dev}'
MYSQL_ALLOW_EMPTY_PASSWORD: 1
TZ: 'UTC'
volumes:
- 'mysql:/var/lib/mysql'
- './mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh'
- './mysql/reset-users-passwords.sh:/docker-entrypoint-initdb.d/10-reset-users-passwords.sh'
networks:
- dev
healthcheck:
test: [ "CMD", "mysqladmin", "ping", "-p${DB_PASSWORD:-dev}" ]
retries: 3
timeout: 5s
redis:
image: 'redis:alpine'
ports:
- '${FORWARD_REDIS_PORT:-6379}:6379'
networks:
- dev
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
retries: 3
timeout: 5s
redis-webui:
image: 'erikdubbelboer/phpredisadmin:latest'
environment:
- 'REDIS_1_HOST=${REDIS_WEBUI_CONNECT_HOST:-redis}'
- 'REDIS_1_PORT=${REDIS_WEBUI_CONNECT_PORT:-6379}'
volumes:
- './redis/includes/config.sample.inc.php:/src/app/includes/config.sample.inc.php'
ports:
- '${REDIS_WEBUI_PORT:-9987}:80'
networks:
- dev
depends_on:
- redis
networks:
dev:
driver: bridge
ipam:
config:
- subnet: 10.66.0.0/16
gateway: 10.66.0.1
volumes:
mysql:
driver: local
После этого нужно удалить созданные контейнеры из докера и пересоздать заново, либо вручную вызвать команды:
$ docker network rm dev_dev
$ docker network create dev_dev --gateway=10.66.0.1 --subnet=10.66.0.0/16
$ docker network connect dev_dev dev-mysql-1
$ docker network connect dev_dev dev-redis-1
$ docker network connect dev_dev dev-redis-webui-1
Плюс этого метода в том, что конфигурация будет сохранена в окружении.
Минусы этого метода в том, что при необходимости одновременного запуска нескольких сред возрастает шанс получить конфликт подсетей между контейнерами, а также назначенный вручную пул может случайно совпасть с тем, который Docker создал автоматически. Особенно это актуально для случаев, когда конфигурация является общедоступной.
Заключение
Метод простого пересоздания сети достаточно рутинный, так как в случае примера имеется всего три контейнера, к которым нужно привязать новую сеть. В случае если контейнеров больше, это может доставить неудобства, особенно если контейнеры часто пересоздаются.
Также данный метод не гарантирует что вновь созданная сеть не займёт конфликтный пул адресов.
Второй же метод позволяет раз и навсегда решить эту проблему, изменив выдаваемый пул адресов.
Комментарии (8)
alter72
12.07.2023 13:14+1Проблема шире - в допущении что две сети должны быть достижимы без маршрутизации. Удаленная приватная докер сеть не должна бриджеваться в локальную
Helldar Автор
12.07.2023 13:14На тестовом стенде установлен докер-контейнер с приложениями и всем прочем, в числе которых postgres и redis, к которым любой разработчик и тестировщик имеют возможность подключиться напрямую со своего компа что позволяет сильно упростить процесс тестирования и дебага приложений перед релизом. Контейнеры под Docker Swarm крутятся.
Кроме того, в нашем случае список занятых сервисами IP-адресов в сети чётко регламентированы девопсами.
rionnagel
Это болезнь такая, выделять сети на /16 и заводить там 3 пода?
jingvar
Больше контейнеров богу контейнеров.
Helldar Автор
У меня, например, подключены две конфигурации контейнеров через Docker Dev Environment и каждая автоматом создаёт свою подсеть при том, что в файле конфигурации указано одинаковое значение в обоих случаях:
Одну запускаю для работы, другую для работы над своими приложениями.
Да, с 16-й маской по количеству адресов перебор, но, во-первых, такой трюк позволит практически никогда не возвращаться к этим настройкам, а во-вторых, конфиг находится на локальной машине и не имеет никакого отношения к серверу. Так что, почему бы и да ¯\_(ツ)_/¯
rionnagel
Ну вообще чем больше ваша инфраструктура разрастётся - тем больше проблем с этим можете словить при подключении к работе по впн или в самом офисе, если сети будут пересекаться. Ну собственно лучше брать сети поменьше и которые вы знаете точно не используются.