CLOVER🍀

That was when it all began.

Docker Engine(Linux)でコンテナからホスト側のサービスにアクセスする(host.docker.internal)

これは、なにをしたくて書いたもの?

Docker Desktopには、host.docker.internalというホスト側のIPアドレスを参照する仕組みがあるようです。

Networking features in Docker Desktop for Windows / Features / Use cases and workarounds / I want to connect from a container to a service on the host

Docker Desktopを使っていないので全然知らなかったのですが、Docker Engine(使っているのはLinux版)でもひと手間加えると使えるようなので、
ちょっと試してみることにしました。

コンテナからホスト側のを参照できるhost.docker.internal

Windows版のhost.docker.internalの説明を見てみます。

host.docker.internalはホストの内部IPアドレスを解決する特別な名前です。Docker Desktop for Windows以外の本番環境では機能しないとも
書かれています。

We recommend that you connect to the special DNS name host.docker.internal which resolves to the internal IP address used by the host. This is for development purpose and will not work in a production environment outside of Docker Desktop for Windows.

Networking features in Docker Desktop for Windows / Features / Use cases and workarounds / I want to connect from a container to a service on the host

macOS版とLinux版ではどうなのでしょう。書かれていることは、ほぼ同じです。

Networking features in Docker Desktop for Mac / Features / Use cases and workarounds / I want to connect from a container to a service on the host

Networking features in Docker Desktop for Mac / Features / Use cases and workarounds / I want to connect from a container to a service on the host

とはいえ、Linux版のDocker Desktopは最近出てきたばかりのものですが。

The Magic of Docker Desktop is Now Available on Linux - Docker

[速報]「Docker Desktop for Linux」が登場、WindowsやMac版と同じ機能や操作を提供、Raspberry Pi OSにも対応 - Publickey

LinuxでDocker Engineしか使っていない自分は、host.docker.internalの存在をまったく知りませんでした。

ところで、リリースノートを見ていると、Docker Engineでも20.10.0からhost.docker.internalが使えるようになったみたいです。

Docker Engine release notes / 20.10.0

Support host.docker.internal in dockerd on Linux moby/moby#40007

Docker Engine release notes / 20.10.0 / Networking

Support host.docker.internal in dockerd on Linux by arkodg · Pull Request #40007 · moby/moby · GitHub

というわけで、ちょっと試してみます。

環境

今回の環境は、こちら。

$ docker version
Client: Docker Engine - Community
 Version:           20.10.16
 API version:       1.41
 Go version:        go1.17.10
 Git commit:        aa7e414
 Built:             Thu May 12 09:17:23 2022
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.16
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.17.10
  Git commit:       f756502
  Built:            Thu May 12 09:15:28 2022
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.4
  GitCommit:        212e8b6fa2f44b9c21b2798135fc6fb7c53efc16
 runc:
  Version:          1.1.1
  GitCommit:        v1.1.1-0-g52de29d
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Docker Composeも。

$ docker compose version
Docker Compose version v2.5.1

host.docker.internalを使ってみる

まずは、ホスト側の準備をします。

ドキュメントに習って、Python組み込みのWebサーバーを使いましょう。

Networking features in Docker Desktop for Mac / Features / Use cases and workarounds / I want to connect from a container to a service on the host

ファイルの作成とWebサーバーの起動。

$ echo 'from host' > message.txt
$ python3 -m http.server

確認。

$ curl localhost:8000/message.txt
from host

このWebサーバーに、コンテナ側からアクセスしたい、という話です。

コンテナを起動。

$ docker container run -it --rm ubuntu:20.04 bash
root@6b4790b61308:/# 

curlをインストール。

# apt update && apt install -y curl

ホスト側へアクセスを試みます。

# curl host.docker.internal:8000/message.txt
curl: (6) Could not resolve host: host.docker.internal

ホスト名が解決できない、と言われました。

/etc/hostsにも、確かに登録されていませんね。

# cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2      6b4790b61308

リリースノートに載っていたPull Requestを見てみると、--add-host=host.docker.internal:host-gatewayというオプションが必要なようです。

Support host.docker.internal in dockerd on Linux by arkodg · Pull Request #40007 · moby/moby · GitHub

というわけで、コンテナを1度終了して

$ docker container run -it --rm --add-host=host.docker.internal:host-gateway ubuntu:20.04 bash
root@90f41cf6f930:/#

curlをインストール。

# apt update && apt install -y curl

今度は、host.docker.internalでホスト側にアクセスできるようになります。

# curl host.docker.internal:8000/message.txt
from host

/etc/hostsにも、host.docker.internalが増えています。

# cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.1      host.docker.internal
172.17.0.2      90f41cf6f930

確認できましたね。

ホストのIPアドレスは、Docker Daemonに--host-gateway-ipオプションを使うことで明示的な指定もできるようです。

Support host.docker.internal in dockerd on Linux by arkodg · Pull Request #40007 · moby/moby · GitHub

--add-host=host.docker.internal:host-gatewayをデフォルトで付与するように設定できないのかな?とも思いましたが、現時点では
そのような話にはなっていないみたいですね。

[RFD] add configuration option to add `host.docker.internal` by default · Issue #2290 · docker/cli · GitHub

Docker Composeでhost.docker.internalを使う

最後に、host.docker.internalをDocker Composeでも使ってみましょう。

Docker Composeの場合は、extra_hostsを使います。

extra_hostsは、コンテナにホスト名のマッピングを追加する設定です(Linuxなら/etc/hostsを利用)。

extra_hosts adds hostname mappings to the container network interface configuration (/etc/hosts for Linux). Values MUST set hostname and IP address for additional hosts in the form of HOSTNAME:IP.

Compose specification / Services top-level element / extra_hosts

サンプルとして、こんな定義で作成。

compose.yaml

services:
  infinispan1:
    image: infinispan/server:13.0.10.Final
    extra_hosts:
      - "host.docker.internal:host-gateway"
  infinispan2:
    image: infinispan/server:13.0.10.Final
    extra_hosts:
      - "host.docker.internal:host-gateway"

起動して

$ docker compose up

確認。

$ docker compose exec infinispan1 bash -c 'cat /etc/hosts | grep host.docker.internal'
172.17.0.1      host.docker.internal


$ docker compose exec infinispan2 bash -c 'cat /etc/hosts | grep host.docker.internal'
172.17.0.1      host.docker.internal

host.docker.internalを使った確認自体は、省略します。

また、compose.yamlからextra_hosts、host.docker.internal:host-gatewayの定義を削除すると、当然ですがhost.docker.internalは
/etc/hostsに登録されなくなります。

これで、今回の確認はOKとしましょう。

まとめ

host.docker.internalを使って、コンテナからホスト側のサービスにアクセスしてみました。

使い方には注意した方がいい気がしますが、知っておくとそれはそれで便利なのかな、と。