Docker网络详解
Docker 网络理论
容器网络实质上是由 Dokcer 为应用程序所创造的虚拟环境的一部分,它能让应用从宿主机操作系统的网络环境中独立出来,形成容器自有的网络设备、IP 协议栈、端口套接字、IP 路由表、防火墙等等与网络相关的模块。
docker使用linux桥接,在宿主机虚拟一个docker容器网桥(docker0)
docker启动一个容器时会根据docker网桥的网段分配给容器一个IP地址,称为Container-IP
同时Docker网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能通过容器的Container-IP直接通信。
docker网桥是宿主机虚拟出来的,并不是真实存在的网络设备,外部网络是无法寻址到的,这也意味着外部网络无法直接通过Container-IP访问到容器。如果容器希望外部访问能够访问到,可以通过映射容器端口到宿主机(端口映射),即docker run 创建容器的时候,通过-p或者-P参数来启用。访问容器的时候,就通过【宿主机IP】:【容器端口】访问容器。
Docker 为实现容器网络,主要采用的架构由三部分组成:CNM、Libnetwork 和驱动。
1.1. CNM
Docker 网络架构采用的设计规范是 CNM(Container Network Model):CNM 中规定了 Docker 网络的基础组成要素:Sandbox、Endpoint、Network。如图所示,
- Sandbox,提供了容器的虚拟网络栈,也即端口套接字、IP 路由表、防火墙、DNS 配置等内容。主要用于隔离容器网络与宿主机网络,形成了完全独立的容器网络环境。
- Network,Docker 内部的虚拟子网,网络内的参与者相互可见并能够进行通讯。Docker 的虚拟网路和宿主机网络是存在隔离关系的,其目的主要是形成容器间的安全通讯环境。
- Endpoint,就是虚拟网络的接口,就像普通网络接口一样,Endpoint 的主要职责是负责创建连接。在 CNM 中,终端负责将沙盒连接到网络。个人理解:Endpoint 与常见的网络适配器类似,也就意味着 Endpoint 只能接入某一个网络。因此,如果容器需要接入到多个网络,就需要多个 Endpoint。
★
如上图所示(我们将图中的三个容器从左到右依次标记为 1、2、3),那么容器 2 有两个 endpoint 并且分别接入 NetworkdA 和 NetworkB。那么容器 1 和容器 2 是可以实现通信的,因为都接入了 NetworkA。但是容器 3 和容器 1,以及容器 2 的两个 Endpoint 之间是不能通信的,除非有三层路由器的支持。
”
1.2. Libnetwork
Libnetwork 是 CNM 的标准实现。Libnetwork 是开源库,采用 Go 语言编写(跨平台的),也是 Docker 所使用的库,Docker 网络架构的核心代码都在这个库中。Libnetwork 实现了 CNM 中定义的全部三个组件,此外它还实现了本地服务发现、基于 Ingress 的容器负载均衡,以及网络控制层和管理层功能。
1.3. 驱动
如果说 Libnetwork 实现了控制层和管理层功能,那么驱动就负责实现数据层。比如网络的连通性和隔离性是由驱动来处理的。驱动通过实现特定网络类型的方式扩展了 Docker 网络栈,例如桥接网络和覆盖网络。
Docker 内置了若干驱动,通常被称作原生驱动或者本地驱动。比如 Bridge Driver、Host Driver、Overlay Driver、MacLan Driver、None Driver 等等。第三方也可以编写 Docker 网络驱动,这些驱动被叫做远程驱动,例如 Calico、Contiv、Kuryr 以及 Weave 等。每个驱动负责创建其上所有网络资源的创建和管理。
其中 Bridge 和 Overlay 在开发过程中使用频率较高。
- Bridge,Docker 容器的默认网络驱动,通过网桥来实现网络通讯。
- Overlay,借助 Docker 集群模块 Docker Swarm 搭建的跨 Docker Daemon 网络。通过它可以搭建跨物理网络主机的虚拟网络,进而让不同物理机中运行的容器感知不到多个物理机的存在。
在 Docker 安装时,会自动安装一块 Docker 网卡称为 docker0,用于 Docker 各容器及宿主机的网络通信。
1 2 3 4 5 6 7 8 | docker0 Link encap:Ethernet HWaddr 02:42:be:6b:61:dc inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0 inet6 addr: fe80::42:beff:fe6b:61dc/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:332 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:30787 (30.7 KB) |
个人理解:CNM 就是一个设计文档,指导你怎么去实现容器网络,而 Libnetwork 和驱动则是其具体实现,从而确保容器网络的通信。
桥接网络
Docker 的 bridge 网络采用内置的 bridge 驱动,而 bridge 的底层采用的是 Linux 内核中 Linux bridge 技术(这意味着 bridge 是高性能并且是非常稳定的)。
那么 Linux 内核中 Linux bridge 应用于容器的话,到底是一个什么样的拓扑图呢?如图所示(这个拓扑关系不清楚接下去的很多东西难以理解,所以先贴出采用 bridge 之后的一个拓扑图),由于容器运行在自己单独的 network namespace 中,所以有单独的协议栈。容器中配置网关为 172.17.0.1,发出去的数据包先到达 br0,然后交给主机的协议栈,由于目的 IP 是外网 IP,且主机会开启 IP forward 功能,于是数据包通过主机的 eth0 发出去。由于 172.17.0.1 是内网 IP ,所以一般发出去之前会做 NAT 转换。由于要进过主机的协议栈并且要做 NAT 转换,所以性能上可能会差点,但是优点就是容器处于内网中,安全性相对要高点。
默认情况下,创建的容器在没有使用 --network 参数指定要加入的 docker 网络时,默认都是加入 Docker 默认的单机桥接网络,也就是下面的 name 为 bridge 的网络。
1 2 3 | $ docker network ls NETWORK ID NAME DRIVER SCOPE 0dda6f303b8b bridge bridge local |
而默认的 bridge 网络是被映射到内核中为 docker0 的网桥上。
1 2 3 4 5 6 | $ ip link show docker0 4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default link/ether 02:**:**:**:**:** brd ff:ff:ff:ff:ff:ff $ docker network inspect bridge | grep bridge.name "com.docker.network.bridge.name": "docker0", |
Docker 默认的 bridge 网络和 Linux 内核中的 “docker0” 网桥是一个对应关系,如图所示。bridge 是 Docker 中对网络的命名,而 docker0 是内核中网桥的名字。(个人理解:你就可以把 bridge 和 docker0 当成 Linux 网桥的两个名字,两个都是代表同一个东西。docker 为了管理网络,又给 docker0 这个网桥取名为 bridge)。
那么,容器在没有指定要加入的网络情况下,都是加入这个网络的,假如之后的拓扑图跟前面的一样。另外,单机桥接网络中的容器想要对外发布服务的话,需要依赖于端口映射,这也是为啥我们在启动容器的时候需要指定端口映射关系 的原因。
下面我们通过创建一个新的 Docker 桥接网络来阐述容器内部的通信、端口映射等情况。
2.1. 创建新的单机桥接网络
使用 docker network create
命令,我们可创建一个名为 “localnet” 的单机桥接网络,并且在内核中还会多出一个新的 Linux 网桥。
1 2 3 4 5 6 | $ docker network create -d bridge localnet $ docker network ls NETWORK ID NAME DRIVER SCOPE ...... f55943e20201 localnet bridge local |
在创建完之后,我们可以通过 brctl 工具来查看系统中的 Linux 网桥。可以看到,输出的内容中包含了两个网桥,docker0 是默认的 Docker bridge 网络所使用的网桥,br-f55943e20201 是 Docker localnet 网络所使用的网桥。这两个网桥目前都没有任何设备接入(看 interface 列)。这两个网桥所处的网段是不同的,一个是 172.18.0.1,另一个则是 172.17.0.1。
1 2 3 4 5 6 7 8 9 10 11 12 | $ brctl show bridge name bridge id STP enabled interfaces br-f55943e20201 8000.02421d9aa3e1 no docker0 8000.0242be6b61dc no $ ifconfig br-f55943e20201 Link encap:Ethernet HWaddr 02:42:1d:9a:a3:e1 inet addr:172.18.0.1 Bcast:172.18.255.255 Mask:255.255.0.0 ...... docker0 Link encap:Ethernet HWaddr 02:42:be:6b:61:dc inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0 ...... |
2.2. 同个网络中的容器间通信
使用下面这条命令即可运行一个新的容器,并且让这个新容器加入到 localnet 这个网络中的。
1 | $ docker container run -d --name demo1 --network localnet alpine sleep 3600 |
我们查看网桥的情况,demo1 的网络接口连接到了网桥 br-f55943e20201 上,如图所示。
1 2 3 4 | $ brctl show bridge name bridge id STP enabled interfaces br-f55943e20201 8000.02421d9aa3e1 no vethf6a3fba docker0 8000.0242be6b61dc no |
如果在相同的网络中继续接入新的容器,那么新接入的容器是可以通过 demo1 这个名称来 ping 通的。如下所示,我们创建了一个新的容器(demo2),并且在这个容器中直接 ping demo1 发现可以的 ping 通的。这是因为,demo2 运行了一个本地 DNS 解析器,该解析器会将该请求转发到 Docker 内部 DNS 服务器中。DNS 服务器中记录了容器启动时通过 --name 或者 --net-alias 参数指定的名称和容器之间的和映射关系。
之外,我们可以看到 demo1 的 IP 地址是 172.18.0.2,这个与网桥 br-f55943e20201 是处于同一个网段内的。
1 2 3 4 5 6 | / # ls bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var / # ping demo1 PING demo1 (172.18.0.2): 56 data bytes 64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.230 ms 64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.161 ms |
★
Docker 默认的 bridge 网络是不支持通过 Docker DNS 服务进行域名解析的,自定义桥接网络是可以的。
2.3. 暴露端口
同一个网络中的容器之间虽然可以互相 ping 通,但是并不意味着可以任意访问容器中的任何服务。Docker 为容器增加了一套安全机制,只有容器自身允许的端口,才能被其他容器所访问。如下所示,我们可以通过 docker container ls
命令可以看到容器暴露给其他容器访问的端口是 80,那么我们只能容器的 80 端口进行访问,而不能对没有开放的 22 端口进行访问。
1 2 3 4 5 6 7 8 9 10 11 12 | $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5a8dece3841d nginx "/docker-entrypoint.…" 3 minutes ago Up 3 minutes 80/tcp web $ telnet 172.18.0.2 80 Trying 172.18.0.2... Connected to 172.18.0.2. Escape character is '^]'. $ telnet 172.18.0.2 20 Trying 172.18.0.2... telnet: Unable to connect to remote host: Connection refused |
我们可以在镜像创建的时候定义要暴露的端口,也可以在容器创建时定义要暴露的端口,使用 --expose。如下所示,就额外暴露了 20、22 这两个端口。
1 2 3 4 5 | $ docker container run -d --name web --expose 22 --expose 20 nginx $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4749dac32711 nginx "/docker-entrypoint.…" 12 seconds ago Up 10 seconds 20/tcp, 22/tcp, 80/tcp web |
容器的端口暴露类似于打开了容器的防火墙,具体能不能通过这个端口访问容器中的服务,还得看容器中有无应用监听并处理来自这个端口的请求。
2.4. 端口映射
上面提到的桥接网络中的容器只能与位于相同网络中的容器进行通信,假如一个容器想对外提供服务的话,需要进行端口映射。端口映射将容器的某个端口映射到 Docker 主机端口上。那么任何发送到该端口的流量,都会被转发到容器中。如图所示,容器内部开放端口为 80,该端口被映射到了 Docker 主机的 10.0.0.15 的 5000 端口上。最终访问 10.0.0.15:5000 的所有流量都会被转发到容器的 80 端口。
如下图所示,假设我们运行了一个新的 web 服务容器,并且将容器 80 端口映射到 Dokcer 主机的 5000 端口。
1 | $ docker container run -d --name web --network localnet -p 5000:80 nginx |
那么,当我们通过 web 浏览器访问 Docker 主机的 5000 端口时,会得到如图所示的结果。外部系统可以通过访问 Docker 主机的 TCP 端口 5000,来访问运行在桥接网络上的 Nginx 容器了。
端口映射之后,假如主机的 5000 端口被占用了,那么其他容器就不能再使用这个端口了。
Docker的网络模式
Host: 容器不会虚拟出自己的网卡,配置主机的IP等,而是使用宿主机的IP和端口
Container: 创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口的范围。
None: 该模式关闭了容器的网络功能。
Bridge: 默认为该模式,桥接,此模式会为每一个容器分配,设置IP等,并将容器连接到一个docker0的虚拟网桥,通过docker0 网桥以及iptables nat表配置与宿主机通信
自定义网络 :自己创建一个虚拟网桥,可以任意分配容器的ip地址。
1、Host模式
① host模式:使用–net=host指定
② 相当于VMware中的桥接模式,与宿主机在同一个网络中,但是没有独立IP地址
③ Docker 使用了Linux 的Namespace 技术来进行资源隔离,如PID Namespace隔离进程,Mount Namespace隔离文件系统,Network Namespace 隔离网络等。
④ 一个Network Namespace 提供了一份独立的网络环境,包括网卡,路由,iptable 规则等都与其他Network Namespace 隔离。
⑤ 一个Docker 容器一般会分配一个独立的Network Namespace
但是如果启动容器的时候使用host 模式,那么这个容器将不会获得一个独立的Network Namespace ,而是和宿主机共用一个Network Namespace 。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口.此时容器不再拥有隔离的、独立的网络栈,不拥有所有端口资源。
#创建容器web 1,指定网络模式为 host
#因为是host模式,所有宿主机和容器共享ip和端口
docker run -d --name web1 --net=host nginx
#访问宿主机的ip和80端口,则可以访问到web3的nginx服务
2、Container模式
① container模式: 使用–net=contatiner:NAME_or_ID 指定
② 这个模式指定新创建的容器和已经存在的一个容器共享一个Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP,端口范围等。可以在一定程度上节省网络资源,容器内部依然不会拥有所有端口。
③ 同样,两个容器除了网络方面,其他的如文件系统,进程列表等还是隔离的。
④ 两个容器的进程可以通过lo网卡设备通信本人提供Oracle(OCP、OCM)、MySQL(OCP)、PostgreSQL(PGCA、PGCE、PGCM)等数据库的培训和考证业务,私聊QQ646634621或微信db_bao,谢谢!
#基于镜像centos:7 创建一个名为test1的容器
docker run -itd --name test1 centos:7 /bin/bash
#查看容器的pid号
docker inspect -f '{{.State.Pid}}' test1
#查看该容器的命名空间编号
ls -l /proc/容器pid号/ns
#创建test2容器,使用container网络模式,和test1共享network Namespace
docker run -itd --name test2 --net=container:test1 centos:7 /bin/bash
#查看test2容器的pid
docker inspect -f '{{.State.Pid}}' test2
#查看该容器的命名空间编号
ls -l /proc/容器pid号/ns
3、none模式
① none模式:使用 --net=none指定
② 使用none 模式,docker 容器有自己的network Namespace ,但是并不为Docker 容器进行任何网络配置。也就是说,这个Docker 容器没有网卡,ip, 路由等信息。
③ 这种网络模式下,容器只有lo 回环网络,没有其他网卡。
④ 这种类型没有办法联网,但是封闭的网络能很好的保证容器的安全性
⑤ 该容器将完全独立于网络,用户可以根据需要为容器添加网卡。此模式拥有所有端口。(none网络模式配置网络)
⑥ 特殊情况下才会用到,一般不用
4、bridge模式
bridge模式是docker的默认网络模式,不写 – net参数,就是bridge模式。
相当于Vmware中的 nat 模式,容器使用独立network Namespace,并连接到docker0虚拟网卡。通过docker0网桥以及iptables nat表配置与宿主机通信,此模式会为每一个容器分配Network Namespace、设置IP等,并将一个主机上的 Docker 容器连接到一个虚拟网桥上。
① 当Docker进程启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。
② 从docker0子网中分配一个IP给容器使用,并设置docker0的IP地址为容器的默认网关。在主机上创建一对虚拟网卡veth pair设备。veth设备总是成对出现的,它们组成了一个数据的通道,数据从一个设备进入,就会从另一个设备出来。因此,veth设备常用来连接两个网络设备。
③ Docker将veth pair 设备的一端放在新创建的容器中,并命名为eth0(容器的网卡),另一端放在主机中,以veth*这样类似的名字命名,并将这个网络设备加入到docker0网桥中。可以通过 brctl show 命令查看。
④ 容器之间通过veth pair进行访问
⑤ 使用 docker run -p 时,docker实际是在iptables做了DNAT规则,实现端口转发功能。可以使用iptables
-t nat -vnL 查看。
docker run -itd --name test3 centos:7 /bin/bash
docker inspect test3 | grep -i 'networkmode'
"NetworkMode": "default",
5、overlay模式
overlay网路模式是一种叠加式的网络模式。
- 使用外部的服务组件作为网关和代理,例如ingress。
- 一般和docker-swarm结合
自定义网络
1、查看网络模式列表
docker network ls
2、查看容器信息(包含配置、环境、网关、挂载、cmd等等信息)
docker inspect 容器ID
3、指定分配容器IP地址
docker run -itd --name test4 --network bridge --ip 172.17.0.10 centos:7 /bin/bash
#以上会报错,因为用户使用的ip地址不被规则所允许,docker0定义的就是按照顺序来,所有需要创建一个
docker network create --subnet=172.18.0.0/16 wxg
3.1 删除 docker网络
docker network ls
#查看所有网卡和他的iddocker network rm 【网卡ID】
#删除某个指定的网卡
4、 自定义网络固定IP
可以先自定义网络,再使用指定IP运行docker
docker network create --subnet=172.18.0.0/16 xg
docker network ls
docker network 【--network bridge】 create --subnet=172.18.0.0/16 lcdb
docker run -itd --name test4 --net lcdb --ip 172.18.0.8 centos:7 /bin/bash
docker inspect test4 | grep IPAdress
为容器创建端口映射
随机映射端口(从32768开始)docker run -d --name 为容器指定名称 -P 镜像
指定映射端口docker run -d --name 为容器指定名称 -p 宿主机端口:容器内端口 镜像
使用nginx镜像创建容器,名称为web1 ,随机映射端口
docker run -d --name web1 -P nginx:latest
docker ps -a
#使用nginx镜像创建容器,名称为web2,将容器内的80端口映射到宿主机的12121端口
docker run -d --name web2 -p 12121:80 nginx:latest
docker ps -a
访问
curl http://192.168.100.20:49154
curl http://192.168.100.20:12121
主机查看端口号
netstat -natp|grep docker
实际上是通过nat表进行转发的
iptables -nL -t nat
相关命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | # 列出运行在本地 docker 主机上的全部网络 docker network ls # 提供 Docker 网络的详细配置信息 docker network inspect <NETWORK_NAME> # 创建新的单机桥接网络,名为 localnet,其中 -d 不指定的话,默认是 bridge 驱动。并且主机内核中也会创建一个新的网桥。 docker network create -d bridge localnet # 删除 Docker 主机上指定的网络 docker network rm # 删除主机上全部未使用的网络 docker network prune # 运行一个新的容器,并且让这个容器加入 Docker 的 localnet 这个网络中 docker container run -d --name demo1 --network localnet alpine sleep 3600 # 运行一个新的容器,并且让这个容器暴露 22、20 两个端口 docker container run -d --name web --expose 22 --expose 20 nginx # 运行一个新的容器,并且将这个容器的 80 端口映射到主机的 5000 端口 docker container run -d --name web --network localnet -p 5000:80 nginx # 查看系统中的网桥 brctl show |
参考
https://mp.weixin.qq.com/s/T1vtKwllafEE68_X4AuRqA
https://zhuanlan.zhihu.com/p/57203485
https://segmentfault.com/a/1190000009491002
http://blog.nsfocus.net/linux-bridge/
https://blog.csdn.net/weixin_71483812/article/details/127388903