一、前言
记录时间 [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