linux系统支持命名空间、cgroup和overlay层次文件系统,docker就是调用操作系统提供的这些接口来实现各种隔离效果
命名空间
由linux系统内核提供的功能,用于资源隔离,产生虚拟化技术,命名空间下运行的程序只能感知到当前命名空间下的其他进程和文件系统,主要分为以下几种命名空间:
命名空间 | 函数参数 | 隔离内容 |
---|---|---|
UTS | CLONE_NEWUST | 主机名和域名,是Unix Time-sharing System简写 |
IPC | CLONE_NEWIPC | 信号量、消息队列和共享内存 |
PID | CLONE_NEWPID | 进程,不同命名空间下的进程ID可以相同 |
Network | CLONE_NEWNET | 网络设备和网卡等 |
Mount | CLONE_NEWNS | 文件挂载 |
User | CLONE_NEWUSER | 用户和用户组 |
以上6种隔离在/proc/<进程ID>/ns
目录下可以找到对应的文件,通过ll
命令查看任意一个进程如下:
ll /proc/1123/ns
lrwxrwxrwx 1 root root 0 Jun 13 14:38 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Jun 13 14:38 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Jun 13 14:38 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 Jun 13 14:38 net -> 'net:[4026531993]'
lrwxrwxrwx 1 root root 0 Jun 13 14:38 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Jun 13 14:38 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Jun 13 14:38 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Jun 13 14:38 uts -> 'uts:[4026531838]'
其中像4026531835
这样的数字是namespace号,再查看其他进程的ns目录,会发现相同的namespace号,namespace号相同表示两个进程处于同一个命名空间下
linux自带的clone函数可用来创建子进程,其中参数flags就可以指定隔离机制,通过man命令查看clone函数
man clone
网络命名空间
通过网络命名空间对网络进行隔离,其他都不隔离,比如安装的软件,常用的命令如下:
// 列出当前存在的网络命名空间
ip netns ls
// 创建一个叫test的网络命名空间
ip netns add test
// 删除test网络命名空间
ip netns delete test
// 进入test网络命名空间
ip netns exec test bash
// 退出网络命名空间
exit
// 不进入命名空间,直接执行命令,可执行任意命令
ip netns exec test netstat -anp
命名空间内的操作
进入指定的网络命名空间后执行
// 查看当前命名空间下的网络接口
ip link
// 输出以下结果
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
创建一个新的网络命名空间后默认会有一个lo,状态是DOWN,表示未启动,此时ping 127.0.0.1
是不通的,可通过以下命令启动
// 启动lo
ip link set lo up
// 关闭lo
ip link set lo down
通过以下命令查看IP地址
// 查看命名空间下的IP
ip addr
命名空间之间通信
在系统命名空间下创建两个虚拟网卡
ip link add interface1 type veth peer name interface2
// 查看接口,可查看到interface2@interface1和interface1@interface2两条记录,分别都是DOWN状态
ip link | grep interface
分别将interface1和interface2移到test1和test2命名空间下
ip link set interface1 netns test1
ip link set interface2 netns test2
// 查看
ip netns exec test1 ip link
ip netns exec test2 ip link
分别设置两个命名空间下网卡的IP,注意这里两个IP必须在同一个网段内,否则ping时肯定会网络不可达
ip netns exec test1 ip addr add dev interface1 192.168.10.10/24
ip netns exec test2 ip addr add dev interface2 192.168.10.20/24
启动两个命名空间下的网卡
ip netns exec test1 ip link set interface1 up
ip netns exec test2 ip link set interface2 up
// 查看网卡状态,状态是UP就表示正常了
ip netns exec test1 ip link
ip netns exec test2 ip link
测试能否连通
ip netns exec test1 ping 192.168.10.20
ip netns exec test2 ping 102.168.10.10
// 下面两个是ping自身,需要启动两个命名空间下的lo网卡才能ping通,这里只是随便说一下
ip netns exec test1 ping 192.168.10.10
ip netns exec test2 ping 192.168.10.20
通过桥进行连接
原理是两个命名空间各自和桥建立连接,再通过桥与对方通信
// 创建桥
ip link add bridge1 type bridge
// 查看
ip link | grep bridge1
// 创建两个网络命名空间
ip netns add test1
ip netns add test2
先将test1与bridge1建立连接
// 创建test1到bridge1和bridge1到test1的网卡
ip link add test1ToBridge1 type veth peer name bridge1ToTest1
// 将test1到bridge1网卡移到test1命名空间下
ip link set test1ToBridge1 netns test1
// 设置IP
ip netns exec test1 ip addr add dev test1ToBridge1 192.168.10.10/24
// 启动两个网卡
ip netns exec test1 ip link set test1ToBridge1 up
ip link set bridge1ToTest1 master bridge1
ip link set bridge1ToTest1 up
// 查看网卡状态
ip netns exec test1 ip link
ip link
将test2与bridge1连接上
// 创建test2到bridge1和bridge1到test2的网卡
ip link add test2ToBridge1 type veth peer name bridge1ToTest2
// 将test2到bridge1网卡移到test2命名空间下
ip link set test2ToBridge1 netns test2
// 设置IP
ip netns exec test2 ip addr add dev test2ToBridge1 192.168.20.20/24
// 启动两个网卡
ip netns exec test2 ip link set test2ToBridge1 up
ip link set bridge1ToTest2 master bridge1
ip link set bridge1ToTest2 up
最后启动桥
ip link set bridge1 up
测试通信
ip netns exec test1 ping 192.168.20.20
这里目前是ping不通的,因为跨了网段,需要指定路由,先在bridge1上添加两个IP
ip addr add dev bridge1 192.168.10.1/24
ip addr add dev bridge1 192.168.20.1/24
在主命名空间下设置路由
// 去往192.168.10.0/24这个网段的数据走bridge1网卡
route add -net 192.168.10.0/24 dev bridge1
// 去往192.168.20.0/24这个网段的数据走bridge1网卡
route add -net 192.168.20.0/24 dev bridge1
再到test1和test2两个命名空间下设置到达对方的路由
ip netns exec test1 route add -net 192.168.20.0/24 gw 192.168.10.1
ip netns exec test2 route add -net 192.168.10.0/24 gw 192.168.20.1
最后再使用DNAT技术修改源IP
// 从192.168.10.0/24发到192.168.20.0/24的数据包,修改IP为192.168.10.1
iptables -t nat -I PREROUTING -s 192.168.10.0/24 -d 192.168.20.0/24 -j DNAT --to 192.168.10.1
此时再ping就可以通了
ip netns exec test1 ping 192.168.20.20
PING 192.168.20.20 (192.168.20.20) 56(84) bytes of data.
64 bytes from 192.168.20.20: icmp_seq=1 ttl=64 time=0.074 ms
64 bytes from 192.168.20.20: icmp_seq=2 ttl=64 time=0.083 ms
64 bytes from 192.168.20.20: icmp_seq=3 ttl=64 time=0.089 ms
64 bytes from 192.168.20.20: icmp_seq=4 ttl=64 time=0.084 ms
64 bytes from 192.168.20.20: icmp_seq=5 ttl=64 time=0.080 ms
cgroup
是control group的简写,对系统资源进行限制,比如CPU和内存在某个命名空间下进行限制
对应的文件在/sys/fs/cgroup
目录下,这些文件不能使用vi命令编辑,可以通过cat命令查看内容,echo命令修改内容
下面就来创建一个cgroup,体验一下:
apt install cgroup-tools
cgcreate -g cpu:test1
这会在/sys/fs/cgroup/cpu
目录下创建test1目录,该目录下的文件就是用来做资源限制的配置文件,并且子目录会继承父目录中的配置文件(即子目录下没有某一项的配置文件,就会找其父目录下的对应配置文件)
tasks目录下ll
可以看到当前cgroup下管理的所有进程的ID
限制CPU
进入/sys/fs/cgroup/cpu/test1
目录,这个目录是上面刚创建的,ll
查看目录下的文件,其中有以下两个重要文件:
cpu.cfs_quota_us
是占用CPU的时间,单位:微秒,默认是-1表示不限制cpu.cfs_period_us
是CPU分配的周期,默认是100000,一般不修改,修改上面那个就好
我们通过echo 30000 > cpu.cfs_quota_us
将cpu.cfs_quota_us
修改为30000表示该cgroup下的所有进程最大一共只能占用30% CPU
现在将一个进程加到当前cgroup中:
cgclassify -g cpu:test1 进程ID
通过上面的方式可以将进程加到cgroup中,也可以echo 进程ID >> tasks文件
,>>
是追加一行,注意不要用>
,这是覆盖,下面绑定CPU和限制内存也可以这么将进程加到cgroup中
绑定CPU
在/sys/fs/cgroup/cpuset
目录下,修改cpuset.cpus
文件,如下:
echo '1-2' > cpuset.cpus
表示该cgroup下进程只能占用1和2两个CPU核心
限制内存
在/sys/fs/cgroup/memory
目录下,修改memory.limit_in_bytes
文件,如下:
echo $((256 * 1024 * 1024)) > memory.limit_in_bytes
overlay
层次文件系统,从下往上分为lower dir
、upper dir
和merged
,其中lower dir
是只读的,upper dir
可读可写,merged
是lower
和upper
的组合,lower
层可以是多个目录,层次文件系统的特点是:上下合并、同名覆盖和写时拷贝
-
上下合并指的是lower和upper中的文件和目录会合并到merged层
-
同名覆盖指的是lower和upper中有同名文件时,upper的文件会覆盖掉lower的文件
-
写时拷贝指的是如果要修改的文件在upper层,则直接修改;如果要修改的文件在lower层,则会将文件拷贝到upper层再对该文件修改
创建文件时,在merged层创建,会保存到upper层
删除文件时,如果文件在upper层,则直接删除;如果文件在lower层,则在upper层创建一个任何用户都没有权限的同名,且大小为0的文件
docker的镜像就是由overlay2层次文件系统组合出来的,构建镜像时每一步操作都会产生一层,当有相同操作时,docker就可以使用缓存了,docker的本地镜像目录默认是/var/lib/docker/overlay2
,进入这个目录可以查看到很多子目录,这些都是一层一层的文件
下面自己动手来创建一个层次目录,测试在不同层次创建和修改文件等操作
// 创建4个目录
mkdir lower upper merged worker
// 创建一些文件,测试合并后的结果
echo 'a-lower' > lower/a.txt
echo 'a-upper' > upper/a.txt
touch lower/b.txt
创建分层的文件,如果lower层有多个目录可用:
隔开
mount -t overlay overlay -o lowerdir=lower,upperdir=upper,workdir=worker merged
一行命令就可以创建出分层的文件,如果执行报错,可查看内核报错信息
journalctl -xe
现在查看merged目录
ll merged
现在可以测试在merged中创建文件
echo 'c-merged' > merged/c.txt
// 查看创建的文件
ll merged
// 查看upper目录
ll upper