郑文峰的博客 郑文峰的博客
首页
  • python之路
  • go之路
  • 其他
  • redis
  • mysql
  • docker
  • k8s
读书破万卷
周刊
关于
  • 导航 (opens new window)
  • 代码片段 (opens new window)
  • 收藏
  • 友链
  • 外部页面

    • 开往 (opens new window)
  • 索引

    • 分类
    • 标签
    • 归档
GitHub (opens new window)

zhengwenfeng

穷则变,变则通,通则久
首页
  • python之路
  • go之路
  • 其他
  • redis
  • mysql
  • docker
  • k8s
读书破万卷
周刊
关于
  • 导航 (opens new window)
  • 代码片段 (opens new window)
  • 收藏
  • 友链
  • 外部页面

    • 开往 (opens new window)
  • 索引

    • 分类
    • 标签
    • 归档
GitHub (opens new window)
  • docker

    • 容器的本质
    • docker容器
    • 手动实现docker容器bridge网络模型
    • docker容器单机网络
      • 前言
      • host
      • bridge
        • 原理
        • 验证
        • 容器网络互通
        • 容器连接其他主机
      • container
      • none
  • k8s

  • 云原生
  • docker
zhengwenfeng
2023-01-08
目录

docker容器单机网络

# 前言

通过文章 容器的本质 (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
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

#docker#云原生#容器
上次更新: 2023/01/15, 15:47:48
手动实现docker容器bridge网络模型
k8s之Pod

← 手动实现docker容器bridge网络模型 k8s之Pod→

最近更新
01
django rest_framework 分页
03-20
02
学习周刊-第03期-第09周
03-03
03
学习周刊-第02期-第08周
02-24
更多文章>
Theme by Vdoing | Copyright © 2022-2023 zhengwenfeng | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式