这里的 "namespace" 指的是 Linux namespace 技术,它是 Linux 内核实现的一种隔离方案。简而言之,Linux 操作系统能够为不同的进程分配不同的 namespace,每个 namespace 都具有独立的资源分配,从而实现了进程间的隔离。如果你的 Linux 安装了 GCC,可以通过运行 man namespaces
命令来查看相关文档,或者你也可以访问在线手册获取更多信息。
下图为各种 namespace 的参数,支持的起始内核版本,以及隔离内容。
Namespace | 系统调用参数 | 内核版本 | 隔离内容 |
---|---|---|---|
UTS (Unix Time-sharing System) | CLONE_NEWUTS | Linux 2.4.19 | 主机名与域名 |
IPC (Inter-Process Communication) | CLONE_NEWIPC | Linux 2.6.19 | 信号量、消息队列和共享内存 |
PID (Process ID) | CLONE_NEWPID | Linux 2.6.19 | 进程编号 |
Network | CLONE_NEWNET | Linux 2.6.24 | 网络设备、网络栈、端口等等 |
Mount | CLONE_NEWNS | Linux 2.6.29 | 挂载点(文件系统) |
User | CLONE_NEWUSER | Linux 3.8 | 用户和用户组 |
PID Namespace:
Net Namespace:
IPC Namespace:
MNT Namespace:
UTS Namespace:
User Namespace:
涉及到三个系统调用(system call)的 API:
lsns –t <type>
ls -la /proc/<pid>/ns/
nsenter -t <pid> -n ip addr
Test:
# Linux命令行中,可以使用`unshare`命令结合`clone()`创建一个新的进程,并在其中使用命名空间隔离参数。 # 创建一个新的进程,并在其中使用命名空间隔离参数 unshare --pid --net -- sleep 600 ps -ef|grep sleep root 37915 34572 0 08:53 pts/1 00:00:00 sudo unshare --pid --net -- sleep 600 root 37916 37915 0 08:53 pts/3 00:00:00 sudo unshare --pid --net -- sleep 600 root 37917 37916 0 08:53 pts/3 00:00:00 sleep 600 zhy 37919 37896 0 08:53 pts/2 00:00:00 grep --color=auto sleep sudo lsns -t net [sudo] password for zhy: NS TYPE NPROCS PID USER NETNSID NSFS COMMAND 4026531840 net 277 1 root unassigned /sbin/init 4026532656 net 1 37347 root 0 /run/docker/netns/c986b82be683 bash 4026532718 net 1 37917 root unassigned sleep 600 sudo nsenter -t 37917 -n ip a 1: lo: <LOOPBACK> 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
docker run --rm -it docker.m.daocloud.io/ubuntu:22.10 bash
ps -ef|grep ubuntu # zhy 37247 34017 0 08:20 pts/0 00:00:00 docker run --rm -it docker.m.daocloud.io/ubuntu:22.10 bash
ls -la /proc/37247/ns/ total 0 dr-x--x--x 2 zhy zhy 0 May 27 08:24 . dr-xr-xr-x 9 zhy zhy 0 May 27 08:23 .. lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 cgroup -> 'cgroup:[4026531835]' lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 ipc -> 'ipc:[4026531839]' lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 mnt -> 'mnt:[4026531841]' lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 net -> 'net:[4026531840]' lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 pid -> 'pid:[4026531836]' lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 pid_for_children -> 'pid:[4026531836]' lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 time -> 'time:[4026531834]' lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 time_for_children -> 'time:[4026531834]' lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 user -> 'user:[4026531837]' lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 uts -> 'uts:[4026531838]'
sudo lsns -t pid NS TYPE NPROCS PID USER COMMAND 4026531836 pid 275 1 root /sbin/init 4026532654 pid 1 37347 root bash sudo lsns -t net NS TYPE NPROCS PID USER NETNSID NSFS COMMAND 4026531840 net 275 1 root unassigned /sbin/init 4026532656 net 1 37347 root 0 /run/docker/netns/c986b82be683 bash
为什么查出来执行 bash
的 pid 和 ps -ef
的不一样?
一个是docker run
的进程 PID
一个是 容器内部 'bash' 进程的 PID 这个进程是由docker run
的进程通过进程复制(process cloning)创建的子进程。
ip addr
在主机执行 nsenter -t <pid> -n ip addr
# 容器内 ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 11: eth0@if12: <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 ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 08:23 pts/0 00:00:00 bash root 360 1 0 09:12 pts/0 00:00:00 ps -ef # 主机 sudo nsenter -t 37347 -n -- ip addr # -n 进入网络namespace执行 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 11: eth0@if12: <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 sudo nsenter -t 37347 -a -- ps -ef # -a 进入所有namespace执行 UID PID PPID C STIME TTY TIME CMD root 1 0 0 08:23 pts/0 00:00:00 bash root 359 0 0 09:12 ? 00:00:00 ps -ef
Linux cgroups 的全称是 Linux Control Groups,它是 Linux 内核的特性,主要作用是限制、记录和隔离进程组(process groups)使用的物理资源(cpu、memory、IO 等)。
可以做到对 cpu,内存等资源实现精细化的控制,容器技术就使用了 cgroups 提供的资源限制能力来完成cpu,内存等部分的资源控制。
subsystem 是一组资源控制的模块,一般包含有:
Cgroup v2手册
是否加载了Cgroup v2内核模块
cat /sys/fs/cgroup/cgroup.controllers cpuset cpu io memory hugetlb pids rdma misc
执行一段go代码
package main func main() { go func() { for{} }() for {} } /* 执行 go run test.go top PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 39268 zhy 20 0 709572 868 584 R 200.0 0.0 2:12.27 test 可以看到使用了2个cpu 因为开个两个goroutine for阻塞 */
限制cpu
sudo mkdir /sys/fs/cgroup/test sudo echo "100000 100000" | sudo tee /sys/fs/cgroup/test/cpu.max >/dev/null sudo echo "39268" | sudo tee /sys/fs/cgroup/test/cgroup.procs >/dev/null # top # PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND # 39268 zhy 20 0 709572 868 584 R 100.3 0.0 7:45.04 test # 马上就只占用一个cpu了
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #define BLOCK_SIZE (100 * 1024 * 1024) #define NUM_ALLOCATIONS 10 #define SLEEP_SECONDS 30 char* allocMemory(int size) { char* out = (char*)malloc(size); memset(out, 'A', size); return out; } int main() { int i; for (i = 1; i <= NUM_ALLOCATIONS; i++) { char* block = allocMemory(i * BLOCK_SIZE); printf("Allocated memory block of size %dMB at address: %p\n", i * 100, block); sleep(SLEEP_SECONDS); } return 0; } /* ps -p 3243 -o rss=,unit=M,cmd= M 308512 session-4.scope ./test2 */
限制内存
sudo echo "300000000" |sudo tee /sys/fs/cgroup/test/memory.max >/dev/null sudo echo "64417" | sudo tee /sys/fs/cgroup/test/cgroup.procs >/dev/null #cat memory.current #299839488
联合文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下 (unite several directories into a single virtual filesystem)。
联合文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
另外,不同 Docker 容器就可以共享一些基础的文件系统层,同时再加上自己独有的改动层,大大提高了存储的效率。
最新版 Docker 使用的是 overlay2。
现在主流基本都是 overlayFS
OverlayFS 属于文件级的存储驱动,包含了最初的 Overlay 和更新更稳定的 overlay2。
Overlay 只有两层:upper 层和 lower 层,Lower 层代表镜像层,upper 层代表容器可写层。
mkdir test && cd test mkdir upper lower merged work echo "file1 from lower" > lower/file1.txt echo "file2 from lowerr" > lower/file2.txt echo "file3 from lower" > lower/file3.txt echo "file2 from upper" > upper/file2.txt echo "file4 from upper" > upper/file4.txt current_dir=$(pwd) sudo mount -t overlay -o lowerdir="$current_dir/lower",upperdir="$current_dir/upper",workdir="$current_dir/work" overlay "$current_dir/merged" cat merged/file1.txt file1 from lower cat merged/file2.txt file2 from upper cat merged/file3.txt file3 from lower cat merged/file4.txt file4 from upper
每一条指令是一层, 下层可以共用
典型的Linux文件系统组成如下:
Linux
Docker 启动
由于镜像具有共享特性,所以对容器可写层的操作需要依赖存储驱动提供的写时复制和用时分配机制,以此来 支持对容器可写层的修改,进而提高对存储和内存资源的利用率。