Как да се свържа с локалния хост на машината от вътрешността на контейнер Docker?

И така, имам Nginx, работещ в контейнер на докер, имам mysql, работещ на localhost, искам да се свържа с MySql от моя Nginx. MySql работи на localhost и не разкрива порт за външния свят, така че е свързан с localhost, а не с ip адреса на машината.

Има ли някакъв начин да се свържа с този MySql или с друга програма на localhost от този контейнер на docker?

Този въпрос се различава от "Как да получа IP адреса на хоста на докера от вътрешността на контейнера на докера" поради факта, че IP адресът на хоста на докера може да бъде публичен IP адрес или частен IP адрес в мрежата, който може да е или да не е достъпен от вътрешността на контейнера на докера (имам предвид публичен IP адрес, ако е хостван в AWS или нещо подобно). Дори да имате IP адреса на хоста на докера, това не означава, че можете да се свържете с хоста на докера от контейнера, като се има предвид този IP адрес, тъй като вашата мрежа на докера може да е покриваща, хост, мост, macvlan, никаква и т.н., което ограничава достижимостта на този IP адрес.

Решение

Обработка: Ако използвате Docker-for-mac или Docker-for-Windows 18.03+, просто се свържете с вашата услуга mysql, като използвате хоста host.docker.internal.

От версия Docker 18.09.3 това не работи на Docker-for-Linux. На 8 март 2019 г. беше подадена поправка, която се надяваме да бъде включена в базата с код. Дотогава обходният вариант е да се използва контейнер, както е описано в отговора на qoomon.


TLDR

Използвайте --network="host" в командата docker run, след което 127.0.0.1 в контейнера на докера ще сочи към вашия хост на докера.

Забележка: Този режим работи само в Docker за Linux според документацията.


Бележка за мрежовите режими на контейнера Docker

Docker предлага различни режими на работа в мрежа при стартиране на контейнери. В зависимост от избрания режим ще се свържете с базата данни MySQL, която работи на хоста на докера, по различен начин.

docker run --network="bridge" (по подразбиране)

Docker създава мост с име docker0 по подразбиране. Както хостът на docker, така и контейнерите на docker имат IP адрес в този мост.

Ако на хоста на Docker въведете sudo ip addr show docker0, ще получите изходна информация, изглеждаща по следния начин:

[vagrant@docker:~] $ sudo ip addr show docker0
4: docker0:  mtu 1500 qdisc noqueue state UP group default
    link/ether 56:84:7a:fe:97:99 brd ff:ff:ff:ff:ff:ff
    inet 172.17.42.1/16 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::5484:7aff:fefe:9799/64 scope link
       valid_lft forever preferred_lft forever

И така, тук моят докер хост има IP адрес 172.17.42.1 на мрежовия интерфейс docker0.

Сега стартирайте нов контейнер и му направете шел: docker run --rm -it ubuntu:trusty bash и в контейнера въведете ip addr show eth0, за да откриете как е настроен основният му мрежов интерфейс:

root@e77f6a1b3740:/# ip addr show eth0
863: eth0:  mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 66:32:13:f0:f1:e3 brd ff:ff:ff:ff:ff:ff
    inet 172.17.1.192/16 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::6432:13ff:fef0:f1e3/64 scope link
       valid_lft forever preferred_lft forever

Тук моят контейнер има IP адрес 172.17.1.192. Сега погледнете таблицата за маршрутизация:

root@e77f6a1b3740:/# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         172.17.42.1     0.0.0.0         UG    0      0        0 eth0
172.17.0.0      *               255.255.0.0     U     0      0        0 eth0

Така че IP адресът на докер хоста 172.17.42.1 е зададен като маршрут по подразбиране и е достъпен от вашия контейнер.

root@e77f6a1b3740:/# ping 172.17.42.1
PING 172.17.42.1 (172.17.42.1) 56(84) bytes of data.
64 bytes from 172.17.42.1: icmp_seq=1 ttl=64 time=0.070 ms
64 bytes from 172.17.42.1: icmp_seq=2 ttl=64 time=0.201 ms
64 bytes from 172.17.42.1: icmp_seq=3 ttl=64 time=0.116 ms

docker run --network="host"

Алтернативно можете да стартирате контейнер на docker с мрежови настройки, зададени на host. Такъв контейнер ще споделя мрежовия стек с хоста на докера и от гледна точка на контейнера localhost (или 127.0.0.1) ще се отнася за хоста на докера.

Имайте предвид, че всеки порт, отворен в контейнера на докера, ще бъде отворен и на хоста на докера. И това без да е необходима опцията -p или -P docker run.

Конфигурация на IP адреса на моя докер хост:

[vagrant@docker:~] $ ip addr show eth0
2: eth0:  mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:98:dc:aa brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe98:dcaa/64 scope link
       valid_lft forever preferred_lft forever

и от докер контейнер в режим хост:

[vagrant@docker:~] $ docker run --rm -it --network=host ubuntu:trusty ip addr show eth0
2: eth0:  mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:98:dc:aa brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe98:dcaa/64 scope link
       valid_lft forever preferred_lft forever

Както виждате, и хостът, и контейнерът на докера използват един и същ мрежов интерфейс и имат един и същ IP адрес.


Свързване към MySQL от контейнери

режим на мост

За да получите достъп до MySQL, работеща на хоста на докера, от контейнери в режим мост, трябва да се уверите, че услугата MySQL слуша за връзки на IP адреса 172.17.42.1.

За да направите това, уверете се, че във файла за конфигуриране на MySQL (my.cnf) имате или bind-address = 172.17.42.1, или bind-address = 0.0.0.0.

Ако трябва да зададете променлива на средата с IP адреса на шлюза, можете да изпълните следния код в контейнер :

export DOCKER_HOST_IP=$(route -n | awk '/UG[ \t]/{print $2}')

след това в приложението си използвайте променливата на средата DOCKER_HOST_IP, за да отворите връзка с MySQL.

Забележка: ако използвате bind-address = 0.0.0.0, вашият MySQL сървър ще слуша за връзки на всички мрежови интерфейси. Това означава, че вашият MySQL сървър може да бъде достигнат от интернет ; не забравяйте да настроите съответно правилата на защитната стена.

Забележка 2: ако използвате bind-address = 172.17.42.1, вашият MySQL сървър няма да слуша за връзки, осъществени към 127.0.0.1. Процесите, работещи на хоста на докера, които искат да се свържат с MySQL, ще трябва да използват IP адреса 172.17.42.1.

host mode

За да получите достъп до MySQL, работеща на хоста на докера, от контейнери в режим хост, можете да запазите bind-address = 127.0.0.1 в конфигурацията на MySQL и всичко, което трябва да направите, е да се свържете към 127.0.0.1 от вашите контейнери:

[vagrant@docker:~] $ docker run --rm -it --network=host mysql mysql -h 127.0.0.1 -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 36
Server version: 5.5.41-0ubuntu0.14.04.1 (Ubuntu)

Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

бележка: Използвайте mysql -h 127.0.0.1, а не mysql -h localhost; в противен случай MySQL клиентът ще се опита да се свърже, използвайки unix сокет.

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

Това работи за мен на NGINX/PHP-FPM стек, без да се докосва никакъв код или мрежа, където приложението просто очаква да може да се свърже с localhost.

Монтирайте mysqld.sock от хоста в контейнера.

Намерете местоположението на файла mysql.sock на хоста, на който работи mysql: netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }'

Монтирайте този файл на мястото, където се очаква в докера: docker run -v /hostpath/to/mysqld.sock:/containerpath/to/mysqld.sock

Възможни местоположения на mysqld.sock:

/tmp/mysqld.sock
/var/run/mysqld/mysqld.sock 
/var/lib/mysql/mysql.sock
/Applications/MAMP/tmp/mysql/mysql.sock # if running via MAMP
Коментари (7)

Не съм съгласен с отговора на Thomasleveil.

Ако накарате mysql да се свърже с 172.17.42.1, това ще попречи на други програми, използващи базата данни на хоста, да я достигнат. Това ще работи само ако всички потребители на базата данни са докеризирани.

Ако направите mysql bind към 0.0.0.0, ще отворите db за външния свят, което не само е много лошо, но и противоречи на това, което авторът на първоначалния въпрос иска да направи. Той изрично казва: "MySql работи на localhost и не разкрива порт за външния свят, затова е свързана на localhost".

В отговор на коментара на ivant

"Защо да не се обвърже mysql и с docker0?"

Това не е възможно. В документацията на mysql/mariadb изрично се казва, че не е възможно да се свърже към няколко интерфейса. Можете да се свържете само с 0, 1 или с всички интерфейси.

Като заключение, НЕ открих никакъв начин да достигна до базата данни (само localhost) на хоста от контейнер docker. Това определено изглежда като много много често срещан модел, но не знам как да го направя.

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