什么是Linux中的namespace
和cgroups
?
为什么它们成为了Docker和k8s奠基性的技术?
k8s中的namespace
和Linux中的namespace
是什么关系?
本文会基于上述问题完成讨论。
例如某个进程集合只能看到某个资源集合;另一个进程集合只能看到一个不同的资源集合。
这么做能保证进程集之间不会互相干扰,保证一定程度的安全性。使得创建一个namespace
就像创建一个虚拟机一样对资源进行隔离。
user namespace
: 在其中的进程拥有自定义的/独占的User IDs
和Group IDs
,并可以使得一个进程在其特定的user namespace
中拥有root权限(在其他user namespace
中不一定)process ID namespace
: 在其中的进程拥有自定义/独占的Process IDs
,在新的PID namespace
中创建的第一个进程拥有PID 1
,此后创建的为PID 2
、PID 3
……
注:在当前
PID namespace
(a
)创建一个子进程p
并将进程p
作为新PID namespace
(b
)的第一个进程,那么p
在b
中拥有PID 1
,并且在a
中拥有它原本的PID
另:一个有趣的事实:基础的namespace隔离性很差。虽然它能保证独立的PID体系,但
/proc
却可能可以直接访问到外部的进程信息(见下文)
network namespace
: 在其中的进程拥有一个独立的网络协议栈,包括路由表、IP地址、套接字、防火墙等网络资源mount namespace
: 在其中的进程拥有一个独立的挂载点列表,这就使得在一个mount namespace
中挂载和解挂载不会影响主文件系统interprocess communication (IPC) namespace
: 保证消息队列等IPC通信资源的独立性UNIX Time-Sharing (UTS) namespace
: 保证了主机名(hostname)和域名(domain names)的隔离,一个主机可以有多个主机名cgroups namespace
: 是的,你甚至可以让cgroups也被namespace隔离,隔离的进程会看到虚拟的/proc/self/cgroup
信息Linux提供了unshare
命令用于创建和持久化namespace
这是man
对它的解释:
NAME
unshare - run program in new name namespaces
以下内容是以一个没有提权能力的普通用户执行unshare的过程
下面这个命令创建一个PID namespace
unshare --pid --fork fish
报错了!
仔细想想:unshare
默认以root用户的形式启动,如果没有root权限自然是无法执行的,1.3节介绍的时候讲过User namespace
可以让普通用户在该namespace
获得root权限,那么我们尝试增加User namespace
:
unshare --pid --user --fork fish
我们这里创建了User namespace
,可是我怎么进入变成nobody/依然报错?
虽然创建了
User namespace
,但默认情况下,unshare
没有把当前用户映射为root,导致root仍然需要外部的root权限,需要在namespace内完成UID
、GID
映射将当前用户映射到root
正确的指令如下:
unshare --pid --user --map-root-user --fork fish
注:为什么这里三条指令都用了
--fork
?不fork会发生什么?(可以自己尝试一下)
1.3节提到过一个进程在两个PID namespace
同时存在的情况下会拥有两个PID,可以这样验证:
在namespace
的外部和内部均执行ps
命令查看fish
对应的进程号:
ps -ef | grep fish
糟糕!怎么在内部无法执行?麻烦真是一堆!
仔细分析:因为当前的隔离环境没有mount namespace
,所以/proc
文件夹还是外面的内容,导致实际环境与/proc
文件夹的内容不一致;而ps -ef
指令需要/proc
文件夹的内容来分析哪些属于当前用户
好说,那就写一段C代码调用API来看PPID(因为在fish
终端执行的程序其父进程就是fish
):
#include <stdio.h>
#include <unistd.h>
int main() {
printf("PPID = %d\n", getppid());
return 0;
}
可以看到,在外部拥有一个PID号的fish
,在内部有独立的PID号:PID=1
注:为了防止环境不完整导致的命令执行失败以及权限等各种问题,统一使用一个较为完整的隔离环境是更好的选择 例如,下面的指令包括PID、User、mount、UTS、cgroups,并且设置好了User映射和
/proc
文件夹的挂载unshare --user --pid --map-root-user --mount-proc --uts --mount --cgroup --fork fish
由于这里的
/proc
是直接通过挂载完成的,故umount
可以让它原形毕露
在namespace的外部可以通过lsns
命令查看所有与当前用户相关的namespace的信息
NS TYPE NPROCS PID USER COMMAND
4026532522 user 2 123916 yyq unshare --user --pid --map-root-user --mount-proc --uts --mount --f
4026532524 mnt 2 123916 yyq unshare --user --pid --map-root-user --mount-proc --uts --mount --f
4026532525 uts 2 123916 yyq unshare --user --pid --map-root-user --mount-proc --uts --mount --f
4026532526 pid 1 123917 yyq └─fish
NPROCS表示在namespace中的进程数,其中
PID namespace
只有1,但如果运行其他程序,就会增加
信息还不够多,可以用lsns --output-all
来试试
4026532527 user /proc/176430/ns/user 2 176430 176229 unshare 1001 yyq 4026531837 4026531837
4026532531 mnt /proc/176430/ns/mnt 2 176430 176229 unshare 1001 yyq 0 4026532527
4026532532 uts /proc/176430/ns/uts 2 176430 176229 unshare 1001 yyq 0 4026532527
4026532533 pid /proc/176431/ns/pid 1 176431 176430 fish 1001 yyq 4026531836 4026532527
4026532534 cgroup /proc/176430/ns/cgroup 2 176430 176229 unshare 1001 yyq 0 4026532527
没想到namespace也是由文件系统来管理的!
实际上这种组织方式是十分合理的:PID namespace
对应的第一个进程既是外部的进程也是内部的进程,可以很好地作为namespace管理信息的入口;其他namespace则由创建它们的unshare
来管理,也是自然而然的。
cgroups提供了对一系列进程的资源(CPU、内存、硬盘I/O、网络等)的限制、管理、隔离
对于使用systemd
的Linux系统,cgroups由systemd
接管,可以使用下面的命令:
# systemctl status
systemd-cgls
注:cgroups本身是由多个资源Hierachy(如CPU、内存等)及单个Hierachy中的依赖关系组织的多个树形结构,但是systemd是以service、slice等脚本文件将重要程序的启动顺序和执行依赖等信息管理,并将这些脚本文件本身生成一个依赖关系树,进而将cgroup变为这些脚本文件的内容、组织为cgroups树
systemd-cgtop
在systemd管理下,可以使用/etc/systemd/system/
文件夹设置自己的cgroups
例如创建文件/etc/systemd/system/my.slice
:
[Slice]
CPUQuota=30%
设置完成后不要忘记daemon-reload:
systemctl daemon-reload
之后root使用systemd-run
即可按照cgroups分配的情况运行一个程序
systemd-run --slice=my.slice ./a.out # 任意一个程序
注:这里可以用
htop
看到程序占用CPU情况,也可以用systemd-cgtop
看到my.slice
是直接在根下面的cgroup
同样是隔离,namespace
和cgroups
有什么区别?
答:
k8s中也有一个概念叫作namespace,它和Linux中的namespace一样么?
Tips: 如果一样还单独列出来做什么呢:)
它与Linux中的namespace类似,提供了对k8s资源的隔离,将同一集群中资源划分为相互隔离的组。同一namespace资源名称唯一。
注:k8s里的资源指的是k8s对象,几乎可以说所有的在配置清单上的都是资源对象。
获得名字空间信息:
kubectl get namespace
请求时设置名字空间:
kubectl run nginx --image=nginx --namespace=\<namespace\>
kubectl get pods --namespace=\<namespace\>
虽然刚刚讲到了绝大多数配置清单上的都是资源,但不是所有资源都在名字空间中
可以用下面的方法检查哪些资源在名字空间中,而哪些不在:
kubectl api-resources --namespaced=true
kubectl api-resources --namespaced=false