reverse proxy として nginx をコンテナ起動し、 Spring Boot Application と連携する

nginx を reverse proxy としてコンテナ起動する際に、 http://host.docker.internal:8081/ と設定している意味について検証したメモ。

アーキテクチャのイメージ

f:id:ponkan1219:20210228160950j:plain


localhost:8081 で起動している Spring Boot で作成したアプリケーションの前段に、 nginx を置いて、 localhost:80 でアプリケーションにアクセスできるようにします。

昔ながらの三層構造(Web,App,DB)だとこのような構成で動かすことが多いかと思います。

クラウド時代のデプロイとしては、

  • Web のレイヤーが AWS の ELB になり、ターゲットグループでアプリケーションの起動ポートを指定したり

  • Kubernetes の Service になったり

と複雑さが増しますが、今回はシンプルな構成としてみます。


設定する

nginx.conf を以下のように記載すると、実現できます。

events {
    worker_connections  16;
}
http {
    server {
        listen 80;
        server_name localhost;

        location / {

            proxy_pass http://host.docker.internal:8081/;
            proxy_redirect off;
        }
    }
}

ここまでは調べると出てくる情報だったのですが、 http://host.docker.internal:8081/ とはなんぞやという感想です。

Docker のドキュメントによると、

ホストの IP アドレスは変動します(あるいは、ネットワークへの接続がありません)。18.03 よりも前は、特定の DNS 名 host.docker.internal での接続を推奨していました。これはホスト上で内部の IP アドレスで名前解決します。これは開発用途であり、Docker Desktop forMac 外の本番環境では動作しません。

コンテナからホスト(今回だと Mac)の private ip を解決するもののようです。


host.docker.internal の動作を検証

上記の図となるような構成でコンテナを立ち上げます。

※参考リポジトリの docker-compose.yamlコメントアウトを解除

$ docker-compose up -d

host.docker.internal への疎通を確認します。

$ docker network inspect todo_default | grep "Gateway"

                    "Gateway": "172.19.0.1"

$ docker inspect reverse-proxy-nginx | egrep 'Gateway|IPAddress'

            "SecondaryIPAddresses": null,
            "Gateway": "",
            "IPAddress": "",
            "IPv6Gateway": "",
                    "Gateway": "172.19.0.1",
                    "IPAddress": "172.19.0.4",
                    "IPv6Gateway": "",

$ docker-compose exec nginx bash -c "apt-get update -y > /dev/null && apt install -y iputils-ping traceroute > /dev/null && ping host.docker.internal -c 2 && traceroute host.docker.internal"

# 結果の抜粋
PING host.docker.internal (192.168.65.2) 56(84) bytes of data.
64 bytes from 192.168.65.2 (192.168.65.2): icmp_seq=1 ttl=37 time=0.142 ms
64 bytes from 192.168.65.2 (192.168.65.2): icmp_seq=2 ttl=37 time=0.232 ms

--- host.docker.internal ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1ms
rtt min/avg/max/mdev = 0.142/0.187/0.232/0.045 ms
traceroute to host.docker.internal (192.168.65.2), 30 hops max, 60 byte packets
 1  172.19.0.1 (172.19.0.1)  0.625 ms  0.557 ms  0.534 ms

host.docker.internal192.168.65.2 に名前解決され、 Gateway を経由して到達しているようです。

Docker for Mac にて設定した、ネットワークのレンジであることが画像や、

f:id:ponkan1219:20210228173806p:plain

Mac のプロセスでも確認できました。

$ ps aux | grep docker | grep '192.168.65.2'

kiyotakeshi       1476   0.0  0.2  6200624 123032   ??  S     4:25PM   0:01.08 com.docker.vpnkit --ethernet fd:3 --diagnostics fd:4 --pcap fd:5 --vsock-path vms/0/connect --gateway-forwards /Users/kiyotakeshi/Library/Group Containers/group.com.docker/gateway_forwards.json --host-names host.docker.internal,docker.for.mac.host.internal,docker.for.mac.localhost --listen-backlog 32 --mtu 1500 --allowed-bind-addresses 0.0.0.0 --http /Users/kiyotakeshi/Library/Group Containers/group.com.docker/http_proxy.json --dhcp /Users/kiyotakeshi/Library/Group Containers/group.com.docker/dhcp.json --port-max-idle-time 300 --max-connections 2000 --gateway-ip 192.168.65.1 --host-ip 192.168.65.2 --lowest-ip 192.168.65.3 --highest-ip 192.168.65.254 --log-destination asl --gc-compact-interval 1800

ということで、 のような、ルーティングとなっているようです。


コンテナ間通信なら、 http://host.docker.internal は使わない

さて、ホストの側にアクセスしないで、コンテナ間通信する場合の、 reverse proxy の設定となると、 http://host.docker.internal を使うのは不適切です。

理由としては、以下のものが考えられます。

  • コンテナ名で名前解決して疎通することができるから

  • (意図してホスト側の private ip に疎通するならまだしも)、直接的なコンテナ間通信をしていないから

Spring Boot のアプリケーションも image 化してコンテナ起動した場合は、
Nginx とアプリケーションでコンテナ間通信をするので、以下の図のような関係性に変わります。

f:id:ponkan1219:20210228161009j:plain

その際の、 nginx.conf の設定はこちら。

events {
    worker_connections  16;
}
http {
    server {
        listen 80;
        server_name localhost;

        location / {
            # container name を指定
            proxy_pass http://todo:8081/;
            proxy_redirect off;
        }
    }
}

コンテナ名を指定するため、 http://host.docker.internal は使用しません。

docker-compose.yaml(参考リポジトリでは、 app.yaml というファイル名)

services:
  postgres:
    image: postgres:11.10
    container_name: todo-postgres
    ports:
      - 5432:5432
    volumes:
      - .docker/postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: todo
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8"
    restart: always
  todo:
    image: todo # ビルドしておいた image を使用
    container_name: todo
    ports:
      - 8081:8081
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://todo-postgres:5432/todo
    depends_on:
      - postgres
  nginx:
    image: reverse-proxy-nginx
    build: ./reverse-proxy # Dockerfile がある任意の場所
    container_name: reverse-proxy-nginx
    ports:
      - 80:80
    depends_on:
      - todo

Dockerfile

FROM nginx:1.19
COPY nginx.conf /etc/nginx/nginx.conf

コンテナを起動後、 todo で名前解決できていることが確認できます。

docker-compose up -d

# app.yaml を使用する場合は、 `docker-compose -f app.yaml exec nginx ...`
$ docker-compose exec nginx bash -c "apt-get update -y > /dev/null && apt-get install dnsutils -y /dev/null && dig todo +short"

192.168.48.3

docker-compose で複数のコンテナを起動する際に、ネットワークモードの設定を明示的にしていないため、 bridge mode ですべてのコンテナが同一ネットワークに所属しています。

$ docker network ls | grep todo 

8af1c7f7ab3c   todo_default                       bridge    local

$ docker network inspect todo_default | jq -r '.[].Containers[] | [.Name, .IPv4Address]'

[
  "todo-postgres",
  "192.168.48.2/20"
]
[
  "reverse-proxy-nginx",
  "192.168.48.4/20"
]
[
  "todo",
  "192.168.48.3/20"
]

$ docker-compose exec nginx bash -c "dig todo-postgres +short"

192.168.48.2

そのため、 reverse-proxy の設定箇所は、コンテナ名の指定の方が適切となります。

proxy_pass http://todo:8081/;