云计算的核心技术是虚拟化,而虚拟化的基础就是隔离和资源限制。对公共的计算、存储、网络资源进行分组(租户),不同组之间进行隔离,在非明确授权的情况下,不同组无法看到对方的资源情况,资源的标识也可以在不同组中独立分配、可以重复,然后对每个组能使用的公共资源进行限制,从而实现公共资源同时被不同的租户使用,每个租户都认为自己独占一部分资源的目的。linux通过在内核中引入namespace和cgroups机制,实现了资源的隔离和限制。
1 cgroups
linux的cgroups(control group
)机制可以实现对进程或进程组能够使用的cpu、内存、网络、硬盘等硬件资源进行限制。我们可以创建一个cgroup,规定该cgroup能使用的cpu、内存、硬盘等资源的数量,然后将需要限制的进程或进程组添加到该cgroup中,那么这些进程和进程组能够使用的最大资源总量就是由该cgroup规定的。
对于开启了cgroup功能的系统,我们查看一下系统的文件系统挂载情况,可以发现有一个文件系统挂载在/sys/fs/cgroup
目录下,如果还没有文件系统挂载在该目录下,我们也可以手动挂载一个:
$ sudo su -
# df
Filesystem 1K-blocks Used Available Use% Mounted on
devtmpfs 65663968 0 65663968 0% /dev
tmpfs 65675528 10244 65665284 1% /dev/shm
tmpfs 65675528 4311396 61364132 7% /run
tmpfs 65675528 0 65675528 0% /sys/fs/cgroup
查看cgroup更详细一点的挂载信息:
# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio)
cgroup on /sys/fs/cgroup/tos_cgroup type cgroup (rw,nosuid,nodev,noexec,relatime,tos_cgroup)
cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/tcp_throt type cgroup (rw,nosuid,nodev,noexec,relatime,tcp_throt)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpu type cgroup (rw,nosuid,nodev,noexec,relatime,cpu)
cgroup on /sys/fs/cgroup/cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
# ls /sys/fs/cgroup/
blkio cpu cpuacct cpuset devices freezer hugetlb memory net_cls net_prio perf_event pids rdma systemd tcp_throt tos_cgroup
/sys/fs/cgroup
目录下有一系列的子目录,每一个目录代表cgroup的一个子系统(Subsystem
),代表了对某一项资源的cgroup配置。这些子系统管理的目标资源和功能为:
Subsystem | 功能简述 |
---|---|
memory | memory使用量限制,当进程申请的内存超过该限额时,会异常结束 |
blkio | 磁盘IO使用量限制 |
cpu | cpu使用量限制 |
cpuacct | cpu使用量统计数据 |
cpuset | cpu绑定,指定属于该cgroup的进程运行在指定的cpu核上 |
devices | 允许或禁止 cgroup 中的任务访问设备。 |
freezer | 暂停/恢复 cgroup 中的任务。 |
hugetlb | 限制使用的内存页数量。 |
net_cls | 使用等级识别符(classid)标记网络数据包,这让 Linux 流量控制器(tc 指令)可以识别来自特定 cgroup 任务的数据包,并进行网络限制。 |
net_prio | 设置网络流量(netowork traffic)的优先级。 |
perf_event | 允许使用 perf 工具来监控 cgroup。 |
pids | pid数量限制 |
1.1 cpu限制
如果我们想为某个Subsystem创建一个cgroup,可以在相应Subsystem目录下创建一个子目录,比如创建一个限制cpu使用量的cgroup:
# cd /sys/fs/cgroup
# cd cpu
# mkdir cpu-test-cgroup
# cd cpu-test-cgroup
# ls
cgroup.clone_children cgroup.procs cpu.bt_shares cpu.cfs_burst_us cpu.cfs_period_us cpu.cfs_quota_us cpu.offline cpu.rt_period_us cpu.rt_runtime_us cpu.shares cpu.stat notify_on_release tasks
可以发现,系统在创建的cpu-test-cgroup目录下自动创建了一些文件。如果我们想限制cgroup使用2个cpu,可以这样做:
# echo 100000 > cpu.cfs_period_us
# echo 200000 > cpu.cfs_quota_us
上面的意思是:在100ms(cpu.cfs_period_us
= 100000)时间周期内,该cgroup能使用200ms(cpu.cfs_quota_us
= 200000)cpu。这个配置有意义的不是它们的绝对值,而是相对值。所以,同样是限制使用2个cpu,也可以这样配置:
# echo 50000 > cpu.cfs_period_us
# echo 100000 > cpu.cfs_quota_us
限制cgroup只能使用0.5个cpu,可以这样配置:
# echo 100000 > cpu.cfs_period_us
# echo 50000 > cpu.cfs_quota_us
要将相应的进程添加到指定的cgroup,将进程PID值添加到tasks文件中即可:
# echo 44971 >> tasks
# echo 43247 >> tasks
查看进程添加的cgroup:
# cat /proc/44971/cgroup
16:rdma:/
15:freezer:/
14:devices:/user.slice
13:memory:/user.slice
12:hugetlb:/
11:perf_event:/
10:pids:/user.slice
9:cpuacct:/user.slice
8:cpu:/cpu-test-cgroup
7:blkio:/user.slice
6:tcp_throt:/
5:cpuset:/
4:net_cls:/
3:tos_cgroup:/
2:net_prio:/
1:name=systemd:/user.slice/user-0.slice/session-7885.scope
查看进程的挂载信息,也能看到cgroup的踪影:
# cat /proc/44971/mountinfo
20 73 0:20 / /sys rw,nosuid,nodev,noexec,relatime - sysfs sysfs rw
21 73 0:4 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
22 73 0:6 / /dev rw,nosuid - devtmpfs devtmpfs rw,size=65663968k,nr_inodes=16415992,mode=755
23 20 0:7 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime - securityfs securityfs rw
24 22 0:21 / /dev/shm rw,nosuid,nodev - tmpfs tmpfs rw
25 22 0:22 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000
26 73 0:23 / /run rw,nosuid,nodev - tmpfs tmpfs rw,mode=755
27 20 0:24 / /sys/fs/cgroup ro,nosuid,nodev,noexec - tmpfs tmpfs ro,mode=755
28 27 0:25 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd
29 20 0:26 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime - pstore pstore rw
30 27 0:27 / /sys/fs/cgroup/net_prio rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,net_prio
31 27 0:28 / /sys/fs/cgroup/tos_cgroup rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,tos_cgroup
32 27 0:29 / /sys/fs/cgroup/net_cls rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,net_cls
33 27 0:30 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuset
34 27 0:31 / /sys/fs/cgroup/tcp_throt rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,tcp_throt
35 27 0:32 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,blkio
36 27 0:33 / /sys/fs/cgroup/cpu rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpu
37 27 0:34 / /sys/fs/cgroup/cpuacct rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuacct
38 27 0:35 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,pids
39 27 0:36 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,perf_event
40 27 0:37 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,hugetlb
41 27 0:38 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,memory
42 27 0:39 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,devices
43 27 0:40 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,freezer
44 27 0:41 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,rdma
71 20 0:19 / /sys/kernel/config rw,relatime - configfs configfs rw
73 0 8:2 / / rw,relatime - ext2 /dev/sda2 rw,errors=continue,user_xattr,acl
18 21 0:18 / /proc/sys/fs/binfmt_misc rw,relatime - autofs systemd-1 rw,fd=32,pgrp=1,timeout=0,minproto=5,maxproto=5,direct
45 20 0:8 / /sys/kernel/debug rw,relatime - debugfs debugfs rw
46 22 0:17 / /dev/mqueue rw,relatime - mqueue mqueue rw
47 22 0:42 / /dev/hugepages rw,relatime - hugetlbfs hugetlbfs rw,pagesize=2M
48 18 0:43 / /proc/sys/fs/binfmt_misc rw,relatime - binfmt_misc binfmt_misc rw
85 73 8:8 / /home rw,relatime - ext4 /dev/sda8 rw,data=ordered
86 73 8:6 / /has rw,relatime - ext2 /dev/sda6 rw,errors=continue,user_xattr,acl
90 85 8:33 / /home/disk2 rw,relatime - ext4 /dev/sdc1 rw,data=ordered
92 85 259:1 / /home/disk4 rw,relatime - ext4 /dev/nvme0n1p1 rw,data=ordered
89 85 8:49 / /home/disk3 rw,relatime - ext4 /dev/sdd1 rw,data=ordered
91 85 8:17 / /home/disk1 rw,relatime - ext4 /dev/sdb1 rw,data=ordered
97 73 8:5 / /matrix rw,relatime - ext2 /dev/sda5 rw,errors=continue,user_xattr,acl
99 73 8:7 / /tmp rw,relatime - ext2 /dev/sda7 rw,errors=continue,user_xattr,acl
101 73 8:3 / /var rw,relatime - ext2 /dev/sda3 rw,errors=continue,user_xattr,acl
103 73 8:4 / /noah rw,relatime - ext2 /dev/sda4 rw,errors=continue,user_xattr,acl
203 101 0:45 / /var/lib/nfs/rpc_pipefs rw,relatime - rpc_pipefs sunrpc rw
253 103 0:46 / /noah/download rw,relatime - tmpfs none rw,size=819200k,mode=755
254 103 0:47 / /noah/modules rw,relatime - tmpfs none rw,size=819200k,mode=755
255 103 0:48 / /noah/tmp rw,relatime - tmpfs none rw,size=409600k,mode=755
256 103 0:49 / /noah/bin rw,relatime - tmpfs none rw,size=102400k,mode=755
257 26 0:50 / /run/user/0 rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=13135108k,mode=700
313 101 8:3 /lib/docker /var/lib/docker rw,relatime shared:102 - ext2 /dev/sda3 rw,errors=continue,user_xattr,acl
312 26 0:52 / /run/user/249958 rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=13135108k,mode=700,uid=249958,gid=100000
314 313 0:53 / /var/lib/docker/overlay2/22e70d61fe729589a13c59eb6fe0b0421b2b9072d0a21240e0665ba968c809cc/merged rw,relatime shared:103 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/XPPLHB3PH6IG7VH4HH4EHWCICD:/var/lib/docker/overlay2/l/DP3PCF7E7YAHQEXZCTYMJW3UPJ,upperdir=/var/lib/docker/overlay2/22e70d61fe729589a13c59eb6fe0b0421b2b9072d0a21240e0665ba968c809cc/diff,workdir=/var/lib/docker/overlay2/22e70d61fe729589a13c59eb6fe0b0421b2b9072d0a21240e0665ba968c809cc/work,index=off
400 26 0:3 net:[4026534085] /run/docker/netns/53974fb2bfb2 rw - nsfs nsfs rw
1.2 memory限制
memory有很多维度的限制项,也包括在内存使用量超出限制时的处理方式。如果我们想创建一个内存使用量限制为1G bytes的cgroup,可以如下配置:
# cd /sys/fs/cgroup/memory
# mkdir memory-test-cgroup
# cd memory-test-cgroup
# echo 1073741824 > memory.limit_in_bytes
1.3 磁盘io限制
限制磁盘/dev/sda1
设备的读速率为1MB/s
,可以如下配置:
# 查看磁盘的设备号
# ls -l /dev/sda1
brw-rw---- 1 root disk 8, 1 Apr 28 15:48 /dev/sda1
# cd /sys/fs/cgroup/blkio
# mkdir blkio-test-cgroup
# cd blkio-test-cgroup
# echo '8:1 1048576' > blkio.throttle.read_bps_device
2 namespace
linux很早就支持进程chroot的功能,进程chroot后,进程看到的根目录就是chroot的目录(往往是进程的工作目录),无法查看和修改文件系统的其他目录,从而将进程的文件系统限制在chroot的范围内。linux的namespace功能可以认为是对chroot功能的升级,不仅扩展和增强了对文件系统的隔离功能,还增加了对其他资源的隔离。
linux namespace将进程使用的资源分为几大类,每类资源可以分别创建自己的namespace,将相应的资源进行隔离,也限制了进程可以使用的资源范围。这几类namespace简述如下:
namespace | 系统调用参数 | 功能简述 |
---|---|---|
Mount namespaces | CLONE_NEWNS | 文件系统namespace。每个namespace有自己的文件系统视图 |
UTS namespaces | CLONE_NEWUTS | UNIX Time-Sharing namespace,主机名和域名namespace。每个namespace有自己的主机名和域名,有独立的/etc/hostame、/etc/hosts、/etc/resovle.conf文件 |
IPC namespaces | CLONE_NEWIPC | 进程间通信namespace。每个namespace有自己的semophore、shm、msgqueue等进程间资源 |
PID namespaces | CLONE_NEWPID | 进程和进程组namespace。每个 namespace有自己的进程树。树根进程在namespace中的PID值为1,它只是整个linux系统进程树的一个子树。 |
User namespaces | CLONE_NEWUSER | 用户和用户组namespace。每个namespace有自己的用户和用户组,比如每个namespace有自己的root用户,但这个root用户只在自己的namespace中具有超级权限 |
Network namespaces | CLONE_NEWNET | 网络namespace。每个namespace有自己独立的网络设备、协议栈、路由表、arp表等资源,namespace间不能直接进行网络通信 |
我们可以通过以下方式查看指定进程的namespace:
# ls -l /proc/44971/ns
total 0
lrwxrwxrwx 1 root root 0 Sep 29 16:10 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Jun 13 19:46 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Jun 13 19:46 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Jun 13 19:42 net -> net:[4026532009]
lrwxrwxrwx 1 root root 0 Jun 13 19:42 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Sep 29 16:10 pid_for_children -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Jun 13 19:46 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jun 13 19:46 uts -> uts:[4026531838]
从上面的结果可知,PID值为44971的进程,它所属的ipc namespace的id为4026531839。
2.1 Mount namespaces
我们查看一下本机的目录:
# ls /
bin boot dev DoorGod etc has home lib lib64 lost+found matrix mnt noah opt proc root run sbin sys tmp usr var
# ls /usr/
bin etc games include lib lib64 libexec local sbin share src tmp
然后启动一个busybox docker容器,查看一下相同目录的内容:
# docker run -it --rm busybox sh
/ # ls /
bin dev etc home proc root sys tmp usr var
/ # ls /usr/
sbin
可见我们启动的busybox容器与宿主机具有独立的文件系统视图
2.2 UTS namespaces
我们查看一下上面启动的busybox容器的hostname:
/ # hostname
763e8f3ab1a6
可见容器的hostname与本机的hostname不一样,而且是一个比较奇怪的,看不出规律的字符串。我们再看一下容器基本信息:
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
763e8f3ab1a6 busybox "sh" 10 minutes ago Up 10 minutes zealous_hertz
原来docker容器的hostname默认就是容器的id,它与宿主机的hostname是隔离的
2.3 PID namespaces
我们继续在busybox容器里查看容器运行的进程的pid:
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 sh
11 root 0:00 ps aux
可见sh
进程在容器中的PID为1,是容器的根进程,但是它在宿主机上的pid是下面这样的:
# ps -ef | grep busybox
root 29266 39096 0 16:22 pts/2 00:00:00 docker run -it --rm busybox sh
2.4 Network namespaces
2.4.1 初识network namespace
network namespace是这些命名空间中最复杂的,linux系统也提供了更多的工具来配置和管理网络命名空间。我们可以使用ip netns list
或ip netns show
命令查看系统已经创建的network namespace:
# ip netns list
表示当前系统还没有创建network namespace。不过需要注意的是,ip netns list
命令查看的是/var/run/netns
目录下的network namespace,而docker创建的network namespace存放在/var/run/docker/netns
目录下:
# ls /var/run/docker/netns
53974fb2bfb2
可见,当前系统其实存在一个由docker创建的network namespace,它对应的文件的inode id是53974fb2bfb2
下面我们创建一个network namespace:
# ip netns add client-ns
创建后我们可以查看已创建的network namespace了:
# ip netns show
client-ns
# ls /var/run/netns
client-ns
我们创建了network namespace,尝试在新创建的network namespace中执行一些网络指令(ip netns exec
命令后面跟network namespace名字,后面再跟具体的命令,表示在指定network namespace中执行对应的命令):
# hostname -i
10.131.21.15
# ip netns exec client-ns ping -c 2 127.0.0.1
connect: Network is unreachable
# ip netns exec client-ns ping -c 2 10.131.21.15
connect: Network is unreachable
可见在client-ns网络命名空间中不仅宿主机的ip无法ping通,连loopback口都无法ping通,查看一下client-ns网络命名空间的接口和路由表内容:
# ip netns exec client-ns ip addr
1: lo: mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
# ip netns exec client-ns ip route
可见在client-ns网络命名空间中,只有一个loopback接口,但是状态为down,路由表没有任何表项
# ip netns exec client ip link set lo up
# ip netns exec client ip link
1: lo: 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
# ip netns exec client ip route
# ip netns exec client ping -c 2 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.025 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.033 ms
# ip netns exec client ping -c 2 10.131.21.15
connect: Network is unreachable
将client-ns网络命名空间的loopback接口设置为up后,loopback接口可以ping通了,但是宿主机还是无法ping通,说明client-ns网络命名空间与宿主机的default网络命名空间还是隔离的
删除network namespace:
# ip netns delete client-ns
2.4.2 veth
既然两个不同的网络命名空间的网络是相互隔离的,那么网络命名空间之间如何实现相互通信呢?可以通过veth
(Virtual Ethernet
),翻译过来就是虚拟以太网。不过这个虚拟以太网比较特殊,它只能连接且必须连接两个虚拟网络设备(虚拟网卡、虚拟网络接口),所以veth又叫做veth pair。同时,以太网的另一个特性:一个网络接口发出的网络报文,其他的接口都能接收到这个网络报文,被虚拟以太网保留了下来。这样,veth相当于一根网线连接了两个网卡,一个网卡发出的网络报文一定被另一个网卡接收到,反方向亦然。
下面我们创建一个veth,这个veth两端的虚拟网络接口名字分别为veth-client、veth-server:
# ip link add veth-client type veth peer name veth-server
这样创建的veth的两端虚拟网络接口都还属于宿主机的default网络命名空间,查看一下:
# ip link
46: veth-server@veth-client: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether a6:b4:ce:a1:39:dd brd ff:ff:ff:ff:ff:ff
47: veth-client@veth-server: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether d6:5a:93:1e:10:88 brd ff:ff:ff:ff:ff:ff
一些常用的网络工具也可以查看创建的虚拟网络设备的信息了:
# ls /sys/class/net/
docker0 erspan0 eth0 eth1 gre0 gretap0 lo natgre srv6br srv6br0 srv6br1 veth12f63d7 veth-client veth-server virbr0 virbr0-nic vnet0 vnet1 xgbe0 xgbe1
# ls /sys/class/net/veth-client
addr_assign_type addr_len carrier dev_id dormant flags ifalias iflink mtu netdev_group phys_port_id phys_switch_id queues statistics tx_queue_len uevent
address broadcast carrier_changes dev_port duplex gro_flush_timeout ifindex link_mode name_assign_type operstate phys_port_name proto_down speed subsystem type
# 查看详细信息
# ip -d link show veth-client
47: veth-client@veth-server: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether d6:5a:93:1e:10:88 brd ff:ff:ff:ff:ff:ff promiscuity 0
veth addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
# 系统虚拟设备中能看到它们
# ls /sys/devices/virtual/net
docker0 erspan0 gre0 gretap0 lo natgre srv6br srv6br0 srv6br1 veth12f63d7 veth-client veth-server virbr0 virbr0-nic vnet0 vnet1
# ethtool -i veth-client
driver: veth
version: 1.0
firmware-version:
expansion-rom-version:
bus-info:
supports-statistics: yes
supports-test: no
supports-eeprom-access: no
supports-register-dump: no
supports-priv-flags: no
根据ethtool -i
命令的driver是veth
可以判断当前虚拟网络设备是一个veth网络设备,如果想知道该veth设备的对端设备是哪一个,可以通过下面的脚本获取:
# peer_ifindex=$(ethtool -S veth-client | awk '/peer_ifindex/ {print $2}')
# ip link | awk -v idx="$peer_ifindex" -F: '$1==idx {print $2}'
veth-server@veth-client
为veth的两个虚拟网络设备配置IP地址:
# ip addr add 172.18.0.11/16 dev veth-client
# ip link set veth-client up
# ip addr add 172.18.0.12/16 dev veth-server
# ip link set veth-server up
查看设备状态,路由表内容,并测试网络连通性:
# ip link list | grep veth
46: veth-server@veth-client: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
47: veth-client@veth-server: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
# 路由表中添加了两条直连路由
# ip route | grep veth
172.18.0.0/16 dev veth-client proto kernel scope link src 172.18.0.11
172.18.0.0/16 dev veth-server proto kernel scope link src 172.18.0.12
# ping -c 2 172.18.0.11
PING 172.18.0.11 (172.18.0.11) 56(84) bytes of data.
64 bytes from 172.18.0.11: icmp_seq=1 ttl=64 time=0.038 ms
64 bytes from 172.18.0.11: icmp_seq=2 ttl=64 time=0.050 ms
# ping -c 2 172.18.0.12
PING 172.18.0.12 (172.18.0.12) 56(84) bytes of data.
64 bytes from 172.18.0.12: icmp_seq=1 ttl=64 time=0.034 ms
64 bytes from 172.18.0.12: icmp_seq=2 ttl=64 time=0.043 ms
既然veth是让两个网络命名空间能够进行通信的,将veth的两个虚拟网络接口放到不同的网络命名空间中,才比较有意义。现在将其中一个网络接口放到client-ns网络命名空间中:
# ip link set veth-client netns client-ns
再次查看宿主机和client-ns网络命名空间的情况:
# ip addr | grep veth
46: veth-server@if47: mtu 1500 qdisc noqueue state LOWERLAYERDOWN group default qlen 1000
inet 172.18.0.12/16 scope global veth-server
# ip netns exec client-ns ip addr | grep veth
47: veth-client@if46: mtu 1500 qdisc noop state DOWN group default qlen 1000
宿主机的default网络命名空间已经没有了veth-client虚拟网络设备,且veth-server的展示名字由veth-server@veth-client
改为veth-server@if47
,47正是veth-client接口的索引号。veth-client配置的ip地址没有了,状态也变为down
重新为veth-client接口配置ip地址,并让接口up:
# ip netns exec client-ns ip addr add 172.18.0.11/16 dev veth-client
# ip netns exec client-ns ip link set veth-client up
继续进行网络连通性测试:
# 宿主机ping网络命令空间中的veth接口
# ping -c 2 172.18.0.11
PING 172.18.0.11 (172.18.0.11) 56(84) bytes of data.
64 bytes from 172.18.0.11: icmp_seq=1 ttl=64 time=0.048 ms
64 bytes from 172.18.0.11: icmp_seq=2 ttl=64 time=0.033 ms
# 网络命名空间中ping宿主机的veth接口
# ip netns exec client-ns ping -c 2 172.18.0.12
PING 172.18.0.12 (172.18.0.12) 56(84) bytes of data.
64 bytes from 172.18.0.12: icmp_seq=1 ttl=64 time=0.067 ms
64 bytes from 172.18.0.12: icmp_seq=2 ttl=64 time=0.053 ms
# 网络命名空间中ping宿主机的其它接口
# ip netns exec client-ns ping -c 2 10.131.21.15
connect: Network is unreachable
查看client-ns中的路由表:
# ip netns exec client-ns ip route
172.18.0.0/16 dev veth-client proto kernel scope link src 172.18.0.11
只有一条路由,所以能ping通veth-client和veth-server,但是无法ping通主机上的其他接口
删除veth:
# ip link delete veth-server type veth peer name veth-client
2.4.3 bridge
系统上往往存在多个network namespace,往往通过bridge将这些虚拟接口连接起来。
我们可以通过以下方式添加一个网桥br0,并将接口veth-server添加到网桥br0
# ip link add br0 type bridge
# ip link set veth-server master br0
# ip link set br0 up
或者通过brctl
命令添加:
# brctl addbr br0
# brctl addif br0 veth-server
创建后查看新增的网桥信息:
# brctl show br0
bridge name bridge id STP enabled interfaces
br0 8000.2aec9e8cd885 no veth-server
# ip addr
46: veth-server@if47: mtu 1500 qdisc noqueue master br0 state UP group default qlen 1000
link/ether 2a:ec:9e:8c:d8:85 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet 172.18.0.12/16 scope global veth-server
valid_lft forever preferred_lft forever
inet6 fe80::28ec:9eff:fe8c:d885/64 scope link
valid_lft forever preferred_lft forever
48: br0: mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 2a:ec:9e:8c:d8:85 brd ff:ff:ff:ff:ff:ff
inet6 fe80::28ec:9eff:fe8c:d885/64 scope link
valid_lft forever preferred_lft forever
为网桥br0配置ip地址,同时删除veth-server上配置的ip地址
# ip addr add 172.18.0.1/16 dev br0
# ip addr delete 172.18.0.12/16 dev veth-server
再次查看接口:
46: veth-server@if47: mtu 1500 qdisc noqueue master br0 state UP group default qlen 1000
link/ether 2a:ec:9e:8c:d8:85 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::28ec:9eff:fe8c:d885/64 scope link
valid_lft forever preferred_lft forever
48: br0: mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 2a:ec:9e:8c:d8:85 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 scope global br0
valid_lft forever preferred_lft forever
inet6 fe80::28ec:9eff:fe8c:d885/64 scope link
valid_lft forever preferred_lft forever
这时可以相互ping通了,但是client-ns中还不能ping通宿主机接口
# ping -c 2 172.18.0.11
PING 172.18.0.11 (172.18.0.11) 56(84) bytes of data.
64 bytes from 172.18.0.11: icmp_seq=1 ttl=64 time=0.049 ms
# ip netns exec client-ns ping -c 2 172.18.0.1
PING 172.18.0.1 (172.18.0.1) 56(84) bytes of data.
64 bytes from 172.18.0.1: icmp_seq=1 ttl=64 time=0.049 ms
# ip netns exec client-ns ping -c 2 10.131.21.15
connect: Network is unreachable
查看网桥br0上的mac转发表,表明网桥br0学习到了client-ns网络命名空间中veth-client接口的mac地址:
# brctl showmacs br0
port no mac addr is local? ageing timer
1 2a:ec:9e:8c:d8:85 yes 0.00
1 2a:ec:9e:8c:d8:85 yes 0.00
1 8a:15:7e:36:73:d5 no 265.65
client-ns无法ping通宿主机的原因是client-ns的路由表中只有到veth-client接口的主机路由,配置一条默认路由就可以ping通了:
# ip netns exec client-ns ip route add default via 172.18.0.1
# ip netns exec client-ns ip route
default via 172.18.0.1 dev veth-client
172.18.0.0/16 dev veth-client proto kernel scope link src 172.18.0.11
# ip netns exec client-ns ping -c 2 10.131.21.15
PING 10.131.21.15 (10.131.21.15) 56(84) bytes of data.
64 bytes from 10.131.21.15: icmp_seq=1 ttl=64 time=0.049 ms
64 bytes from 10.131.21.15: icmp_seq=2 ttl=64 time=0.048 ms
如果还是不能ping通,检查/proc/sys/net/ipv4/ip_forward
配置是否允许路由转发,并保证ip_forward值为1:
# cat /proc/sys/net/ipv4/ip_forward
0
# echo 1 > /proc/sys/net/ipv4/ip_forward
在client-ns网络命名空间中配置了默认路由后,已经可以ping通宿主机的对外连接物理网卡了,这时client-ns中ping外部的ip地址的报文已经可以到达外部目的主机了,但是因为linux并没有运行路由协议,外部网络还没有client-ns网络命名空间中的ip路由信息,所以它的响应报文无法发送给client-ns网络空间中的虚拟主机。我们可以在宿主机上配置一个SNAT
功能,在client-ns空间发出的报文离开对外连接物理网卡时将client-ns空间的ip地址修改为对外物理网卡的地址,报文返回时,再恢复为client-ns空间中的ip地址
# iptables -t nat -A POSTROUTING -s 172.18.0.0/16 -o eth0 -j MASQUERADE
这样就可以ping通网络中其他设备了(假设该eth0接口可以访问域名:webrelay.weiyun.com):
# ip netns exec ns1 ping -c 1 webrelay.weiyun.com
PING webrelay.weiyun.com (10.27.5.56) 56(84) bytes of data.
64 bytes from 10.27.5.56 (10.27.5.56): icmp_seq=1 ttl=54 time=51.0 ms