helios741 / myblog

觉得好请点小星星,欢迎有问题交流(issue/email)
109 stars 21 forks source link

浅谈Docker的核心概念和原理(中) #49

Closed helios741 closed 5 years ago

helios741 commented 5 years ago

rootfs

容器就是一个进程,已经在宿主机上对容器进行了隔离,对容器资源进行限制,那么容器的文件系统呢?

容器的文件系统也应该和network/PID一样和宿主机进行隔离,这个就要用到了Mount Namespace了。

看一下在DOCKER基础技术:LINUX NAMESPACE(上)的一个程序:

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

/* 定义一个给 clone 用的栈,栈大小1M */
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];

char* const container_args[] = {
    "/bin/bash",
    NULL
};

int container_main(void* arg)
{
    printf("Container - inside the container!\n");
    /* 直接执行一个shell,以便我们观察这个进程空间里的资源是否被隔离了 */
    execv(container_args[0], container_args); 
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    printf("Parent - start a container!\n");
    /* 调用clone函数,其中传出一个函数,还有一个栈空间的(为什么传尾指针,因为栈是反着的) */
    int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWNS  | SIGCHLD, NULL);
    /* 等待子进程结束 */
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

我们通过clone系统调用创建了一个新的子进程container_main,并且声明要为它启动Mount Namespace(CLONE_NEWNS)。

小知识🈯️:

为什么Mount Namespace对应的宏叫做CLONE_NEWNS而不是CLONE_NEWMOUNT的呢,这是因为Mount Namespace是Linux中的第一个namespace,当时以为就这一个了,所以也就这样了。

编译:


# gcc -o ns ns.c
# ./ns

Parent - start a container!
Container - inside the container!

这时候我们的shell就在container_main这个进程里面,我们可以通过ps看到只有这个namespace的进程。但是我们通过ls /或者ls /tmp发现文件系统并没有做隔离,还都是宿主机的文件系统。

这个是因为容器进程会默认继承父进程的文件系统,所以创建新进程的时候还有显示去声明那些目录要重新mount,比如我们要重新挂在/tmp这个目录,所以子进程的代码可以改为:

int container_main(void* arg)
{
    printf("Container - inside the container!\n");
    mount("none", "/tmp", "tmpfs", 0, ""); // 告诉容器以tmpfs(内存盘)的格式重新挂在/tmp目录
    /* 直接执行一个shell,以便我们观察这个进程空间里的资源是否被隔离了 */
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

编译之后我们通过ls /tmp查看这个目录下面就没有什么文件了。 image

我们每次启动容器,大多时候不是想继承父进程的文件系统,我们首先想到的就是直接把根目录(/)重新挂在了不就完了么。

在Linux中的chroot就能帮助搞定这个事情,chroot的用法比较简单,

通过chroot就能把ubuntu/centos的文件系统挂在到容器的根目录。这个就是rootfs了。

由于rootfs打包的不仅仅是应用程序,而是将整个操作系统的文件和目录一起打包进去,这也就意味着应用和它的依赖,这也就解决了云端和本地的配置不一样的问题了。

rootfs的分层

image

分为三层:只读层(readonly + whiteout)init层(ro + wh)可读写层(rw)

联合文件系统(UnionFS)

ubuntu和centos用得不一样

docker的volume在这里面说一说

参考