k8s基础应用二:linux的namespace和cgroups机制

2023年 10月 2日 110.9k 0

云计算的核心技术是虚拟化,而虚拟化的基础就是隔离和资源限制。对公共的计算、存储、网络资源进行分组(租户),不同组之间进行隔离,在非明确授权的情况下,不同组无法看到对方的资源情况,资源的标识也可以在不同组中独立分配、可以重复,然后对每个组能使用的公共资源进行限制,从而实现公共资源同时被不同的租户使用,每个租户都认为自己独占一部分资源的目的。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 listip 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

既然两个不同的网络命名空间的网络是相互隔离的,那么网络命名空间之间如何实现相互通信呢?可以通过vethVirtual 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

相关文章

服务器端口转发,带你了解服务器端口转发
服务器开放端口,服务器开放端口的步骤
产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
如何使用 WinGet 下载 Microsoft Store 应用
百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

发布评论