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

Maak verbinding met een dienst op een Docker host vanaf een Docker container

Bij gebruik van de standaard netwerkfunctie bridge moet u een dienst op de Docker host (ook) naar docker0 laten luisteren.

11 augustus 2022
In Docker
post main image
https://www.pexels.com/@vladbagacian/

Als u met een of ander Docker probleem zit en op het Internet zoekt, zult u vrijwel zeker op de vraag stuiten: Hoe kan ik verbinding maken met localhost? Wat mensen bedoelen is: Hoe kan ik verbinding maken met een service op de Docker host vanuit een Docker container. Toen ik Docker begon te gebruiken, worstelde ik hier ook mee.

Ik gebruik Linux, Ubuntu, en toen eindelijk host.docker.internal beschikbaar was voor Linux dachten veel mensen dat al hun problemen voorbij waren. Maar er is niets veranderd. Het probleem blijft hetzelfde.

Er zijn veel berichten op het internet hierover, en dit is er weer een. Ik zal alleen de standaard netwerk bridge mode bespreken, en niet de 'network=host' mode.

Gebruikte gereedschappen

In dit artikel gebruik ik een paar command-line tools, die je wellicht wilt installeren. Om de laatste versie te krijgen, of voeg ze toe aan de repository op je machine:

sudo apt update

netcat

Wordt gebruikt om een listener service te maken.

sudo apt install netcat

netstat

Wordt gebruikt om listeners op de host te tonen.

sudo install net-tools

nmap

Wordt gebruikt om poorten te scannen (op open).

sudo apt install nmap

Listener diensten en netwerken

Laten we een listener service opstarten in een terminal venster, luisterend naar poort 2345 op localhost, waarbij localhost 127.0.0.1 is. We voegen de -k (open houden) optie toe, om te voorkomen dat netcat wordt afgesloten als de verbinding wordt verbroken:

netcat -k -l 127.0.0.1 2345

Controleer of we een netcat dienst hebben die luistert:

sudo netstat -tunlp

Of, als je een erg lange lijst hebt:

sudo netstat -tunlp | grep 2345

Resultaat:

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
...

Dit betekent dat netcat luistert op IP address 127.0.0.1 naar poort 2345.

Start nu een Busybox Docker container in een ander terminal venster:

docker run -it --add-host=host.docker.internal:host-gateway --rm busybox

En probeer dan in de container toegang te krijgen tot onze listener service:

telnet host.docker.internal 2345

Resultaat:

telnet: can't connect to remote host (172.17.0.1): Connection refused

Als telnet niet reageert, controleer UFW

Wat ook kan gebeuren is dat telnet hangt, en na een zeer lange tijd antwoordt met:

telnet: can't connect to remote host (172.17.0.1): Connection timed out

In dit geval wordt het verzoek van Docker waarschijnlijk geblokkeerd door UFW (Uncomplicated Firewall). U kunt controleren of UFW het verzoek blokkeert door te kijken naar de UFW-regels in /var/log/syslog. Typ in een ander terminalvenster het volgende:

tail -f /var/log/syslog | grep UFW

Start in de Docker container opnieuw het telnet commando. Als UFW uw verzoek blokkeert, ziet u berichten als:

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

De belangrijke informatie hier is:

SRC=172.17.0.2   <--- SOURCE
DST=172.17.0.1   <--- DESTINATION
SPT=35690        <--- SOURCE PORT
DPT=2345         <--- DESTINATION PORT

Merk op dat poort 2345 geblokkeerd is door UFW. U kunt hier een van de twee dingen doen:

  • Schakel UFW uit tijdens deze tests, of
  • Voeg een regel toe aan UFW om toegang tot poort 2345 toe te staan:
    sudo ufw allow from 172.17.0.0/16 to any port 2345.
    Dit staat netwerken 172.17.x.y toe om toegang te krijgen tot poort 2345.

Voor dit moment stel ik voor om UFW uit te schakelen. Nadat je dit gedaan hebt, zou telnet moeten reageren zoals eerder vermeld:

telnet: can't connect to remote host (172.17.0.1): Connection refused

Ok, dus waarom wordt de verbinding geweigerd?

Hiervoor moet je enkele netwerk basisprincipes begrijpen. De listener service zegt dat hij luistert naar 'Local Address': 127.0.0.1:2345. En telnet in de Docker container zei: can't connect to remote host (172.17.0.1).

Merk op dat 127.0.0.1 en 172.17.0.1 zich op verschillende netwerken bevinden. Dit betekent dat er nooit een verbinding tot stand kan komen!

Uit de post 'How to Connect to Localhost Within a Docker Container', zie links hieronder:

Een valkuil van deze aanpak is dat u misschien geen verbinding kunt maken met diensten die direct aan localhost binden. U moet ervoor zorgen dat uw diensten luisteren naar verbindingen op uw Docker bridge IP, en ook op localhost en 127.0.0.1. Anders zie je connection refused of soortgelijke fouten in je container.

Precies. Dit betekent dat onze listener service niet alleen naar 127.0.0.1 moet luisteren, maar ook naar 172.17.0.1.
Laten we onze listener service opnieuw starten, en deze laten luisteren naar 172.17.0.1:

netcat -l 172.17.0.1 2345

Start telnet opnieuw in de Docker container:

telnet host.docker.internal 2345

Resultaat:

Connected to host.docker.internal

Bingo! Dit betekent dat de service op de Docker host ook moet luisteren naar docker0. Je kunt wat tekst typen en het zal worden geëchood door netcat.

Wat we hier ook geleerd hebben is dat host.docker.internal NIET equivalent is aan localhost

host.docker.internal is gewoon een naam voor docker0. Docker0 is een virtuele interface, een bridge, aangemaakt door Docker, en host.docker.internal is de (domein)naam van de docker0 bridge. Alle Docker containers zijn standaard verbonden met de docker0 bridge . Hoewel de IP address van docker0 vaak 172.17.0.1 is, kan het om het even wat zijn uit de particuliere reeks die door RFC 1918 wordt gedefinieerd. Daarom moeten we niet de IP address gebruiken, maar verwijzen naar docker0 als host.docker.internal.

      +-------------+        +-------------+
      | container A |        | container B |
      +-------------+        +-------------+
             |                      |
             |                      |
         +------------------------------+ 
    +----|           docker0            |---+
    |    +------------------------------+   |
    |                                       |
    |                  Host                 |
    |                                       |
    +---------------------------------------+
                         |
                      network
                         

Nogmaals, hoe kan ik verbinding maken met een service op localhost vanuit een Docker container?

Toen de listener service luisterde op 127.0.0.1 konden we deze niet bereiken vanuit onze docker container. Maar toen de listener service luisterde op 172.17.0.1 konden we wel verbinding maken vanuit onze docker container.

Met andere woorden, de oorzaak van het probleem is dat de service op de host NIET luistert naar 172.17.0.1.

In Linux, kunnen de meeste services gemaakt worden om te luisteren naar alle interfaces, netwerken. We doen dit door het adres '0.0.0.0' te gebruiken. We kunnen netcat starten met of zonder IP address '0.0.0.0', het is hetzelfde:

netcat -l 2345

of,

netcat -l 0.0.0.0 2345

Controle van de luisterpoorten

sudo netstat -tunlp | grep 2345

Resultaat:

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' betekent dat het zal luisteren naar alle netwerken inclusief 127.0.0.1 en 172.17.0.1.

Start telnet in de container:

telnet host.docker.internal 2345

Resultaat:

Connected to host.docker.internal

Zoals verwacht is de verbinding geslaagd.

Mooi, maar wat moet ik doen om verbinding te maken met een service op localhost vanuit een Docker container?

Het gaat erom dat je ervoor zorgt dat de service op localhost (ook) luistert naar 172.17.0.1, of docker0. Op Ubuntu is de IP address van docker0 172.17.0.1, maar Docker zegt dat het ook willekeurig gekozen kan worden, zie ook hierboven.

Dit betekent dat een herstart van Docker een andere docker0 IP address kan geven, misschien niet vandaag, maar misschien over een paar maanden, of volgend jaar(?). Aangezien we niet kunnen vertrouwen op de docker0 IP address, is de enige andere optie om uw dienst op de host te laten luisteren naar '0.0.0.0'. Het is mogelijk om de bridge IP address en subnet in te stellen, maar dat valt buiten het bereik van dit bericht.

Om toegang te krijgen tot MySQL op de Docker host vanuit een container, kunt u een regel wijzigen in /etc/mysql/my.cnf:
From:

bind-address = 127.0.0.1

To:

bind-address = 0.0.0.0

Merk op dat er oplossingen op het Internet zijn die de service op de Docker host laten luisteren op de docker0 IP address. Zij vertrouwen op het feit dat deze IP address niet zal veranderen. Tenzij u weet wat u doet, kan ik u dit niet aanbevelen.

Creëer een apart Docker netwerk voor de dienst op de host

Je kunt de dienst op de host isoleren door er bijvoorbeeld een Docker netwerk voor aan te maken:

docker network create --driver=bridge --subnet=172.17.33.0/24 --gateway=172.17.33.10 my_subnet

Start de service op de host:

netcat -k -l 172.17.33.10 2345

Start dan de Docker container:

docker run -it --add-host=my_host:172.17.33.10 --network=my_subnet --rm busybox

En benader de service vanuit de Docker container:

telnet my_host 2345

Krijg toegang tot een service op de Docker host met behulp van een Unix socket

Tenslotte is er een totaal andere manier om toegang te krijgen tot een dienst op de Docker host, maar het hangt er vanaf of de dienst dit ondersteunt. Sommige diensten gebruiken een 'unix socket' een bestand. Bijvoorbeeld, MySQL gebruikt het bestand:

/run/mysqld/mysqld.sock

Dit betekent dat we ook toegang kunnen krijgen tot MySQL vanuit een container met behulp van volume mapping, in Docker-Compose:

    volumes:
      # connect to mysql via unix socket 
      - /var/run/mysqld:/var/run/mysqld

Dit werkt prima. Ik heb applicaties op de host en applicaties in Docker container die allemaal MariaDb op de host gebruiken.

Docker IP addresses en UFW regels

Dit is belangrijk omdat UFW waarschijnlijk gebruikt wordt om de toegang van en naar de machine te regelen. Zoals hierboven vermeld, elke keer dat een container wordt aangemaakt, kan de IP address veranderen en sommige UFW regels zullen niet meer werken!

Standaard wijst Docker een IP address toe aan een container uit een van de volgende netwerkbereiken voor bridge netwerken:

  • 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

Dit betekent dat als we een UFW regel specificeerden met '172.17.0.0/16', deze regel niet meer zal werken als Docker de volgende keer een IP address van het netwerk '192.168.0.0/16' toekent.

Een manier om dit op te lossen is om zelf de standaard adres pools voor Docker netwerken op te geven, zoals ik heb beschreven in de post 'Docker containers suddenly using 192.168.0.0/16 instead of 172.17.0.0/16: services lost', zie links hieronder.

We kunnen standaard adres pools specificeren voor onze Docker netwerken door het bestand aan te maken (het was er niet):

/etc/docker/daemon.json

Dit voorbeeld vertelt Docker om alleen IP address van het netwerk 172.17.0.0/16 te gebruiken:

{
	"default-address-pools":
	[
		{"base":"172.17.0.0/16","size":24}
	]
}

Scan altijd uw poorten na het aanbrengen van wijzigingen!

Scan altijd de poorten op uw machine en de productieserver nadat u wijzigingen hebt aangebracht. Vooral bij gebruik van Docker moeten we er zeker van zijn dat er geen poorten per ongeluk geopend zijn!

Als we dit zelf willen doen, kunnen we het commando nmap gebruiken. Het probleem is dat de resultaten afhangen van de manier waarop je verbinding maakt met het Internet. U moet de firewall op uw machine controleren en de firewall op uw Internet toegangspunt. Ook kan de firewall van uw Internet provider verbindingen blokkeren. De enige manier om uw productieserver op open poorten te scannen is door een andere server (VPS) op hetzelfde netwerk, of op het Internet, te gebruiken die geen uitgaande beperkingen heeft.

sudo apt install nmap

Een voorbeeld van dit commando om poorten 80-10000 te scannen:

nmap -p 80-10000 www.example.com

Je kunt ook de IP address van je ontwikkelmachine gebruiken, en nmap op deze machine uitvoeren, maar ik ontdekte dat de resultaten niet juist zijn.

nmap -p 80-10000 <your-development-machine-ip-address>

Het liet zien dat poort 0.0.0.0:2345 open was. Scannen vanaf een andere computer in mijn lokale netwerk liet dit niet zien.

Samenvatting

In dit bericht heb ik besproken hoe je verbinding kunt maken met een service op een Docker host vanaf een Docker container. Er zijn beperkingen, maar meestal is het mogelijk. Wees je bewust van Docker toegewezen IP addresses, ze kunnen veranderen elke keer dat een container wordt aangemaakt. En controleer altijd uw systemen op open poorten nadat u wijzigingen heeft aangebracht!

Links / credits

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

Lees meer

Docker

Laat een reactie achter

Reageer anoniem of log in om commentaar te geven.

Opmerkingen (1)

Laat een antwoord achter

Antwoord anoniem of log in om te antwoorden.

avatar

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