桥接网络

没有指定网络下,默认的网络模式 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
外部访问容器有些人没注意有个坑,有些应用默认监听 127.0.0.1,这种情况 -p 映射后,发往容器的协议栈里,由于没有 bind 0.0.0.0:x 会导致外面无法访问。

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 评论

你还没有登录。你所做的任何更改会将作者标记为匿名用户。 如果你已经拥有帐户,请登录