Docker 学习笔记(九):Docker 网络原理,理解 docker0,虚拟网卡,容器互联,以及跨网络连通

发布于:2024-04-20 ⋅ 阅读:(27) ⋅ 点赞:(0)

一、前言

记录时间 [2024-4-16]

系列文章简摘:
Docker 学习笔记(六):挑战容器数据卷技术一文通,实战多个 MySQL 数据同步,能懂会用,初学必备
Docker 学习笔记(七):介绍 Dockerfile 相关知识,使用 Dockerfile 构建自己的 centos 镜像
Docker 学习笔记(八):Dockerfile实战篇,制作 Tomcat 镜像,发布镜像到 DockerHub 和阿里云

更多 Docker 相关文章请参考上面专栏哦,入门篇 1~5 已完结6 开始是精髓篇:容器数据卷、Dockerfile、Docker 网络

本文介绍了 Docker 网络原理,介绍了 docker0,Veth-pair 技术,以及 Docker 给容器分配虚拟网卡的方式。同时讲述了两种容器互联的方式,分别是 Link 和 自定义网络。还有容器之间跨网络连通的方式。


二、划重点

当我们在 Linux 宿主机中安装了 Docker:

  • 宿主机中就会配置 docker0 网卡,docker0 使用的是 Linux 桥接模式,使用的技术是 Veth-pair 技术。
  • 每启动一个 Docker 容器,Docker 就会给该容器分配一个 ip,一对虚拟网卡。

关于 Docker0 的结论:

  • Docker 容器 tomcat01 和 tomcat02,二者共用一个路由器,即 docker0;
  • 所有容器在不指定网络的情况下,都使用 docker0 路由,Docker 会给它们默认分配一个可用的 IP 地址;
  • Docker 中所有的网络接口都是虚拟的,因为虚拟网卡转发效率高,传输文件快。
  • 移除容器之后,分配给它的虚拟网卡自动消失,对应的网桥就没了。

关于自定义网络:

  • Docker 中的自定义网络,修复了 docker0 不能使用名字 ping 连接的问题;
  • 给不同的集群使用不同的自定义网络,可以保证集群的安全和健康。

关于跨网络连通:

  • 假设容器之间要跨网络通信,其中一个容器需要拥有多个 IP 地址。

三、清空 Docker 环境

在之前的 Docker 学习中,我们下载 / 提交 / 构建了很多镜像,运行了各种不同的容器,真是不错的收获!现在我们将进入 Docker 网络的学习,为了便于学习和理解 Docker 网络的内部原理,我们先清空一下 Docker 环境,把里面的镜像和容器通通删除。

当然啦,如果有特别想要保留的镜像,就把它们发布到阿里云镜像仓库吧。发布镜像,请参考这篇文章

不清空也没有关系,就是会看着有亿点点乱罢了。

删除镜像 / 容器命令:

# 删除所有 docker 容器
docker rm -f $(docker ps -aq)

# 删除所有 docker 镜像
docker rmi -f $(docker images -aq)

# 查看是否删除成功
docker images		# 查看所有 docker 镜像
docker ps -a		# 查看所有 docker 容器

四、介绍 Docker0

1. 理解网络 Docker0

删除完成后,我们来查看 Linux 宿主机网络

[root@localhost ~]# ip addr
1: lo: 本机回环地址
2: eth0: 阿里云内网地址
3: docker0: docker0 地址
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
# 172.17.0.1 相当于一个路由器 网关

通过查询发现,Linux 宿主机中一共有 3 个网络,除却本机回环地址 127.0.0.1,以及本机内网地址,剩下的 docker0 便与 Docker 网络相关了。

当我们在 Linux 宿主机中安装了 Docker:

  • 宿主机中就会配置 docker0 网卡,docker0 使用的是 Linux 桥接模式,使用的技术是 Veth-pair 技术。
  • 每启动一个 Docker 容器,Docker 就会给该容器分配一个 ip,一对虚拟网卡。

2. 提前解决报错

由于远程仓库中的 tomcat 镜像是精简版的,镜像内部缺少 iproute2 和 iputils-ping 依赖,直接使用此镜像来运行是无法使用 ip 命令的,会报出如下错误:

OCI runtime exec failed: exec failed: unable to start container process: exec: "ip": executable file not found in $PATH: unknown
OCI runtime exec failed: exec failed: unable to start container process: exec: "ping": executable file not found in $PATH: unknown

不要慌,解决办法也很简单,就是自己安装一下 iproute2 和 iputils-ping 依赖

因为后面我们要运行好多容器,手动给每个容器都去安装一遍的话就显得累赘,所以我们把这个容器 commit 为本地镜像

安装 iproute2 和 iputils-ping 方法:

# 1. 用基础镜像 tomcat 运行一个容器 tomcat01
docker run -d -P --name tomcat01 tomcat

# 2. 进入容器
docker exec -it tomcat01 /bin/bash

# 3. 更新 apt,安装 iproute2,安装 iputils-ping
apt-get update
apt install -y iproute2
apt install -y iputils-ping

# 4. 安装完以后退出 exit
# 5. 提交 容器 tomcat01 为本地镜像
docker commit -a="yuanyuan" -m="add apt ip and ping" tomcat01 yuanyuan/tomcat

3. 一对虚拟网卡

那么,Docker 如何处理网络访问?或者说,两个 Docker 容器之间如何通信?

我们在 Docker0 网络下运行两个 tomcat 容器,来进行测试。

查看容器 tomcat01 内部的网络地址:

  • 追加命令:ip addr
docker exec -it 容器名 ip addr

# tomcat01 的 ip 172.17.0.2
[root@localhost ~]# docker exec -it tomcat01 ip addr
144: eth0@if145: inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0

每启动一个 Docker 容器,Docker 就会给该容器分配一个 ip,一对虚拟网卡。

我们发现,容器 tomcat01 启动时,得到了 Docker 分配的 ip 地址和虚拟网卡。

  • ip 地址:172.17.0.2
  • 一对虚拟网卡:144: eth0@if145

思考:Linux 宿主机可以同 容器 tomcat01 通信吗?

通过宿主机 ping 容器 ip 的方式,我们来测试一下:发现可以 ping 通,因为它们在同一网段。

[root@localhost ~]# ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.124 ms

此时,我们再来查看 Linux 宿主机网络

# Linux 多了一对虚拟网卡
[root@localhost ~]# ip addr
3: docker0: docker0 inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
145: veth57938f5@if144: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc

发现 Linux 多了一对虚拟网卡,这对虚拟网卡和 tomcat01 有很大的联系。


再创启动一个 tomcat02 容器试试:

# 镜像 yuanyuan/tomcat 是我们刚刚提交的那一个
docker run -d -P --name tomcat02 yuanyuan/tomcat

然后查看一下 Linux 的网络:Linux 又多了一对虚拟网卡。

# Linux 又多了一对虚拟网卡
[root@localhost ~]# ip addr
3: docker0: docker0 inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
145: veth57938f5@if144: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc 
149: veth31fb2d8@if148: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc

查看下 tomcat02 容器的网络配置:

# tomcat02 ip 172.17.0.3; 虚拟网卡 148: eth0@if149
[root@localhost ~]# docker exec -it tomcat02 ip addr
148: eth0@if149: inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0

容器的虚拟网卡和宿主机的虚拟网卡是相对应的!

随着容器带来的虚拟网卡,都是一对一对的,这便是 Veth-pair 技术。

Veth-pair 是一对虚拟设备接口,它们都是成对出现的,一端连接协议,一端彼此相连。正因为有这个特性,Veth-pair 能充当桥梁,来连接各种虚拟网络设备。


4. 使用 Docker0

由于容器 tomcat01 和 tomcat02 都是通过 Docker0 方式桥接的,所以它们之间能够通信,但是,只能通过 ip 地址,不能用名字。

我们测试一下上述容器 tomcat01 和 tomcat02 之间能否 ping 通。

用 tomcat02 ping tomcat01:(反之亦然)

[root@localhost ~]# docker exec -it tomcat02 ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.076 ms

不难发现,容器和容器之间是可以互相 ping 通的。

绘制一个网络模型图来加深理解:tomcat01 想要和 tomcat02 通信,得借助 docker0 这个路由器

在这里插入图片描述


现在,我们把 tomcat01 容器删除:

docker rm -f tomcat01

再次查看 Linux 的网络配置:发现 tomcat01 容器对应的网桥被一同移除了。

# 删掉容器,少了一对虚拟网卡,对应的网桥就没了
[root@localhost ~]# ip addr
3: docker0: inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
149: veth31fb2d8@if148: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc

关于局域网的知识补充:

每个 IP 地址由二进制的四段组成:00000000.00000000.00000000.00000000,每一段的范围是 0~255。

比如有这样一个子网掩码 255.255.0.0/16,这里转换成十进制表示,

16 表示 IP 的前 16 位用来固定网段 255.255,后 16 位用来给局域网内设备分配 IP,255.255.0.1 一般是路由器网关。

那么 IP 为 255.255.xxx.xxx 这样的设备都在这个局域网中,可以互相 ping 通。


关于 Docker0 的结论:

  • Docker 容器 tomcat01 和 tomcat02,二者共用一个路由器,即 docker0;
  • 所有容器在不指定网络的情况下,都使用 docker0 路由,Docker 会给它们默认分配一个可用的 IP 地址;
  • Docker 中所有的网络接口都是虚拟的,因为虚拟网卡转发效率高,传输文件快。
  • 移除容器之后,分配给它的虚拟网卡自动消失,对应的网桥就没了。

如图所示,Docker 使用 Veth-pair 技术实现容器间的通信。

在这里插入图片描述


5. 查看网络模式

我们来查看一下 Docker 中的几种网络模式:

  • bridge:桥接模式,默认的 docker0 用的就是这种;
  • host:和宿主机共享网络;
  • none:不配置网络;
  • container:容器网络连通(这种用得少,局限性很大)
[root@localhost ~]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
949536a4c28c   bridge    bridge    local
03fb5b594ce1   host      host      local
1e8dd7dab9b3   none      null      local

然后具体查看一下桥接模式 bridge:

  • Subnet:子网掩码
  • Gateway:网关
  • Containers:给容器分配的默认 IP 信息
[root@localhost ~]# docker network inspect 949536a4c28c
{
	"Subnet": "172.17.0.0/16",
	"Gateway": "172.17.0.1"
}

# 如果容器没有指定 IP,docker0 会随机分配
"Containers": {
	"Name": "tomcat01",        
	"IPv4Address": "172.17.0.2/16",
}

五、容器互联

1. Link 方式

思考一个场景:假如我们编写了一个微服务,需要连接数据库,但数据库每次启动 ip 都会发生变化,为了使得微服务不受数据库 ip 变化的影响,我们希望通过名字来进行数据库访问,实现微服务的高可用。

在默认的 docker0 中,添加 --link 参数,可以达到这个要求。

重新运行一个 tomcat01 容器吧:

docker run -d -P --name tomcat01 yuanyuan/tomcat

默认 docker0 情况下,容器之间只能通过 ip 来 ping,名字是 ping 不通的。

试一下吧,用 tomcat02 ping tomcat01,直接 ping 容器名字。

[root@localhost ~]# docker exec -it tomcat02 ping tomcat01
ping: tomcat01: Name or service not known

名字 ping 不通,怎么解决呢?添加 --link 参数。

我们再运行一个新的容器 tomcat03 测试下,把 tomcat03 link 到 tomcat02

docker run -d -P --name tomcat03 --link tomcat02 yuanyuan/tomcat

测试 tomcat02 和 tomcat03 之间用名字 ping 一下:tomcat03 可以 ping tomcat02,但 tomcat02 不可以 ping tomcat03。

# tomcat03 ping tomcat02 可以
[root@localhost ~]# docker exec -it tomcat03 ping tomcat02
PING tomcat02 (172.17.0.3) 56(84) bytes of data.
64 bytes from tomcat02 (172.17.0.3): icmp_seq=1 ttl=64 time=0.202 ms

# tomcat02 ping tomcat03 不可以
[root@localhost ~]# docker exec -it tomcat02 ping tomcat03
ping: tomcat03: Name or service not known

为什么呢?inspect 探究一下吧。可以在 tomcat03 容器中找到 Link 配置,而 tomcat02 容器中没有配置。

[root@localhost ~]# docker inspect tomcat03
"Links": ["/tomcat02:/tomcat03/tomcat02"]

也可以通过查看 hosts 配置来发现原理。tomcat03 在本地配置了 tomcat02 的配置,它把 tomcat02 直接写入了 hosts 文件,ping tomcat02 / b71a0a6566a3 就相当于 ping 对应的 ip 172.17.0.3。

tomcat02 的 hosts 文件里就没有写 tomcat03,大家可以查看验证一下。

# --link 就是在 tomcat03 的 hosts 里绑定了 172.17.0.3  tomcat02 b71a0a6566a3

[root@localhost ~]# docker exec -it tomcat03 cat /etc/hosts
172.17.0.3      tomcat02 b71a0a6566a3

这就是为什么 tomcat03 可以 ping tomcat02,但 tomcat02 不可以 ping tomcat03 的原因啦。


2. 自定义网络

创建自定义网络

之前是使用的是默认网络 docker0:

# 我们直接启动命令 --net bridge,就是 docker0
# 以下两条命令是等同的,--net bridge 默认情况下可以省略。
docker run -d -P --name tomcat01 yuanyuan/tomcat
docker run -d -P --name tomcat01 --net bridge yuanyuan/tomcat

先清空一下容器:

docker rm -f $(docker ps -aq)

接下来我们自定义一个桥接网络 mynet,配置两个容器到 mynet 网络中。

创建一个自定义网络:

  • --driver:设置网络模式;
  • --subnet:设置子网掩码;
  • --gateway:设置网关;
  • mynet:自定义网络的名字。
# 创建一个自定义网络
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet

# 查看网络模式
docker network ls

# 查看自定义网络 mynet 的详细信息
docker network inspect mynet

启动两个容器 tomcat-net-01 和 tomcat-net-02,把 tomcat 发布到自定义网络中。

# 运行容器
docker run -d -P --name tomcat-net-01 --net mynet yuanyuan/tomcat
docker run -d -P --name tomcat-net-02 --net mynet yuanyuan/tomcat

# 查看 mynet 给容器们分配的 ip
docker network inspect mynet

测试 ping 连接

接下来,我们测试一下 tomcat-net-01 (192.168.0.2)tomcat-net-02 (192.168.0.3) 之间的 ping 连接。

  • tomcat-net-01 ping 192.168.0.3:成功
  • tomcat-net-01 ping tomcat-net-02:成功
  • tomcat-net-02 ping tomcat-net-01:成功
# 现在不使用 --link 也可以 ping 名字了!

# tomcat-net-01 ping 192.168.0.3
[root@localhost ~]# docker exec -it tomcat-net-01 ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.092 ms

# tomcat-net-01 ping tomcat-net-02
[root@localhost ~]# docker exec -it tomcat-net-01 ping tomcat-net-02
PING tomcat-net-02 (192.168.0.3) 56(84) bytes of data.
64 bytes from tomcat-net-02.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.135 ms

# tomcat-net-02 ping tomcat-net-01
[root@localhost ~]# docker exec -it tomcat-net-02 ping tomcat-net-01
PING tomcat-net-01 (192.168.0.2) 56(84) bytes of data.
64 bytes from tomcat-net-01.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.046 ms

关于自定义网络:

  • Docker 中的自定义网络,修复了 docker0 不能使用名字 ping 连接的问题;
  • 给不同的集群使用不同的自定义网络,可以保证集群的安全和健康。

六、跨网络连通

1. 连通方式

当前,在 Linux 宿主机中,有两个 Docker 网络,分别是 docker0 和 mynet,两个都是桥接模式,网段不同。

如图所示,在 docker0 局域网中,运行了 container01 和 container02 两个容器;在 mynet 局域网中,运行了 container03 和 container04 两个容器。

在不经过多余配置的情况下,container01 和 container03 是无法通信的,就是 ping 不通的。

那如果,container01 就非常想和 container03 通信,有啥办法嘛?

首先,docker0 和 mynet 这两个网段肯定是不能打通的,否则就违背了我们给不同的集群使用不同的自定义网络的初衷。

那么,container01 就只能取得 mynet 的联系了。

container01 先联络上 mynet,再通过 mynet 联络上 container03。

在这里插入图片描述


2. 操作实现

在 docker0 中运行两个容器 tomcat01,tomcat02,之前的被删了。

不死心,tomcat01 先去 ping 一下 mynet 中的 tomcat-net-01:

docker run -d -P --name tomcat01 yuanyuan/tomcat
docker run -d -P --name tomcat02 yuanyuan/tomcat

# tomcat01 ping tomcat-net-01
[root@localhost ~]# docker exec -it tomcat01 ping tomcat-net-01
ping: tomcat-net-01: Name or service not known

显然,tomcat01 ping tomcat-net-01,失败咯。


那么,tomcat01 要怎么联络上 mynet 呢?我们来研究一下 docker 中 network 的使用方法。

docker network --help

# 发现了 connect 方法,连接一个容器到一个网络
[root@localhost ~]# docker network --help
Usage:  docker network COMMAND
Commands:
  connect     Connect a container to a network

测试连接 tomcat01 容器和 mynet 网络:

# 测试打通 tomcat01--mynet
docker network connect mynet tomcat01

现在再尝试 tomcat01 ping tomcat-net-01,会发现:成功了!

而且, tomcat01 不仅可以通信 tomcat-net-01,也可以通信 tomcat-net-02,tomcat01 可以和 mynet 中任何一个容器通信

但是,和 tomcat01 一起的 tomcat02 就依然不行。

# tomcat01 ping tomcat-net-01 可以
[root@localhost ~]# docker exec -it tomcat01 ping tomcat-net-01
PING tomcat-net-01 (192.168.0.2) 56(84) bytes of data.
64 bytes from tomcat-net-01.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.115 ms

# tomcat01 ping tomcat-net-02 可以
[root@localhost ~]# docker exec -it tomcat01 ping tomcat-net-02
PING tomcat-net-02 (192.168.0.3) 56(84) bytes of data.
64 bytes from tomcat-net-02.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.081 ms

# tomcat02 ping tomcat-net-01 不行哦
[root@localhost ~]# docker exec -it tomcat02 ping tomcat-net-01
ping: tomcat-net-01: Name or service not known

3. 一个容器,两个 IP

这是怎么实现的呀?就是给 tomcat01 加了一个 IP,这个容器现在有两个 IP 地址了,一个在原来的 docker0 下,另一个在 mynet 下。

这就好比阿里云服务器,它有一个公网 IP 和一个私网 IP。

可以在 mynet 配置中查看 tomcat01 的配置信息:

docker network inspect mynet

"Name": "tomcat01",
"IPv4Address": "192.168.0.4/16"

七、总结

本文介绍了 Docker 网络原理,介绍了 docker0,Veth-pair 技术,以及 Docker 给容器分配虚拟网卡的方式。同时讲述了两种容器互联的方式,分别是 Link 和 自定义网络。还有容器之间跨网络连通的方式。

一些参考资料

狂神说系列 Docker 教程:https://www.bilibili.com/video/BV1og4y1q7M4/
Docker 官方文档:https://docs.docker.com/engine/install/centos/
Docker 远程仓库:https://hub.docker.com/
FinalShell 下载:http://www.hostbuf.com/t/988.html