12、docker网络
1. Docker 网络理论
容器网络实质上是由 Dokcer 为应用程序所创造的虚拟环境的一部分,它能让应用从宿主机操作系统的网络环境中独立出来,形成容器自有的网络设备、IP 协议栈、端口套接字、IP 路由表、防火墙等等与网络相关的模块。
Docker 为实现容器网络,主要采用的架构由三部分组成:CNM、Libnetwork 和驱动。
1.1. CNM
- 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 各容器及宿主机的网络通信。
2. 桥接网络(docker0 docker网桥)
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 的网络。(bridge 模式)
[root@VM-8-9-centos ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
686230481b0b bridge bridge local
21ed29c01538 host host local
cc88e8f2cdd9 none null local
而默认的 bridge 网络是被映射到内核中为 docker0 的网桥上。
docker network inspect bridge
Docker 默认的 bridge 网络和 Linux 内核中的 “docker0” 网桥是一个对应关系,如图所示。bridge 是 Docker 中对网络的命名,而 docker0 是内核中网桥的名字。(个人理解:你就可以把 bridge 和 docker0 当成 Linux 网桥的两个名字,两个都是代表同一个东西。docker 为了管理网络,又给 docker0 这个网桥取名为 bridge)。
bridge是docker 的网络模式,docker0是docker安装时,虚拟化出来的网桥
那么,容器在没有指定要加入的网络情况下,都是加入这个网络的,假如之后的拓扑图跟前面的一样。另外,单机桥接网络中的容器想要对外发布服务的话,需要依赖于端口映射,这也是为啥我们在启动容器的时候需要指定端口映射关系 的原因。
下面我们通过创建一个新的 Docker 桥接网络来阐述容器内部的通信、端口映射等情况。
[root@VM-8-9-centos ~]# docker network create -d bridge localnet # 创建一个新的网络
a516a89475f6d264a0771228794337834421869b686b633aea687a9159f468a8
[root@VM-8-9-centos ~]# docker network ls 查看docker的网络信息
NETWORK ID NAME DRIVER SCOPE
686230481b0b bridge bridge local
21ed29c01538 host host local
f55943e20201 localnet bridge local
cc88e8f2cdd9 none null local
通过 ifconfig 可以看到 一一个新的网络/网桥 docker0 为docker创建时,产生的网桥,docker0网桥目前没有任何容器连接
$ 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 这个网络中的。
# docker run -d -p 9999:8080 --name demo1 --network localnet tomcat
--network 指定新运行的容器加入指定的网桥
注意:如果没有指定 --network 默认接入docker0网桥
我们查看网桥的情况,demo1 的网络接口连接到了网桥 br-f55943e20201 上,如图所示。
# brctl show
bridge name bridge id STP enabled interfaces
br-f55943e20201 8000.02421d9aa3e1 no vethf6a3fba
docker0 8000.0242be6b61dc no
此时,一个新的容器,已经正式接入了网桥(也即我们通过docker network create创建的这个网络),并且分配给了容器一个ip,通过vethpart 实现互联
如果在相同的网络中继续接入新的容器,那么新接入的容器是可以通过 demo1 这个名称来 ping 通的。如下所示,我们创建了一个新的容器(demo2),并且在这个容器中直接 ping demo1 发现可以的 ping 通的。这是因为,demo2 运行了一个本地 DNS 解析器,该解析器会将该请求转发到 Docker 内部 DNS 服务器中。DNS 服务器中记录了容器启动时通过 --name 或者 --net-alias 参数指定的名称和容器之间的和映射关系。
为什么,可以ping通,因为处于同一网段
2.3. 暴露端口
同一个网络中的容器之间虽然可以互相 ping 通,但是并不意味着可以任意访问容器中的任何服务。Docker 为容器增加了一套安全机制,只有容器自身允许的端口,才能被其他容器所访问。如下所示,我们可以通过 docker ps
命令可以看到容器暴露给其他容器访问的端口是 80,那么我们只能容器的 80 端口进行访问,而不能对没有开放的 22 端口进行访问。
2.3.1 telnet安装
yum list | grep telnet*
yum -y install telnet-server.x86_64
yum -y install telnet.x86_64
2.3.2 启动容器,默认接入docker0网桥
docker run -d --name nginx01 nginx # 默认80端口
2.3.3 查看网络情况,和网桥
[root@VM-8-9-centos ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
686230481b0b bridge bridge local
21ed29c01538 host host local
a516a89475f6 localnet bridge local
cc88e8f2cdd9 none null local
[root@VM-8-9-centos ~]# brctl show
bridge name bridge id STP enabled interfaces
br-a516a89475f6 8000.02420cf9fa2e no
docker0 8000.0242f503119d no veth9e0765d
可以看到docker0网桥,已经被连接
2.3.4 查看容器分配的ip地址 docker inspect container
docker inspect 9c6e0ff1111a
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "686230481b0b69d79e62859cc2fae73635384782595b694e449de0be492c42a9",
"EndpointID": "e5b31ab99546b6f27ebdd1b1a93e2ae7c6f8dbec972a86fe09bd301fc4f2fa72",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02",
"DriverOpts": null
}
}
2.3.5 测试
[root@VM-8-9-centos ~]# telnet 172.17.0.2 80
Trying 172.17.0.2...
Connected to 172.17.0.2.
Escape character is '^]'.
^CConnection closed by foreign host.
[root@VM-8-9-centos ~]# telnet 172.17.0.2 20
Trying 172.17.0.2...
telnet: connect to address 172.17.0.2: Connection refused
我们可以在镜像创建的时候定义要暴露的端口,也可以在容器创建时定义要暴露的端口,使用 --expose。如下所示,就额外暴露了 20、22 这两个端口。
$ 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 端口。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UzK6xcZY-1624331956452)(C:\Users\Meet\AppData\Roaming\Typora\typora-user-images\image-20210621163154564.png)]
对应命令
docker run -d --name web --network localnet -p 5000:80 nginx
2.5. 容器没有ping命令
1、进入容器
docker exec -it container /bin/bash
2、下载ping
apt-get update && apt-get install iputils-ping
2.5. 不同网络中的容器互通(docker network connect)
1、环境准备
准备运行四个容器,两两位于不同的网络
[root@VM-8-9-centos ~]# docker run -d -P --network localnet --name nginx01-localnet nginx
5ed00194d587e6ba733147ac0bd7e081207c05762d749652424224b64c83f8d8
[root@VM-8-9-centos ~]# docker run -d -P --network localnet --name nginx02-localnet nginx
329d4c8c010d5983f8f47adf16a407f52f51f54f25a73b757bbe5135595cb7e3
[root@VM-8-9-centos ~]# docker run -d -P --name nginx01-docker0 nginx
00f4d93b39346b3405629b8a68b534ae83a786cea2ec49f670503f3048dcb8f3
[root@VM-8-9-centos ~]# docker run -d -P --name nginx02-docker0 nginx
d3ed4313286d0887a6d9317c660bbab3956bf4b21237e1985ff99fd86d74b195
[root@VM-8-9-centos ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d3ed4313286d nginx "/docker-entrypoint.…" 6 seconds ago Up 5 seconds 0.0.0.0:49157->80/tcp, :::49157->80/tcp nginx02-docker0
00f4d93b3934 nginx "/docker-entrypoint.…" 13 seconds ago Up 12 seconds 0.0.0.0:49156->80/tcp, :::49156->80/tcp nginx01-docker0
329d4c8c010d nginx "/docker-entrypoint.…" 43 seconds ago Up 42 seconds 0.0.0.0:49155->80/tcp, :::49155->80/tcp nginx02-localnet
5ed00194d587 nginx "/docker-entrypoint.…" 51 seconds ago Up 50 seconds 0.0.0.0:49154->80/tcp, :::49154->80/tcp nginx01-localnet
查看网卡信息
[root@VM-8-9-centos ~]# brctl show
bridge name bridge id STP enabled interfaces
br-a516a89475f6 8000.02420cf9fa2e no veth8e5bac5
vethcdb74fa
docker0 8000.0242f503119d no vethc5f6fd1
vethef2810b
2、测试(docker exec -it nginx01-docker0 ping nginx01-localnet)
[root@VM-8-9-centos ~]# docker network connect localnet nginx01-docker0
[root@VM-8-9-centos ~]# docker exec -it nginx01-docker0 ping nginx01-localnet
PING nginx01-localnet (172.18.0.2) 56(84) bytes of data.
64 bytes from nginx01-localnet.localnet (172.18.0.2): icmp_seq=1 ttl=64 time=0.106 ms64 bytes from nginx01-localnet.localnet (172.18.0.2): icmp_seq=2 ttl=64 time=0.049 ms