Docker 是新瓶装旧酒的产物, 依赖于 Linux 内核技术 chroot namespace 和 cgroup 本篇先来看 namespace 技术
Docker 和虚拟机技术一样, 从操作系统级上实现了资源的隔离, 它本质上是宿主机上的进程 (容器进程), 所以资源隔离主要就是指进程资源的隔离实现资源隔离的核心技术就是 Linux namespace 这技术和很多语言的命名空间的设计思想是一致的 (如 C++ 的 namespace)
隔离意味着可以抽象出多个轻量级的内核 (容器进程), 这些进程可以充分利用宿主机的资源, 宿主机有的资源容器进程都可以享有, 但彼此之间是隔离的, 同样, 不同容器进程之间使用资源也是隔离的, 这样, 彼此之间进行相同的操作, 都不会互相干扰, 安全性得到保障
为了支持这些特性, Linux namespace 实现了 6 项资源隔离, 基本上涵盖了一个小型操作系统的运行要素, 包括主机名用户权限文件系统网络进程号进程间通信
这 6 项资源隔离分别对应 6 种系统调用, 通过传入上表中的参数, 调用 clone() 函数来完成
int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);
clone() 函数相信大家都不陌生了, 它是 fork() 函数更通用的实现方式, 通过调用 clone(), 并传入需要隔离资源对应的参数, 就可以建立一个容器了 (隔离什么我们自己控制)
一个容器进程也可以再 clone() 出一个容器进程, 这是容器的嵌套
如果想要查看当前进程下有哪些 namespace 隔离, 可以查看文件 /proc/[pid]/ns (注: 该方法仅限于 3.8 版本以后的内核)
可以看到, 每一项 namespace 都附带一个编号, 这是唯一标识 namespace 的, 如果两个进程指向的 namespace 编号相同, 则表示它们同在该 namespace 下同时也注意到, 多了一个 cgroup, 这个 namespace 是 4.6 版本的内核才支持的 Docker 目前对它的支持普及度还不高所以我们暂时先不考虑它
下面通过简单的代码来实现 6 种 namespace 的隔离效果, 让大家有个直观的印象
UTS namespace
UTS namespace 提供了主机名和域名的隔离, 这样每个容器就拥有独立的主机名和域名了, 在网络上就可以被视为一个独立的节点, 在容器中对 hostname 的命名不会对宿主机造成任何影响
首先, 先看总体的代码骨架:
- #define _GNU_SOURCE
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <stdio.h>
- #include <sched.h>
- #include <signal.h>
- #include <unistd.h>
- #define STACK_SIZE (1024 * 1024)
- static char container_stack[STACK_SIZE];
- char* const container_args[] = {
- "/bin/bash",
- NULL
- };
- // 容器进程运行的程序主函数
- int container_main(void *args)
- {
- printf("在容器进程中!\n");
- execv(container_args[0], container_args); // 执行 / bin/bash return 1;
- }
- int main(int args, char *argv[])
- {
- printf("程序开始 \ n");
- // clone 容器进程
- int container_pid = clone(container_main, container_stack + STACK_SIZE, SIGCHLD, NULL);
- // 等待容器进程结束
- waitpid(container_pid, NULL, 0);
- return 0;
- }
该程序骨架调用 clone() 函数实现了子进程的创建工作, 并定义子进程的执行函数, clone() 第二个参数指定了子进程运行的栈空间大小, 第三个参数即为创建不同 namespace 隔离的关键
对于 UTS namespace, 传入 CLONE_NEWUTS, 如下:
int container_pid = clone(container_main, container_stack + STACK_SIZE, SIGCHLD | CLONE_NEWUTS, NULL);
为了能够看出容器内和容器外主机名的变化, 我们子进程执行函数中加入:
sethostname("container", 9);
最终运行可以看到效果如下:
IPC namespace
IPC namespace 实现了进程间通信的隔离, 包括常见的几种进程间通信机制, 如信号量, 消息队列和共享内存我们知道, 要完成 IPC, 需要申请一个全局唯一的标识符, 即 IPC 标识符, 所以 IPC 资源隔离主要完成的就是隔离 IPC 标识符
同样, 代码修改仅需要加入参数 CLONE_NEWIPC 即可, 如下:
int container_pid = clone(container_main, container_stack + STACK_SIZE, SIGCHLD | CLONE_NEWUTS | CLONE_NEWIPC, NULL);
为了看出变化, 首先在宿主机上建立一个消息队列:
然后运行程序, 进入容器查看 IPC, 没有找到原先建立的 IPC 标识, 达到了 IPC 隔离
PID namespace
PID namespace 完成的是进程号的隔离, 同样在 clone() 中加入 CLONE_NEWPID 参数, 如:
int container_pid = clone(container_main, container_stack + STACK_SIZE, SIGCHLD | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID, NULL);
效果如下, echo $$ 输出 shell 的 PID 号, 发生了变化
但是对于 ps/top 之类命令却没有改变:
具体的原因和接下来的内容 (包括 mount namespace,network namespace 和 user namespace), 大家可以关注我的公众号阅读, 那里的阅读体验会更好一些
PS: 对云计算感兴趣的小伙伴可以关注我的微信公众号: aCloudDeveloper, 专注云计算领域, 坚持分享干货
来源: https://www.cnblogs.com/bakari/p/8560437.html