深入理解 Docker 网络——单机通信

网络是云原生的灵魂,这篇文章将带你深入理解 Docker 在单机中是如何通信的

目录

引入:单机创建 2 个容器

绕不开的话题:网卡

计算机网络模型回顾

Linux 中的网卡

网卡常用命令

查看网卡

网卡新增 / 删除 IP 地址

启动 / 停止网卡

Linux Network Namespace

常用命令

多个 Namespace 通信实战

Bridge 网络

docker0

容器内访问互联网

通过容器名称通信

默认及自定义 bridge 网络的区别

其他网络模式

host 网络

none 网络


引入:单机创建 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 启动之初,会自动创建 hostnone 类型的网络驱动, 刚开始这两种网络驱动下都没有关联容器

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 网络将在下一篇进行介绍

猜你喜欢

转载自blog.csdn.net/u013481793/article/details/128225585