angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Подключение к службе на хосте Docker из контейнера Docker

При использовании сетевого режима по умолчанию bridge , вы должны заставить службу на хосте Docker слушать (также) docker0.

11 августа 2022
В Docker
post main image
https://www.pexels.com/@vladbagacian/

Если у вас возникла какая-то проблема с 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', см. ссылки ниже:

Одним из недостатков этого подхода является то, что вы не сможете подключиться к службам, которые связываются непосредственно с localhost. Вам нужно убедиться, что ваши службы прослушивают соединения на вашем IP Docker bridge , а также localhost и 127.0.0.1. В противном случае вы увидите отказ в подключении или подобные ошибки внутри вашего контейнера.

Именно так. Это означает, что наша служба прослушивания должна слушать не только 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

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии (1)

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.

avatar

Спасибо! Мне очень помогла статья