定义
从目的上来说,容器其实是一种沙盒技术。容器的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”。
从操作系统的角度来说,Docker实例,就是一个特殊的被限制和隔离的进程。而Docker引擎,则是用来创建和管理Docker实例的进程。
实现
glibc
提供了 clone
的方法来新建进程, 函数声明为:
int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...
/* pid_t *parent_tid, void *tls, pid_t *child_tid */ );
其中 flags 可以用来控制子进程的属性。
对于 linux 内核来说,其任务结构体中,就包含了这些属性
struct task_struct {
/* Namespaces: */
struct nsproxy *nsproxy;
/* Control Group info protected by css_set_lock: */
struct css_set __rcu *cgroups;
/* cg_list protected by css_set_lock and tsk->alloc_lock: */
struct list_head cg_list;
};
struct nsproxy {
atomic_t count;
struct uts_namespace *uts_ns;
struct ipc_namespace *ipc_ns;
struct mnt_namespace *mnt_ns;
struct pid_namespace *pid_ns_for_children;
struct net *net_ns;
struct time_namespace *time_ns;
struct time_namespace *time_ns_for_children;
struct cgroup_namespace *cgroup_ns;
};
namespace
Namespace 机制对被隔离应用的进程空间做了手脚,使得这些进程只能看到重新计算过的进程编号。
当 调用 clone
时传入的flags 包含 CLONE_NEWPID
(since Linux 2.6.24)时,linux 就会在在新的PID名称空间创建进程。
同理还有以下 flags
CLONE_NEWNS
CLONE_NEWUSER
CLONE_NEWUTS
cgroup
Linux Cgroups 的全称是 Linux Control Group。它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。
在 linux 下,执行
# 查看 cgroup 管理的资源
ls /sys/fs/cgroup/
# 输出
# blkio cpu cpuacct cpu,cpuacct cpuset devices freezer memory net_cls net_cls, net_prio, perf_event pids systemd
可以看到 cgroup 管理的资源类型:
- blkio,为块设备设定I/O 限制,一般用于磁盘等设备;
- cpuset,为进程分配单独的 CPU 核和对应的内存节点;
- memory,为进程设定内存使用的限制。
以 cpu 为案例继续执行
# 查看 cpu 目录
ls /sys/fs/cgroup/cpu
# 输出
# cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release cgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks docker
cfs_period
和 cfs_quota
, 这两个参数需要组合使用,可以用来限制进程在长度为 cfs_period 的一段时间内,只能被分配到总量为 cfs_quota
的 CPU 时间.
可以看到有一个 docker 的目录,这就是 docker 的控制组。
可以直接在这个目录下, 新建目录,也就是新增控制组。
也可以通过修改这些文件的内容来设置限制。
最后通过将 tasks 文件的内容改成要限制的进程的pid,就可以应用此限制。
rootfs
rootfs 就是挂载在容器根目录上、用来为容器进程提供隔离后执行环境的文件系统,也就是所谓的容器镜像。
Mount Namespace 跟其他 Namespace 的使用略有不同的地方:它对容器进程视图的改变,一定是伴随着挂载操作(mount)才能生效。
docker 执行隔离需要对 docker 进程的根目录进行隔离,也就是 chroot
。
需要明确的是,rootfs 并不包括操作系统内核。在 Linux 操作系统中,这两部分是分开存放的,操作系统只有在开机启动时才会加载指定版本的内核镜像。
由于 rootfs 里打包的不只是应用,而是整个操作系统的文件和目录,也就意味着,应用以及它运行所需要的所有依赖,都被封装在了一起。
Docker 公司在实现 Docker 镜像时并没有沿用以前制作 rootfs 的标准流程,而是基于 UnionFS(aufs/overlayfs ) ,将多个不同位置的目录联合挂载(union mount)到同一个目录下, 引入了层(layer)的概念。也就是说,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量 rootfs。
具体来说,是包含了
-
只读层 ro + wh
对应操作系统的基础文件
-
Init 层 ro + wh
Init 层是 Docker 项目单独生成的一个内部层,专门用来存放 /etc/hosts、/etc/resolv.conf 等信息。
-
可读写层 rw
rootfs 最上面的一层。
其中:
-
ro 只读
也就是 readonly
-
wh 白障
也就是 whiteout 删除的时候在可读写层创建一个 whiteout 文件,把只读层里的文件“遮挡”起来。
-
rw 可读写
总结
对 Docker 项目来说,它最核心的原理实际上就是为待创建的用户进程:
- 启用 Linux Namespace 配置;
- 设置指定的 Cgroups 参数;
- 切换进程的根目录(Change Root)
因此,docker 命令的本质也就是
-
docker build
加载当前目录下的 Dockerfile 文件,然后按照顺序,执行文件中的原语, 建立 UnonFS 目录。
-
docker run
启用 Linux Namespace 配置,设置指定的 Cgroups 参数,切换进程的根目录
-
docker exec
加入到一个已经存在的 Namespace 当中