Подключение к службе на хосте Docker из контейнера Docker
При использовании сетевого режима по умолчанию bridge , вы должны заставить службу на хосте Docker слушать (также) docker0.
Если у вас возникла какая-то проблема с Docker и вы ищете информацию в Интернете, вы почти наверняка наткнетесь на вопрос: Как я могу подключиться к localhost? Люди имеют в виду следующее: как я могу подключиться к службе на хосте Docker из контейнера Docker . Когда я начал использовать Docker , у меня тоже были проблемы с этим.
Я использую Linux, Ubuntu, и когда наконец host.docker.internal был доступен для Linux , многие люди думали, что все их проблемы закончились. Но ничего не изменилось. Проблема осталась прежней.
В интернете есть много постов об этом, и это еще один. Я буду обсуждать только стандартный сетевой режим bridge , а не режим 'network=host'.
Используемые инструменты
В этой заметке я использую несколько инструментов командной строки, которые вы, возможно, захотите установить. Чтобы получить последнюю версию, или добавьте их в репозиторий на вашей машине:
sudo apt update
netcat
Используется для создания службы-приемника.
sudo apt install netcat
netstat
Используется для показа слушателей на хосте.
sudo install net-tools
nmap
Используется для сканирования (на предмет открытых) портов.
sudo apt install nmap
Слушательские службы и сетевое взаимодействие
Давайте запустим службу-слушатель в окне терминала, будем слушать порт 2345 на localhost, где localhost - это 127.0.0.1. Мы добавляем опцию -k (keep open), чтобы предотвратить завершение работы netcat при отключении:
netcat -k -l 127.0.0.1 2345
Проверьте, прослушивает ли нас служба netcat :
sudo netstat -tunlp
Или, если у вас очень длинный список:
sudo netstat -tunlp | grep 2345
Результат:
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
...
tcp 0 0 127.0.0.1:2345 0.0.0.0:* LISTEN 2961983/netcat
...
Это означает, что netcat прослушивает на IP address 127.0.0.1 порт 2345.
Теперь запустите контейнер Busybox Docker в другом окне терминала:
docker run -it --add-host=host.docker.internal:host-gateway --rm busybox
А затем в контейнере попробуйте получить доступ к нашей службе listener:
telnet host.docker.internal 2345
Результат:
telnet: can't connect to remote host (172.17.0.1): Connection refused
Если telnet не отвечает, проверьте UFW.
Также может случиться так, что telnet зависает, и через очень долгое время отвечает:
telnet: can't connect to remote host (172.17.0.1): Connection timed out
В этом случае запрос от Docker , вероятно, блокируется UFW (Uncomplicated Firewall). Вы можете проверить, блокирует ли UFW запрос, просмотрев записи UFW в /var/log/syslog. В другом окне терминала введите следующее:
tail -f /var/log/syslog | grep UFW
В контейнере Docker перезапустите команду telnet . Если UFW блокирует ваш запрос, вы увидите сообщения типа:
Aug 11 09:48:05 myra kernel: [247791.035929] [UFW BLOCK] IN=docker0 OUT= PHYSIN=vethe3b7837 MAC=02:42:80:b6:f4:ea:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=172.17.0.1 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=9274 DF PROTO=TCP SPT=35690 DPT=2345 WINDOW=64240 RES=0x00 SYN URGP=0
Важной информацией здесь является:
SRC=172.17.0.2 <--- SOURCE
DST=172.17.0.1 <--- DESTINATION
SPT=35690 <--- SOURCE PORT
DPT=2345 <--- DESTINATION PORT
Обратите внимание, что порт 2345 заблокирован UFW. Здесь вы можете сделать одно из двух:
- Отключить UFW во время этих тестов, или
- Добавить правило в UFW для разрешения доступа к порту 2345:
sudo ufw allow from 172.17.0.0/16 to any port 2345.
Это позволяет сети 172.17.x.y получить доступ к порту 2345.
На данный момент я предлагаю вам отключить UFW. После этого telnet должен ответить, как было сказано ранее:
telnet: can't connect to remote host (172.17.0.1): Connection refused
Хорошо, так почему же соединение отказано?
Для этого вам нужно понять некоторые основы сетевых технологий. Служба прослушивания сказала, что она слушает 'Local Address': 127.0.0.1:2345. А telnet в контейнере Docker сказал: can't connect to remote host (172.17.0.1).
Обратите внимание, что 127.0.0.1 и 172.17.0.1 находятся в разных сетях. Это означает, что соединение никогда не может быть установлено!
Из сообщения 'How to Connect to Localhost Within a Docker Container', см. ссылки ниже:
Именно так. Это означает, что наша служба прослушивания должна слушать не только 127.0.0.1 , но и 172.17.0.1.
Давайте перезапустим нашу службу прослушивания и заставим ее слушать 172.17.0.1:
netcat -l 172.17.0.1 2345
Снова запустите telnet в контейнере Docker :
telnet host.docker.internal 2345
Результат:
Connected to host.docker.internal
Есть! Это означает, что служба на хосте Docker также должна слушать docker0. Вы можете набрать текст, и он будет передан эхом на netcat.
Здесь мы также узнали, что host.docker.internal НЕ эквивалентен localhost
host.docker.internal - это просто название для docker0. Docker0 - это виртуальный интерфейс, bridge, созданный Docker, а host.docker.internal - это (доменное) имя docker0 bridge. Все контейнеры Docker по умолчанию подключены к docker0 bridge . Хотя IP address из docker0 часто является 172.17.0.1, это может быть что угодно из частного диапазона, определенного RFC 1918. Поэтому мы не должны использовать IP address , а обращаться к docker0 как к host.docker.internal.
+-------------+ +-------------+
| container A | | container B |
+-------------+ +-------------+
| |
| |
+------------------------------+
+----| docker0 |---+
| +------------------------------+ |
| |
| Host |
| |
+---------------------------------------+
|
network
Опять же, как я могу подключиться к службе на localhost из контейнера Docker ?
Когда служба listener слушала на 127.0.0.1 , мы не могли подключиться к ней из нашего докер-контейнера. Но когда служба прослушивания прослушивала 172.17.0.1 , мы могли подключиться к ней из нашего докер-контейнера.
Другими словами, причина проблемы в том, что служба на хосте НЕ прослушивает 172.17.0.1.
В Linux большинство сервисов можно заставить слушать все интерфейсы, сети. Мы делаем это, используя адрес '0.0.0.0'. Мы можем запустить netcat с или без IP address '0.0.0.0', это одно и то же:
netcat -l 2345
или,
netcat -l 0.0.0.0 2345
Проверка прослушиваемых портов
sudo netstat -tunlp | grep 2345
Результат:
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
...
tcp 0 0 0.0.0.0:2345 0.0.0.0:* LISTEN 1606615/netcat
...
Local Address '0.0.0.0' означает, что он будет прослушивать все сети, включая 127.0.0.1 и 172.17.0.1.
Запустите telnet в контейнере:
telnet host.docker.internal 2345
Результат:
Connected to host.docker.internal
Как и ожидалось, соединение удалось.
Отлично, но что нужно сделать, чтобы подключиться к службе на localhost из контейнера Docker ?
Все дело в том, чтобы убедиться, что сервис на localhost (также) слушает 172.17.0.1 или docker0. На Ubuntu, IP address из docker0 является 172.17.0.1, но Docker говорит, что он также может быть выбран случайным образом, см. также выше.
Это означает, что перезапуск Docker может дать другой docker0 IP address, возможно, не сегодня, а через несколько месяцев или в следующем году(?). Поскольку мы не можем полагаться на docker0 IP address, единственным вариантом является то, чтобы ваша служба на хосте слушала '0.0.0.0'. Можно установить bridge IP address и subnet, но это выходит за рамки данной статьи.
Чтобы получить доступ к MySQL на хосте Docker из контейнера, вы можете изменить строку в /etc/mysql/my.cnf:
From:
bind-address = 127.0.0.1
To:
bind-address = 0.0.0.0
Обратите внимание, что в интернете есть решения, которые заставляют службу на хосте Docker слушать docker0 IP address. Они полагаются на то, что этот IP address не изменится. Если вы не знаете, что делаете, я не могу рекомендовать это.
Создайте отдельную сеть Docker для службы на хосте
Вы можете изолировать службу на хосте, создав для нее сеть Docker , например:
docker network create --driver=bridge --subnet=172.17.33.0/24 --gateway=172.17.33.10 my_subnet
Запустите службу на хосте:
netcat -k -l 172.17.33.10 2345
Затем запустите контейнер Docker :
docker run -it --add-host=my_host:172.17.33.10 --network=my_subnet --rm busybox
И получить доступ к службе из контейнера Docker :
telnet my_host 2345
Доступ к службе на хосте Docker с помощью Unix socket.
Наконец, существует совершенно другой способ доступа к службе на хосте Docker , но это зависит от того, поддерживает ли служба этот способ. Некоторые службы используют 'unix socket' файл. Например, MySQL использует файл:
/run/mysqld/mysqld.sock
Это означает, что мы также можем получить доступ к MySQL из контейнера, используя отображение томов, в Docker-Compose:
volumes:
# connect to mysql via unix socket
- /var/run/mysqld:/var/run/mysqld
Это работает нормально. У меня есть приложения на хосте и приложения в контейнере Docker , все они используют MariaDb на хосте.
Docker IP addresses и правила UFW
Это важно, поскольку вы, вероятно, используете UFW для контроля доступа к машине и из нее. Как упоминалось выше, каждый раз при создании контейнера IP address может измениться, и некоторые правила UFW перестанут работать!
По умолчанию Docker назначает IP address контейнеру из одного из следующих сетевых диапазонов для сетей bridge :
- 172.17.0.0/16
- 172.18.0.0/16
- 172.19.0.0/16
- 172.20.0.0/14
- 172.24.0.0/14
- 172.28.0.0/14
- 192.168.0.0/16
Это означает, что если мы указали правило UFW с '172.17.0.0/16', то это правило больше не будет работать, если Docker назначит в следующий раз IP address из сети '192.168.0.0/16'.
Одним из способов решения этой проблемы является самостоятельное задание пулов адресов по умолчанию для сетей Docker , как я описал в посте 'Docker containers suddenly using 192.168.0.0/16 instead of 172.17.0.0/16: services lost', см. ссылки ниже.
Мы можем указать пулы адресов по умолчанию для наших сетей Docker , создав файл (его там не было):
/etc/docker/daemon.json
Этот пример указывает Docker использовать только IP address из сети 172.17.0.0/16:
{
"default-address-pools":
[
{"base":"172.17.0.0/16","size":24}
]
}
После внесения изменений всегда сканируйте порты!
После внесения изменений всегда сканируйте порты на своей машине и на рабочем сервере. Особенно при использовании Docker мы должны быть уверены, что никакие порты не были открыты по ошибке!
Если мы хотим сделать это самостоятельно, мы можем использовать команду nmap . Проблема в том, что результаты зависят от способа подключения к Интернету. Вам необходимо проверить firewall на вашей машине и firewall на вашей точке доступа в Интернет. Кроме того, firewall вашего интернет-провайдера может блокировать соединения. Единственный способ просканировать ваш рабочий сервер на наличие открытых портов - использовать другой сервер (VPS) в той же сети или в Интернете, который не имеет ограничений на исходящие соединения.
sudo apt install nmap
Пример этой команды для сканирования портов 80-10000:
nmap -p 80-10000 www.example.com
Вы также можете использовать IP address вашей машины разработки, и запустить nmap на этой машине, но я обнаружил, что результаты не верны.
nmap -p 80-10000 <your-development-machine-ip-address>
Он показал, что порт 0.0.0.0:2345 был открыт. Сканирование с другого компьютера в моей локальной сети не показало этого.
Резюме
В этой статье я рассказал о том, как можно подключиться к службе на хосте Docker из контейнера Docker . Существуют ограничения, но в большинстве случаев это возможно. Помните о Docker назначенных IP addresses, они могут меняться при каждом создании контейнера. И всегда проверяйте свои системы на наличие открытых портов после внесения изменений!
Ссылки / кредиты
Change default docker0 bridge ip address
https://stackoverflow.com/questions/52225493/change-default-docker0-bridge-ip-address
Connecting Docker to localhost PostgreSQL on an Ubuntu 18.04 Host
https://gregtczap.com/blog/docker-postgres-ubuntu-localhost
Docker containers suddenly using 192.168.0.0/16 instead of 172.17.0.0/16: services lost
https://www.peterspython.com/en/blog/docker-containers-suddenly-using-192-168-0-0-16-instead-of-172-17-0-0-16-services-lost
From inside of a Docker container, how do I connect to the localhost of the machine?
https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach
How to Connect to Localhost Within a Docker Container
https://www.howtogeek.com/devops/how-to-connect-to-localhost-within-a-docker-container
What's the `docker0` there?
https://stackoverflow.com/questions/63948879/whats-the-docker0-there
Подробнее
Docker
Оставить комментарий
Комментируйте анонимно или войдите в систему, чтобы прокомментировать.
Комментарии (2)
Оставьте ответ
Ответьте анонимно или войдите в систему, чтобы ответить.
thanks, only solution that actually worked!
Недавний
- Использование Ingress для доступа к RabbitMQ на кластере Microk8s
- Простая видеогалерея с Flask, Jinja, Bootstrap и JQuery
- Базовое планирование заданий с помощью APScheduler
- Коммутатор базы данных с HAProxy и HAProxy Runtime API
- Docker Swarm rolling updates
- Скрытие первичных ключей базы данных UUID вашего веб-приложения
Большинство просмотренных
- Использование PyInstaller и Cython для создания исполняемого файла Python
- Уменьшение времени отклика на запросы на странице Flask SQLAlchemy веб-сайта
- Используя Python pyOpenSSL для проверки SSL-сертификатов, загруженных с хоста
- Подключение к службе на хосте Docker из контейнера Docker
- Использование UUID вместо Integer Autoincrement Primary Keys с SQLAlchemy и MariaDb
- SQLAlchemy: Использование Cascade Deletes для удаления связанных объектов