网络是云原生的灵魂,这篇文章将带你深入理解 Docker 在单机中是如何通信的
目录
引入:单机创建 2 个容器
这里以镜像 redis:7.0.5 为例创建容器
docker run -d --name redis-01 -p 6301:6379 redis:7.0.5
docker run -d --name redis-02 -p 6302:6379 redis:7.0.5
分别进入容器安装 iputils-ping 命令和 iproute2 命令
# 分别进入容器,redis-02步骤相同
docker exec -it redis-01 /bin/bash
# 更新apt-get
apt-get update
# 安装iputils-ping,安装之后可以正常使用ping命令
apt-get install iputils-ping
# 安装iproute2,安装之后可以正常使用ip命令
apt-get install iproute2
安装完成后查看 redis-01 和 redis-02 容器内的 IP 地址
docker inspect redis-01 | grep IPAddress
# redis-01的IP 172.17.0.2
docker inspect redis-02 | grep IPAddress
# redis-02的IP 172.17.0.3
执行如下命令后,发现两个容器可以互相 ping 通
# 172.17.0.3为redis-02的IP
docker exec redis-01 ping 172.17.0.3 -c 3
# 172.17.0.2为redis-01的IP
docker exec redis-02 ping 172.17.0.2 -c 3
两个容器的资源是互相隔离的,但是却可以互相通信,那么问题来了,Docker 内部是如何实现的呢?
绕不开的话题:网卡
计算机网络模型回顾
在计算机网络的分层体系结构中,有 OSI 7层网络模型和 TCP/IP 4层网络模型 2 种常见的划分方式,TCP/IP 4层网络模型如下图所示
2 台计算机之间的网络通信过程如下图所示
从上图可以看出,网卡在网络通信极为关键,下面介绍 Linux 中的网卡
Linux 中的网卡
网卡 (Network Interface Controller) 是用于网络通讯的计算机硬件,每一块网卡都拥有独一无二的 MAC 地址,这个地址被保存在网卡上的 ROM 中,网卡也被称为网络适配器、网络接口卡
虚拟机内的网卡对应宿主机上的虚拟网卡
Linux 中的网卡其实就是一个文件,保存在 /etc/sysconfig/network-scripts 目录下
cd /etc/sysconfig/network-scripts && ls -F
ifcfg-lo,ifcfg-eth0 ~ ifcfg-eth4 都对应一块网卡
网卡常用命令
查看网卡
ls /sys/class/net
# 查看网卡简略信息,不显示IP地址
ip link
# 查看网卡详细信息
ip a
ip a 命令结果分析
网卡设备名称后的状态,有 UP / DOWN / UNKOWN 等
link/ether: MAC地址
inet: IP地址
inet6: IPv6信息
网卡新增 / 删除 IP 地址
# dev为device的缩写,用于指定网卡
ip addr add 192.168.0.100/24 dev eth0
ip addr delete 192.168.0.100/24 dev eth0
启动 / 停止网卡
# 重启网卡
service network restart
# 重启网卡的另一种方式
systemctl restart network
# 启动网卡
ip link set eth0 up
ifup eth0
# 停止网卡
ip link set eth0 down
ifdown eth0
Linux Network Namespace
常用命令
在 Linux 中,使用 Network Namespace 来实现不同网络之间的隔离,每个 Namespace 都有独立的网络协议栈
相关的操作的子命令是 ip netns
###### 管理当前机器上的Network Namespace
# 查看namespace列表
ip netns
ip netns ls
ip netns list
# 添加namespace
ip netns add ns1
# 删除namespace
ip netns delete ns1
对某个 Namespace 执行命令
# 语法:ip netns exec namespaceName command
ip netns exec ns1 ip a
ip netns exec ns1 ip link
# 如果执行的命令是和ip相关,还可以用一种简写形式
# 比如 ip -n ns1 link 等价于 ip netns exec ns1 ip link
ip -n ns1 link
多个 Namespace 通信实战
先创建两个 Namespace,并为其添加 IP
ip netns add red
ip netns add blue
这两个新建的 Namespace 还不能和宿主机及其他 Namespace 通信,需要借助 Linux 的 veth 才能实现网络通信。veth 是 Linux 的一种虚拟网络设备,总是成对出现
先创建一对 veth
ip link add veth-red type veth peer name veth-blue
ip link
创建完成后,用 ip link 命令查看时,可以发现多了 2 张网卡
veth-blue@veth-red
veth-red@veth-blue
将 veth-red 派发到 red
ip link set veth-red netns red
ip link
此时用 ip link 命令查看时,可以有 1 张网卡已经派发成功
veth-blue@if4
将 veth-blue 派发到 blue
ip link set veth-blue netns blue
ip link
此时 veth-red 和 veth-blue 还没有 IP 地址,还缺少通信条件。分别为其添加 IP 地址并启动
# 这里使用简写形式
ip -n red addr add 192.168.0.21/24 dev veth-red
ip -n blue addr add 192.168.0.22/24 dev veth-blue
# 这里使用原始形式
ip netns exec red ip link set veth-red up
ip netns exec blue ip link set veth-blue up
互相 ping 一下,可以正常访问
ip netns exec red ping 192.168.0.22
ip netns exec blue ping 192.168.0.21
Bridge 网络
之前创建了两个容器,分别是 redis-01 和 redis-02,在不指定网络的情况下,Docker 默认使用 bridge 网络驱动
按照以上的描述,很容易做出这样的猜想:每个 Docker 容器都有独立的 Network Namespace,并且容器之间是通过 veth-pair 进行通信的
可以用如下命令进行验证
docker exec redis-01 ip a
docker exec redis-02 ip a
从上图可知,容器 redis-01 内的 veth-pair为 eth0@if31, 而容器 redis-02 内的 veth-pair 为eth0@if33,序号并不连续,eth0@if32 去哪里了?
docker0
其实,在宿主机中执行 ip a 命令时,可以看到 docker0,这是 Docker 在首次启动时自动创建的,用于为设置为默认 bridge 网络驱动的容器之间互相通信。docker0 是一种虚拟设备,也就是 Linux 网桥,绑定的 IP 地址为 172.17.0.1
可以用 bridge-utils 命令查看
# 如果未安装,执行安装命令
yum install bridge-utils
brctl show
之后,每创建一个容器时,Docker 就自动生成一对 veth-pair,并依次派发到 docker0 和新建的容器中,如下图所示
设计 docker0 的一个好处是减少 veth-pair 的数量,如上图所示,4 个容器两两之间都可以互相通信,此时只有 4 对 veth-pair。反之,如果是两个容器之间互相设置,则需要 6 对;此外,新建容器时,只需要处理好当前容器和 docker0 之间的通信问题,不需要考虑和其他容器的网络配置
容器内访问互联网
在 redis-01 当前的网络设置下,是可以访问互联网的
docker exec redis-01 ping www.baidu.com -c 3
这是通过 iptables 实现的,将私有 IP 地址转换为公网 IP 地址,如下图所示
通过容器名称通信
现在假设容器之间需要通过 containerName 进行通信,比如
docker exec redis-01 ping redis-02 -c 3
可惜的是,执行这条命令时,会提示域名无法解析,查阅官方文档会发现,默认的 bridge 网络只能通过 IP 地址进行通信。如果要通过容器名称或别名通信,需要使用自定义 bridge 网络,这是因为自定义 bridge 网络会为容器提供自动域名解析功能 (automatic DNS resolution)
接下来进行自定义 bridge 网络实战
01. 创建自定义 bridge 网络
# 创建自定义 bridge 网络,默认子网信息:172.18.0.0/16
docker network create nw-redis-b
# 创建自定义 bridge 网络时,指定子网
docker network create --subnet=172.18.0.0/24 nw-redis-b
02. 创建两个容器,并指定刚创建的网络
docker run -d --name redis-05 -p 6305:6379 --network nw-redis-b redis:7.0.5
docker run -d --name redis-06 -p 6306:6379 --network nw-redis-b redis:7.0.5
03. 查看自定义 bridge 网络
docker network inspect nw-redis-b
可以看到刚创建的容器已经关联到自定义 bridge 网络上了
{
...
"Containers": {
"687c8ef528817aa952e6e7afe266fc91d50590cb6a96e691ac1ed7bbcb1a6a2c": {
"Name": "redis-05",
"EndpointID": "5bfc8289b76cb07974d3b35ae1f0d1f5309586641c95b141f8f22640511bce1d",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/24",
"IPv6Address": ""
},
"d292b5dd614d846e0fca9bd658ee4ae218dba64ac04dc1a09099dad80a8fdb5c": {
"Name": "redis-06",
"EndpointID": "2f2021e6f4727534b6be55ecc9f6a527ed5cabfa911edc3b4e5c8267b8427a99",
"MacAddress": "02:42:ac:12:00:03",
"IPv4Address": "172.18.0.3/24",
"IPv6Address": ""
}
}
...
}
04. 为容器安装 iputils-ping 后检验
安装命令参考引入部分
docker exec redis-05 ping redis-06 -c 3
可以正常通信
默认及自定义 bridge 网络的区别
- 自定义网络会为容器提供自动域名解析功能,默认网络没有
- 可以在不停止容器的情况下,连接到自定义网络,或者从自定义网络中断开;而将容器从默认网络中断开,需要先停止容器
- 自定义网络的隔离性更好
- 自定义网络的配置更灵活,修改配置不会影响其他网络
其他网络模式
Docker 启动之初,会自动创建 host 和 none 类型的网络驱动, 刚开始这两种网络驱动下都没有关联容器
docker network ls
# =========================================
NETWORK ID NAME DRIVER SCOPE
f812841a19a9 bridge bridge local
ad91f2cf179b host host local
1a899b2be928 none null local
host 网络
使用 host 网络模式的容器,会共享宿主机的网络空间,因此不会分配 IP 地址。此时,端口映射参数 -p 不会生效
docker run -d --name redis-07 --network host redis:7.0.5
需要注意的是,宿主机的 6379 端口已经被 redis-07 占用了,如果再创建一个 host 网络模式的容器 redis-08,虽然可以创建成功,但由于端口冲突,容器启动会失败
host 网络模式使用场景较少,对于需要使用大量端口的容器,由于这些端口没有创建额外的 userland-proxy 资源,可以获得一些性能提升
none 网络
这种模式下也不会分配 IP 地址,且进入容器后,也无法连接网络,相当于网络被完全禁用了
docker run -d --name redis-08 --network none redis:7.0.5
Docker 还有其他网络模式,比如 IPvlan 网络、Macvlan 网络、overlay 网络。IPvlan 网络、Macvlan 网络就不展开了,overlay 网络将在下一篇进行介绍