專一於大數據及容器雲核心技術解密,可提供全棧的大數據+雲原平生臺諮詢方案,請持續關注本套博客。若有任何學術交流,可隨時聯繫。更多內容請關注《數據雲技術社區》公衆號。 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
專一於大數據及容器雲核心技術解密,可提供全棧的大數據+雲原平生臺諮詢方案,請持續關注本套博客。若有任何學術交流,可隨時聯繫。更多內容請關注《數據雲技術社區》公衆號。 網絡