热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Docker容器基础入门认知网络篇

这篇文章中,会从docker中的单机中的 netns到veth,再到单机多个容器之间的bridge网络交互,最后到跨主机容器之间的nat和vxlan通信过程,让大家对docker中

这篇文章中,会从 docker 中的单机中的 netns 到 veth,再到单机多个容器之间的 bridge 网络交互,最后到跨主机容器之间的 nat 和 vxlan 通信过程,让大家对 docker 中的网络大概有个初步的了解。

先从 docker 里所使用的网络ns说起。在不同的容器中,docker 会为每个容器自动分配 ip 地址。并且在宿主机上是可以互相 ping 通的。比如下面我们起两个 busybox

$ docker run busybox sh -c "while true;do sleep 3600;done;"

这两个容器中,网络是互通的,并且在任何其他的容器内去 ping 这两个容器的 ip 也是联通的,这就说明在整个 docker 网络中,容器和 ip 分配还有相关的路由转发都是由 docker 内部来进行维护的。我们查看一下这两个容器的 ip

第一个容器中:

第二个容器中:

在除此之外的另一个容器中去 ping 172.17.0.2172.17.0.3

在 docker 中,不同的容器之间网络连通也是使用了 linux 的命名空间,用一个小实验来说明这里所用的原理其实就是使用了 veth 来实现命名空间的互联

实验步骤分为以下几个步骤:

  1. 创建端口
  2. 产生网线
  3. 分配ip

在 docker 中网络的分配也是根据这三个步骤来生成容器 ip 的,首先我们先产生两个网络虚拟命名空间

# 产生命名空间 test1, test2
$ sudo ip netns add test1
$ sudo ip netns add test2

  $ ip netns list
  test2 (id: 3)
  test1 (id: 2) 

产生一对 veth ,也就是所说的网线

# 产生一对 veth (veth 都是成对出现)
sudo ip link add veth1-test1 type veth peer name veth2-test2

将虚拟命名空间的网卡和 veth 进行绑定

# 将两个虚拟网卡 veth 分配给 test1 和 test2
$ sudo ip link set veth1-test1 netns test1
$ sudo ip link set veth2-test2 netns test2

开启网卡,并且尝试 ping

# 开启网卡(因为新加的状态都是 DOWN)
$ sudo ip netns exec test1 ip link set veth1-test1 up
$ sudo ip netns exec test2 ip link set veth2-test2 up

# 尝试 ping
$ sudo ip netns exec test1 ping 192.168.1.2 -c 3
PING 192.168.1.2 (192.168.1.2) 56(84) bytes of data.
64 bytes from 192.168.1.2: icmp_seq=1 ttl=64 time=0.034 ms
64 bytes from 192.168.1.2: icmp_seq=2 ttl=64 time=0.045 ms
64 bytes from 192.168.1.2: icmp_seq=3 ttl=64 time=0.513 ms

--- 192.168.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 0.034/0.197/0.513/0.223 ms

这上面只是带给大家关于 veth 比较浅显的初步认知,如果有兴趣对虚拟网路中的 veth-pair 有深入的了解,建议大家看看这个文章:Linux 虚拟网络设备 veth-pair 详解

这篇文章详细讲解了 veth 两端之间数据的联通底层原理

除此之外,我相信你了解过 docker 的网络,一定也知道有 bridge 的网络模式,bridge 其实起的就是桥梁的作用,可以理解为路由器,负责中转,连接,路由所有连接在它上面的容器

安装 bridge 工具

sudo yum install -y bridge-utils

可以查看 docker 内网桥与各个容器之间连接的关系

$ brctl show
bridge name    bridge id        STP enabled    interfaces
docker0        8000.0242bfb37b66    no        vethc9f5f33
                                              vethfb9006b
# 发现这里有两个 veth 连着,这两个 veth 另一端连着的就是 docker 容器 test1 和 test2 # 在宿主机中打印所有的网卡 [vagrant@docker
-node2 ~]$ ip a 1: lo: 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: eth0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 52:54:00:4d:77:d3 brd ff:ff:ff:ff:ff:ff inet 10.0.2.15/24 brd 10.0.2.255 scope global noprefixroute dynamic eth0 valid_lft 71714sec preferred_lft 71714sec inet6 fe80::5054:ff:fe4d:77d3/64 scope link valid_lft forever preferred_lft forever 3: docker0: mtu 1500 qdisc noqueue state UP group default link/ether 02:42:bf:b3:7b:66 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:bfff:feb3:7b66/64 scope link valid_lft forever preferred_lft forever 4: eth1: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 08:00:27:33:4f:b9 brd ff:ff:ff:ff:ff:ff inet 192.168.205.11/24 brd 192.168.205.255 scope global noprefixroute eth1 valid_lft forever preferred_lft forever inet6 fe80::a00:27ff:fe33:4fb9/64 scope link valid_lft forever preferred_lft forever 8: vethc9f5f33@if7: mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 8e:12:0f:a0:7e:48 brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet6 fe80::8c12:fff:fea0:7e48/64 scope link valid_lft forever preferred_lft forever 10: vethfb9006b@if9: mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 16:4f:bc:53:ae:5d brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::144f:bcff:fe53:ae5d/64 scope link valid_lft forever preferred_lft forever

可以看到上面的网卡 8 和 10 正是连着 docker0 的网桥的 veth,因为 veth 都成对出现的,所以这两个 veth,名字为 vethc9f5f33 和 vethfb9006b 的 veth,另外一端是连着上面所创建的两个容器

在上面创建的第一个 name=test1 容器中,可以看到 id=9 的网卡设备:

 

这里连接的就是宿主机中 id=10 的 vethfb9006b。

 

在讲完容器和容器之间的网络连通的底层后,我们再来看看外部访问和 docker 容器之间是怎么进行数据交换的?在容器中请求外部网址,他是怎么做到的呢?当在容器内 ping www.baidu.com 的时候,数据是怎么交互的呢?

还是在上面的容器内,宿主机 host 的 ip 为 172.31.243.112,如下面所视

[root@izm5e37rlunev9ij58ixy9z ~]# ifconfig
docker0: flags=4163  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:2c:a8:7c:9d  txqueuelen 0  (Ethernet)
        RX packets 22  bytes 1362 (1.3 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 22  bytes 2099 (2.0 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth0: flags=4163  mtu 1500
        inet 172.31.243.112  netmask 255.255.240.0  broadcast 172.31.255.255
        ether 00:16:3e:05:96:e1  txqueuelen 1000  (Ethernet)
        RX packets 2247026  bytes 271820827 (259.2 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 5270453  bytes 378089658 (360.5 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1  (Local Loopback)
        RX packets 2098  bytes 104900 (102.4 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2098  bytes 104900 (102.4 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth05c8be8: flags=4163  mtu 1500
        ether c6:8c:35:49:68:69  txqueuelen 0  (Ethernet)
        RX packets 14  bytes 1048 (1.0 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 14  bytes 1334 (1.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

创建一个容器 busybox ,此容器位于 docker0 这个私有 bridge 网络中(172.17.0.0/16),当 busybox 从容器向外 ping 时,数据包是怎样到达 bing.com 的呢?这里的关键就是 NAT。我们查看一下 docker host 上的 iptables 规则:

docker run busybox sh -c "while true;do sleep 3600;done;"

查看此主机上的 iptables,因为所有容器和外部的网络交互,都是通过 NAT 来实现的

[root@izm5e37rlunev9ij58ixy9z ~]# iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN

注意一下这里

-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

其含义是:如果网桥 docker0 收到来自 172.17.0.0/16 网段的外出包,把它交给 MASQUERADE 处理,而 MASQUERADE 的处理方式是将包的源地址替换成 host 的地址发送出去,即做了一次网络地址转换(NAT);

下面我们通过 tcpdump 查看地址是如何转换的。先查看 docker host 的路由表:

[root@izm5e37rlunev9ij58ixy9z ~]# ip r
default via 172.31.255.253 dev eth0
169.254.0.0/16 dev eth0  scope link  metric 1002
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1
172.31.240.0/20 dev eth0  proto kernel  scope link  src 172.31.243.112

默认路由通过 enp0s3 发出去,所以我们要同时监控 eth0 和 docker0 上的 icmp(ping)数据包。

busybox ping baidu.com 时,

/ # ping -c 3 www.baidu.com
PING www.baidu.com (110.242.68.4): 56 data bytes
64 bytes from 110.242.68.4: seq=0 ttl=50 time=17.394 ms
64 bytes from 110.242.68.4: seq=1 ttl=50 time=17.433 ms
64 bytes from 110.242.68.4: seq=2 ttl=50 time=17.453 ms

这个时候在分别在宿主机对 docker0 和 eth0 抓包:

[root@izm5e37rlunev9ij58ixy9z ~]# sudo tcpdump -i docker0 -n icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on docker0, link-type EN10MB (Ethernet), capture size 65535 bytes
14:26:05.241364 IP 172.17.0.2 > 110.242.68.4: ICMP echo request, id 14, seq 0, length 64
14:26:05.258772 IP 110.242.68.4 > 172.17.0.2: ICMP echo reply, id 14, seq 0, length 64
14:26:06.241458 IP 172.17.0.2 > 110.242.68.4: ICMP echo request, id 14, seq 1, length 64
14:26:06.258835 IP 110.242.68.4 > 172.17.0.2: ICMP echo reply, id 14, seq 1, length 64
14:26:07.241578 IP 172.17.0.2 > 110.242.68.4: ICMP echo request, id 14, seq 2, length 64
14:26:07.258940 IP 110.242.68.4 > 172.17.0.2: ICMP echo reply, id 14, seq 2, length 64
[root@izm5e37rlunev9ij58ixy9z ~]# sudo tcpdump -i eth0 -n icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
14:33:00.015219 IP 172.31.243.112 > 110.242.68.4: ICMP echo request, id 15, seq 0, length 64
14:33:00.032516 IP 110.242.68.4 > 172.31.243.112: ICMP echo reply, id 15, seq 0, length 64
14:33:01.015332 IP 172.31.243.112 > 110.242.68.4: ICMP echo request, id 15, seq 1, length 64
14:33:01.032650 IP 110.242.68.4 > 172.31.243.112: ICMP echo reply, id 15, seq 1, length 64
14:33:02.015433 IP 172.31.243.112 > 110.242.68.4: ICMP echo request, id 15, seq 2, length 64
14:33:02.032787 IP 110.242.68.4 > 172.31.243.112: ICMP echo reply, id 15, seq 2, length 64

docker0 收到 busybox 的 ping 包,源地址为容器 IP 172.17.0.2,然后交给 MASQUERADE 处理。这个规则就是上面我们通过 iptables 查到的 -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

ping 包的源地址变成了 eth0 的 IP 172.31.243.112,也就是宿主机 host ip,这就是 iptable NAT 规则处理的结果,从而保证数据包能够到达外网。

来总结一下整个的过程:

  • busybox 发送 ping 包:172.17.0.2 >  www.baidu.com;
  • docker0 收到包,发现是发送到外网的,交给 NAT 处理;
  • NAT 将源地址换成 enp0s3 的 IP:172.31.243.112 > www.baidu.com;
  • ping 包从 enp0s3 发送出去,到达 www.baidu.com;

即通过 NAT,docker 实现了容器对外网的访问;

那么外部网络如何访问到容器?答案是:端口映射

docker 可将容器对外提供服务的端口映射到 host 的某个端口,外网通过该端口访问容器。容器启动时通过-p参数映射端口:

[root@izm5e37rlunev9ij58ixy9z ~]# docker run --name nginx-test -p 8080:80 -d nginx

查看映射:

[root@izm5e37rlunev9ij58ixy9z ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
9a7edd9e4133        nginx               "/docker-entrypoint.…"   4 seconds ago       Up 3 seconds        0.0.0.0:8080->80/tcp   nginx-test

容器启动后,可通过 docker ps 或者 docker port 查看到 host 映射的端口。可以看到,httpd 容器的 80 端口被映射到 host 8080 上,这样就可以通过 :<8080 > 访问容器的 web 服务了;

[root@izm5e37rlunev9ij58ixy9z ~]# curl 172.31.243.112:8080







Welcome to nginx!

If you see this page, the nginx web server is successfully installed and working. Further configuration is required.

For online documentation and support please refer to "http://nginx.org/">nginx.org.
Commercial support is available at "http://nginx.com/">nginx.com.

Thank you for using nginx.

每一个映射的端口,host 都会启动一个 docker-proxy 进程来处理访问容器的流量:

[root@izm5e37rlunev9ij58ixy9z ~]# ps -ef | grep docker-proxy
root     29833  6912  0 15:15 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.17.0.3 -container-port 80

具体的流程为

  • docker-proxy 监听 host 的 8080 端口

  • 当 curl 访问 172.31.243.112:8080 时,docker-proxy 转发给容器 172.31.243.112:80。

  • httpd 容器响应请求并返回结果

如图是 nat 和外部网络交互的示意图:

至于在多机通信过程中,docker 是怎么组织跨主机的统一网络的呢?这里就简单介绍一下 overlay 网络,底层使用的是 vxlan 协议。

VXLAN(Virtual Extensible Local Area Network,虚拟可扩展局域网),通过将物理服务器或虚拟机发出的数据包封装到UDP中,并使用物理网络的IP/MAC作为外层报文头进行封装,然后在IP网络上传输,到达目的地后由隧道端点解封装并将数据发送给目标物理服务器或虚拟机,扩展了大规模虚拟机网络通信。由于VLAN Header头部限制长度是12bit,导致只能分配4095个VLAN,也就是4095个网段,在大规模虚拟网络。VXLAN标准定义Header限制长度24bit,可以支持1600万个VLAN,满足大规模虚拟机网络需求。

在不同的跨主机网络中,想让分布在不同主机的容器能够在统一的内网中互相访问互通,那么首先第一是集群中所有主机必须是可以互联并且是可以感知的,第二是所有容器的内网ip必定是和当前的宿主ip所绑定。这些信息是维护在 etcd 存储中的。当某一个容器内需要跨机器去访问另一机器上的容器时,会第一时间在本地宿主机内查找对应的宿主机器,这一部分是 iptables 进行维护,查找到对应的 host 后直接进行 vxlan 的协议封装,让其变成普通的网络包可以在外部网络中进行传输。最后在目标宿主机上接受数据包后,发现是 vxlan 协议,会交由 docker 进行解包的操作,当把 vxlan 协议剥离后,内部的真正的容器数据包会被发送给目标容器。

关于docker中跨主机通信怎么维护一个统一的二层和三层网络,我建议读一下这一篇文章,会比较好理解:二层网络三层网络理解

 

在实际工程中,跨主机跨集群的网络是非常复杂的,具体看 k8s 中层出不穷的网络插件就可以了解到,建议看看相关的网络插件实现原理,比如 weave,calico,flannel 这些,具体可以看看这一篇官网文档:https://feisky.gitbooks.io/kubernetes/content/network/network.html 


推荐阅读
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 本文讨论了微软的STL容器类是否线程安全。根据MSDN的回答,STL容器类包括vector、deque、list、queue、stack、priority_queue、valarray、map、hash_map、multimap、hash_multimap、set、hash_set、multiset、hash_multiset、basic_string和bitset。对于单个对象来说,多个线程同时读取是安全的。但如果一个线程正在写入一个对象,那么所有的读写操作都需要进行同步。 ... [详细]
  • 如何使用PLEX播放组播、抓取信号源以及设置路由器
    本文介绍了如何使用PLEX播放组播、抓取信号源以及设置路由器。通过使用xTeve软件和M3U源,用户可以在PLEX上实现直播功能,并且可以自动匹配EPG信息和定时录制节目。同时,本文还提供了从华为itv盒子提取组播地址的方法以及如何在ASUS固件路由器上设置IPTV。在使用PLEX之前,建议先使用VLC测试是否可以正常播放UDPXY转发的iptv流。最后,本文还介绍了docker版xTeve的设置方法。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了如何找到并终止在8080端口上运行的进程的方法,通过使用终端命令lsof -i :8080可以获取在该端口上运行的所有进程的输出,并使用kill命令终止指定进程的运行。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • 本文介绍了在Docker容器技术中限制容器对CPU的使用的方法,包括使用-c参数设置容器的内存限额,以及通过设置工作线程数量来充分利用CPU资源。同时,还介绍了容器权重分配的情况,以及如何通过top命令查看容器在CPU资源紧张情况下的使用情况。 ... [详细]
  • 开发笔记:Docker 上安装启动 MySQL
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Docker上安装启动MySQL相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文总结了在编写JS代码时,不同浏览器间的兼容性差异,并提供了相应的解决方法。其中包括阻止默认事件的代码示例和猎取兄弟节点的函数。这些方法可以帮助开发者在不同浏览器上实现一致的功能。 ... [详细]
author-avatar
杨胤才_669
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有