- 创建者: 虚拟的现实,上次更新时间:9月 06, 2024 需要 3 分钟阅读时间
桥接网络
没有指定网络下,默认的网络模式 bridge,每个容器在 network namespace 隔离下有单独的协议栈,然后通过 veth peer 链接到宿主机上的 docker0 网桥。
- 容器内的 lo 没有画,不影响理解
- 宿主机需要 modprobe br_netfilter 后开启 ip_forward
- docker info 最下面几行出现 warning 代表没开
- docker0 的网段不要配置和内网的网段一样,否则会冲突
容器内访问外部的流量
使用 tcpdump 抓包后,我们起个下面容器:
$ tcpdump -nn -i any icmp and host 223.5.5.5 # 另一个窗口上开启 ping 一个包 $ docker run --rm -d --name t1 nginx:alpine ping -c1 223.5.5.5
抓包输出为如下:
# 出去的报文 11:12:44.213854 veth91af98c P ifindex 45 02:42:0a:b9:00:02 ethertype IPv4 (0x0800), length 104: 172.17.0.2 > 223.5.5.5: ICMP echo request, id 2, seq 0, length 64 11:12:44.213854 docker0 In ifindex 3 02:42:0a:b9:00:02 ethertype IPv4 (0x0800), length 104: 172.17.0.2 > 223.5.5.5: ICMP echo request, id 2, seq 0, length 64 11:12:44.213895 ens160 Out ifindex 2 00:50:56:ad:6f:fa ethertype IPv4 (0x0800), length 104: 192.168.2.112 > 223.5.5.5: ICMP echo request, id 2, seq 0, length 64 # 回来的报文 11:12:44.233494 ens160 In ifindex 2 78:2c:29:c4:00:01 ethertype IPv4 (0x0800), length 104: 223.5.5.5 > 192.168.2.112: ICMP echo reply, id 2, seq 0, length 64 11:12:44.233522 docker0 Out ifindex 3 02:42:50:71:f0:99 ethertype IPv4 (0x0800), length 104: 223.5.5.5 > 172.17.0.2: ICMP echo reply, id 2, seq 0, length 64 11:12:44.233527 veth91af98c Out ifindex 45 02:42:50:71:f0:99 ethertype IPv4 (0x0800), length 104: 223.5.5.5 > 172.17.0.2: ICMP echo reply, id 2, seq 0, length 64
流量会根据图中箭头走:
$ ip route show default via 192.168.2.1 dev eth0 proto static 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.112 $ ip route get 223.5.5.5 223.5.5.5 via 192.168.2.1 dev eth0 src 192.168.2.112 uid 0 cache $ iptables -w -t nat -S POSTROUTING -P POSTROUTING ACCEPT -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE # 要添加下面内核参数,iptables 对 bridge 的数据进行处理 net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-arptables = 1
iptables 做 NAT 时候,是有记录状态的,可以使用 conntrack 查看:
$ conntrack -L -p icmp icmp 1 29 src=172.17.0.2 dst=223.5.5.5 type=8 code=0 id=1 src=223.5.5.5 dst=192.168.2.112 type=0 code=0 id=1 mark=0 use=1
外部访问容器
$ docker run --rm -d --name t2 -p 80:80 nginx:alpine # 外部机器 192.168.2.5 上 $ curl 192.168.2.112
流量会根据图中箭头走
$ iptables -t nat -S ... -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER -A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.3:80
相关 conntrack 信息:
conntrack -L |& grep -P =80 tcp 6 117 TIME_WAIT src=192.168.2.5 dst=192.168.2.112 sport=51894 dport=80 src=172.17.0.3 dst=192.168.2.5 sport=80 dport=51894 [ASSURED] mark=0 use=1
docker-proxy 进程
如果你细心观察的话,你会发现有 docker-proxy 的进程存在:
$ ss -nlpt sport 80 | awk '{print $4,$5,$6}' | column -t Local Address:Port Peer 0.0.0.0:80 0.0.0.0:* users:(("docker-proxy",pid=3709090,fd=4)) [::]:80 [::]:* users:(("docker-proxy",pid=3709098,fd=4))
本机回环接口(localhost)的流量不会经过 iptables 的 PREROUTING 链。因此,如果没有 docker-proxy,这些请求不会被正确地重定向到容器。
$ curl -I localhost:80 HTTP/1.1 200 OK ...
然后让进程完全停止后再 curl 看看 http 状态码:
$ kill -STOP 3709090 $ curl -I localhost:80 ^C # 外部机器上访问这个容器宿主机 80 端口依然可以访问 # 是因为外部进来是走的 PRE_ROUTING 被 DNAT 了 $ curl -I 192.168.2.112:80 HTTP/1.1 200 OK ...
然后恢复进程,能访问
$ kill -CONT 3709090 $ curl -I localhost:80 HTTP/1.1 200 OK ...
docker-proxy 存在的另一个原因也是因为 iptables 和内核可以处理 IPv4 的 DNAT 和 SNAT,但对于 IPv6 的支持,尤其是在早期版本的 Docker 中并不完善(新的 Linux 发行版系统里有 ip6tables 命令)。docker-proxy 可以在用户空间处理 IPv6 请求。
none 和 host 网络
每个容器在 network namespace 隔离下有单独的协议栈,没有网络接口(eth0)以及没有 veth 连接到 docker0 网桥上
用途:
- 用户自定义配置网络接口、路由之类的
- 运行不依赖网络的进程,例如编译、压测之类的
host 网络和它的注意点
host 网络和 k8S 的 hostNetwork 理解为网络不单独的 network namespace 而是和宿主机一样的网络即可,它的其他 namespace 还是隔离的例如 mount、pid。
它的应用场景:
- 端口很多应用,或者被动增加端口监听数量的,例如 ftp,直接 host 网络简单粗暴
- 有些应用例如 kafka 在桥接下,会检测到自己监听的信息 (172.17.0.2:9092)和别人进来的应用层层面的信息(例如 192.168.2.112:9092)对不上而有问题,有两种方式解决方式:
- 1. 应用层配置对外宣告链接自己的信息,例如 kafka 配置 KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.2.112:9092
- 2. host 网络粗暴解决,比如桥接就慢的情况下,host 网络就没问题
要注意的一个点是 Docker 的 init 层的一些文件:
- /etc/hostname
- /etc/hosts
- /etc/resolv.conf
默认参数下,不使用额外参数,host 网络的容器启动时候,这三个的单独文件都会从宿主机的内容拷贝生成的:
$ docker inspect t2 | grep -Pi '(hosts|hostname|resolv).*path' "ResolvConfPath": "/var/lib/docker/containers/30d40445175c3c9e83ba7d7b387eb2e9be7fb3f8a3b37822807c6ff818335536/resolv.conf", "HostnamePath": "/var/lib/docker/containers/30d40445175c3c9e83ba7d7b387eb2e9be7fb3f8a3b37822807c6ff818335536/hostname", "HostsPath": "/var/lib/docker/containers/30d40445175c3c9e83ba7d7b387eb2e9be7fb3f8a3b37822807c6ff818335536/hosts",
加参数可以让和宿主机的不一样,例如 --add-host,还有个问题是因为 init 层是单独的文件,存在两种情况:
- 修改容器内这些文件内容后,容器 restart 后,会回到之前的宿主机一样的配置
- 宿主机修改了自己的文件后,容器内不会同步,需要重启下该容器
host 网络下,java 相关的要注意,java 有些库启动会解析自己的 hostname,无法解析会无法启动,例如 zookeeper,之前也遇到过 springboot 的一个项目在 /etc/hosts 里没有 hostname 的解析记录启动要 15 分钟,加了 hostname 的解析后 3 分钟就启动了。
- 无标签
0 评论