1. 启动一个Docker容器

一般来说,我们起一个容器比如一个简单的nginx服务会向这样

docker run -d --rm nginx:XXX

OK容器起来了,但是并不能通过宿主机被外面的机器访问,此时我们需要对容器做一个端口映射,像是这样

docker run -d --rm -p 80:80 nginx:XXX

这样外面就可以通弄映射出来的宿主机端口访问到容器里的服务了。那么这在这其中流量包时如何在网口,netfilter,路由,网络隔离的namespace,linux虚拟网桥,veth pair设备间送到指定的容器内进程呢?

2. 先说说docker的网络资源隔离是如何做到的

2.1 Network Namespace

linux network namespace(网络命名空间),linux 可以通过namespce对系统的资源做隔离,包括网络资源,我们先做一个小实验。对这一块比较熟悉的同学可以跳过。

建立一个net1和net2的网络名称空间

# 创建net1
ip netns add net1
# 创建net2
ip netns add net2
# 打印名称空间
ip netns list
# 在指定的名称空间执行命令,ip netns exec [network namespace name] [command]
ip netns exec net1 ip addr

我们可以看到回显为

[root@localhost ~]# ip netns exec net1 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
[root@localhost ~]#

OK, 看不到宿主机网络的任何信息,完全是一个空的网络配置。这就是network namespace的效果。

2.2 不同Network Namespace间的通讯

在docker 容器中,不同的容器是可以互相访问的,也就是说namespace间是可以互通的,那么应该如何做到?。

先来了解下veth pair这个东西,它是linux的中虚拟网络设备,注意是“虚拟”,是模拟出来的并不真实存在。veth设备总是成对出现,也叫veth pair,一端发送的数据会由另外一端接收。[滑稽]机灵的同学应该已经发现了,有两端而且两段的数据互通这特么不就是网线么,哎理解的很到位,再展开一下上面的net1, net2不就是两台虚机的主机(从网络资源上讲可以这么说)么,那么我把它们之间用虚拟的网线(veth pair)一连那么不就通了。bingo理论行对了,让我们来实践一下,继续上一实验的环境。

先创建一对veth pair设备 veth1, veth2

ip link add veth1 type veth peer name veth2

在用这对veth设备连接两个namespace

ip link set veth1 netns net1
ip link set veth2 netns net2

进入net1配置并启动虚拟网卡

[root@localhost ~]# ip netns exec ip addr
Cannot open network namespace "ip": No such file or directory
[root@localhost ~]# ip netns exec net1 bash
[root@localhost ~]# ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
7: veth1@if6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 0e:ad:28:91:48:51 brd ff:ff:ff:ff:ff:ff link-netnsid 1
[root@localhost ~]# ip addr add 10.0.0.1/24 dev veth1
[root@localhost ~]# ip link set dev veth1 up
[root@localhost ~]#

进入net2配置并启动网卡

[root@localhost ~]# ip netns exec net2 bash
[root@localhost ~]# ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
6: veth2@if7: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 4a:4a:a2:0a:cc:05 brd ff:ff:ff:ff:ff:ff link-netnsid 0
[root@localhost ~]# ip addr add 10.0.0.2/24 dev veth2
[root@localhost ~]# ip link set dev veth2 up
[root@localhost ~]#

OK, 理论上这就相当于两台直连主机(就好像你的一台pc直接把网线怼到另一台pc),让我们来ping一下

# 进入net1 ping net2的地址
[root@localhost ~]# ip netns exec net1 bash
[root@localhost ~]# ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.073 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.069 ms
64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=0.055 ms
64 bytes from 10.0.0.2: icmp_seq=4 ttl=64 time=0.065 ms
^C
--- 10.0.0.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3001ms
rtt min/avg/max/mdev = 0.055/0.065/0.073/0.010 ms
[root@localhost ~]#

通了ok,让我们把vnet2 的veth2 down 了试下(相当于把对端pc的网线拔了)

[root@localhost ~]# ip netns exec net2 bash
[root@localhost ~]# ip link set dev veth2 down
[root@localhost ~]# exit
exit
[root@localhost ~]# ip netns exec net1 bash
[root@localhost ~]# ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
^C
--- 10.0.0.2 ping statistics ---
5 packets transmitted, 0 received, 100% packet loss, time 3999ms [root@localhost ~]#

意料之中,完全不同(都拔线了咋可能通)。

2.3 比起插线(veth pair)更好的方案 bridge

细心的你一定想到了一个问题,我在机器上有这么多的容器每个容器都要实现互通,难道每个namespace之间都要通过veth pair来连接么,这样得差n的二次方的网线啊,这也太离谱的。

这个问题可以回归到现实是如果做到的,在公司的一个网段里有那么多的主机,互插线的话网口都不够用,那么插哪里呢?交换机。由交换机帮我把对应的数据送到对应的网口,像是物流站一样,所有的pc把网线插上去就完事。这里我们引入另一个linux的虚拟网络设备 bridge(网桥),你可以理解为这就是一个二层交换机的功能。所以无论我们新增多少容器,都可以通过veth pair 连接到 bridge上实现容器互通,而在docker里这个网桥就是docker0。

[root@localhost ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:9b:21:ef brd ff:ff:ff:ff:ff:ff
inet 192.168.144.128/24 brd 192.168.144.255 scope global noprefixroute ens33
valid_lft forever preferred_lft forever
inet6 fe80::2ac9:5d64:5e4b:6619/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:91:d3:be:6c brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:91ff:fed3:be6c/64 scope link
valid_lft forever preferred_lft forever
5: veth299c707@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 8a:eb:25:8f:78:f0 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::88eb:25ff:fe8f:78f0/64 scope link
valid_lft forever preferred_lft forever

docker 会在启动是创建docker0网桥,在启动容器时创建一对veth pair设备,一端会放置在容器里命名为eth0,另一端会放在host主机的上叫做veth*****并加载网桥上。

我们可以手动模拟这个过程,我们新建一个网桥br0, 再建立两个网络名称空间net3, net4, 并通过veth pair把它们连接到这个网桥上。

# 创建网络名称空间net3 net4
ip netns add net3
ip netns add net4 # 创建两队veth pair设备 veth*in 放置于namespace, veth*out放置于网桥
ip link add veth3in type veth peer name veth3out
ip link add veth4in type veth peer name veth4out # 连接net3并配置ip
ip link set veth3in netns net3
ip netns exec net3 ip addr add 10.0.0.3/24 dev veth3in
ip netns exec net3 ip link set dev veth3in up # 连接net4并配置ip
ip link set veth4in netns net4
ip netns exec net4 ip addr add 10.0.0.4/24 dev veth4in
ip netns exec net4 ip link set dev veth4in up # 可以试不连网桥是否ping通 # 创建br0网桥
ip link add name br0 type bridge # 连接网桥
ip link set dev veth3out master br0
ip link set dev veth3out up
ip link set dev veth4out master br0
ip link set dev veth4out up # 先网桥还没有启动你看看是否能ping通 # 启动网桥
ip link set br0 up
# 启动网桥后net3 终于能ping通net4了

如果宿主机想访问这个bridge上的namespace怎么办,再加个veth pair 给一端配置ip 另一端接到bridge上? 正确,但是不必这么麻烦。其实我们在ip addr 是看到的那个br0并不是网桥的本地,br0也只是网桥的一个口子罢了,我们是可给这个OK配置上IP直接访问在这个二层网络的,像是这样:

[root@localhost ~]# ip addr add 10.0.0.254/24 dev br0
[root@localhost ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.144.2 0.0.0.0 UG 100 0 0 ens33
10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 br0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.144.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33
[root@localhost ~]# ping 10.0.0.4
PING 10.0.0.4 (10.0.0.4) 56(84) bytes of data.
64 bytes from 10.0.0.4: icmp_seq=1 ttl=64 time=0.068 ms
64 bytes from 10.0.0.4: icmp_seq=2 ttl=64 time=0.051 ms
64 bytes from 10.0.0.4: icmp_seq=3 ttl=64 time=0.048 ms
^C
--- 10.0.0.4 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2024ms
rtt min/avg/max/mdev = 0.048/0.055/0.068/0.012 ms
[root@localhost ~]#

所以我们在安装了docker的主机中可以看到带有与ip 172.17.0.1 的docker0网桥。

2.4 小总结

到这一节,其实就是run一个容器时不加端口映射的全部情况了。可以梳理下就是。docker 通过Network Namespace, bridge和veth pair这三个虚拟设备实现一个简单的二层网络,不同的namespace实现了不同容器的网络隔离让他们分别有自己的ip,通过veth pair 连接到docker0网桥上实现了容器间和宿主机的互通。其实就这么简单。

3. docker端口映射原理

docker 的端口映射是通过netfilter实现的,netfilter是内核的一个数据包处理框架,可以在数据包在路由前,路由后,进入用户态前,从用户态出来后等等好几个点对数据包进行处理包括但不限于拦截,源地址转换,目的地址换等,用户态表现为iptables工具和防火墙。一般而言都喜欢称呼为docker的端口映射是通过iptables实现,其实iptables只是用户态的一个规则下发工具而已,实际工作的是netfilter,看个人理解吧,其实都差不多。关于iptables的使用内容太多,此处不加以赘述如果有同学这方面知识比较生疏强烈安利一篇博客[https://www.zsythink.net/archives/tag/iptables/]。

一下内容我都是简历在对iptables规则较为熟悉的前提下

3.1 数据包是如何到容器里面的又是如何从容器出去的

我们先创建一个容器在开放8000端口的服务到宿主机的8000端口,命令如下:

# 用别的服务也可以,都行,映射了端口就好
docker run -d -p 8000:8000 centos:7 python -m SimpleHTTPServer

确认端口开发成功,外部可以访问后我们先来看下docker在iptables配置了什么

# 查看nat表(这个表负责转发和地址转换)
[root@localhost ~]# iptables -t nat -nvL
Chain PREROUTING (policy ACCEPT 32 packets, 4465 bytes)
pkts bytes target prot opt in out source destination
2 112 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT 25 packets, 3913 bytes)
pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 184 packets, 13926 bytes)
pkts bytes target prot opt in out source destination
10 740 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT 185 packets, 14010 bytes)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * docker0 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match src-type LOCAL
0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:8000 Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8000 to:172.17.0.2:8000
[root@localhost ~]#

可以看到在 PREROUTING 链中(路由前)有一条自定义的DOCKER链,DOCKER链中有一条规则,翻译下就是“只要是tcp的包目的地址的端口是8000的统统把目的地址改为172.17.0.2,目的端口改为8000”, 这样一来原先的数据包src_ip:src_port->dst_ip:8000就会在路由前变成src_ip:src_port->172.17.0.2:8000。再看路由表:

[root@localhost ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.144.2 0.0.0.0 UG 100 0 0 ens33
10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 br0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.144.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33

匹配到172.17.0.0这条,命中路由走input这条链,被直接发往Iface:docker0也就是网桥在host主机的这个口子上。然后这个数据包会通过veth pair 设备走到容器中。

3.2 抓包验证

口说无凭眼见为实,我觉得只要兄弟们不是明天项目要上限有闲功夫来看我得我文章的都应该去抓个包看看。

同事对物理网卡和docker0抓包

[root@localhost ~]# tcpdump -i ens33 -n -vvv tcp port 8000
tcpdump: listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
00:31:38.768744 IP (tos 0x0, ttl 64, id 28655, offset 0, flags [DF], proto TCP (6), length 60)
192.168.144.129.47106 > 192.168.144.128.irdmi: Flags [S], cksum 0x3e4b (correct), seq 2908588551, win 29200, options [mss 1460,sackOK,TS val 12707304 ecr 0,nop,wscale 7], length 0
00:31:38.768941 IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto TCP (6), length 60)
192.168.144.128.irdmi > 192.168.144.129.47106: Flags [S.], cksum 0xa281 (incorrect -> 0x90e0), seq 2883195023, ack 2908588552, win 28960, options [mss 1460,sackOK,TS val 12710174 ecr 12707304,nop,wscale 7], length 0
00:31:38.772124 IP (tos 0x0, ttl 64, id 28656, offset 0, flags [DF], proto TCP (6), length 52)
192.168.144.129.47106 > 192.168.144.128.irdmi: Flags [.], cksum 0x2fe5 (correct), seq 1, ack 1, win 229, options [nop,nop,TS val 12707307 ecr 12710174], length 0
00:31:38.772185 IP (tos 0x0, ttl 64, id 28657, offset 0, flags [DF], proto TCP (6), length 136)
192.168.144.129.47106 > 192.168.144.128.irdmi: Flags [P.], cksum 0x0f8c (correct), seq 1:85, ack 1, win 229, options [nop,nop,TS val 12707307 ecr 12710174], length 84
00:31:38.772281 IP (tos 0x0, ttl 63, id 57777, offset 0, flags [DF], proto TCP (6), length 52)
192.168.144.128.irdmi > 192.168.144.129.47106: Flags [.], cksum 0xa279 (incorrect -> 0x2f90), seq 1, ack 85, win 227, options [nop,nop,TS val 12710177 ecr 12707307], length 0
00:31:38.779728 IP (tos 0x0, ttl 63, id 57778, offset 0, flags [DF], proto TCP (6), length 69)
192.168.144.128.irdmi > 192.168.144.129.47106: Flags [P.], cksum 0xa28a (incorrect -> 0x6fab), seq 1:18, ack 85, win 227, options [nop,nop,TS val 12710184 ecr 12707307], length 17
00:31:38.780273 IP (tos 0x0, ttl 63, id 57779, offset 0, flags [DF], proto TCP (6), length 990)
192.168.144.128.irdmi > 192.168.144.129.47106: Flags [FP.], cksum 0xa623 (incorrect -> 0x8580), seq 18:956, ack 85, win 227, options [nop,nop,TS val 12710185 ecr 12707307], length 938
00:31:38.780359 IP (tos 0x0, ttl 64, id 28658, offset 0, flags [DF], proto TCP (6), length 52)
192.168.144.129.47106 > 192.168.144.128.irdmi: Flags [.], cksum 0x2f6d (correct), seq 85, ack 18, win 229, options [nop,nop,TS val 12707316 ecr 12710184], length 0
00:31:38.780566 IP (tos 0x0, ttl 64, id 28659, offset 0, flags [DF], proto TCP (6), length 52)
192.168.144.129.47106 > 192.168.144.128.irdmi: Flags [.], cksum 0x2bb3 (correct), seq 85, ack 957, win 243, options [nop,nop,TS val 12707316 ecr 12710185], length 0
00:31:38.780806 IP (tos 0x0, ttl 64, id 28660, offset 0, flags [DF], proto TCP (6), length 52)
192.168.144.129.47106 > 192.168.144.128.irdmi: Flags [F.], cksum 0x2bb2 (correct), seq 85, ack 957, win 243, options [nop,nop,TS val 12707316 ecr 12710185], length 0
00:31:38.780864 IP (tos 0x0, ttl 63, id 55149, offset 0, flags [DF], proto TCP (6), length 52)
192.168.144.128.irdmi > 192.168.144.129.47106: Flags [.], cksum 0x2bc1 (correct), seq 957, ack 86, win 227, options [nop,nop,TS val 12710186 ecr 12707316], length 0
^C
[root@localhost ~]# tcpdump -i docker0 -n -vvv tcp port 8000
tcpdump: listening on docker0, link-type EN10MB (Ethernet), capture size 262144 bytes
00:31:38.768858 IP (tos 0x0, ttl 63, id 28655, offset 0, flags [DF], proto TCP (6), length 60)
192.168.144.129.47106 > 172.17.0.2.irdmi: Flags [S], cksum 0xe360 (correct), seq 2908588551, win 29200, options [mss 1460,sackOK,TS val 12707304 ecr 0,nop,wscale 7], length 0
00:31:38.768929 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
172.17.0.2.irdmi > 192.168.144.129.47106: Flags [S.], cksum 0xfd6b (incorrect -> 0x35f6), seq 2883195023, ack 2908588552, win 28960, options [mss 1460,sackOK,TS val 12710174 ecr 12707304,nop,wscale 7], length 0
00:31:38.772152 IP (tos 0x0, ttl 63, id 28656, offset 0, flags [DF], proto TCP (6), length 52)
192.168.144.129.47106 > 172.17.0.2.irdmi: Flags [.], cksum 0xd4fa (correct), seq 1, ack 1, win 229, options [nop,nop,TS val 12707307 ecr 12710174], length 0
00:31:38.772190 IP (tos 0x0, ttl 63, id 28657, offset 0, flags [DF], proto TCP (6), length 136)
192.168.144.129.47106 > 172.17.0.2.irdmi: Flags [P.], cksum 0xb4a1 (correct), seq 1:85, ack 1, win 229, options [nop,nop,TS val 12707307 ecr 12710174], length 84
00:31:38.772270 IP (tos 0x0, ttl 64, id 57777, offset 0, flags [DF], proto TCP (6), length 52)
172.17.0.2.irdmi > 192.168.144.129.47106: Flags [.], cksum 0xfd63 (incorrect -> 0xd4a5), seq 1, ack 85, win 227, options [nop,nop,TS val 12710177 ecr 12707307], length 0
00:31:38.779674 IP (tos 0x0, ttl 64, id 57778, offset 0, flags [DF], proto TCP (6), length 69)
172.17.0.2.irdmi > 192.168.144.129.47106: Flags [P.], cksum 0xfd74 (incorrect -> 0x14c1), seq 1:18, ack 85, win 227, options [nop,nop,TS val 12710184 ecr 12707307], length 17
00:31:38.780084 IP (tos 0x0, ttl 64, id 57779, offset 0, flags [DF], proto TCP (6), length 990)
172.17.0.2.irdmi > 192.168.144.129.47106: Flags [FP.], cksum 0x010e (incorrect -> 0x2a96), seq 18:956, ack 85, win 227, options [nop,nop,TS val 12710185 ecr 12707307], length 938
00:31:38.780389 IP (tos 0x0, ttl 63, id 28658, offset 0, flags [DF], proto TCP (6), length 52)
192.168.144.129.47106 > 172.17.0.2.irdmi: Flags [.], cksum 0xd482 (correct), seq 85, ack 18, win 229, options [nop,nop,TS val 12707316 ecr 12710184], length 0
00:31:38.780578 IP (tos 0x0, ttl 63, id 28659, offset 0, flags [DF], proto TCP (6), length 52)
192.168.144.129.47106 > 172.17.0.2.irdmi: Flags [.], cksum 0xd0c8 (correct), seq 85, ack 957, win 243, options [nop,nop,TS val 12707316 ecr 12710185], length 0
00:31:38.780818 IP (tos 0x0, ttl 63, id 28660, offset 0, flags [DF], proto TCP (6), length 52)
192.168.144.129.47106 > 172.17.0.2.irdmi: Flags [F.], cksum 0xd0c7 (correct), seq 85, ack 957, win 243, options [nop,nop,TS val 12707316 ecr 12710185], length 0
00:31:38.780847 IP (tos 0x0, ttl 64, id 55149, offset 0, flags [DF], proto TCP (6), length 52)
172.17.0.2.irdmi > 192.168.144.129.47106: Flags [.], cksum 0xd0d6 (correct), seq 957, ack 86, win 227, options [nop,nop,TS val 12710186 ecr 12707316], length 0
^C
11 packets captured
11 packets received by filter
0 packets dropped by kernel

可以明显看到目的地址192.168.144.128:8000 在路由前被篡改为了172.17.0.2:8000,并在数据包返回时iptables自动帮我们吧返回的数据包源地址改回了192。168.144.128:8000。

3.2 手动添加端口映射

既然docker自己都是通过iptables实现映射的那么,当然我们手敲命令也可以实现同样的效果。我们新增一条host主机9000端口,命令如下

iptables -t nat -A DOCKER -p tcp --dport 9000 -j DNAT --to-destination 172.17.0.2:8000
# 访问成功,丝滑
[root@localhost ~]# curl 192.168.144.128:9000
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for /</title>
<body>
<h2>Directory listing for /</h2>
<hr>
<ul>
<li><a href=".dockerenv">.dockerenv</a>
<li><a href="anaconda-post.log">anaconda-post.log</a>
<li><a href="bin/">bin@</a>
<li><a href="dev/">dev/</a>
<li><a href="etc/">etc/</a>
<li><a href="home/">home/</a>
<li><a href="lib/">lib@</a>
<li><a href="lib64/">lib64@</a>
<li><a href="media/">media/</a>
<li><a href="mnt/">mnt/</a>
<li><a href="opt/">opt/</a>
<li><a href="proc/">proc/</a>
<li><a href="root/">root/</a>
<li><a href="run/">run/</a>
<li><a href="sbin/">sbin@</a>
<li><a href="srv/">srv/</a>
<li><a href="sys/">sys/</a>
<li><a href="tmp/">tmp/</a>
<li><a href="usr/">usr/</a>
<li><a href="var/">var/</a>
</ul>
<hr>
</body>
</html>

3.3 小节

docker对于转发的实现还是比较简单的,只要耐心的去查看相应的转发规则都可以读懂数据的走象,至于在容器里发出的请求,他的snat是如果做的,本地的docker-proxy的服务存在的意义可以自己好好思考下,我这里就不多BB了。

Docker默认桥接网络是如何工作的的更多相关文章

  1. docker 配置桥接网络

    2.5 docker配置桥接网络(上): 为了使本地网络中的机器和Docker 容器更方便的通信,我们经常会有将Docker容器 配置到和主机同一网段的需求. 这个需求其实很容器实现, 我们只需要将D ...

  2. Docker之 默认桥接网络与自定义桥接网卡

    docker引擎会默认创建一个docker0网桥,它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和宿主机都放到同一个二层网络. 1. docker如何使用网桥 1.1 Linux虚拟网桥的特点 ...

  3. docker配置桥接网络

    [root@localhost ~]# cd /etc/sysconfig/network-scripts/ [root@localhost network-scripts]# cp ifcfg-et ...

  4. docker从零开始网络(二)桥接网络

    使用桥接网络 在网络方面,桥接网络是链路层设备,它在网络段之间转发流量.桥接网络可以是硬件设备或在主机内核中运行的软件设备. 就Docker而言,桥接网络使用软件桥接器,该软件桥接器允许连接到同一桥接 ...

  5. Docker、K8S网络工作原理

    一.Docker 网络模式 在讨论 Kubernetes 网络之前,让我们先来看一下 Docker 网络.Docker 采用插件化的网络模式,默认提供 bridge.host.none.overlay ...

  6. 高级网络功能(Docker支持的网络定制配置)

    网络的高级知识,包括网络的启动和配置参数.DNS的使用配置.容器访问和端口映射的相关实现. 在一些具体场景中,Docker支持的网络定制配置,通过Linux命令来调整.补充.甚至替换Docker默认的 ...

  7. docker单主机网络

    当你安装Docker时,它会自动创建三个网络.你可以使用以下docker network ls命令列出这些网络: [root@localhost ~]# docker network ls NETWO ...

  8. Docker 四种网络模式

    原文 https://www.cnblogs.com/gispathfinder/p/5871043.html 我们在使用docker run创建Docker容器时,可以用--net选项指定容器的网络 ...

  9. Kubernetes网络之Flannel工作原理

    目录 1.Docker网络模式 1.1 bridge网络的构建过程 1.2 外部访问 2.Kubernetes网络模式 2.1 同一个Pod中容器之间的通信 2.2 不同Pod中容器之间的通信 2.3 ...

随机推荐

  1. 渗透:EWSA

    EWSA全称Elcomsoft Wireless Security Auditor.ElcomSoft是一家俄罗斯软件公司,出品过不少密码破解软件,涉及Office.SQL.PDF.EFS等等. EW ...

  2. unity---点击事件

    点击事件 点击触发的事件脚本 脚本挂载方式 On Click() 如果点击后触发,调用Button物体下,Button_lick脚本中的func函数/func_text 结果

  3. 134_Power BI Report Server之某消费品运营数据监控

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一背景 最近很久都没有更新文章了,研究了下Power BI Report Server(下文简称pbirs). 今天把pb ...

  4. SQL表的创建

    ​  一,创建表 1.使用鼠标创建表 1,进入SQL进行连接 ​编辑 2,在左边会有一个对象资源管理器,右键数据库,在弹出的窗口中选择新建数据库 ​编辑 3,给这个包取个名字,在这个界面可以给这个表选 ...

  5. monit 配置详解(monitrc)

    monitrc是Monit的主配置文件(控制文件). monitrc的内容主要分为全局(golbal)和服务(services)两个部分. 默认情况下monitrc文件在/etc/monit目录下. ...

  6. 《Effective C++》阅读总结(四): 设计、声明与实现

    第四章: 设计与声明 18. 让接口更容易被正确使用,不易被误用 将你的class的public接口设计的符合class所扮演的角色,必要时不仅对传参类型限制,还对传参的值域进一步限制. 19. 设计 ...

  7. KNN算法推理与实现

    Overview K近邻值算法 KNN (K - Nearest Neighbors) 是一种机器学习中的分类算法:K-NN是一种非参数的惰性学习算法.非参数意味着没有对基础数据分布的假设,即模型结构 ...

  8. POJ1821 Fence 题解报告

    传送门 1 题目描述 A team of $k (1 <= K <= 100) $workers should paint a fence which contains \(N (1 &l ...

  9. 重学ES系列之拓展运算符

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. 命令行工具tabby--gi t仓库Token的使用

    命令行工具tabby--git仓库Token的使用 欢迎关注H寻梦人公众号 前言 再见 Xshell !这款开源的终端工具逼格更高! 终端神器--Tabby Terminal electerm is ...