自宅サーバー更新にあたり、 Docker が使うネットワークを当該サーバーに閉じさせ、外部のルーティングをさせる方式にしようとした。
ネットワーク構成自体は簡略化した例だが、
- ルーター: 192.168.1.1/24
- サーバー: 192.168.1.2/24
という構成だとする。
この時、ルーター側には ip route の出力で
192.168.2.0/24 via 192.168.1.2 dev eth1
となるルーティングを書き、サーバー側で 192.168.2.0/24 の管理を完結させ、 192.168.1.0/24 内のサーバーからは自由に 192.168.2.0/24 に接続できる、 192.168.2.0/24 は IP アドレスさえ異なっていれば同じポートを使える、といったことをやろうとした。結果からするとやること自体は難しくなかったが、どうやればいいのかの情報がなかなか見当たらず迷走して苦戦した。
結果としては、 Docker コンテナ以外が当該サブネットを使う必要がなければ Docker 外で当該ネットワークをどうこうしようとはせず、普通に Docker 上で network を作成したうえで、 driver のオプションで com.docker.network.bridge.gateway_mode_ipv4 に routed を設定すれば、 Docker がいい感じでルーティングしてくれるようだ。ただしファイヤーウォールも(気が利くと感じるかお節介と感じるかは人によりそうだが)コンテナの port 指定を外部露出の設定とみなせるよう設定されるので、 192.168.2.0/24 外(例えば 192.168.1.0/24)から接続させる必要のある port は明示的に -p (Docker compose の ports)で指定する必要があり、その際 “192.168.2.2:443:443” のような形で 192.168.2.2 のみで LISTEN してホストで LISTEN するわけではないという指定が必要になる。
Docker compose を使ったので、 compose.yaml の書き方としては以下のようになった。なお、別途 service 要素があってそこから当該ネットワークが参照されていないと、 docker compose up -d でも作成されないようだ。ip_range に関しては直接指定すればこの範囲外からでも利用できるので、手動割り当てでは使わなそうな領域を指定している。
network: apps_ext_net: name: apps_ext_net driver: bridge driver_opts: com.docker.network.bridge.gateway_mode_ipv4: routed ipam: driver: default config: - subnet: 192.168.2.0/24 ip_range: 192.168.2.128/26
この方法で、例えば GitLab で
services: gitlab: networks: apps_ext_net: ipv4_address: 192.168.2.2 ports: - "192.168.2.2:22:22" - "192.168.2.2:80:80" - "192.168.2.2:443:443" - "192.168.2.2:5050:5050" # environments, volumes, image などは省略
等と書くと、サーバーのホスト側で既に sshd が 0.0.0.0:22 で LISTEN している場合でも、それとは別途に 192.168.2.2:22 で GitLab の ssh サーバーを待ち受けさせることができた。また、 192.168.2.2 への ping も応答が返ってきた。なお、余談だが、この構成だと GitLab のコンテナ自体が https をホスティングする必要があるが、 GitLab には nginx が同梱されているため、使いたい証明書を設定すれば https を直接待ち受けられる。また、docker exec gitlab gitlab-ctl restart nginx といったコマンドで証明書の更新にも対応できそうだ(細かな確認はしていないが、 Docker registry 用の nginx もこれで再起動していそうだ)。
なお、今回の環境は Ubuntu Server 24.04 を物理機に直接インストールした。Docker をインストールした影響かはわからないが、確認した時点では /proc/sys/net/ipv4/ip_forward は既に1となっていた。