版本比较
标识
- 该行被添加。
- 该行被删除。
- 格式已经改变。
桥接网络
没有指定网络下,默认的网络模式 bridge,每个容器在 network namespace 隔离下有单独的协议栈,然后通过 veth peer 链接到宿主机上的 docker0 网桥。
Image Modified
- 容器内的 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 |
流量会根据图中箭头走:
Image Added
代码块 | ||
---|---|---|
| ||
$ 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 |
+-----------------------------------+--------------------------+
| Host | Container 1 |
| 路由后走 forward 默认路由 eth0 发出 | |
| +----------------------------+ | +----------------------+ |
| | Network Protocol Stack | | |Network Protocol Stack| |
| +----------------------------+ | +----------------------+ |
| | ↑ | | |
|.......|...........|...............|.............|............|
| | | | | |
| POST_ROUTING | | | |
| SNAT 2.112 | | | |
| | | | | |
| ↓ | | ↓ |
| +------+ +----------+ | +-------+ |
| |.2.112| |172.17.0.1| | | .0.2 | |
| +------+ +----------+ | +-------+ |
| | eth0 | | docker0 | | | eth0 | |
| +------+ +----------+ | +-------+ |
| ↓ ↑ | | |
| | | | | |
| | +-------+ | | |
| | | veth |<------------------+ |
| | +-------+ | |
| | | |
+-------↓---------------------------+--------------------------+
$ iptables -w -t nat -S POSTROUTING
-P POSTROUTING ACCEPT
-A POSTROUTING -s
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 |
流量会根据图中箭头走:
流量会根据图中箭头走
Image Added
代码块 | ||
---|---|---|
| ||
$ 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 |
| Host | Container 1 |
| 路由后走 forward docker0 | |
| +----------------------------+ | +----------------------+ |
| | Network Protocol Stack | | |Network Protocol Stack| |
| +----------------------------+ | +----------------------+ |
| ↑ ↓ | ↑ |
|.......|................|..........|.............|............|
| | | | | |
| PRE_ROUTING | | | |
| DNAT 172.17.0.3:80 | | | |
| | | | | |
| ↑ ↓ | ↑ |
| +------+ +----------+ | +-------+ |
| |.2.112| |172.17.0.1| | | .0.3 | |
| +------+ +----------+ | +-------+ |
| | eth0 | | docker0 | | | eth0 | |
| +------+ +----------+ | +-------+ |
| ↑ ↓ | ↑ |
| | | | | |
| | | | | |
| | +-------+ | | |
| | | veth |>-------------------+ |
| | +-------+ | |
| | | |
| | | |
+-------↑---------------------------+--------------------------+
相关 conntrack 信息:conntrack -L |& grep -P =80
tcp 6 117 TIME_WAIT
相关 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 网桥上
Image Added
用途:
- 用户自定义配置网络接口、路由之类的
- 运行不依赖网络的进程,例如编译、压测之类的
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 分钟就启动了。
目录 |
---|