站内链接:

Install

Mac

下载 docker for mac, 安装即可

Ubuntu

安装: sudo apt-get install -susdo docker.io

添加用户: sudo usermod -a -G docker <username>

启动服务: sudo service docker start

虚拟网络

多 IP

一个网卡上设置多个 IP

1
2
3
4
# 1. 给eth0网卡设备添加新的IP, 新的名字必须为: ifconfig <interface>:<number>, 否则不会生效
ifconfig eth0:1 192.168.56.2 netmask 255.255.255.0
# 2. 查看
ip address eth0

tap 和 tun

tap/tun虚拟网卡或者虚拟网络设备是 linux2.4 之后实现的虚拟网络设备, 该虚拟网卡完全由软件来实现, 功能同硬件实现没有区别, 都可以配置 IP, 都归属于网络设备管理模块统一调度. 作为网络设备, tap/tun也需要相应的驱动程序: 字符设备驱动, 网卡驱动, 其中他们的字符驱动设备文件如下:

  • tap: /dev/tap0, tap 是一个二层或者以太网设备, 仅仅处理以太网帧(mac)
  • tun: /dev/net/tun, tun 是一个点对点的三层或者网络层设备, 仅仅处理 IP 数据包

tap/tun常常用于用于隧道通信, 比如 VPN. tap/tun和网络协议栈的数据传输同物理网卡流程大体类似, 但是有一些区别:

  • 物理网卡: 应用程序 — 网络协议栈(network protocol stack) — eth0 — 物理设备
  • tap/tun: 应用程序 — 网络协议栈 — tun0 — 应用程序的用户空间, 类似一个管道

用户程序向/dev/tap0或者/dev/net/tun中 send 数据, 内核协议栈从相应接口 recv 数据并通过其他接口转化出去, 反之亦然.

veth-pair

veth-pair网络是一对的虚拟设备接口, veth-pair不同于tap/tun, 这是一对设备接口, 一端连接着协议栈, 一端连接着彼此, 故它常常作为桥梁连接着各种虚拟网络, 例如两个 NS 之间, docker 容器之间的连接, 并基于此构建复杂的虚拟网络结构. 下面是 docker 中宿主机和容器之间的veth-pair网卡的抓包记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 在宿主机上监视veth虚拟设备接口
tcpdump -nnt -i veth4465607

# 2. 宿主机ping该虚拟网络设备接口对应的ip
ping -c 2 172.17.0.2
# 或者(注意走的是docker0)
ping -I docker0 -c 2 172.17.0.2

# 3. 此时tcpdump的输出
listening on veth4465607, link-type EN10MB (Ethernet), capture size 262144 bytes
ARP, Request who-has 172.17.0.2 tell 172.17.0.1, length 28
ARP, Reply 172.17.0.2 is-at 02:42:ac:11:00:02, length 28
IP 172.17.0.1 > 172.17.0.2: ICMP echo request, id 4, seq 1, length 64
IP 172.17.0.2 > 172.17.0.1: ICMP echo reply, id 4, seq 1, length 64

我们可以使用ethtoolip addr或者ip link简单看下veth-pair的对应关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 宿主机中查看某个veth, veth4465607就是上面抓包的监听虚拟网卡(if7 -> if8)
ethtool -S veth4465607
# ---> output:
NIC statistics:
peer_ifindex: 7

# 2. 宿主机中查看ip link
8: veth4465607@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether 52:7c:f1:ac:80:70 brd ff:ff:ff:ff:ff:ff link-netnsid 0

# 3. 进入容器查看ip link或者 ip addr, 发现上面的ifindex(peer_ifindex)值就是容器中的网络设备索引号
ip link
# ---> ouput(首字符数字就是ifindex):
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
7: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0

关于多个 namespace 之间通过veth-pair直接相连, 基于 linux bridge 连接veth-pair, 基于 ovs 连接veth-pair等结构这里暂不详细了解.

vlan

vlan(virtual LAN)也是一种网络设备(虚拟局域网), 一般指代由路由器分割的网络或者广播域. 广播域, 指的是广播帧(目标 MAC 地址全部为 1)所能传递到的范围, 亦即能够直接通信的范围. 当然, 在虚拟化上 linux 也支持 VLAN 网络虚拟化. 通过在交换机上设置 vlan 网络:

  • 避免了广播域在整个大网络上的发送和占用带宽(每一个广播到达到所有的终端)
  • 提高了网络传递的效率, 减少了终端的负载压力.

一旦设置了 vlan, 即使是在一台交换机上设置了两个不同的 vlan, 则在没有设置 vlan 间通信的配置之前, 两个 vlan 的终端是无法互相通信的. 如果希望 vlan 间能互相通信, 则需要router提供中继服务, 即Vlan间路由, 这个后续在其他文章中再详细介绍.

docker

网络模式

Linux 上默认的 Docker 的网络结构如下图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
+------------------------------------------------------------------------+
| Host |
| |
| +------------------+ +------------------+ |
| | | | | |
| | | | | |
| | Container1 | | Container2 | |
| | | | | |
| | +--------+ | | +--------+ | |
| | | eth0 | | | | eth0 | | |
| +----+---+----+----+ +----+----+---+----+ |
| ^ 172.17.0.2 ^ 172.17.0.3 |
| | | |
| | | |
| v v |
| +---+-----+ +----+----+ |
| | | | | |
| +----+ vethXX +------------------------------+ vethYY +----+ |
| | | | | | | |
| | +---------+ docker0 +---------+ | |
| | +------------+ | |
| +-----------------------+ docker0 +-----------------------+ |
| +-----+------+ 172.17.0.1/16 |
| |
| +--------------------------+ |
+-----------------------| eth0 |---------------------+
+--------------------------+
10.10.10.1

默认情况下, 会新建一个docker0的网桥, docker0 通过在内部软件虚拟出一个交换机, 该网桥会在 docker 服务器启动的时候自动创建, 其 IP 地址一般默认为172.17.0.1/16, 该网桥将主机和所有使用该网络的容器放在同一个网络命名空间(物理网络), 实现了主机和容器之间的相互通信. docker 利用特殊的技术虚拟出vethXX(即 2.3 节的 veth-pair 结构), 之后利用这些接口与容器中的ethXX来进行通信, 这样宿主机和容器, 容器和容器之间就可以互相通信.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1. 获取当前ubuntu系统中两个容器的IP地址
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mysql-slave
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mysql-master
# ---> 输出:
172.17.0.2
172.17.0.3

# 查看宿主机docker0网桥
ip addr show docker0
# ---> 输出:
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:40:2f:01:44 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:40ff:fe2f:144/64 scope link
valid_lft forever preferred_lft forever

# 3. 实际上该网桥即docker默认的bridge网络
docker network ls

# 4. 获取命令bridge默认网络的子网
docker inspect -f '{{range.IPAM.Config}}{{.Subnet}}{{end}}' bridge
# ---> 输出:
172.17.0.0/16

注意, mac 和 windows 上的 docker 和宿主机之间还封装了一个虚拟机, 所以后两者的网络结构有一些不同. 一般而言, docker 有四种网络模式:

  • bridge: 默认模式, 独立的 network namespace, 每一个容器有独立的container-ip, 并见容器连接到docker0虚拟网桥中, 通过 docker0 和宿主机通信
  • host: 容器和宿主机共享 Network namespace, 使用宿主机的 IP, 端口
  • container: 容器和另外一个容器共享 Network namespace, 例如 k8s 中的 pod 就是多个容器共享一个 NS
  • none: 容器有独立的 NS, 但是 IP 地址等信息未配置, 需要使用者自行设置网络信息

在 docker 服务启动之后, 系统会默认创建三个网络模式:

1
2
3
4
5
6
docker network ls
# ---> output:
NETWORK ID NAME DRIVER SCOPE
4d2c9e94895b bridge bridge local
87be1e06cac9 host host local
eafecb332664 none null local

当然, 除了这三个默认创建的网络模式之外, 用户可以自行创建自定义网络模式, 这是一种非常通用普通的做法, 比如一个 docker-compose 一般都会创建一个唯一的网络用于相关容器的通信.

网桥实战

关于 bridge 的特点和使用场景上面已经提及, 下面是测试例子, 首先创建一个独立的自定义 bridge 网络

1
2
3
4
5
6
# 1. 创建网络
docker network create -d bridge localnet
# 2. 查看网络以及网络关联的所有容器
docker network inspect localnet
# 3. 如果希望删除网络, 则可以使用下面的命令
docker network rm localnet

在 2.1 节中我们创建了两个容器分别表示主从, 他们使用默认的 docker0 网桥, 现在让我们看下网桥的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. 通过brctl查看网桥, 其中interfaces栏输出表示插在该网桥上的网卡信息, 每次新增一个自定义网络, 这里的输出就多一行. 一旦网桥新增网卡信息, 则interfaces就增加一个描述
brctl show
# ---> output
root@bamboo:~# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242402f0144 no veth02c8df0
vethc2bd32e

# 2. 此时在宿主机上可以使用ip addr查看这两个网卡的信息veth02c8df0(device name)
ip addr show veth02c8df0
# ---> output
10: veth02c8df0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether d6:b0:b8:fc:7f:04 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::d4b0:b8ff:fefc:7f04/64 scope link
valid_lft forever preferred_lft forever

# 3. 使用同一个网桥的两个容器互相通信(可以ping通)
docker exec -it u1 bash
ping 172.17.0.3
docker exec -it u2 bash
ping 172.17.0.2

下面我们随便启动两个容器(为了测试, 暂时关闭了上面的 mysql 主从), 在启动之后进入容器之中查看他们的网卡信息确认和上面在宿主机中的输出信息保持一致:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 1. 容器
docker run -td --name u1 ubuntu:bifeng bash
docker run -td --name u2 ubuntu:bifeng bash
docker ps

# ---> output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
382476cbcf79 ubuntu:bifeng "bash" 2 seconds ago Up 1 second u2
8b01d5c7fdca ubuntu:bifeng "bash" 3 seconds ago Up 1 second u1

# 2. 宿主机查看网络地址
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' u1 u2
# ---> output:
172.17.0.2
172.17.0.3

# 3. 进入容器并查看
ip addr show eth0
# ---> output: 此时你会发现序号和网卡序号是反着
15: eth0@if16: <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 link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
# 宿主机上
ip addr show veth613f48a
# ---> output:
16: veth613f48a@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether d6:e9:4e:8b:af:e9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::d4e9:4eff:fe8b:afe9/64 scope link
valid_lft forever preferred_lft forever

2.1 节的网络地址结构图可以发现, docker0 网桥为每个容器创建了一个对等虚拟网卡veth pair设备:

  • vethNUM: 容器在宿主机网桥中的网络设备名
  • eth0: 容器中的网络设备名

这两者是一一对应的. 注意, 查看上面宿主机的网络地址输出, 发现其输出 container ip 和 mysql 主从 IP 是一模一样, 这是因为 mysql 主从已经关闭了, 新的 container 的网络 IP 地址分配仍然是从172.17.0.1开始, 这也是为什么自定义 bridge 或者其他模式提出的意义, 在同一个网络 NS 中, 不同的容器不是使用网络 IP 而是使用网络alias来进行相互通信, 当然, 对外暴露的服务仍然需要分配公网 IP 地址.

host 模式

注意, macOS 中docker for mac不支持该模式.在该模式下, 容器将不会拥有自己的网络 namespace, 根据第二节的介绍我们了解到 namespace 是 linux 实现网络虚拟化的关键, 但是在 host 模式下, 容器和宿主机共有一个 NS, 容器不再拥有自己的网卡,IP 信息, 但是除了网络之外的其他资源仍然拥有自己的 NS.

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

┌──────────────────────────────────────────────────┐
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ container1 │ │ container2 │ │
│ │ │ │ │ │
│ └──────┬──────┘ └───────┬─────┘ │
│ 80 │ 443 3306 │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ └───────────────┬───────────────┘ │
│ │ │
│ │ │
│ ┌────────▼─────────┐ │
│ │ │ │
└────────────────┤ eth0 ├──────────────┘
│ │
└──────────────────┘
80 443 3306

none 和 container

container 模式下新创建的容器和已经存在的一个容器共享一个 Network Namespace, 新创建的容器不会拥有自己的网卡, IP 地址信息, 但是除了网络之外的其他资源仍然拥有自己的 NS.

none 模式下类似 bridge 模式, 但像 bridge 的初始化未赋值, 此时各个 NS 之中没有网卡, IP 地址信息.

参考: