reverse proxy として nginx をコンテナ起動し、 Spring Boot Application と連携する
nginx を reverse proxy としてコンテナ起動する際に、 http://host.docker.internal:8081/
と設定している意味について検証したメモ。
アーキテクチャのイメージ
localhost:8081 で起動している Spring Boot で作成したアプリケーションの前段に、 nginx を置いて、 localhost:80 でアプリケーションにアクセスできるようにします。
昔ながらの三層構造(Web,App,DB)だとこのような構成で動かすことが多いかと思います。
クラウド時代のデプロイとしては、
Web のレイヤーが AWS の ELB になり、ターゲットグループでアプリケーションの起動ポートを指定したり
Kubernetes の Service になったり
- その場合も、さらに前段には、パブリッククラウドが提供する LB がいるのが一般的
と複雑さが増しますが、今回はシンプルな構成としてみます。
設定する
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/
とはなんぞやという感想です。
ホストの 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.internal
が 192.168.65.2
に名前解決され、 Gateway を経由して到達しているようです。
Docker for Mac にて設定した、ネットワークのレンジであることが画像や、
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 とアプリケーションでコンテナ間通信をするので、以下の図のような関係性に変わります。
その際の、 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/;