大白话浅析容器网络
起因
使用夜莺纳管时序库,需要在夜莺里配置时序库的地址,但是经常会遇到连不通的问题,使用 Docker compose 方式部署夜莺遇到问题的概率尤其高。很多朋友解决不了这类问题,核心是因为网络知识太匮乏了,尤其是容器网络,本文用大白话介绍一下 Docker compose 两种网络模式:host network 和 bridge network。希望对大家有帮助。
基础知识
容器网络涉及到 network namespace 的概念,大家可以通过 GPT 或 Google 学习。简单来讲,容器被看作轻量级虚拟机,和宿主之间有一些隔离举措,核心就是靠 namespace 实现的。比如 mount namespace 隔离文件系统挂载点,pid namespace 隔离进程,network namespace 隔离网络等等。
通常来讲,在一台机器上,两个进程的 pid 是不会重复的,但是你在宿主机可以看到 pid 为 1 的进程,在容器里仍然可以看到 pid 为 1 的进程,这就是 pid namespace 在搞鬼;通常来讲,在一台机器上,端口是不能重复监听的,但是你在宿主上监听一个 8080 端口,在容器里仍然可以监听一个 8080 端口,这就是 network namespace 在搞鬼。
host network
host network 是 Docker compose 默认的网络模式,也是最简单的网络模式。host network 模式下,容器和宿主共享网络命名空间,即在网络方面没有隔离。
假设容器里有个进程 Prometheus 监听在 9090 端口,那么在宿主上直接访问 127.0.0.1:9090 就可以访问到 Prometheus 服务。举例:
[root@aliyun-2c2g40g3m compose-host-network]# curl -s http://127.0.0.1:9090/api/v1/labels | python3 -mjson.tool
{
"status": "success",
"data": [
"__name__",
"branch",
"call",
"type",
"url",
"version"
]
}
我们知道,通过容器的 pid 是可以看到容器的 network namespace 的,我们来对比一下此时 prometheus 进程的 network namespace 和 宿主上的 systemd 进程(1 号进程)的 network namespace,理论上是相同的。
# 通过 pgrep 可以找到 prometheus 进程的 pid
[root@aliyun-2c2g40g3m compose-host-network]# pgrep prometheus
2101063
# /proc/<PID>/ns/net 下面是 <PID> 进程的 network namespace 信息
[root@aliyun-2c2g40g3m compose-host-network]# ll /proc/2101063/ns/net
lrwxrwxrwx 1 nobody nobody 0 Apr 23 15:10 /proc/2101063/ns/net -> 'net:[4026531994]'
# 1 号进程是 systemd,我们看看它的 network namespace 信息
[root@aliyun-2c2g40g3m compose-host-network]# ll /proc/1/ns/net
lrwxrwxrwx 1 root root 0 Apr 23 15:12 /proc/1/ns/net -> 'net:[4026531994]'
很明显,systemd 进程和 prometheus 进程的 network namespace 是相同的,都是 net:[4026531994]
,这就是 host network 的特点。
另外,如果通过 ip link ls
命令查看网络接口信息,可以看到:host network 模式在 compose 启动前后,网络接口信息是不变的,因为容器和宿主共享网络命名空间。
[root@aliyun-2c2g40g3m compose-host-network]# ip link ls
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether 00:16:3e:36:ec:33 brd ff:ff:ff:ff:ff:ff
altname enp0s5
altname ens5
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether 02:42:66:48:4c:0d brd ff:ff:ff:ff:ff:ff
另外要注意,MacOS 不支持 host network 模式。
所以,在这种模式下,如果 Nightingale 和 Prometheus 都是 host network 下的两个容器,在一台宿主上,在夜莺的数据源管理页面,直接配置:http://127.0.0.1:9090
作为数据源地址即可。
bridge network
夜莺的发布包下载之后,进入 docker/compose-bridge 目录下,即可看到 docker-compose.yaml 文件,文件开头部分的内容如下:
version: "3.7"
networks:
nightingale:
driver: bridge
这就表示要创建一个 bridge 类型的 network,名字为 nightingale,后面在各个 service 中会引用。我就用夜莺的这个 compose 环境来验证一下 bridge network 的特点。先启动一下:
[root@aliyun-2c2g40g3m compose-bridge]# docker compose up -d
[+] Running 6/6
✔ Network compose-bridge_nightingale Created 0.1s
✔ Container mysql Started 0.1s
✔ Container redis Started 0.2s
✔ Container victoriametrics Started 0.1s
✔ Container nightingale Started 0.0s
✔ Container categraf Started 0.1s
启动之后,我们来查看网络接口,立刻就会看到一些新接口:
[root@aliyun-2c2g40g3m compose-bridge]# ip link ls
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether 00:16:3e:36:ec:33 brd ff:ff:ff:ff:ff:ff
altname enp0s5
altname ens5
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether 02:42:66:48:4c:0d brd ff:ff:ff:ff:ff:ff
196: br-f7e89bf52fb1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:1c:ee:72:51 brd ff:ff:ff:ff:ff:ff
198: vethccaf5df@if197: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-f7e89bf52fb1 state UP mode DEFAULT group default
link/ether de:10:51:ed:c1:f3 brd ff:ff:ff:ff:ff:ff link-netnsid 0
200: veth4be2140@if199: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-f7e89bf52fb1 state UP mode DEFAULT group default
link/ether 22:1d:46:e9:51:ca brd ff:ff:ff:ff:ff:ff link-netnsid 1
202: vethe500119@if201: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-f7e89bf52fb1 state UP mode DEFAULT group default
link/ether 32:cb:7e:9a:14:af brd ff:ff:ff:ff:ff:ff link-netnsid 2
204: veth18aab27@if203: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-f7e89bf52fb1 state UP mode DEFAULT group default
link/ether f2:03:0d:e8:5f:76 brd ff:ff:ff:ff:ff:ff link-netnsid 3
206: vethd2c49b3@if205: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-f7e89bf52fb1 state UP mode DEFAULT group default
link/ether a2:65:bd:bf:04:ba brd ff:ff:ff:ff:ff:ff link-netnsid 4
编号为 196 的是 br-f7e89bf52fb1,br 这个前缀表示这是一个 bridge 网桥。编号为 198 的那一行,具体表示什么我让 Kimi 给大家解释一下,解释一下下面这行:
198: vethccaf5df@if197: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-f7e89bf52fb1 state UP mode DEFAULT group default
link/ether de:10:51:ed:c1:f3 brd ff:ff:ff:ff:ff:ff link-netnsid 0
这段输出是 ip link ls
命令的一部分,描述了一个特定的虚拟网络接口 vethccaf5df
的详细信息。下面是对输出中各项内容的解释:
-
接口索引:
198
是这个网络接口的索引号。 -
接口名称:
vethccaf5df@if197
表示这是一个虚拟以太网对(veth pair),它与另一个接口if197
配对。@if197
表示这个接口是一对中的一个端点,另一个端点是if197
。 -
接口状态标志:
<BROADCAST,MULTICAST,UP,LOWER_UP>
:这些标志描述了接口的能力:BROADCAST
:接口能够发送广播消息。MULTICAST
:接口能够发送多播消息。UP
:接口已经被激活。LOWER_UP
:接口的物理层(比如网线)也是激活状态的。
mtu 1500
:接口的最大传输单元(MTU)是 1500 字节,这是以太网的标准 MTU 大小。
-
队列规则:
qdisc noqueue
表示这个接口没有使用任何队列规则,即它没有排队任何数据包。 -
控制器:
master br-f7e89bf52fb1
表示这个接口是br-f7e89bf52fb1
网桥的从设备。 -
状态:
state UP
表示接口处于激活状态。 -
模式:
mode DEFAULT
表示接口处于默认模式。 -
组:
group default
表示接口属于默认的网络组。 -
硬件地址:
link/ether de:10:51:ed:c1:f3
是这个接口的 MAC 地址。 -
广播地址:
brd ff:ff:ff:ff:ff:ff
是这个接口的广播地址,用于广播传输。 -
网络命名空间 ID:
link-netnsid 0
表示这个接口位于编号为 0 的网络命名空间中。在 Linux 中,网络命名空间用于隔离网络设备和网络状态,0 通常表示全局或宿主机的网络命名空间。
这个输出表明 vethccaf5df
是一个虚拟接口,它与另一个接口 if197
形成了一对,并且它被配置为一个网桥的从设备。这种配置通常用于容器技术,如 Docker 或 Kubernetes,以便在容器和宿主机之间提供网络连接。
我们进入 nightingale 容器,查看一下容器内的网络接口信息:
# 使用 docker exec 进入容器
[root@aliyun-2c2g40g3m ~]# docker exec -it nightingale /bin/bash
# 使用 ip link ls 查看容器内部的网络接口信息,很遗憾,容器内没有 ip 这个命令
root@nightingale:/app# ip link ls
bash: ip: command not found
# 使用 exti 退出容器
root@nightingale:/app# exit
exit
# 既然容器里没有相关命令,我们就使用 nsenter 进入容器的 network namespace,相当于变相进入了容器
# 通过 inspect 找到 Pid
[root@aliyun-2c2g40g3m ~]# docker inspect nightingale | grep Pid
"Pid": 2104897,
"PidMode": "",
"PidsLimit": null,
# 把 pid 作为参数传给 nsenter,-n 表示进入 network namespace
[root@aliyun-2c2g40g3m ~]# nsenter -t 2104897 -n
# 在容器的 network namespace 里,执行 ip link ls 查看网口信息
[root@aliyun-2c2g40g3m ~]# ip link ls
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
203: eth0@if204: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:ac:1f:00:05 brd ff:ff:ff:ff:ff:ff link-netnsid 0
上面的命令我做了详细解释,其中比较关键的是 nsenter 的使用,之前录制过一个小视频讲解,大家可以到视频号:SRETalk 观看。
从上面的输出可以看出,bridge network 下,容器内部的网络接口信息和宿主机是不一样的,这就是 bridge network 的特点。容器内有个网口,索引编号是 203,对端是 if204,if204 其实就是宿主机上的 veth pair 接口的另一半:veth18aab27@if203
。
总结来看,bridge network 下,宿主上会创建一个 bridge,bridge 上挂了很多从设备,各个从设备又分别和某个容器里的 eth0 组成了一个 veth pair。
通过 docker inspect nightingale | grep Pid
我们可以看到 Pid 是 2104897,我们也对比一下这个进程的 network namespace 和宿主上的 systemd 进程的 network namespace,理论上是不同的。
[root@aliyun-2c2g40g3m ~]# ll /proc/2104897/ns/net
lrwxrwxrwx 1 root root 0 Apr 23 16:03 /proc/2104897/ns/net -> 'net:[4026532543]'
[root@aliyun-2c2g40g3m ~]# ll /proc/1/ns/net
lrwxrwxrwx 1 root root 0 Apr 23 16:04 /proc/1/ns/net -> 'net:[4026531994]'
一个是 net:[4026532543]
,另一个是 net:[4026531994]
,明显不同。
Docker compose bridge network 模式下,服务之间的访问,可以通过服务名访问,也可以通过容器 IP 访问,比如在 nightingale 里配置 VictoriaMetrics 数据源地址,可以配置为:http://victoriametrics:8428
,也可以配置为:http://172.31.0.3:8428
。其中 172.31.0.3 就是 VictoriaMetrics 的容器 IP,通过 nsenter 进入 VictoriaMetrics 容器的 network namespace,执行 ip addr
或者 ifconfig
都可以看到。
千万不要配置为:http://127.0.0.1:8428
,不同的容器是有自己各自的回环网卡的,访问 127.0.0.1 就只能访问自己容器里的服务,无法访问其他容器的服务。
附
文末请允许我插播一个小广告。本人创业两年了,我们公司也是做可观测性,和本文思路有些相像之处。如果你有这方面的需求,欢迎联系我们做产品技术交流哈。
🎯 关于快猫星云
快猫星云是一家云原生智能运维科技公司,由知名开源项目“夜莺(Nightingale)”的核心开发团队组成,创始团队均来⾃阿⾥、百度、滴滴等互联⽹公司。夜莺是一款开源云原生监控工具,是中国计算机学会接受捐赠并托管的第一个开源项目,在GitHub上有超过8000颗星,迭代发布了超过100多个版本,上百位社区贡献者,是国内领先的开源可观测性解决方案。
快猫星云以开源夜莺为内核打造的“Flashcat平台”,是国内顶级互联⽹公司可观测性实践的产品化落地,致力于让可观测性技术更好的服务企业,保障服务稳定性。Flashcat 平台具有以下特点:
- 统一采集:采用插件化思路,内置集成上百种采集插件,服务器、网络设备、中间件、数据库、应用、业务,均可监控,开箱即用。
- 统一告警:支持几十种数据源对接,收集各类监控系统的告警事件,进行统一的告警收敛、降噪、排班、认领、升级、协同,大幅提升告警处理效率。
- 统一观测:将 Metrics、Logs、Traces、Events、Profiling 等多种可观测性数据融会贯通,并预置行业最佳实践,既提供全局业务视角、技术视角的驾驶舱,也提供层层下钻的故障定位能力,有效缩短故障发现和定位时间。
快猫星云,让可观测性数据更有价值!
https://flashcat.cloud/