惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
V
V2EX
爱范儿
爱范儿
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Martin Fowler
Martin Fowler
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
宝玉的分享
宝玉的分享
Last Week in AI
Last Week in AI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
罗磊的独立博客
小众软件
小众软件
Vercel News
Vercel News
博客园 - 司徒正美
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
J
Java Code Geeks
P
Proofpoint News Feed
MongoDB | Blog
MongoDB | Blog
B
Blog
美团技术团队
量子位

郑文峰的博客

使用dify对接飞书多维表格 使用n8n对接飞书多维表格 服务启动时出现 OOM 一次服务升级时pg表DDL执行超时失败 Go语言高效IO缓冲技术详解 Go语言延迟初始化(Lazy Initialization)最佳实践 Go语言字符串拼接性能对比与优化指南 Go语言结构体内存对齐完全指南 Go语言空结构体:零内存消耗的高效编程 Go语言堆栈分配与逃逸分析深度解析 Go语言原子操作完全指南 Go语言内存预分配完全指南 Go语言不可变数据共享:无锁并发编程实践 Go语言零拷贝技术完全指南 Go语言遍历性能深度解析:从原理到优化实践 Go语言Interface Boxing原理与性能优化指南 Go协程池深度解析:原理、实现与最佳实践 使用etcd分布式锁导致的协程泄露与死锁问题 基于pre-commit的Python代码规范落地实践 初识 MCP Server pulsar阻塞导致logstash无法接入日志 django-prometheus使用及源码分析 kube-proxy源码分析 kubernetes service如何通过iptables转发 tcp缓存引起的日志丢失 django-apschedule定时任务异常停止 理解calico容器网络通信方案原理 理解flannel的三种容器网络方案原理 理解Linux IPIP隧道 理解VXLAN网络
docker容器单机网络
zhengwenfeng · 2023-01-08 · via 郑文峰的博客

# 前言

通过文章 容器的本质 (opens new window)可知,容器只是一个进程,而容器所能看到的网络栈,是隔离在自己的 Network Namespace (opens new window) 中。docker 容器单机网络支持四种网络模式,也都是基于 Network Namespace 实现的。本文主要是介绍这四种模式的使用方法及实现原理。

# host

使用该模式的容器和宿主机是在同一个 Network Namespace 中的,所以和宿主机用的是同一个网络栈,那么容器暴露的端口,也就是宿主机上端口。

注意,使用该模式,需要关注端口冲突

通过添加 --net=host 参数即可开启 host 模式

docker run -d --net=host nginx

1

因为和宿主机使用的是同一个网络栈,所以容器与宿主机是可以互相连通的,在宿主机上直接可以通过 127.0.0.1 访问到该容器的的端口。

运行另一个容器进入其中执行 curl 127.0.0.1 可以看到一样可以访问到 nginx 暴露的 80 端口,因为都是使用宿主机网络栈。

 docker run -it --net=host curlimages/curl curl 127.0.0.1

1

# bridge

# 原理

该模式为桥接模式,创建容器时会创建属于自己的 Network Namepsace,该容器和宿主机使用的是不同的 Network Namespace,也就是说它们使用的是不同的网络栈。

bridge 网络模型的实现原理可以参考文章 手动实现docker容器bridge网络模型 (opens new window)

宿主机创建了 docker0 作为虚拟网桥,其作用主要是作为交换机在二层网络,再将使用 bridge 模式创建的容器通过 veth pair 连接到 dcoker0 上,这样连接到 docker0 上的容器都可以互相网络通信。

veth pair 类似一个管道,数据包会从一端到另一端。

# 验证

默认运行容器时使用的就是 bridge 模式,docker 会自动为容器添加 veth pair 并配置好其 ip 地址,这里的 eth0 就是其中的一端,可以看到其 ip 地址为 172.17.0.2

[root@localhost ~]# docker run -d --name nginx1 nginx

[root@localhost ~]# docker exec -it nginx1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
20: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

1
2
3
4
5
6
7
8
9
10
11

veth pair 的另一端会接入到 docker0 上,在容器中执行以下命令可以看到 veth pair 另一端的序号

[root@localhost ~]# docker exec nginx1 cat /sys/class/net/eth0/iflink
21

1
2

在宿主机上可以看到 21 序号上的 veth pair 的名称是 veth702ba20,也就是管道的另一端。

[root@localhost ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether fe:fc:fe:af:4b:ea brd ff:ff:ff:ff:ff:ff
    inet 10.61.74.37/23 brd 10.61.75.255 scope global noprefixroute ens18
       valid_lft forever preferred_lft forever
    inet6 fe80::1bdd:fe7:4a90:1a67/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:84:c1:3b:ea brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:84ff:fec1:3bea/64 scope link 
       valid_lft forever preferred_lft forever
21: veth702ba20@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 0a:21:51:0c:7e:db brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::821:51ff:fe0c:7edb/64 scope link 
       valid_lft forever preferred_lft forever

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

再查看 docker0 插入的设备可以发现 veth702ba20 是插入在 docker0 上的。

[root@localhost ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.024284c13bea       no              veth702ba20

1
2
3

# 容器网络互通

再创建另一个容器 nginx2,查看其 ip 为 172.17.0.3,可以发现 nginx1 是可以 ping 通 nginx2 的该 ip 的。

[root@localhost ~]# docker run -d --name nginx2 nginx
[root@localhost ~]# docker exec nginx2 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
54: eth0@if55: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

1
2
3
4
5
6
7
8
9
10
11

为什么可以互通呢?

我们来看下 nginx1 的路由,当 ping nginx2 的 ip 时,会匹配到第二条路由,然后走 eth0 网卡,因为其是 veth pair 的一端,数据包会在另一端出现,另一端接入到了 docker0 上,最终数据包到达 docker0

[root@localhost ~]# docker exec nginx1 ip r
default via 172.17.0.1 dev eth0 
172.17.0.0/16 dev eth0  proto kernel  scope link  src 172.17.0.2 

1
2
3

当通过 nginx1 ping nginx2 的 ip 时,我过监听 docker0 网卡看一下数据包:

[root@localhost ~]# docker exec -it nginx1 ping 172.17.0.3 -c 3

[root@localhost ~]# tcpdump -i docker0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on docker0, link-type EN10MB (Ethernet), capture size 262144 bytes
04:32:57.596814 ARP, Request who-has 172.17.0.3 tell 172.17.0.2, length 28
04:32:57.596848 ARP, Reply 172.17.0.3 is-at 02:42:ac:11:00:03 (oui Unknown), length 28
04:32:57.596853 IP 172.17.0.2 > 172.17.0.3: ICMP echo request, id 17, seq 1, length 64
04:32:57.596896 IP 172.17.0.3 > 172.17.0.2: ICMP echo reply, id 17, seq 1, length 64
04:32:58.596437 IP 172.17.0.2 > 172.17.0.3: ICMP echo request, id 17, seq 2, length 64
04:32:58.596492 IP 172.17.0.3 > 172.17.0.2: ICMP echo reply, id 17, seq 2, length 64
04:32:59.596444 IP 172.17.0.2 > 172.17.0.3: ICMP echo request, id 17, seq 3, length 64
04:32:59.596491 IP 172.17.0.3 > 172.17.0.2: ICMP echo reply, id 17, seq 3, length 64
04:33:02.598361 ARP, Request who-has 172.17.0.2 tell 172.17.0.3, length 28
04:33:02.598386 ARP, Reply 172.17.0.2 is-at 02:42:ac:11:00:02 (oui Unknown), length 28

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

由上可知,nginx1(10.17.0.2) 会发送 ARP 获取 nginx2(10.17.0.3) 的 mac 地址,然后使用该 mac 地址通过二层设备 bridge 向 nginx2 转发数据包,进入到了 nginx2 的 Network Namespace 中,由它的网络栈处理该数据包,最后回包。

# 容器连接其他主机

容器内连接其他主机时,比如 ping 10.65.132.187 时,会先通过 docker0 达到宿主机上,然后通过宿主机的网络栈处理。

通过查看宿主机路由表,到达宿主机的数据表会走第一条默认路由,通过 eth0 网卡下一跳到 10.61.74.1,然后最终达到另一台主机的 eth0 中。

[root@localhost ~]# ip r
default via 10.61.74.1 dev eth0 proto static metric 100 
10.61.74.0/23 dev eth0 proto kernel scope link src 10.61.74.37 metric 100 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1

1
2
3
4

# container

使用该模式的容器会加入到指定容器的 Network Namespace 中,也就是两个容器共用同一个网络栈。

首先使用 bridge 模式创建容器 nginx1,该容器会拥有自己的 Network Namespace,然后再使用 container 模式创建 nginx2 容器并加入 nginx1 的 Network Namespace 中。

通过查看两个容器的网卡可以发现两个是一样的。

[root@localhost ~]# docker run -d --name nginx1 nginx
[root@localhost ~]# docker exec -it nginx1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
56: eth0@if57: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

[root@localhost ~]# docker run -it --name nginx2 --net=container:nginx1 nginx /bin/bash

[ root@20069e4c2bde:/etc/nginx ]$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
56: eth0@if57: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# none

该模式创建容器也会创建新的属于自己的 Network Namespace,但是容器内不会有任何的网络配置,没有网卡、路由、路由等信息,需要由我们自己去配置。

[root@localhost ~]# docker run -it --name nginx --net=none nginx /bin/bash

[ root@52480b0a4725:/etc/nginx ]$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
[ root@52480b0a4725:/etc/nginx ]$ ip r

1
2
3
4
5
6
7
8