docker-proxy #01
dockerを使ってコンテナを外部ネットワークに公開する場合、例えば次のようなdocker-compose.yamlを使用するとホストの80と443を使用する。
version: '2.3'
services:
httpd:
image: nginx:1.19.0-alpine
container_name: 'httpd'
init: true
networks:
nw:
ipv4_address: "192.168.10.10"
ports:
- 80:80/tcp
- 443:443/tcp
restart: unless-stopped
networks:
nw:
driver: bridge
ipam:
driver: default
config:
- subnet: "192.168.10.0/24"
gateway: "192.168.10.1"
driver_opts:
com.docker.network.bridge.name: docker_httpd
この際、ホスト上ではdocker-proxyがポートをバインドする。
# netstat -anp | grep docker-proxy
tcp6 0 0 :::443 :::* LISTEN 3000/docker-proxy
tcp6 0 0 :::80 :::* LISTEN 3027/docker-proxy
#
# lsof -i:80
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
docker-pr 3027 root 4u IPv6 38568 0t0 TCP *:http (LISTEN)
#
# lsof -i:443
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
docker-pr 3000 root 4u IPv6 39366 0t0 TCP *:https (LISTEN)
IPv6を有効にしたホストではIPv4のポートバインドが見えないが、dockerの場合はIPv4でもアクセスは可能。IPv6ソケットでIPv4の通信を行っていると思われる。(参考:https://www.geekpage.jp/blog/?id=2017-3-8-1)
Netfilterの処理は次に大別できる。
- NW-IF→PREROUTING→INPUT→Local Process
- NW-IF→PREROUTING→FORWARD→POSTROUTING→NW-IF
- Local Process→OUTPUT→POSTROUTING→NW-IF
Dockerでは通常、次のようなルールをiptablesに挿入する。
# iptables -nL -t nat (抜粋)
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
PREROUTING_direct all -- 0.0.0.0/0 0.0.0.0/0
PREROUTING_ZONES_SOURCE all -- 0.0.0.0/0 0.0.0.0/0
PREROUTING_ZONES all -- 0.0.0.0/0 0.0.0.0/0
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
# iptables -nvL (抜粋)
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
target prot opt in out source destination
ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
ACCEPT all -- lo * 0.0.0.0/0 0.0.0.0/0
INPUT_direct all -- * * 0.0.0.0/0 0.0.0.0/0
INPUT_ZONES_SOURCE all -- * * 0.0.0.0/0 0.0.0.0/0
INPUT_ZONES all -- * * 0.0.0.0/0 0.0.0.0/0
DROP all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate INVALID
REJECT all -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
target prot opt in out source destination
DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE-1 all -- * * 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
ACCEPT all -- lo * 0.0.0.0/0 0.0.0.0/0
FORWARD_direct all -- * * 0.0.0.0/0 0.0.0.0/0
FORWARD_IN_ZONES_SOURCE all -- * * 0.0.0.0/0 0.0.0.0/0
FORWARD_IN_ZONES all -- * * 0.0.0.0/0 0.0.0.0/0
FORWARD_OUT_ZONES_SOURCE all -- * * 0.0.0.0/0 0.0.0.0/0
FORWARD_OUT_ZONES all -- * * 0.0.0.0/0 0.0.0.0/0
DROP all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate INVALID
REJECT all -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
PREROUTING chainに追加されるDOCKER chainによりコンテナ向けの通信はNATされ、FORWARD chianに渡される。FORWARD chainではDOCKER chain内で通信の転送が許可されるようになっているので、コンテナへの通信をホストで制限したい場合はDOCKER chainよりも先に処理されるDOCKER-USER chainを使用する。
IPv6ではPREROUTINGの処理が自動で挿入されないため、そのままだとINPUT chainでの処理になる。特に困らなければINPUT_direct chainで制御すればよいと思う。INPUT chainで許可された通信はホストのdocker-proxyプロセスに到達することになる。
尚、docker-proxyを経由させる分パフォーマンスは落ちると思われるので気にするようなら転送設定をした方が良い。
docker-proxyを停止したい場合はuserland-proxyをfalseに設定し、dockerdを再起動する。
# vi /etc/docker/daemon.json
{
"userland-proxy": false,
~省略~
}
# systemctl restart docker