Docker 网络
1. Docker 网络理论基础
Docker
网络中的相关命令非常少,但需要掌握的底层原理相对较多。
1.1 Network Namespace
Docker
网络的底层原理是 Linux
的 Network Namespace
,所以对于 Linux Network Namespace
的理解对 Docker
网络底层原理的理解非常重要。
1.1.1 简介
Network Namespace
是 Linux
内核提供的用于实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,每个独立的网络空间内的防火墙、网卡、路由表、邻居表、协议栈都是独立的。不管是虚拟机还是容器,当运行在独立的命名空间时,就像是一台单独的主机一样。
1.1.2 需求
下面要通过手工方式创建两个 Network Namespace
,并最终让它们相互连通,即可以通过 ping
命令测试成功。以使大家能够理解 Docker
网络的底层原理。
1.1.3 创建两个命名空间
分别创建两个命名空间 ns1
与 ns2
。
因为每个网络空间都是独立的,所以每个Network Namespace
都具有一个回环网络适配器 lo
。
1.1.4 创建网络接口 veth pair
如果要让两个命名空间连通,则需要用到虚拟设备接口技术 veth pair
。该技术需要一对网络接口分别置于两个命名空间中。
以下命令用于创建一对网络接口 veth-ns1
与 veth-ns2
。
此时通过 ip link
查看当前的网络地址情况,可以看到新增了两个相互连通的 veth pair
,它们都具有 MAC
地址,但它们的状态都是 DOWN
,且都不具有 IP
。
1.1.5 命名空间添加 veth 接口
通过 ip link set
命令,将这两个网络接口分别分配给两个命名空间。
此时分别在两个命名空间中执行 ip link
命令,可以查看到,它们中分别新增了前面指定的一个网络接口
此时再在主机中查看 ip link
,发现原来的那两个网络接口已经消失了。
1.1.6 为 veth 接口分配 IP
前面创建的两个网络接口是没有 IP
的。下面要通过 ip netns exec
命令,为每个指定的命名空间执行IP
添加命令 ip addr add [ip] dev [网络接口]
。
为 ns1
的 veth-ns1
网络接口分配的 IP
为 192.168.1.1
,掩码为 24
;为 ns2
的 veth-ns2
网络接口分配的 IP
为 192.168.1.2
,掩码为 24
。
此时通过在 ns1
与 ns2
中运行 ip a
命令,便可看到为接口分配的 IP
了。
1.1.7 启动 veth 接口
以上两个命名空间中的 veth
接口已经具有了 IP
,但其状态仍为 DOWN
,还没有开启。下面要通过 ip link set dev [接口] up
来启动指定的网络接口。
此时再通过 ip a
命令查看两接口的状态,已经变为了 UP
。
1.1.8 相互 ping
此时可以通过在两个命名空间中执行 ping
命令来与对方进行连通性测试了。
1.2 CNM
Docker
网络架构由三个主要部分构成:CNM
、Libnetwork
与 Driver
。
CNM
,Container Network Model
,容器网络模型,其是一种网络连接的解决方案,是一种设计规范、设计标准,其规定了 Docker
网络的基础组成要素。
CNM
中定义了三个基本要素:沙盒 Sandbox
,终端 Endpoint
与网络 Network
。
- 沙盒:一个独立的网络栈,其中包括以太网接口、端口号、路由表、
DNS
配置等。Linux Network Namespace
是沙盒的标准实现。 - 终端:虚拟网络接口,主要负责创建连接,即将沙盒连接到网络上。一个终端只能接入某一个网络。
- 网络:
802.1d
网桥的软件实现,是需要交互的终端的集合。
1.3 Libnetwork
CNM
是设计规范,而 Libnetwork
是开源的、由 Go
语言编写的、跨平台的 CNM
的标准实现。
Libnetwork
除了实现了 CNM
的三个组件,还实现了本地服务发现、容器负载均衡,以及网络控制层与管理层功能。
1.4 Driver
每种不同的网络类型都有对应的不同的底层 Driver
,这些 Driver
负责在主机上真正实现需要的网络功能,例如创建 veth pair
设备等。
不过,无论哪种网络类型,其工作方式都是类似的。通过调用 Docker
引擎的 API
发出请求,然后由 Libnetwork
做出框架性的处理,然后将请求转发给相应的 Driver
。
通过 docker network ls
命令可以查看当前主机所连接的网络及网络类型。
2. bridge 网络
bridge
网络,也称为单机桥接网络,是 Docker
默认的网络模式。该网络模式只能存在于单个 Docker
主机上,其只能用于连接所在 Docker
主机上的容器。
2.1 docker0网桥
2.1.1 查看 docker0 网桥
bridge
网络模式中具有一个默认的虚拟网桥 docker0
,通过 ip a
或 ifconfig
命令都可查看到。
当然,通过 docker network inspect bridge
也可以查看到网络名称为 bridge
的网络的详情
可以看到该网络的驱动为 bridge
,其网桥名称为 docker0
。只不过,目前该网络上还没有连接任何容器。
2.1.2 docker0 网桥工作原理
在 Linux
主机上,Docker
的 bridge
网络由 Bridge
驱动创建,其在创建时会创建一个默认的网桥 docker0
。容器与网桥间是通过 veth pair
技术实现的连接,网桥与外网间是通过“网络地址转换 NAT 技术”实现的连接,即将通信的数据包中的内网地址转换为外网地址
Bridge
驱动的底层是基于 Linux
内核的 Linux Bridge
技术。该技术已经经历了近 20 年的考验,这就意味着该模式是高性能且非常稳定的。
2.2 查看网络连接详情
2.2.1 查看 bridge 网络整体连接
现在通过 docker network inspect
命令查看当前 bridge
网络的整体连接情况。
在 Containers
中可以查看到当前名称为 bridge
的网络中连接的 myubuntu2
、 myubuntu
、myvols
三个容器。这三个容器及宿主机,其实就是四个完全独立的 Network Namespace
。
2.2.2 查看宿主机接口
此时在宿主机上通过 ip a
命令查看当前主机的网络接口情况。
发现除了回环地址 lo
,本地网卡 ens33
,网桥 docker0
外,还有两个 veth
网络接口。这两个 veth
就是由 Libnetwork
生成的 veth pair
中的宿主机中的 EndPoint
9: vethxxx@if8
表示这是第 9 个接口,其用于连接外部的第 8 个接口27: vethxxx@if26
表示这是第27 个接口,其用于连接外部的第 26 个接口
2.2.3 查看容器接口
出现这个错误表示容器中没有IP这个命令
此时你需要先安装iproute2
来解决问题
#首先进入容器
docker exec -it bash/sh
#更新软件包列表
apt-get update
如果出现下面错误:
bash: apt-get: command not found
你可以使用yum来安装
yum install -y iproute
安装完毕就可以使用了
在这个容器中使用ip a
命令查看它的地址情况,可以看到包含 eth0
的接口。其中 myvols
中的接口 18,即 eth0@if19,其用于连接宿主机的第 19个接口
2.2.4 查看网桥连接
这里要使用一个专门用于网桥控制管理的命令 brctl
。由于该命令默认在 Linux
中没有安装,所以需要首先安装一个网桥的工具包 bridge-utils
。
yum -y install bridge-utils
使用brctl show
命令可以查看本机当前所有网桥及其连接情况。可以看到,当前宿主机中只有一个网桥 docker0
,其上连接着 vethxxx
的接口,就是前面连接 myvols
eth0 的接口。
brctl show
2.2.5 查看容器详情
通过 docker inspect
查看容器的详情,可以看到,其网络连接中的网关 Geteway
的 IP
地址就是 docker0
网桥的地址。
2.3 网络创建
2.3.1 创建网络
通过 docker network create
命令可以创建指定名称与类型的网络。
-d
选项用于指定要创建网络时所使用的驱动,即创建的网络类型。最后的 bridge2
则是新创建网络的名称。
2.3.2 查看宿主机支持网络
此时通过 docker network ls
可查看到新创建的网络。
2.3.3 查看宿主机网桥
通过 brctl show
命令可以查看到新增了一个网桥,只不过该网桥暂时还没有任何连接的网络接口。该网桥就是在创建新的网络时自动创建的。
2.4 创建容器指定网络
2.4.1 创建容器
现在要创建一个新的 myvols
容器 myvols1
,其连接在新建的 bridge2
网络上。
在创建容器时通过--network
指定要连接到的网络,如果不指定,默认连接到默认的bridge
网络。
2.4.2 查看新建网络详情
此时查看 bridge2
的网络详情,可以看到容器 myvols1
已经连接到了上面。
2.4.3 查看宿主机网络接口
此时查看当前宿主机的网络接口情况,发现多出了两个接口。其中一个是 20 号接口,其为网桥 br-xxx,一个是 30号接口,其是连接 myvols1
的接口 vethxxx@if29。
2.4.4 查看新增容器网络接口
此时查看容器 myvols1
的网络接口,发现其 29 号接口正好是宿主机 30 号接口的 pair 接口
2.4.5 查看宿主机网桥
此时再查看网桥情况,发现新增网桥上增加了一个接口,而该接口正好就是宿主机上的30号接口。并且,该接口的 IP 为 172.18.xx.xx/16
,与 bridge
网络 172.17.xx.xx/16
不是同一网段,所以它们之间是不能相互通信的。
2.5 容器连接到指定网络
2.5.1 连接到指定网络
现在要将容器 myvols
连接到新建的 bridge2
网络上。可以使用 docker network connect
命令。
2.5.2 查看两个网络详情
此时查看 bridge2
的网络详情中的容器情况,发现 myvols
与 myvols1
都在该网络上
然后再查看原来的 bridge
网络详情的容器情况,发现 myvols
仍连接在其上。即,myvols
容器同时连接在了两个网络上。
2.5.3 查看容器接口
此时查看 myvols
的网络接口情况,发现其同时具有两个网络接口,分别连接在两个不同的网络上。
2.5.4 查看容器详情
查看 myvols
容器详情,可以看到其连接在两个网络上,具有两个 IP。
2.5.5 容器互 ping
容器 myvols
与 myvols1
互 ping
是可以 ping
通的。
但 myvols1
要 ping 容器 myvols
的另一网段的 IP 是 ping 不通的。
2.5.6 容器互 ping 容器名
除了可以直接 ping
通指定的 IP
外,还可以直接去 ping
对方的容器名称。
该方式在生产中非常重要。因为生产中容器的 IP 可能会发生变化,但容器名称一般是不会变的。如果某服务总是直接通过 IP 与容器相连接,那么一旦容器 IP 变化,则该服务将连接不上容器。但如果是通过容器名称相连接的,那么无论容器 IP 如何变化,都将不影响服务与容器的连接。
2.5.7 创建定向连接容器
对于自定义的 bridge
网络,其具有一个特性:该网络上的容器可以通过容器名互 ping
。但默认的 bridge
网络是不行的。如果在默认的 bridge
网络上实现通过容器名进行的连接,则需要创建容器时通过--link
选项指定。
注意我这里的有一个镜像和容器都是
myvols
名称;这里的--link myvols
后面接的容器名称 ,而后的myvols
是镜像名称
此时容器 myvols2
直接通过 myvol
的容器名称就是连接上 myvol
。
但容器 myvols2
是无法通过容器名称来连接 myvols1
的。然后 myvols
也无法通过容器名称连接 myvols2
。
所以,--link
指定的连接是一种定向连接,是带有指向性与方向性的。
2.5.8 创建共享网络命名空间容器
在创建容器时可以指定其与某已经存在的容器共享 Network Namespace
,但要求该已经存在的容器采用的是 bridge
网络模式。
上面的命令创建了一个myvols-1
的容器,其共享了 myvols
容器的 Network Namespace
。查看两个容器的接口情况,发现完全相同。
查看容器 myvols-1
的详情,可以发现,其没有自身的网络设置。因为其共享的 myvols
容器的网络设置。
3. none 网络
none
网络,即没有网络。容器仍是一个独立的 Network Namespace
,但没有网络接口,没有 IP。
3.1 创建none网络容器
在 docker run
命令中,通过--network none
选项指定创建的容器没有网络功能。
通过 docker inspect
命令查看该容器的详情,发现其没有 IP,没有网关,没有 MAC 地址。
3.2 查看容器网络接口
通过ip a
命令查看容器的网络接口,发现其只有一个回环地址 lo
,没有其它接口。
4. host 网络
host
网络,即与宿主机 host
共用一个 Network Namespace
。该网络类型的容器没有独立的网络空间,没有独立的 IP,全部与 host
共用。
4.1 创建host网络容器
在 docker run
命令中,通过--network host
选项指定创建的容器为 host 网络。
4.2 查看网络详情
通过 docker network inspect host
命令查看网络详情,发现容器 myvols4
连接在该网络上,但容器 myvols4
却没有 IP、MAC,并且该网络模式中居然没有网关 Gateway。因为该网络模式实际相当于没有网络,容器与宿主机共用 Network Namespace
,根本就不需要网络连接。
4.3 查看host与容器网络接口
通过 ip a
与 docker exec myvols4 ip a
命令分别查看宿主机与容器 myvols4
的网络接口,发现是一样的。因为它们共用一个 Network Namespace
,所以也就共用了所有网络接口。
4.4 关于端口映射7.4.4
由于容器与宿主机共用一个 Network Namespace
,所以无论是 IP
还是应用程序的 Port
,容器与宿主机的都是相同的,所以对于容器中应用程序的 Port
不存在映射的问题,host 中的 Port
与容器中的 Port
相同。
上面的 tomcat
容器由于指定了网络模式为 host
,在启动时指定的端口映射不会起作用。系统给出的 WARNING
指出,当使用 host
网络模式时,已发布的端口号被丢弃。
此时,通过仍需通过 8080
端口访问。
也正因为 host
与容器中的应用使用的是相同的端口号,所以当采用 host
网络模式时,在一个宿主机中只能启动一个应用的一个容器,否则会出现端口号冲突问题。
5. 总结
5.1 Docker网络理论基础
一个 Network Namespece
就代表一个独立的主机。一个容器就对应一个 Namespece
,所以一个容器就代表了网络中的一个独立主机。
CNM
是规范,Libnetwork
是 CNM 规范的实现,Driver
是 Libnetwork
中不同网络模式的实现。
5.2 bridge网络
加入网络的容器具有独立的 namespace
,具有自己独立的网络接口与 IP。默认的网络模式,是使用最多的网络模式。
5.3 none网络
加入网络的容器具有独立的 namespace
,但其根本就没有连接外网的网络接口,也就不可能会有 IP 了。
5.4 host网络
加入网络的容器没有自己独立的 namespace
,没有自己独立的网络接口与 IP,全部与宿主机共享。