专一于大数据及容器云核心技术解密,可提供全栈的大数据+云原平生台咨询方案,请持续关注本套博客。若有任何学术交流,可随时联系。更多内容请关注《数据云技术社区》公众号。 linux
IPC:隔离System V IPC和POSIX消息队列。
Network:隔离网络资源。
Mount:隔离文件系统挂载点。每一个容器能看到不一样的文件系统层次结构。
PID:隔离进程ID。
UTS:隔离主机名和域名。
User:隔离用户ID和组ID。
int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);
参数child_func传入子进程运行的程序主函数。
参数child_stack传入子进程使用的栈空间
参数flags表示使用哪些CLONE_*标志位
参数args则可用于传入用户参数
clone()其实是传统UNIX系统调用fork()的一种更通用的实现方式,它能够经过flags来控制使用多
少功能。一共有二十多种CLONE_*的flag(标志位)参数用来控制clone进程的方方面面(如是否与父
进程共享虚拟内存等等)。
复制代码
在进程都结束的状况下,也能够经过挂载的形式把namespace保留下来,保留namespace的目的天然是
为之后有进程加入作准备。经过setns()系统调用,你的进程从原先的namespace加入咱们准备好的新
namespace,使用方法以下:
int setns(int fd, int nstype)
参数fd表示咱们要加入的namespace的文件描述符。上文已经提到,它是一个指向/proc/[pid]/ns目录
的文件描述符,能够经过直接打开该目录下的连接或者打开一个挂载了该目录下连接的文件获得。
参数nstype让调用者能够去检查fd指向的namespace类型是否符合咱们实际的要求。若是填0表示不检查。
复制代码
后要提的系统调用是unshare(),它跟clone()很像,不一样的是,unshare()运行在原先的进程上,
不须要启动一个新进程,使用方法以下:
int unshare(int flags);
调用unshare()的主要做用就是不启动一个新进程就能够起到隔离的效果,至关于跳出原先的
namespace进行操做。这样,你就能够在原进程进行一些须要隔离的操做。Linux中自带的
unshare命令,就是经过unshare()系统调用实现的。
复制代码
startContainer() => createContainer() => loadFactory() => libcontainer.New()
复制代码
startContainer() => runner.run() => newProcess() => runner.container.Run(process)
=> linuxContainer.strat() => linuxContainer.newParentProcess(process)
=>linuxContainer.commandTemplate() => linuxContaine.newInitProcess() =>parent.start()
=> initProcess.start()
复制代码
func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) {
parentPipe, childPipe, err := newPipe()
if err != nil {
return nil, newSystemError(err)
}
cmd, err := c.commandTemplate(p, childPipe)
if err != nil {
return nil, newSystemError(err)
}
if !doInit {
return c.newSetnsProcess(p, cmd, parentPipe, childPipe), nil
}
return c.newInitProcess(p, cmd, parentPipe, childPipe)
}
复制代码
func (l *LinuxFactory) StartInitialization() (err error) {
//...
i, err := newContainerInit(it, pipe, consoleSocket, fifofd)
//...
// newContainerInit()返回的initer实现对象的Init()方法调用 "linuxStandardInit.Init()"
return i.Init()
}
func (l *linuxStandardInit) Init() error {
//...
// 配置network,
// 配置路由
// selinux配置
// + 准备rootfs
// 配置console
// 完成rootfs设置
// 主机名设置
// 应用apparmor配置
// Sysctl系统参数调节
// path只读属性配置
// 告诉runC进程,咱们已经完成了初始化工做
// 进程标签设置
// seccomp配置
// 设置正确的capability,用户以及工做目录
// 肯定用户指定的容器进程在容器文件系统中的路径
// 关闭管道,告诉runC进程,咱们已经完成了初始化工做
// 在exec用户进程以前等待exec.fifo管道在另外一端被打开
// 咱们经过/proc/self/fd/$fd打开它
// ......
// 向exec.fifo管道写数据,阻塞,直到用户调用`runc start`,读取管道中的数据
// 此时当前进程已处于阻塞状态,等待信号执行后面代码
//
if _, err := unix.Write(fd, []byte("0")); err != nil {
return newSystemErrorWithCause(err, "write 0 exec fifo")
}
// 关闭fifofd管道 fix CVE-2016-9962
// 初始化Seccomp配置
// 调用系统exec()命令,执行entrypoint
if err := syscall.Exec(name, l.config.Args[0:], os.Environ()); err != nil {
return newSystemErrorWithCause(err, "exec user process")
}
return nil
}
复制代码
void nsexec()
{
char *namespaces[] = { "ipc", "uts", "net", "pid", "mnt" };
const int num = sizeof(namespaces) / sizeof(char *);
jmp_buf env;
char buf[PATH_MAX], *val;
int i, tfd, child, len, pipenum, consolefd = -1;
pid_t pid;
char *console;
val = getenv("_LIBCONTAINER_INITPID");
if (val == NULL)
return;
pid = atoi(val);
snprintf(buf, sizeof(buf), "%d", pid);
if (strcmp(val, buf)) {
pr_perror("Unable to parse _LIBCONTAINER_INITPID");
exit(1);
}
val = getenv("_LIBCONTAINER_INITPIPE");
if (val == NULL) {
pr_perror("Child pipe not found");
exit(1);
}
pipenum = atoi(val);
snprintf(buf, sizeof(buf), "%d", pipenum);
if (strcmp(val, buf)) {
pr_perror("Unable to parse _LIBCONTAINER_INITPIPE");
exit(1);
}
console = getenv("_LIBCONTAINER_CONSOLE_PATH");
if (console != NULL) {
consolefd = open(console, O_RDWR);
if (consolefd < 0) {
pr_perror("Failed to open console %s", console);
exit(1);
}
}
/* Check that the specified process exists */
snprintf(buf, PATH_MAX - 1, "/proc/%d/ns", pid);
tfd = open(buf, O_DIRECTORY | O_RDONLY);
if (tfd == -1) {
pr_perror("Failed to open \"%s\"", buf);
exit(1);
}
for (i = 0; i < num; i++) {
struct stat st;
int fd;
/* Symlinks on all namespaces exist for dead processes, but they can't be opened */ if (fstatat(tfd, namespaces[i], &st, AT_SYMLINK_NOFOLLOW) == -1) { // Ignore nonexistent namespaces. if (errno == ENOENT) continue; } fd = openat(tfd, namespaces[i], O_RDONLY); if (fd == -1) { pr_perror("Failed to open ns file %s for ns %s", buf, namespaces[i]); exit(1); } // Set the namespace. if (setns(fd, 0) == -1) { pr_perror("Failed to setns for %s", namespaces[i]); exit(1); } close(fd); } if (setjmp(env) == 1) { // Child if (setsid() == -1) { pr_perror("setsid failed"); exit(1); } if (consolefd != -1) { if (ioctl(consolefd, TIOCSCTTY, 0) == -1) { pr_perror("ioctl TIOCSCTTY failed"); exit(1); } if (dup3(consolefd, STDIN_FILENO, 0) != STDIN_FILENO) { pr_perror("Failed to dup 0"); exit(1); } if (dup3(consolefd, STDOUT_FILENO, 0) != STDOUT_FILENO) { pr_perror("Failed to dup 1"); exit(1); } if (dup3(consolefd, STDERR_FILENO, 0) != STDERR_FILENO) { pr_perror("Failed to dup 2"); exit(1); } } // Finish executing, let the Go runtime take over. return; } // Parent // We must fork to actually enter the PID namespace, use CLONE_PARENT // so the child can have the right parent, and we don't need to forward
// the child's exit code or resend its death signal. child = clone_parent(&env); if (child < 0) { pr_perror("Unable to fork"); exit(1); } len = snprintf(buf, sizeof(buf), "{ \"pid\" : %d }\n", child); if (write(pipenum, buf, len) != len) { pr_perror("Unable to send a child pid"); kill(child, SIGKILL); exit(1); } exit(0); } 复制代码
Docker Namespace源码隔离过于高深,本文先浅析于此。bash
专一于大数据及容器云核心技术解密,可提供全栈的大数据+云原平生台咨询方案,请持续关注本套博客。若有任何学术交流,可随时联系。更多内容请关注《数据云技术社区》公众号。 网络