小说网页网站建设,网站策划方案案例,免费自学编程,苏州营销型网站建设哪家好对操作系统了解多少#xff0c;仅仅敲个命令吗 操作系统虚拟化#xff08;容器技术#xff09;的发展历程
1979 年#xff0c;UNIX 的第 7 个版本引入了 Chroot 特性。Chroot 现在被认为是第一个操作系统虚拟化#xff08;Operating system level virtualization#x… 对操作系统了解多少仅仅敲个命令吗 操作系统虚拟化容器技术的发展历程
1979 年UNIX 的第 7 个版本引入了 Chroot 特性。Chroot 现在被认为是第一个操作系统虚拟化Operating system level virtualization技术的原型本质是一种操作系统文件系统层的隔离技术。
2006 年Google 发布了在 Linux 上运行的 Process Container进程容器技术其目标是提供一种类似于 Virtual Mahine计算机虚拟化技术的、但主要针对 Process 的操作系统级别资源限制、优先级控制、资源审计能力和进程控制能力。
2007 年Google 推动 Process Container 代码合入 Linux Kernel。同时由于 Container 这一命名在 Kernel 具有许多不同的含义所以为了避免代码命名的混乱就将 Process Container 更名为了 Control Groups简称Cgroups。
2008 年Linux 社区整合了 Chroot、Cgroups、Namespaces、SELinux、Seccomp 等多种技术并发布了 LXCLinux Containerv0.1.0 版本。LXC 通过将 Cgroups 的资源配额管理能力和 Namespace 的资源视图隔离能力进行组合实现了完备的轻量级操作系统虚拟化。
2013 年 3 月 15 日在加利福尼亚州圣克拉拉召开的 Python 开发者大会上DotCloud 的创始人兼首席执行官 Solomon Hvkes 在一场仅 5 分钟的微型演讲中首次发布了基于 LXC 封装的 Docker Container并于会后将其源码开源并托管到 Github。 2. 容器的优势
传统模式的部署直接将多个应用运行在物理服务器上如果其中一个应用占用了大部分资源可能会导致其他应用的性能下降。
虚拟化部署时代可以在单个物理服务器的 CPU 上运行多个虚拟机VM每个 VM 是一台完整的计算机在虚拟化硬件之上运行所有组件包括了操作系统。因此可以让不同的应用在 VM 之间安全地隔离运行更好地利用物理服务器上的资源。
容器与 VM 类似具有自己的文件系统、CPU、内存、进程空间等但与 VM 不同的是容器之间共享操作系统OS。 所以容器被认为是一种轻量级的操作系统层面的虚拟化技术。
相比于 VM 轻量级的容器更适合云原生模式的实践。 3. 容器的本质 容器是一种轻量级的操作系统层面的虚拟化技术。
重点是 “操作系统层面” 即容器本质上是利用操作系统提供的功能来实现虚拟化。
容器技术的代表之作 Docker 则是一个基于 Linux 操作系统使用 Go 语言编写调用了 Linux Kernel 功能的虚拟化工具。
为了更好地理解容器的本质我们来看看容器具体使用了哪些 Linux Kernel 技术以及在 Go 中应该如何去调用。 3.1 Chroot
Chroot 是一个可供 User Process 调用的 System Call 接口可以让一个 Process 把指定的目录作为根目录Root Directory随后 Process 所有的文件系统操作都只能在这个指定目录中进行。故称之为 Change Root。
chroot() 的函数原型非常简单 调用权限Root 用户。 形参列表 path一个指向字符串的指针是一个绝对路径表示将 Process 的根目录更改为的该目录路径。 函数返回 成功返回 0 失败返回 -1。
#include unistd.hint chroot(const char *path);需要注意的是在更改了 Process 的根目录后Process 只能访问新的根目录以及其子目录中的文件和资源。因此在调用 chroot() 后应确保 Process 所需要访问的所有文件和资源都存在于新的根目录下。
chroot() 目前主要主要用于
安全隔离场景限制将 Process 的访问范围以此提高系统的安全性。
调试环境场景创建一个与主系统隔离的环境用于调试、测试和运行 Process。
系统救援场景在 Linux 操作系统损坏或遭受攻击时可以使用 chroot 将 Process 切换到受损系统的根目录中以便进行修复和救援操作。
可见chroot() 确实在 Linux File System文件系统层面提供了针对 Process 的隔离性但并不提供完全的安全隔离无法阻止其他方式的攻击。因此要想实现 Processes 之间的安全隔离还需要需采取其他安全措施。
3.2 NameSpaces
Linux Namespaces命名空间是一种操作系统层级的资源视图隔离技术能够将 Linux 的全局资源划分为 Namespace 范围内可见的资源。
由于容器之间共享 OS 对于操作系统而言容器的实质就是进程多个容器运行对应操作系统也就是运行着多个进程。
当进程运行在自己单独的命名空间时命名空间的资源隔离可以保证进程之间互不影响大家都以为自己身处在独立的一个操作系统里。这种进程就可以称为容器。
Namespaces 具有多种类型基本上涵盖了构成一个操作系统所需要的基本元素
命名空间系统调用参数作用Mount (mnt)CLONE_NEWNS文件目录挂载隔离。用于隔离各个进程看到的挂载点视图Process ID (pid)CLONE_NEWPID进程 ID 隔离。使每个命名空间都有自己的初始化进程PID 为 1作为所有进程的父进程Network (net)CLONE_NEWNET网络隔离。使每个 net 命名空间有独立的网络设备IP 地址路由表/proc/net 目录等网络资源Interprocess Communication (ipc)CLONE_NEWIPC进程 IPC 通信隔离。让只有相同 IPC 命名空间的进程之间才可以共享内存、信号量、消息队列通信UTSCLONE_NEWUTS主机名或域名隔离。使其在网络上可以被视作一个独立的节点而非主机上的一个进程User ID (user)CLONE_NEWUSER用户 UID 和组 GID 隔离。例如每个命名空间都可以有自己的 root 用户Control group (cgroup) NamespaceCLONE_NEWCGROUPCgroup 信息隔离。用于隐藏进程所属的控制组的身份使命名空间中的 cgroup 视图始终以根形式来呈现保障安全Time NamespaceCLONE_NEWTIME系统时间隔离。允许不同进程查看到不同的系统时间
通过 /proc/{pid}/ns 文件可以查看指定 Process 运行在哪些 Namespaces Instance 中并且每个 Namespace Instance 都具有一个唯一的标识。
[root192 dongguangming]# ls -l --time-style /proc/$$/ns
total 0
lrwxrwxrwx. 1 root root 0 ipc - ipc:[4026531839]
lrwxrwxrwx. 1 root root 0 mnt - mnt:[4026531840]
lrwxrwxrwx. 1 root root 0 net - net:[4026531956]
lrwxrwxrwx. 1 root root 0 pid - pid:[4026531836]
lrwxrwxrwx. 1 root root 0 user - user:[4026531837]
lrwxrwxrwx. 1 root root 0 uts - uts:[4026531838]
[root192 dongguangming]#
最终用户可以通过创建多种不同类型的 Namespaces Instance 来提供的操作系统资源的隔离再结合创建多种不同类型的 cgroups 来提供操作系统的资源配额就构成了一个最基本的操作系统容器即Process Container。
UTS namespace
UTS namespace 为 Container 提供了 Hostname 和 Domain Name 的隔离。
Container 中的 Process 可以根据需要调用 sethostname 和 setdomainname 指令来进行配置让每个 Container 都可以被视为网络中的一个独立的节点。
PID namespace
PID namespace 为 Container 提供了进程号的隔离。
每个 Containers 都拥有自己的进程环境Container 的 init Process 都是 PID 1 号进程它作为所有子进程的父进程。要想做到进程的隔离首先需要创建出 PID 1 号进程它具有以下特性 如果某个子进程脱离了父进程父进程没有 wait 它那么 init Process 就会负责回收资源并结束这个子进程。 如果 init Process 被终止那么 Kernel 就会调用 SIGKILL 终止此 PID namespace 中的所有进程。
IPC namespace
IPC namespace 为 Container 提供了 IPC进程间通信机制的隔离包括信号量、消息队列、共享内存等机制。
每个 Containers 都拥有以下 /proc 文件接口 /proc/sys/fs/mqueuePOSIX Message Queues 接口类型 /proc/sys/kernelSystem V IPC 接口类型 /proc/sysvipcSystem V IPC 接口类型。
Mount namespace
Mount namespace 为 Container 提供了 Filesystem 挂载点的隔离继而实现了 VFS 的隔离。
每个 Containers 都拥有以下 /proc 文件接口可以构成一个独立的 rootfsRoot 文件系统 /proc/[pid]/mounts /proc/[pid]/mountinfo /proc/[pid]/mountstats
实际上Mount namespace 是基于 Chroot 的不断改良而开发出来的。为 Container 创建的 rootfs 只是一个操作系统发行版所包含的文件、目录和配置并不包括 Kernel 的文件。
Network namespace
Network namespace 为 Container 提供了网络资源的隔离包括 Network devices网络设备 IPv4 and IPv6 protocol stacksIPv4、IPv6 的协议栈 IP routing tablesIP 路由表 Firewall rules防火墙规则 Sockets 套接字 /proc/[pid]/net /sys/class/net /proc/sys/net
需要注意的是同一个 Network device 只能存在于一个 Namespace Instance 中所以常常结合虚拟网络设备来使用。 User namespace
User namespace 为 Container 提供了用户权限和安全属性相关的隔离包括User ID、User Group ID、Root 目录以及特殊的权限。
每个 Containers 都拥有以下 /proc 文件接口 /proc/[pid]/uid_map /proc/[pid]/gid_map NameSpace 的具体描述可以查看 Linux man 手册中的 namespaces(7) - Linux manual page手册中还描述了几个 NameSpace API 主要是和进程相关的系统调用函数。 clone()
int clone(int (*fn)(void *), void *stack, int flags, void *arg, .../* pid_t *parent_tid, void *tls, pid_t *child_tid */ );clone() 用于创建新进程通过传入一个或多个系统调用参数 flags 参数可以创建出不同类型的 NameSpace 并且子进程也将会成为这些 NameSpace 的成员。
setns()
int setns(int fd, int nstype);setns() 用于将进程加入到一个现有的 Namespace 中。其中 fd 为文件描述符引用 /proc/[pid]/ns/ 目录里对应的文件nstype 代表 NameSpace 类型。
unshare()
int unshare(int flags);unshare() 用于将进程移出原本的 NameSpace 并加入到新创建的 NameSpace 中。同样是通过传入一个或多个系统调用参数 flags 参数来创建新的 NameSpace 。
ioctl()
int ioctl(int fd, unsigned long request, ...);ioctl() 用于发现有关 NameSpace 的信息。
上面的这些系统调用函数我们可以直接用 C 语言调用创建出各种类型的 NameSpace 这是最直观的做法。而对于 Go 语言其内部已经帮我们封装好了这些函数操作可以更方便地直接使用降低心智负担。
先来看一个简单的小工具
package mainimport (osos/exec
)func main() {switch os.Args[1] {case run:run()default:panic(help)}
}func run() {cmd : exec.Command(os.Args[2], os.Args[3:]...)cmd.Stdin os.Stdincmd.Stdout os.Stdoutcmd.Stderr os.Stderrmust(cmd.Run())
}func must(err error) {if err ! nil {panic(err)}
} 这个程序接收用户命令行传递的参数并使用 exec.Command 运行例如当我们执行 go run main.go run echo hello 时会创建出 main 进程 main 进程内执行 echo hello 命令创建出一个新的 echo 进程最后随着 echo 进程的执行完毕main 进程也随之结束并退出。 但是上面创建的进程太快退出了不便于我们观察。如果让 main 进程启动一个 bash 进程会怎样呢
为了直观对比我们先看看当前会话的进程信息。
[root192 dongguangming]# psPID TTY TIME CMD1407 pts/1 00:00:00 bash2057 pts/1 00:00:00 ps
[root192 dongguangming]# echo $$
1407
[root192 dongguangming]# 当前我们正处于 PID 1407 的 bash 会话进程中继续下一步操作
[root192 dongguangming]# go run main.go run /bin/bash
[root192 dongguangming]# psPID TTY TIME CMD1407 pts/1 00:00:00 bash2058 pts/1 00:00:00 go2076 pts/1 00:00:00 main2079 pts/1 00:00:00 bash2089 pts/1 00:00:00 ps
[root192 dongguangming]# echo $$
2079
[root192 dongguangming]# exit
exit
[root192 dongguangming]# psPID TTY TIME CMD1407 pts/1 00:00:00 bash2090 pts/1 00:00:00 ps
[root192 dongguangming]# echo $$
1407
[root192 dongguangming]# 在执行 go run main.go run /bin/bash 后我们的会话被切换到了 PID 2709的 bash 进程中而 main 进程也还在运行着当前所处的 bash 进程是 main 进程的子进程main 进程必须存活着才能维持 bash 进程的运行。当执行 exit 退出当前所处的 bash 进程后main 进程随之结束并回到原始的 PID 1407的 bash 会话进程。
我们说过容器的实质是进程你现在可以把 main 进程当作是 “Docker” 工具把 main 进程启动的 bash 进程当作一个 “容器” 。这里的 “Docker” 创建并启动了一个 “容器”。
为什么打了双引号是因为在这个 bash 进程中我们可以随意使用操作系统的资源并没有做资源隔离。
要想实现资源隔离也很简单在 run() 函数增加 SysProcAttr 配置先从最简单的 UTS 隔离开始传入对应的 CLONE_NEWUTS 系统调用参数并通过 syscall.Sethostname 设置主机名
func run() {cmd : exec.Command(os.Args[2], os.Args[3:]...)cmd.Stdin os.Stdincmd.Stdout os.Stdoutcmd.Stderr os.Stderrcmd.SysProcAttr syscall.SysProcAttr{Cloneflags: syscall.CLONE_NEWUTS,}must(syscall.Sethostname([]byte(mycontainer)))must(cmd.Run())
} 这段代码看似没什么问题但仔细思考一下。
syscall.Sethostname 这一行到底是哪个进程在执行main 进程还是 main 进程创建的子进程
不用想子进程都还没 Run 起来呢现在调用肯定是 main 进程在执行main 进程可没进行资源隔离相当于直接更改宿主机的主机名了。
子进程还没 Run 起来还不能更改主机名等子进程 Run 起来后又会进入到阻塞状态无法再通过代码方式更改到子进程内的主机名。那有什么办法呢
看来只能把 /proc/self/exe 这个神器请出来了。
在 Linux 2.2 内核版本及其之后/proc/[pid]/exe 是对应 pid 进程的二进制文件的符号链接包含着被执行命令的实际路径名。如果打开这个文件就相当于打开了对应的二进制文件甚至可以通过重新输入 /proc/[pid]/exe 重新运行一个对应于 pid 的二进制文件的进程。
对于 /proc/self 当进程访问这个神奇的符号链接时可以解析到进程自己的 /proc/[pid] 目录。
合起来就是当进程访问 /proc/self/exe 时可以运行一个对应进程自身的二进制文件。
这有什么用呢继续看下面的代码
package mainimport (osos/execsyscall
)func main() {switch os.Args[1] {case run:run()case child:child()default:panic(help)}
}func run() {cmd : exec.Command(/proc/self/exe, append([]string{child}, os.Args[2:]...)...)cmd.Stdin os.Stdincmd.Stdout os.Stdoutcmd.Stderr os.Stderrcmd.SysProcAttr syscall.SysProcAttr{Cloneflags: syscall.CLONE_NEWUTS,}must(cmd.Run())
}func child() {must(syscall.Sethostname([]byte(mycontainer)))cmd : exec.Command(os.Args[2], os.Args[3:]...)cmd.Stdin os.Stdincmd.Stdout os.Stdoutcmd.Stderr os.Stderrmust(cmd.Run())
}func must(err error) {if err ! nil {panic(err)}
} 在 run() 函数中我们不再是直接运行用户所传递的命令行参数而是运行 /proc/self/exe 并传入 child 参数和用户传递的命令行参数。
同样当执行 go run main.go run echo hello 时会创建出 main 进程 main 进程内执行 /proc/self/exe child echo hello 命令创建出一个新的 exe 进程关键也就是这个 exe 进程我们已经为其配置了 CLONE_NEWUTS 系统调用参数进行 UTS 隔离。也就是说exe 进程可以拥有和 main 进程不同的主机名彼此互不干扰。
进程访问 /proc/self/exe 代表着运行对应进程自身的二进制文件。因此按照 exe 进程的启动参数会执行 child() 函数而 child() 函数内首先调用 syscall.Sethostname 更改了主机名此时是 exe 进程执行的并不会影响到 main 进程接着和本文最开始的 run() 函数一样再次使用 exec.Command 运行用户命令行传递的参数。
总结一下就是 main 进程创建了 exe 进程exe 进程已经进行 UTS 隔离exe 进程更改主机名不会影响到 main 进程 接着 exe 进程内执行 echo hello 命令创建出一个新的 echo 进程最后随着 echo 进程的执行完毕exe 进程随之结束exe 进程结束后 main 进程再结束并退出。 那经过 exe 这个中间商所创建出来的 echo 进程和之前由 main 进程直接创建的 echo 进程两者有何不同呢。
我们知道创建 exe 进程的同时我们传递了 CLONE_NEWUTS 标识符创建了一个 UTS NameSpace Go 内部帮我们封装了系统调用函数 clone() 的调用我们也说过由 clone() 函数创建出的进程的子进程也将会成为这些 NameSpace 的成员所以默认情况下创建新进程时无继续指定系统调用参数由 exe 进程创建出的 echo 进程会继承 exe 进程的资源 echo 进程将拥有和 exe 进程相同的主机名并且同样和 main 进程互不干扰。
因此借助中间商 exe 进程 echo 进程可以成功实现和宿主机 main 进程资源隔离拥有不同的主机名。 再次通过启动 /bin/bash 进行验证主机名是否已经成功隔离
[root192 dongguangming]# hostname
192.168.0.103
[root192 dongguangming]# touch main.go
[root192 dongguangming]# vi main.go
[root192 dongguangming]# go run main.go run /bin/bash
[rootmycontainer dongguangming]# hostname
mycontainer
[rootmycontainer dongguangming]# psPID TTY TIME CMD1407 pts/1 00:00:00 bash2112 pts/1 00:00:00 go2133 pts/1 00:00:00 main12136 pts/1 00:00:00 exe2140 pts/1 00:00:00 bash2150 pts/1 00:00:00 ps
[rootmycontainer dongguangming]# exit
exit
[root192 dongguangming]# hostname
192.168.0.103
[root192 dongguangming]# 当执行 go run main.go run /bin/bash 时我们也可以在另一个 ssh 会话中使用 ps afx 查看bash 会话进程的层次信息 以此类推新增资源隔离只要继续传递指定的系统调用参数即可
package mainimport (fmtosos/execsyscall
)func main() {switch os.Args[1] {case run:run()case child:child()default:panic(help)}
}func run() {fmt.Println([main], pid:, os.Getpid())cmd : exec.Command(/proc/self/exe, append([]string{child}, os.Args[2:]...)...)cmd.Stdin os.Stdincmd.Stdout os.Stdoutcmd.Stderr os.Stderrcmd.SysProcAttr syscall.SysProcAttr{Cloneflags: syscall.CLONE_NEWUTS |syscall.CLONE_NEWPID |syscall.CLONE_NEWNS,Unshareflags: syscall.CLONE_NEWNS,}must(cmd.Run())
}func child() {fmt.Println([exe], pid:, os.Getpid())must(syscall.Sethostname([]byte(mycontainer)))must(os.Chdir(/))must(syscall.Mount(proc, proc, proc, 0, ))cmd : exec.Command(os.Args[2], os.Args[3:]...)cmd.Stdin os.Stdincmd.Stdout os.Stdoutcmd.Stderr os.Stderrmust(cmd.Run())must(syscall.Unmount(proc, 0))
}func must(err error) {if err ! nil {panic(err)}
} Cloneflags 参数新增了 CLONE_NEWPID 和 CLONE_NEWNS 分别隔离进程 pid 和文件目录挂载点视图Unshareflags: syscall.CLONE_NEWNS 则是用于禁用挂载传播如果不设置该参数container 内的挂载会共享到 host 挂载传播不在本文的探讨范围内。
当我们创建 PID Namespace 时exe 进程包括其创建出来的子进程的 pid 已经和 main 进程隔离了这一点可以通过打印 os.Getpid() 结果或执行 echo $$ 命令得到验证。但此时还不能使用 ps 命令查看因为 ps 和 top 等命令会使用 /proc 的内容所以我们才继续引入了 Mount Namespace 并在 exe 进程挂载 /proc 目录。
[root192 dongguangming]# psPID TTY TIME CMD1407 pts/1 00:00:00 bash2195 pts/1 00:00:00 ps
[root192 dongguangming]# echo $$
1407
[root192 dongguangming]# go run main.go run /bin/bash
[main] pid: 2217
[exe] pid: 1
[rootmycontainer /]# psPID TTY TIME CMD1 pts/1 00:00:00 exe4 pts/1 00:00:00 bash13 pts/1 00:00:00 ps
[rootmycontainer /]# echo $$
4
[rootmycontainer /]# exit
exit
[root192 dongguangming]# 通过 /proc/{pid}/ns 文件可以查看指定 Process 运行在哪些 Namespaces Instance 中并且每个 Namespace Instance 都具有一个唯一的标识。
此时exe 作为初始化进程pid 为 1 创建出了 pid 4 的 bash 子进程而且已经看不到 main 进程了。
剩下的 IPC 、NET、 USER 等 NameSpace 就不在本文一一展示了。 3.3 Cgroups
借助 NameSpace 技术可以帮进程隔离出自己单独的空间成功实现出最简容器。但是怎样限制这些空间的物理资源开销CPU、内存、存储、I/O 等就需要利用 Cgroups 技术了。
限制容器的资源使用是一个非常重要的功能如果一个容器可以毫无节制的使用服务器资源那便又回到了传统模式下将应用直接运行在物理服务器上的弊端。这是容器化技术不能接受的。
Cgroups 的全称是 Control groups 即控制组最早是由 Google 的工程师主要是 Paul Menage 和 Rohit Seth在 2006 年发起一开始叫做进程容器process containers。在 2007 年时因为在 Linux Kernel 中容器container这个名词有许多不同的意义为避免混乱被重命名为 cgroup 并且被合并到 2.6.24 版本的内核中去。 Cgroups 是对进程分组管理的一种机制提供了对一组进程及它们的子进程的资源限制、控制和统计的能力并为每种可以控制的资源定义了一个 subsystem 子系统的方式进行统一接口管理因此 subsystem 也被称为 resource controllers 资源控制器。
几个主要的 subsystem 如下 Cgroups V1
子系统作用cpu限制进程的 cpu 使用率cpuacct统计进程的 cpu 使用情况cpuset在多核机器上为进程分配单独的 cpu 节点或者内存节点仅限 NUMA 架构memory限制进程的 memory 使用量blkio控制进程对块设备例如硬盘 io 的访问devices控制进程对设备的访问net_cls标记进程的网络数据包以便可以使用 tc 模块traffic control对数据包进行限流、监控等控制net_prio控制进程产生的网络流量的优先级freezer挂起或者恢复进程pids限制 cgroup 的进程数量更多子系统参考 Linux man cgroups[3]文档https://man7.org/linux/man-pages/man7/cgroups.7.html
借助 Cgroups 机制可以将一组进程task group和一组 subsystem 关联起来达到控制进程对应关联的资源的能力。如图 Cgroups 的层级结构称为 hierarchy 即 cgroup 树是一棵树由 cgroup 节点组成。
系统可以有多个 hierarchy 当创建新的 hierarchy 时系统所有的进程都会加入到这个 hierarchy 默认创建的 root cgroup 根节点中在树中子节点可以继承父节点的属性。
对于同一个 hierarchy进程只能存在于其中一个 cgroup 节点中。如果把一个进程添加到同一个 hierarchy 中的另一个 cgroup 节点则会从第一个 cgroup 节点中移除。
hierarchy 可以附加一个或多个 subsystem 来拥有对应资源如 cpu 和 memory 的管理权其中每一个 cgroup 节点都可以设置不同的资源限制权重而进程 task 则绑定在 cgroup 节点中并且其子进程也会默认绑定到父进程所在的 cgroup 节点中。
基于 Cgroups 的这些运作原理可以得出如果想限制某些进程的内存资源就可以先创建一个 hierarchy 并为其挂载 memory subsystem 然后在这个 hierarchy 中创建一个 cgroup 节点在这个节点中将需要控制的进程 pid 和控制属性写入即可。
接下来我们就来实践一下。 Linux 一切皆文件。 在 Linux Kernel 中为了让 Cgroups 的配置更直观使用了目录的层级关系来模拟 hierarchy 以此通过虚拟的树状文件系统的方式暴露给用户调用。 创建一个 hierarchy 并为其挂载 memory subsystem 这一步我们可以跳过因为系统已经默认为每个 subsystem 创建了一个默认的 hierarchy 我们可以直接使用。
例如 memory subsystem 默认的 hierarchy 就在 /sys/fs/cgroup/memory 目录。
[roothost go]# mount | grep memory
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
[roothost go]# cd /sys/fs/cgroup/memory
[roothost memory]# pwd
/sys/fs/cgroup/memory
[roothost memory]#只要在这个 hierarchy 目录下创建一个文件夹就相当于创建了一个 cgroup 节点
[roothost memory]# mkdir hello
[roothost memory]# cd hello/
[roothost hello]# ls
cgroup.clone_children memory.kmem.slabinfo memory.memsw.failcnt memory.soft_limit_in_bytes
cgroup.event_control memory.kmem.tcp.failcnt memory.memsw.limit_in_bytes memory.stat
cgroup.procs memory.kmem.tcp.limit_in_bytes memory.memsw.max_usage_in_bytes memory.swappiness
memory.failcnt memory.kmem.tcp.max_usage_in_bytes memory.memsw.usage_in_bytes memory.usage_in_bytes
memory.force_empty memory.kmem.tcp.usage_in_bytes memory.move_charge_at_immigrate memory.use_hierarchy
memory.kmem.failcnt memory.kmem.usage_in_bytes memory.numa_stat notify_on_release
memory.kmem.limit_in_bytes memory.limit_in_bytes memory.oom_control tasks
memory.kmem.max_usage_in_bytes memory.max_usage_in_bytes memory.pressure_level
[roothost hello]#其中我们创建的 hello 文件夹内的所有文件都是系统自动创建的。常用的几个文件功能如下
文件名功能taskscgroup 中运行的进程 PID列表。将 PID 写入一个 cgroup 的 tasks 文件可将此进程移至该 cgroupcgroup.procscgroup 中运行的线程群组列表 TGID 。将 TGID 写入 cgroup 的 cgroup.procs 文件可将此线程组群移至该 cgroupcgroup.event_controlevent_fd() 的接口。允许 cgroup 的变更状态通知被发送notify_on_release用于自动移除空 cgroup 。默认为禁用状态0。设定为启用状态1时当 cgroup 不再包含任何任务时即cgroup 的 tasks 文件包含 PID而 PID 被移除致使文件变空kernel 会执行 release_agent 文件仅在 root cgroup 出现的内容并且提供通向被清空 cgroup 的相关路径与 root cgroup 相关作为参数memory.usage_in_bytes显示 cgroup 中进程当前所用的内存总量以字节为单位memory.memsw.usage_in_bytes显示 cgroup 中进程当前所用的内存量和 swap 空间总和以字节为单位memory.max_usage_in_bytes显示 cgroup 中进程所用的最大内存量以字节为单位memory.memsw.max_usage_in_bytes显示 cgroup 中进程的最大内存用量和最大 swap 空间用量以字节为单位memory.limit_in_bytes设定用户内存包括文件缓存的最大用量memory.memsw.limit_in_bytes设定内存与 swap 用量之和的最大值memory.failcnt显示内存达到 memory.limit_in_bytes 设定的限制值的次数memory.memsw.failcnt显示内存和 swap 空间总和达到 memory.memsw.limit_in_bytes 设定的限制值的次数memory.oom_control可以为 cgroup 启用或者禁用“内存不足”Out of MemoryOOM 终止程序。默认为启用状态0尝试消耗超过其允许内存的任务会被 OOM 终止程序立即终止。设定为禁用状态1时尝试使用超过其允许内存的任务会被暂停直到有额外内存可用。更多文件的功能说明可以查看 kernel 文档中的 cgroup-v1/memory[4]https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt
在这个 hello cgroup 节点中我们想限制某些进程的内存资源只需将对应的进程 pid 写入到 tasks 文件并把内存最大用量设定到 memory.limit_in_bytes 文件即可。
[roothost hello]# cat memory.oom_control
oom_kill_disable 0
under_oom 0
[roothost hello]# cat memory.failcnt
0
[roothost hello]# echo 100M memory.limit_in_bytes
[roothost hello]# cat memory.limit_in_bytes
104857600
[roothost hello]#hello cgroup 节点默认启用了 OOM 终止程序因此当有进程尝试使用超过可用内存时会被立即终止。查询 memory.failcnt 可知目前还没有进程内存达到过设定的最大内存限制值。
我们已经设定了 hello cgroup 节点可使用的最大内存为 100M 此时新启动一个 bash 会话进程并将其移入到 hello cgroup 节点中
[roothost hello]# /bin/bash
[roothost hello]# echo $$
4123
[roothost hello]# cat tasks
[roothost hello]# echo $$ tasks
[roothost hello]# cat tasks
4123
4135
[roothost hello]# cat memory.usage_in_bytes
196608
[roothost hello]#后续在此会话进程所创建的子进程都会加入到该 hello cgroup 节点中例如 pid 4135 就是由于执行 cat 命令而创建的新进程被系统自动加入到了 tasks 文件中。
同样篇幅问题剩下的 subsystem 也不在本文一一展示了。
其实到这里我们已经通过 NameSpace 技术帮进程隔离出自己单独的空间并使用 Cgroups 技术限制和监控这些空间的资源开销这种特殊的进程就是容器的本质。 4. Docker 对 Cgroups 和 Namespaces 的应用
当我们创建了一个 Docker Container 之后就可以查看这个 Container 所具有的 cgroups 和 namespaces 了。 查看 Container 的 IDcfca1212d140和 PID2240配置。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cfca1212d140 centos:centos7.9.2009 bash 18 months ago Up 2 hours vim-ide$ docker inspect --format{{.State.Pid}} cfca1212d140
2240查看 Container 的 cgroups 配置。
$ ll /sys/fs/cgroup/memory/docker/
总用量 0
drwxr-xr-x. 2 root root 0 6月 2 03:40 cfca1212d1407a89632a439e974e246d1f6edd0bbef9079f06addf2613e1d46f$ cat /sys/fs/cgroup/memory/docker/cfca1212d1407a89632a439e974e246d1f6edd0bbef9079f06addf2613e1d46f/cgroup.procs
2240$ cat /sys/fs/cgroup/memory/docker/cfca1212d1407a89632a439e974e246d1f6edd0bbef9079f06addf2613e1d46f/memory.limit_in_bytes
9223372036854771712查看 Container 的 namespaces 配置。
$ ls -l --time-style /proc/2240/ns
总用量 0
lrwxrwxrwx. 1 root root 0 ipc - ipc:[4026532433]
lrwxrwxrwx. 1 root root 0 mnt - mnt:[4026532431]
lrwxrwxrwx. 1 root root 0 net - net:[4026531956]
lrwxrwxrwx. 1 root root 0 pid - pid:[4026532434]
lrwxrwxrwx. 1 root root 0 user - user:[4026531837]
lrwxrwxrwx. 1 root root 0 uts - uts:[4026532432] 总结
其实容器的底层原理并不难本质上就是一个特殊的进程特殊在为其创建了 NameSpace 隔离运行环境用 Cgroups 为其控制了资源开销这些都是站在 Linux 操作系统的肩膀上实现的包括 Docker 的镜像实现也是利用了 UnionFS 的分层联合技术。 参考 Linux Namespace和Cgroup Linux Namespace和Cgroup - Linux程序员 - SegmentFault 思否 linux namespace and cgroup linux namespace and cgroup - 掘金 Linux Namespace 入门系列Namespace API Linux Namespace 入门系列Namespace API - 知乎 Docker 工作原理及容器化简易指南 http://dockone.io/article/8788 昨天有读者说他不会 docker今天就给你肝出来了 https://mp.weixin.qq.com/s?__bizMzU2NDg0OTgyMAmid2247492639idx1snac607fbbe221e50c24db1a7ccb12518b Docker 系列核心原理和实现(2) Docker 系列核心原理和实现(2) | 郭宁的个人博客 What Are Linux Namespaces and What Are They Used for? What Are Linux Namespaces and What Are They Used for? namespaces(7) — Linux manual page namespaces(7) - Linux manual page 极好 A deep dive into Linux namespaces A deep dive into Linux namespaces – Chord Simple Linux – cgroup namespace Linux – cgroup namespace – Benjr.tw cgroup_namespaces(7) — Linux manual pages https://manpages.courier-mta.org/htmlman7/cgroup_namespaces.7.html Overview of Linux Namespace Linux namespaces pid,network,mount,ipc,uts,user,cgroup Container and Cgroups https://enqueuezero.com/container-and-cgroups.html Everything You Need to Know about Linux Containers, Part I: Linux Control Groups and Process Isolation Everything You Need to Know about Linux Containers, Part I: Linux Control Groups and Process Isolation | Linux Journal Anatomy of a Container: Namespaces, cgroups Some Filesystem Magic - LinuxConf https://www.slideshare.net/jpetazzo/anatomy-of-a-container-namespaces-cgroups-some-filesystem-magic-linuxcon What even is a container: namespaces and cgroups What even is a container: namespaces and cgroups https://events.static.linuxfound.org/sites/events/files/slides/cgroup_and_namespaces.pdfResource management: Linux kernel Namespaces and cgroups https://sites.cs.ucsb.edu/~rich/class/cs293b-cloud/papers/lxc-namespace.pdfhttp://www.haifux.org/lectures/299/netLec7.pdf #14 - Introduction to Linux Control Groups (Cgroups) Introduction to Linux Control Groups (Cgroups) Control Group v2 https://www.kernel.org/doc/Documentation/cgroup-v2.txtDocker 背后的内核知识——cgroups 资源限制 Docker背后的内核知识——cgroups资源限制_语言 开发_孙健波_InfoQ精选文章 在Linux中使用namespace和cgroup实现进程的资源隔离和限制 在Linux中使用namespace和cgroup实现进程的资源隔离和限制 | 滩之南 CGroup Namespaces https://lwn.net/Articles/618873/ CGroup 介绍、应用实例及原理描述 IBM Developer Restricting process CPU usage using nice, cpulimit, and cgroups Restricting process CPU usage using nice, cpulimit, and cgroups | Scout APM Blog Docker DCA – Linux Namespaces and cgroups Docker DCA - Linux Namespaces and cgroups - buildVirtual Demystifying Containers - Part I: Kernel Space https://medium.com/saschagrunert/demystifying-containers-part-i-kernel-space-2c53d6979504 Containers: Namespaces and Dockers http://www.i3s.unice.fr/~urvoy/docs/VICC/1_2_vicc.pdf Docker Namespace and Cgroups https://medium.com/kasunmaduraeng/docker-namespace-and-cgroups-dece27c209c7 Understanding Linux Container Scheduling Understanding Linux Container Scheduling — Squarespace / Engineering Linux Container Primitives: PID and Network Namespaces - SCHUTZWERK Linux Container Primitives: PID and Network Namespaces - SCHUTZWERK Containers: cgroups, Linux kernel namespaces, ufs, Docker, and intro to Kubernetes pods Containers: cgroups, Linux kernel namespaces, ufs, Docker, and intro to Kubernetes pods - IBM MediaCenter Container Creation Using Namespaces and Bash Container Creation Using Namespaces and Bash | Nicolas Mesa Linux containers in 500 lines of code Linux containers in 500 lines of code Linux Container Primitives: cgroups, namespaces, and more! https://www.youtube.com/watch?vx1npPrzyKfs Container Creation Using Namespaces and Bash Container Creation Using Namespaces and Bash | Nicolas Mesa Containers from Scratch Containers from Scratch | posts Hands on Linux sandbox with namespaces and cgroups https://blogs.rdoproject.org/2015/08/hands-on-linux-sandbox-with-namespaces-and-cgroups/ How to run Docker containers using common Linux tools (without Docker) unshare – I Learned How To… Understanding containers (Part 1/3) Understanding containers (Part 1/3) – Bibis blog Docker 学习笔记10 容器技术原理 PID Namespace Docker 学习笔记10 容器技术原理 PID Namespace | 码农家园 Understanding Linux containers Containers explained: What they are and why you should care runc source code——bootstrap分析2 runc source code——bootstrap分析2 – freesky-edward Demystifying Containers – Part I: Kernel Space Demystifying Containers - Part I: Kernel Space | SUSE Communities Linux Namespaces Linux Namespaces Containerization Mechanisms: Namespaces Containerization Mechanisms: Namespaces - Selectel Blog Runc 容器初始化和容器逃逸 Runc 容器初始化和容器逃逸_Linux云计算网络-商业新知 chroot, cgroups and namespaces — An overview https://itnext.io/chroot-cgroups-and-namespaces-an-overview-37124d995e3d Everything You Need to Know about Linux Containers, Part I: Linux Control Groups and Process Isolation Everything You Need to Know about Linux Containers, Part I: Linux Control Groups and Process Isolation | Linux Journal Namespaces and Cgroups – the basis of Linux Containers Meetup - We are what we doLXC, Cgroups and Advanced Linux Container Technology Lecture http://www.novell.com/feeds/nih/wp-content/uploads/2012/05/SUS15_lec.pdfLinux Containers https://www.cl.cam.ac.uk/~lc525/files/Linux_Containers.pdf Linux Container Primitives: cgroups, namespaces, and more! (LinuxFest Northwest 2019) https://speakerdeck.com/samuelkarp/linux-container-primitives-cgroups-namespaces-and-more-linuxfest-northwest-2019 Container Performance Analysis http://www.brendangregg.com/Slides/LISA2017_Container_Performance_Analysis.pdf Understanding cgroups https://www.grant.pizza/blog/understanding-cgroups/ cgroups cgroups - ArchWiki Understanding Linux Container Scheduling Understanding Linux Container Scheduling — Squarespace / Engineering Understanding Linux Container Scheduling Understanding Linux Container Scheduling — Kevin Lynch Linux Kernel Isolation Features From the Frogs mouth - JFrog Blog Introduction to Linux Containers Introduction to Linux Containers | CloudNativeLab All things Linux containers containerz.info src/syscall/exec_linux.go - The Go Programming Language https://golang.org/src/syscall/exec_linux.go