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.2 和 172.17.0.3:
在 docker 中,不同的容器之间网络连通也是使用了 linux 的命名空间,用一个小实验来说明这里所用的原理其实就是使用了 veth 来实现命名空间的互联
实验步骤分为以下几个步骤:
- 创建端口
- 产生网线
- 分配ip
在 docker 中网络的分配也是根据这三个步骤来生成容器 ip 的,首先我们先产生两个网络虚拟命名空间
# 产生命名空间 test1, test2
$ sudo ip netns add test1
$ sudo ip netns add test2
产生一对 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: <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: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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<UP,BROADCAST,RUNNING,MULTICAST> 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<UP,BROADCAST,RUNNING,MULTICAST> 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<UP,LOOPBACK,RUNNING> 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<UP,BROADCAST,RUNNING,MULTICAST> 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 上,这样就可以通过 <host ip>:<
8080 >
访问容器的 web 服务了;
[root@izm5e37rlunev9ij58ixy9z ~]# curl 172.31.243.112:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p> <p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p>
</body>
</html>
每一个映射的端口,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
Docker容器基础入门认知-网络篇的更多相关文章
- docker容器基础
一.docker容器基础6种名称空间:UTS.MOunt.IPC.PID.User.Net (1) Linux Namespaces:namespace 系统调用参数 隔离内容 内核版本 UTS ...
- Linux基础入门之网络属性配置
Linux基础入门之网络属性配置 摘要 Linux网络属性配置,最根本的就是ip和子网掩码(netmask),子网掩码是用来让本地主机来判断通信目标是否是本地网络内主机的,从而采取不同的通信机制. L ...
- 使用pipework将Docker容器配置到本地网络环境中
使用pipework将Docker容器配置到本地网络环境中 需求 在使用Docker的过程中,有时候我们会有将Docker容器配置到和主机同一网段的需求.要实现这个需求,我们只要将Docker容器和主 ...
- Docker容器基础认知
## 一. [docker](http://www.itxdm.me/archives/tag/docker/)概念 [Docker](http://www.itxdm.me/wp-content/p ...
- 使用pipework将Docker容器桥接到本地网络环境中
在使用Docker的过程中,有时候我们会有将Docker容器配置到和主机同一网段的需求.要实现这个需求,我们只要将Docker容器和主机的网卡桥接起来,再给Docker容器配上IP就可以了.pipew ...
- Docker容器基础知识学习
Docker作为操作系统层面的轻量级的虚拟化技术,凭借简易的使用.快速的部署以及灵活敏捷的集成等优势,迅速发展目前最为火热的技术. 1.云计算服务是一种资源管理的资源服务,该模式可以实现随时随地.便捷 ...
- Kubernetes 普及系列:容器基础入门
随着云原生时代的来临,云以及分布式计算已经是时下最受欢迎的技术之一了.其中 Docker 作为最知名的容器平台,到底有着怎样的魅力来让其无人不知无人不晓?废话不多说,让我们开始逐层掀开容器技术的神秘面 ...
- Linux从入门到放弃、零基础入门Linux(第二篇):在虚拟机vmware中安装linux(一)超详细手把手教你安装centos分步图解
一.Vmware vmware介绍:VMware,Inc. (Virtual Machine ware)是一个“虚拟PC”软件公司,提供服务器.桌面虚拟化的解决方案.其虚拟化平台的产品包括播放器:它能 ...
- Linux从入门到放弃、零基础入门Linux(第一篇):计算机操作系统简介、linux介绍
一.计算机操作系统简介 操作系统的定义: 操作系统是一个用来协调.管理和控制计算机硬件和软件资源的系统程序,它位于硬件和应用程序之间. 操作系统的内核的定义: 操作系统的内核是一个管理和控制程序,负责 ...
随机推荐
- SPOJ16636 Journey IE2
SPOJ16636 Journey IE2 更好的阅读体验 在Byteland有n个城市,编号从1到n.这些城市由m条双向道路网络连接.众所周知,每一对城市最多只能由一条道路连接. Byteman最近 ...
- Java(20)参数传递之类名、抽象类、接口
作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201632.html 博客主页:https://www.cnblogs.com/testero ...
- scala基础篇 使用getter和setter方法而不使用public的情形
主要是基于2种情形 1) 提供读只取/只写入方法,不能随意读写 2)做赋值时变量控制,比如设定值的区间范围等 例子: object test{ def main(args: Array[String] ...
- TypeError: Restaurant() takes no arguments
1. 错误描述 TypeError: Restaurant() takes no arguments 2. 原因:在编写__init__时,pycharm会自动添加关键字,有时会直接写称整型int, ...
- LeetCode:“剑指 Offer”
LeetCode:"剑指 Offer" 刷题小菜鸡,花了几天时间做了一遍 LeetCode 上给出的 "剑指 Offer" 在此做一下记录 LeetCode主页 ...
- Noip模拟35 2021.8.10
考试题目变成四道了,貌似确实根本改不完... 不过给了两个小时颓废时间确实很爽(芜湖--) 但是前几天三道题改着不是很费劲的时候为什么不给放松时间, 非要在改不完题的时候颓?? 算了算了不碎碎念了.. ...
- python中yield的理解
首先我要吐槽一下,看程序的过程中遇见了yield这个关键字,然后百度的时候,发现没有一个能简单的让我懂的,讲起来真TM的都是头头是道,什么参数,什么传递的,还口口声声说自己的教程是最简单的,最浅显易懂 ...
- python pip whl安装和使用
转载:https://www.cnblogs.com/klb561/p/9271322.html 1 python的安装 首先,从python的官方网站 www.python.org下载需要的pyth ...
- Luogu P3758 [TJOI2017]可乐 | 矩阵乘法
题目链接 让我们先来思考一个问题,在一张包含$n$个点的图上,如何求走两步后从任意一点$i$到任意一点$j$的方案数. 我们用$F_p(i,j)$来表示走$p$步后从$i$到$j$的方案数,如果存储原 ...
- lvs 四层负载相关
都打开 /etc/sysctl.conf 中的 net.ip4.ip_forward=1.开启路由转发功能. 分发器 : eth0:192.168.1.66 (VIP) eth1:192.168.2. ...