自己动手写Docker

原文链接

链接

容器与开发语言

容器

随着云计算领域的兴起,容器这个词出现了,但是什么是容器?

容器英文名Container,是基于Linux Namespace以及Cgroups技术实现的具备隔离特性的一组进程。

OK,他是一组具备隔离特性的进程。

虚拟机

虚拟机是使用Hypervisor技术提供的虚拟化硬件的操作系统。

OK,虚拟机是一个操作系统。

操作系统和进程的区别

操作系统是管理软件、硬件的一组进程。

GO

这里不做介绍(其实我只能看懂一点点Go代码,没时间学,后面有机会再出这方面的Blog吧)

基础技术

Linux Namespace

Namespace即为名称空间,这是一个树状的结构,父名称空间可以看到子名称空间的所有内容,反之则不行。这类似于Spring框架的父子Beanfactory。

Linux 一个实现了6个不同的Namespace

| Namespace 类型 | 系统调用参数 | 备注 | | :---------------: | :-----------: | :------------: | | Mount Namespace | CLONE NEWNS | 文件系统挂载点 | | UTS Namespace | CLONE NEWUTS | 主机名 | | IPC Namespace | CLONE NEWIPC | 进程通信 | | PID Namespace | CLONE NEWPID | 进程ID | | Network Namespace | CLONE NEWNET | 网络 | | User Namespace | CLONE NEWUSER | 用户 |

对于这些Namespace,Linux提供了3个系统调用。

| API | 备注 | | :-----: | :-------------------------------: | | clone | 创建新进程,并为其分配6个名称空间 | | unshare | 把进程移出名称空间 | | setns | 把进程加入名称空间 |

UTS Namespace

下面是一个main.go文件,我们使用指令go run main.go

package main
import (
  "os/exec"
  "syscall"
  "os"
  "log"
)
func main () {
  cmd := exec.Command("sh")
  cmd.SysProcAttr = &syscall.SysProcAttr{
    Cloneflags: syscall.CLONE_NEWUTS,
  }
  cmd.Stdin = os.Stdin
  cmd.Stdout = os.Stdout
  cmd.Stderr = os.Stderr
  if err := cmd.Run(); err!= nil {
    log.Fatal(err)
  }
}

取自原书第10页

然后我们发现我们进入到了一个shell命令中。

[root@VM-4-4-centos tmp]# go run main.go 
sh-4.4# 

接下来我们查看hostname并更改然后再次查看。

[root@VM-4-4-centos tmp]# go run main.go 
sh-4.4# hostname
VM-4-4-centos
sh-4.4# hostname
VM-4-4-centos
sh-4.4# hostname wsx
sh-4.4# hostname
wsx
sh-4.4# 

回到宿主机上使用指令hostname,发现宿主机的hostname并没有发生改变。

[root@VM-4-4-centos src]# hostname
VM-4-4-centos
[root@VM-4-4-centos src]# 

我们使用指令ps -ef | grep $$查看当前进程的pid为1539189, ppid为1539185。

sh-4.4# ps -ef | grep $$
root     1539189 1539185  0 15:01 pts/0    00:00:00 sh
root     1540099 1539189  0 15:05 pts/0    00:00:00 ps -ef
root     1540100 1539189  0 15:05 pts/0    00:00:00 grep 1539189

然后分别查看他们的ns空间, 不难发现只有uts空间不一样。

sh-4.4# ls -l /proc/$$/ns
total 0
lrwxrwxrwx 1 root root 0 Apr 16 15:08 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Apr 16 15:08 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Apr 16 15:08 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 Apr 16 15:08 net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Apr 16 15:08 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Apr 16 15:08 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Apr 16 15:08 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Apr 16 15:06 uts -> 'uts:[4026532643]'
lrwxrwxrwx 1 root root 0 Apr 16 15:07 uts -> 'uts:[4026531838]'
sh-4.4# ls -l /proc/1539185/ns
total 0
lrwxrwxrwx 1 root root 0 Apr 16 15:08 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Apr 16 15:08 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Apr 16 15:08 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 Apr 16 15:08 net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Apr 16 15:08 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Apr 16 15:08 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Apr 16 15:08 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Apr 16 15:07 uts -> 'uts:[4026531838]'

其他的Namespace

更多的例子可以查看原书11-19页,即可实现其他5个空间的隔离,其实只需要修改代码为下面这样即可。我们只需要使用符号 |就能同时开启多个资源的隔离。

  cmd.SysProcAttr = &syscall.SysProcAttr{
    Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC,
  }

Linux Cgroups

有了资源隔离,还差一点东西才能实现容器,那就是资源限制、控制、统计(包括CPU、Memory、IO等)。Linux Cgroups就是干这个事的。

cgroups(Control Groups)最初叫 Process Container,由 Google 工程师(Paul Menage 和 Rohit Seth)于 2006 年提出,后来因为 Container 有多重含义容易引起误解,就在 2007 年更名为 Control Groups,并被整合进 Linux 内核。顾名思义就是把进程放到一个组里面统一加以控制。

引用自: 原文链接

Task

在Cgroups术语中,Task就是一个进程。

Cgroup

即一个控制组,可以对一组进程进行配置。

Subsystem

具体的配置子系统,例如cpu子系统可以配置Cgroup中进程被调度的策略,memory子系统可以控制Cgroup中进程的内存占用。

Hierarchy

hierarchy把cgroup描述为一个树状结构,在这个树状结构中,Cgroups完成了继承,就和前面的Linux Namespace一样。

安装Cgroup库

yum install -y libcgroup-tools.x86_64

查看cgroup

我们可以看到这里有很多cgroup,冒号左边是子系统,右边是cgroup。

[root@VM-4-4-centos cpu]# lscgroup | head -n 15
cpu,cpuacct:/
cpu,cpuacct:/YunJing
cpu,cpuacct:/docker
cpu,cpuacct:/docker/de0ca6c8064d53b51a3076317c90c472b3b62e31d5110c08a9e158d4470bde2a
cpu,cpuacct:/docker/5e0cd00a0390669e38f844b1ecf56c63dc8d406d0c12d330d278ff137aafd2d2
cpu,cpuacct:/docker/29f1d2a9d2d3baaf7e108696dace633e84a75dca7182b3015d0a632d72b2f1f8
cpu,cpuacct:/docker/d97f48af25382fad8175a285dfe3c6ebc93fe28d022c534c20c35152a47e9a09
cpu,cpuacct:/docker/dfdc0ad0ca19f736e8bb70abcbdbbd75b483b9830920097566e01bb8dc83d1b6
cpu,cpuacct:/docker/28c73206d27e9bfcbca5fd9f801ff96a57cd07c5b6806f1bcf5932e4643296e1
cpu,cpuacct:/docker/9852a95cfef5ff071cb4554ea73b6699be0ab5aa08f873abe1575ef07c31c68f
cpu,cpuacct:/docker/a3845de540c9eaa8dc609b67d09fb40f262a0bc286eef8363607d4fba68dad36
cpu,cpuacct:/docker/d46d67f40ea34e5a9e0aaf9e32d0438d9fc9c0db614c2e60139971ab7917602f
cpu,cpuacct:/docker/a3fa62cd8e9ec321a708e81c0f5aa1048b636815db225e1f9b3252dd9f676913
cpu,cpuacct:/user.slice
cpu,cpuacct:/init.scope

查看子系统

下面的指令会列出所有的子系统,一般就几个子系统。

[root@VM-4-4-centos cpu]# lssubsys -a
cpuset
cpu,cpuacct
blkio
memory
devices
freezer
net_cls,net_prio
perf_event
hugetlb
pids
rdma

查看子系统挂载

[root@VM-4-4-centos src]# lssubsys -m
cpuset /sys/fs/cgroup/cpuset
cpu,cpuacct /sys/fs/cgroup/cpu,cpuacct
blkio /sys/fs/cgroup/blkio
memory /sys/fs/cgroup/memory
devices /sys/fs/cgroup/devices
freezer /sys/fs/cgroup/freezer
net_cls,net_prio /sys/fs/cgroup/net_cls,net_prio
perf_event /sys/fs/cgroup/perf_event
hugetlb /sys/fs/cgroup/hugetlb
pids /sys/fs/cgroup/pids
rdma /sys/fs/cgroup/rdma

Cgroup例子

如下图所示,cgroup(粉色) 是一个树状结构,组成了一个Hierarchy(绿色),而每一个子系统(蓝色)可以分配到一个Hierarchy上。

Cgroups三个组件的约束

系统在创建了新的 hierarchy之后,系统中所有的进程都会加入这个 hierarchy的 cgroup根节点,这个 cgroup根节点是 hierarchy默认创建的。

一个 subsystem只能附加到一个 hierarchy上面。

一个 hierarchy可以附加多个 subsystem。

一个进程可以作为多个 cgroup的成员,但是这些 cgroup必须在不同的 hierarchy中。

一个进程fork出子进程时,子进程是和父进程在同一个 cgroup中的,也可以根据需要将其移动到其他 cgroup中。

Cgroups 实战

我们安装下面的方式即可创建一个cgroup,这个cgroup在子系统cpu所附着的Hierarchy上。

只需要创建一个文件夹,cgroup就被创建了。

[root@VM-4-4-centos cpu]# cd /sys/fs/cgroup/cpu
[root@VM-4-4-centos cpu]# mkdir my-cpu
[root@VM-4-4-centos cpu]# cd my-cpu/
[root@VM-4-4-centos my-cpu]# ll
total 0
-rw-r--r-- 1 root root 0 Apr 17 14:32 cgroup.clone_children
-rw-r--r-- 1 root root 0 Apr 17 14:32 cgroup.procs
-r--r--r-- 1 root root 0 Apr 17 14:32 cpuacct.stat
-rw-r--r-- 1 root root 0 Apr 17 14:32 cpuacct.usage
-r--r--r-- 1 root root 0 Apr 17 14:32 cpuacct.usage_all
-r--r--r-- 1 root root 0 Apr 17 14:32 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 Apr 17 14:32 cpuacct.usage_percpu_sys
...

接下来,我们来看两个文件

[root@VM-4-4-centos my-cpu]# cat cpu.cfs_period_us 
100000
[root@VM-4-4-centos my-cpu]# cat cpu.cfs_quota_us 
-1

cfsperiodus用来配置时间周期长度,cfsquotaus用来配置当前cgroup在设置的周期长度内所能使用的CPU时间数,两个文件配合起来设置CPU的使用上限。两个文件的单位都是微秒(us),cfsperiodus的取值范围为1毫秒(ms)到1秒(s),cfsquotaus的取值大于1ms即可,如果cfsquotaus的值为-1(默认值),表示不受cpu时间的限制。下面是几个例子:

1.限制只能使用1个CPU(每250ms能使用250ms的CPU时间)
   # echo 250000 > cpu.cfs_quota_us /* quota = 250ms */
   # echo 250000 > cpu.cfs_period_us /* period = 250ms */

2.限制使用2个CPU(内核)(每500ms能使用1000ms的CPU时间,即使用两个内核)
   # echo 1000000 > cpu.cfs_quota_us /* quota = 1000ms */
   # echo 500000 > cpu.cfs_period_us /* period = 500ms */

3.限制使用1个CPU的20%(每50ms能使用10ms的CPU时间,即使用一个CPU核心的20%)
   # echo 10000 > cpu.cfs_quota_us /* quota = 10ms */
   # echo 50000 > cpu.cfs_period_us /* period = 50ms */

引用: 原文链接

紧接着我们编写一个CPU密集型的算法,计算斐波拉契数列第100000000项的最后4位数字。

#include<bits/stdc++.h>
using namespace std;

int main(){
    int n = 100000000;    
    int a[] = {0,1,1};
    for(int i=3;i<=n;i++){
        int*fib = a-i+2;
        fib[i-2] = fib[i-1];
        fib[i-1] = fib[i];
        fib[i] = (fib[i-1]+fib[i-2])%10000;
    }
    cout<<a[2]<<endl;    
}

我们运行他,发现大概执行了1秒钟

[root@VM-4-4-centos tmp]# g++ main.cpp -o main && time ./main
6875

real    0m1.020s
user    0m0.967s
sys     0m0.001s

现在我们构建一个只占用10%CPU的Cgroups,并让这个进程运行在这个Cgroups中。我们可以看到,这个进程在9.610秒内占用了0.963秒的CPU时间,这和我们希望看到的10%的CPU时间是相符合的。

[root@VM-4-4-centos tmp]# echo 10000 > /sys/fs/cgroup/cpu/my-cpu/cpu.cfs_quota_us
[root@VM-4-4-centos tmp]# echo 100000 > /sys/fs/cgroup/cpu/my-cpu/cpu.cfs_period_us 
[root@VM-4-4-centos tmp]# g++ main.cpp -o main && time cgexec -g cpu:my-cpu ./main
6875

real    0m9.610s
user    0m0.963s
sys     0m0.005s

类似如内存占用的实战,在这里可以看到更多

Union File System

联合文件系统(Union File System):2004年由纽约州立大学石溪分校开发,它可以把多个目录(也叫分支)内容联合挂载到同一个目录下,而目录的物理位置是分开的。UnionFS允许只读和可读写目录并存,就是说可同时删除和增加内容。

作者:一叶

链接:https://www.jianshu.com/p/3ba255463047

写时复制(copy-on-wrie,下文简称CoW),也叫隐式共享,是一种对可修改资源实现高效复制的资源管理技术。它的思想是,如果一个资源是重复的,但没有任何修改,这时并不需要立即创建一个新的资源,这个资源可以被新旧实例共享。创建新资源发生在第一次写操作,也就是对资源进行修改的时候。通过这种资源共享的方式,可以显著地减少未修改资源复制带来的消耗,但是也会在进行资源修改时增加小部分的开销。

引用: 原书27页

AUFS

Advanced Multi-Layered Unification Filesystem 改写了UFS,提高其可靠性和性能。

AUFS实战

我们先创建如下目录

[root@VM-4-4-centos aufs]# tree 
.
├── container-layer
├── image-layer1
│   └── file1.txt
├── image-layer2
│   └── file2.txt
├── image-layer3
│   └── file3.txt
├── image-layer4
│   └── file4.txt
└── mnt

内容如下

[root@VM-4-4-centos aufs]# cat image-layer1/file1.txt 
I'm in layer1
[root@VM-4-4-centos aufs]# cat image-layer2/file2.txt 
I'm in layer2
[root@VM-4-4-centos aufs]# cat image-layer3/file3.txt 
I'm in layer3
[root@VM-4-4-centos aufs]# cat image-layer4/file4.txt 
I'm in layer4

开始挂载

[root@VM-4-4-centos aufs]#  sudo mount -t aufs -o dirs=container-layer:image-layer1:image-layer2:image-layer3:image-layer4 none mnt
mount: /data/src/tmp/aufs/mnt: unknown filesystem type 'aufs'.

GG 现在的centos上的docker,使用的不是aufs,而是OverlayFS,不信你看

[root@VM-4-4-centos aufs]# df
Filesystem     1K-blocks     Used Available Use% Mounted on
devtmpfs          407188        0    407188   0% /dev
tmpfs             420616       48    420568   1% /dev/shm
tmpfs             420616     1052    419564   1% /run
tmpfs             420616        0    420616   0% /sys/fs/cgroup
/dev/vda1       25736400 21166108   3421600  87% /
overlay         25736400 21166108   3421600  87% /var/lib/docker/overlay2/665fa7b38b75b193df7995e1535782c88e9922d6097e16472ac94d254e18c850/merged
overlay         25736400 21166108   3421600  87% /var/lib/docker/overlay2/1f2ddfe4b78e83f2ba5b721d167b584187726a3fae80ce4ffcf400fa0b0ad1db/merged
overlay         25736400 21166108   3421600  87% /var/lib/docker/overlay2/d76eb09f0d9388bd10daeba94112025dd55c0b6c67cc37fd05ac6664fd4f9947/merged
overlay         25736400 21166108   3421600  87% /var/lib/docker/overlay2/9c8e0bf244fc65294eb2d4455994ca470cd48d3a7b65318d7382bf53ac611cfe/merged
overlay         25736400 21166108   3421600  87% /var/lib/docker/overlay2/0cbbbfc5ed25c5583abe765b06f367add27d0834cedea47e8e5bf12d633af1ee/merged
overlay         25736400 21166108   3421600  87% /var/lib/docker/overlay2/434275a20afd962e4dfea51329f1520fad94ea49b2b9c3d299a06cbf55423b40/merged
overlay         25736400 21166108   3421600  87% /var/lib/docker/overlay2/180399a29eeb51f456a165adf7ea52a79d1219882fc707b9988b93a8c60a2000/merged
overlay         25736400 21166108   3421600  87% /var/lib/docker/overlay2/f7afc7f62355db8f11449d0559fad921c929418b5b8fd80c6b20e7db93054546/merged
overlay         25736400 21166108   3421600  87% /var/lib/docker/overlay2/46d0b181bfd0218a5fa88f3ec0b3f55ef710fc5e8de9ebbac3dc8025c084a372/merged
overlay         25736400 21166108   3421600  87% /var/lib/docker/overlay2/4b64e9d775bdc157a476a2b7edc6a8141916e57107f2bca0bb795bd18ebe79ea/merged
tmpfs              84120        0     84120   0% /run/user/0
AUFS细节
读文件

下图是一个AUFS挂载情况,当我们对/mnt处进行读取文件的时候,他会从顶层依次向下寻找,如果在layer4层找到了这个文件,则直接读取,如果找不到就递归向下寻找。

写文件

写文件比较特殊,如果底层文件无法写入,则通过COW技术直接写mnt层即可。如果底层文件可读写,则直接写入底层文件。

删除文件
  • 文件在mnt层,下层只读,且下层无此文件,直接删除
  • 文件在mnt层,下层只读,且下层有此文件,删除mnt层,然后创建一个隐藏文件.wh.{filename}表示该文件被删除
  • 文件在下层读写层,直接删除。
了解更多

拓展阅读

OverlayFS

如下图所示,Overlay在主机上用到2个目录,这2个目录被看成是overlay的层。 upperdir为容器层、lowerdir为镜像层使用联合挂载技术将它们挂载在同一目录(merged)下,提供统一视图

原文: 链接

了解更多

拓展阅读

构造容器

Proc文件系统

Linux下的proc文件系统是由内核提供的,它其实不是一个真正的文件系统只包含了系统运行时的信息(比如系统内存、 mount设备信息、一些硬件配置等),它只存在于内存中,而不占用外存空间。它以文件系统的形式,为访问内核数据的操作提供接口。实际上,很多系统工具都是简单地去读取这个文件系统的某个文件内容,比如 Ismo,其实就是cat proc/modules

当遍历这个目录的时候,会发现很多数字,这些都是为每个进程创建的空间,数字就是它们的PID。

[root@VM-4-4-centos aufs]# ll /proc | head -n 15
total 0
dr-xr-xr-x  9 root             root         0 Apr  9 15:16 1
dr-xr-xr-x  9 root             root         0 Apr 17 16:26 10
dr-xr-xr-x  9 root             root         0 Apr 17 16:26 11
dr-xr-xr-x  9 root             root         0 Apr 17 15:57 1102
dr-xr-xr-x  9 root             root         0 Apr 17 15:57 1106
dr-xr-xr-x  9 root             root         0 Apr 17 15:57 1108
dr-xr-xr-x  9 root             root         0 Apr 17 15:57 1111
dr-xr-xr-x  9 root             root         0 Apr 17 15:57 1112
dr-xr-xr-x  9 root             root         0 Apr 17 15:57 1116
dr-xr-xr-x  9 root             root         0 Apr 17 15:57 1135
dr-xr-xr-x  9 root             root         0 Apr 17 15:57 1136
dr-xr-xr-x  9 root             root         0 Apr 17 16:26 12
dr-xr-xr-x  9 root             root         0 Apr 17 16:26 13
dr-xr-xr-x  9 root             root         0 Apr 17 16:17 1411993

目录结构

| 目录 | 备注 | | --------------- | ------------------------------------ | | /proc/N | PID为N的进程信息 | | /proc/N/cmdline | 进程启动命令 | | /proc/N/cwd | 链接到进程当前工作目录 | | /proc/N/environ | 进程环境变量列表 | | /proc/N/exe | 链接到进程的执行命令文件 | | /proc/N/fd | 包含进程相关的所有文件描述符 | | /proc/N/maps | 与进程相关的内存映射信息 | | /proc/N/mem | 指代进程持有的内存,不可读 | | /proc/N/root | 链接到进程的根目录 | | /proc/N/stat | 进程的状态 | | /proc/N/statm | 进程使用的内存状态 | | /proc/N/status | 进程状态信息,比stat/ statm更具可读性 | | /proc/self/ | 链接到当前正在运行的进程 |

有Go我不用

就用C++,哎,就是玩。

3.1版本

项目地址

直接看main.cpp中的主函数, 首先是解析参数,然后使用clone 系统调用制造一个进程。

int main(int argc, char *argv[]) {
    Param *param = ParamParse::parse(argc, argv);

    void *stack = malloc(FIBER_STACK);//为子进程申请系统堆栈


    int containerFlag = SIGCHLD | CLONE_NEWUTS | CLONE_NEWPID
                        | CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC | CLONE_NEWUSER;
    int pid = clone(doContainer, (char *) stack + FIBER_STACK, containerFlag, param);//创建子线程
    waitpid(pid, NULL, 0);
    cout << "parent exit " << endl;
}

然后看main.cpp的子进程, 这里挂在proc目录是为了隔离,然后由于我们并没有编写镜像,所以我们mock了一个只支持centos的镜像。紧接着就是子进程调用exec替换掉自己的代码。

int doContainer(void *param) {
    auto *runParam = (RunParam *) param;

    if (mount("proc", "/proc", "proc", MS_NOEXEC | MS_NOSUID | MS_NODEV, NULL)) {
        cerr << "mount proc error" << endl;
        exit(-1);
    }
    if (runParam->getImage() == "centos") {
        vector<string> v = runParam->getExec();

        switch (v.size()) {
            case 1:
                execlp(v[0].data(), nullptr);
                break;
            case 2:
                execlp(v[0].data(), v[1].data(), NULL);
                break;
            case 3:
                execlp(v[0].data(), v[1].data(), v[2].data(), NULL);
                break;
            case 4:
                execlp(v[0].data(), v[1].data(), v[2].data(), v[3].data(), NULL);
                break;
            default:
                cerr << "too many params, only four params support" << endl;
                exit(0);
        }
        cerr << "exec error: " << endl;
        exit(-1);
    } else {
        cerr << "could not find image '" << runParam->getImage() << "'" << endl;
        exit(-1);
    }
}

编译运行3.1

mkdir build
cd build
cmake ..
make
[root@wsx pocker]# mkdir build
[root@wsx pocker]# cd build
[root@wsx build]# cmake ..
-- The C compiler identification is GNU 8.3.1
-- The CXX compiler identification is GNU 8.3.1
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /data/src/pocker/build
[root@wsx build]# make
Scanning dependencies of target pocker
[ 25%] Building CXX object CMakeFiles/pocker.dir/src/param/parse/ParamParse.cpp.o
[ 50%] Building CXX object CMakeFiles/pocker.dir/src/param/parse/RunParamParse.cpp.o
[ 75%] Building CXX object CMakeFiles/pocker.dir/src/main.cpp.o
[100%] Linking CXX executable pocker
[100%] Built target pocker

默认的提示

[root@wsx build]# ./pocker 
please run:
      ./pocker run --help

pocker run的提示

[root@wsx build]# ./pocker run --help
usage: ./pocker [options] ... image...
options:
  -i, --interactive    interactive
  -t, --tty            tty
  -d, --detach         detach
  -?, --help           print this message

调用ls指令

[root@wsx build]# ./pocker run -it centos ls .
CMakeCache.txt  CMakeFiles  cmake_install.cmake  Makefile  pocker
parent exit 

调用ps指令

[root@wsx build]# ./pocker run -it centos ps aux
    PID TTY          TIME CMD
      1 pts/0    00:00:00 ps
parent exit 

调用bash指令

[root@wsx build]# ./pocker run -it centos bash
[nobody@wsx build]$ ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
nobody         1       0  0 02:38 pts/0    00:00:00 [bash]
nobody        22       1  0 02:38 pts/0    00:00:00 ps -ef
[nobody@wsx build]$ exit
exit
parent exit 

pocker3.1 in docker

笔者还为大家准备了一份开箱即用的3.1版本。大家可以一起来学习。

s@s ~ % docker run  --privileged  -it --rm 1144560553/pocker:3.1 bash 
Unable to find image '1144560553/pocker:3.1' locally
3.1: Pulling from 1144560553/pocker
7a0437f04f83: Already exists 
4bbae049836d: Pull complete 
Digest: sha256:8340325bed1f0c26a1a24f0052e03fd06cb873d47a1b5d750f827d2ad4691b9e
Status: Downloaded newer image for 1144560553/pocker:3.1
[root@ed07427b0ebd /]# pocker 
please run:
      pocker run --help
[root@ed07427b0ebd /]# pocker run --help
usage: pocker [options] ... image...
options:
  -i, --interactive    interactive
  -t, --tty            tty
  -d, --detach         detach
  -?, --help           print this message
[root@ed07427b0ebd /]# pocker run -it centos ps ef
  PID TTY          TIME CMD
    1 pts/0    00:00:00 ps
parent exit 
[root@ed07427b0ebd /]# pocker run -it centos ls . 
bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc
root  run  sbin  srv  sys  tmp  usr  var
parent exit 
[root@ed07427b0ebd /]# pocker run -it centos bash
[nobody@ed07427b0ebd /]$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
nobody       1     0  0 07:24 pts/0    00:00:00 [bash]
nobody      14     1  0 07:24 pts/0    00:00:00 ps -ef
[nobody@ed07427b0ebd /]$ exit
exit
parent exit 
[root@ed07427b0ebd /]# exit
exit

3.2-cpu版本

这个版本笔者主要增加了一些cpu的限制,先看main.cpp中多了一个函数,这个函数是在容器启动前就已经调用了的,目的就是创建CPU子系统,我们可以看到这里主要使用了system系统调用,在目录/sys/fs/cgroup/cpu下用容器的id为文件夹名字创建了一个cpu子系统,在其中修改cfsquotaus和cfsperiodus来解决cpu资源的问题。具体可见[Cgroups 实战](#Cgroups 实战)。

void prepareContainer(RunParam *runParam) {
    // create cpu subsystem
    char cmd[128];
    string cmdList[] = {
            "cd /sys/fs/cgroup/cpu ",
            "&& mkdir %s ",
            "&& cd %s ",
            "&& echo %d > cpu.cfs_quota_us ",
            "&& echo 50000 > cpu.cfs_period_us "
    };
    string cmdOrigin;
    for (string &s:cmdList) {
        cmdOrigin += s;
    }
    sprintf(cmd, cmdOrigin.data(),
            runParam->getContainerId().data(),
            runParam->getContainerId().data(),
            int(runParam->getCpus() * 50000)
    );
    system(cmd);
}

然后就是在容器启动后增加了下面这一段代码, 依然使用系统调用system,把容器所在的进程ID加入到tasks文件中,其目的就是让当前进程加入cpu子系统,来限制cpu的使用率。然后为了测试cpu子系统的有效性,笔者还在其中加了一个0到1e9的for循环来完成一个计算密集型任务。pocker会输出这个任务所占用的时间(单位为秒)。

    // add cpu subsystem
    char cmd[128];
    sprintf(cmd, "echo %d >> /sys/fs/cgroup/cpu/%s/tasks",
            getpid(), runParam->getContainerId().data());
    system(cmd);

    int cur = time(0);
    int a = 1;
    for (int i = 0; i < 1e9; i++) {
        a += i;
    }
    cout << a << endl;
    cout << "cost: " << time(0) - cur << endl;

当然笔者依然准备了一份开箱即用的docker版本,大家可以自行尝试。下面是笔者测试的结果。 我们可以看到当cpus取值为0.5的时候,花费了7秒,当取值为0.25的时候,花费了18秒。

s@s ~ % docker run  --privileged  -it --rm 1144560553/pocker:3.2-cpu bash
Unable to find image '1144560553/pocker:3.2-cpu' locally
3.2-cpu: Pulling from 1144560553/pocker
7a0437f04f83: Pull complete 
cc5cd42589c1: Pull complete 
Digest: sha256:01ac27156f599acfa588a51301455b92eca7c4963b0e6c6ab943d66f4c5cb06f
Status: Downloaded newer image for 1144560553/pocker:3.2-cpu
[root@37ab95d1bfdb /]# pocker
please run:
      pocker run --help
[root@37ab95d1bfdb /]# pocker run -idt --memory 50m --cpus 0.5 --memory-swap 1024m centos ps ef
RunParam:: tty: 1, interactive: 1, detach: 1, memory: 50, memorySwap: 1024, cpus: 0.500000, image: centos,containerId: 205b8eaf-76a3-474f-9a3f-4d5a9e5b64ad,containerName: 
-1243309311
cost: 7
  PID TTY          TIME CMD
    1 pts/0    00:00:03 ps
parent exit 
[root@37ab95d1bfdb /]# pocker run -idt --memory 50m --cpus 0.25 --memory-swap 1024m centos ps ef
RunParam:: tty: 1, interactive: 1, detach: 1, memory: 50, memorySwap: 1024, cpus: 0.250000, image: centos,containerId: 322904df-a8b0-46c4-b7c2-ec3e6ea66f17,containerName: 
-1243309311
cost: 18
  PID TTY          TIME CMD
    1 pts/0    00:00:04 ps
parent exit 

3.2-mem版本

新增的主要部分还是在main.cpp中,这个部分完成了内存子系统,这里直接看到,我们在位置/sys/fs/cgroup/memory新建了一个文件夹,并限制了内存和交换内存的大小,注意到一旦进程发生了内存溢出,默认将会被kill

  // create memory subsystem
  cmdList = {"cd /sys/fs/cgroup/memory ", "&& mkdir %s ",
                            "&& cd %s ", "&& echo %d > memory.limit_in_bytes ",
                            "&& echo %d > memory.memsw.limit_in_bytes "};
  sprintf(cmd, StringUtils::join(cmdList).data(),
          runParam->getContainerId().data(), runParam->getContainerId().data(),
          int(runParam->getMemory()), int(runParam->getMemorySwap()));
  system(cmd);

笔者还是准备了一份开箱即用的docker版本(以后的docker版本都将直接转移到账号fightinggg下,而不是1144560553),大家可以直接尝试。

这里首先使用docker创建了一个pocker,然后使用pocker创建了一个内存空间10mb的容器,最后在容器中使用大量的内存,之后发现这个容器被Killed。

s@s ~ % docker run  --privileged  -it --rm fightinggg/pocker:3.2-mem bash
[root@6b67dbf3b43e /]# pocker run -itd -m 10m --memory-swap 10m centos bash
RunParam:: tty: 1, interactive: 1, detach: 1, memory: 10485760, memorySwap: 10485760, cpus: 1.000000, image: centos,containerId: 2b90e451-8d10-4efc-b105-42000930b51d,containerName: 
[nobody@6b67dbf3b43e /]$ bash -c "arr=(1 2 3); for((i=1;i<=1000000;i++));  do arr[i]=i; done; echo ${arr[@]}"
Killed
[nobody@6b67dbf3b43e /]$ exit
exit
container exit, thanks for using pocker 
[root@6b67dbf3b43e /]# 

构造镜像

4.1-busybox版本

这个版本中,我们实现了容器根目录的隔离,首先从docker容器中导出busybox的文件系统,然后将其挂载到pocker所构造的容器中,主要代码在main.cpp中,这里增加了系统调用SYS_pivot_root,把busybox的文件系统挂在到当前根目录,然后卸载旧的根目录。

// mount busyBox 1. change workdir
  if (chdir(busyBoxDir.data())) {
    cerr << "chdir to busyBoxDir error" << endl;
    exit(-1);
  }
  // mount busyBox 2. mount busyBox
  if (mount(busyBoxDir.data(), busyBoxDir.data(), "bind", MS_BIND | MS_REC,
            NULL)) {
    cerr << "mount busyBox error" << endl;
    exit(-1);
  }
  String privotRootName = ".pivot_root" + runParam->getContainerId();
  String privotRoot = busyBoxDir + "/" + privotRootName;
  // mount busyBox 3. mkdir put_old
  if (mkdir(privotRoot.data(), S_IRWXU | S_IRWXG | S_IRWXO)) {
    cerr << "mkdir privotRoot error" << endl;
    exit(-1);
  }
  // mount busyBox 4. privot_root()
  if (syscall(SYS_pivot_root, busyBoxDir.data(), privotRoot.data())) {
    cerr << "privot_root error" << endl;
    exit(-1);
  }
  // mount busyBox 5. to dir /
  if (chdir("/")) {
    cerr << "chdir to / error" << endl;
    exit(-1);
  }
  // mount busyBox 6. unmount .privot_root
  if (umount2(("/" + privotRootName).data(), MNT_DETACH)) {
    cerr << "unmount .pivot_root  error " << getErr() << endl;
    exit(-1);
  }
  // mount busyBox 7. delete dir
  if (rmdir(("/" + privotRootName).data())) {
    cerr << "rm .pivot_root  error " << getErr() << endl;
    exit(-1);
  }

笔者还是准备了一个docker版本,可以看到这时候使用ls,已经能发现根目录下的文件系统发生了变化。

s@s hexo-blog % docker run -it --rm --privileged fightinggg/pocker:4.1-busybox  
[root@2d2a5c1bcd01 /]# pocker run -it busybox sh
RunParam:: tty: 1, interactive: 1, detach: 0, memory: 10485760, memorySwap: 10485760, cpus: 1.000000, image: busybox,containerId: 0ee090e5-9829-4140-bfd1-48982952f4e0,containerName: 
container begin: sh
/ # ps ef
PID   USER     TIME  COMMAND
    1 root      0:00 sh
    4 root      0:00 ps ef
/ # ls -a
.           ..          .dockerenv  bin         dev         etc         home        proc        root        sys         tmp         usr         var
/ # df
Filesystem           1K-blocks      Used Available Use% Mounted on
overlay               16447356   7816736   7775428  50% /
/ # exit
container exited, status=0,  thanks for using pocker
[root@2d2a5c1bcd01 /]# exit
exit
s@s hexo-blog % 

4.2-overlay版本

这部分笔者并没有选择和原书中一样的aufs文件系统,而是使用了overlay文件系统,哎就是玩。主要的修改还是在main.cpp中。

大概是先创建一个disk.img文件,然后将其挂载到目录disk下,在disk目录下创建三个文件夹,upper、tmp和overlay,最后使用overlay文件系统以busybox为lower构造出一个两层文件系统。

这里有一个很重要的点,为什么要挂载disk.img,首先注意到一个事实,docker默认使用overlay文件系统来构造容器,所以我们下面这个程序是有可能运行在overlay文件系统下的。

overlay文件系统挂载时对upper有一定的要求,这导致了overlay文件系统没办法做为upper层挂载在另一个overlay文件系统下。

所以我们必须虚拟一个文件系统。

挂载流程和代码都在下面

  String containerDataDir = containerDir + "/" + runParam->getContainerId();
  if (mkdir(containerDataDir.data(), 0777)) {
    cerr << " mkdir " << containerDataDir << " error" << endl;
    exit(-1);
  }

  String diskFile = containerDataDir + "/disk.img";
  String containerDisk = containerDataDir + "/disk";

  // create container disk 1. create disk
  sprintf(cmd, "dd if=/dev/zero bs=1M count=20 of=%s", diskFile.data());
  system(cmd);

  // 2. init file system
  if (system(("mkfs.ext4 " + diskFile).data())) {
    cerr << "mkfs error " << diskFile << endl;
    exit(-1);
  }

  mkdir(containerDisk.data(), 0777);

  // 3. mount container disk system
  sprintf(cmd, "mount %s %s", diskFile.data(), containerDisk.data());
  printf("mount upper: %s\n", cmd);
  if (system(cmd)) {
    cerr << "mount " << diskFile << " error" << endl;
    exit(-1);
  }

  String containerUpper = containerDisk + "/upper";
  String containerTmp = containerDisk + "/tmp";
  String containerMerge = containerDisk + "/overlay";

  mkdir(containerUpper.data(), 0777);
  mkdir(containerTmp.data(), 0777);
  mkdir(containerMerge.data(), 0777);

  // system(("ls -al " + containerDisk).data());

  // 4. build overlay
  sprintf(cmd,
          "mount -t overlay overlay "
          "-olowerdir=%s,upperdir=%s,workdir=%s %s",
          busyBoxDir.data(), containerUpper.data(), containerTmp.data(),
          containerMerge.data());
  printf("do: %s\n", cmd);
  if (system(cmd)) {
    cerr << "build overlay error " << getErr() << endl;
    exit(-1);
  }

笔者依旧准备了一份开箱即用的docker版本。如下。首先演示了在第一个容器中创建文件abc,然后退出容器,接着在第二个进入第二个容器,查看目录,是看不到文件abc的。

s@s pocker % docker run -it --rm --privileged fightinggg/pocker:4.2-overlay bash
Unable to find image 'fightinggg/pocker:4.2-overlay' locally
4.2-overlay: Pulling from fightinggg/pocker
7a0437f04f83: Already exists 
00386041dce7: Pull complete 
7b2a7c3db2a0: Pull complete 
eeb1e29037fb: Pull complete 
74f885815b4e: Pull complete 
Digest: sha256:300f607e39217465a547593fda5c93c2a4d20e133dab82a519127bec66976b1e
Status: Downloaded newer image for fightinggg/pocker:4.2-overlay
[root@e070166a0ba7 /]# pocker run -it busybox sh
RunParam:: tty: 1, interactive: 1, detach: 0, memory: 10485760, memorySwap: 10485760, cpus: 1.000000, image: busybox,containerId: f5212791-e818-4fe2-b040-e7f779473e23,containerName: 
20+0 records in
20+0 records out
20971520 bytes (21 MB, 20 MiB) copied, 0.0562303 s, 373 MB/s
mke2fs 1.45.6 (20-Mar-2020)
Discarding device blocks: done                            
Creating filesystem with 20480 1k blocks and 5136 inodes
Filesystem UUID: 61b1d479-fca0-41b0-86be-5d9c3c501714
Superblock backups stored on blocks: 
    8193

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (1024 blocks): done
Writing superblocks and filesystem accounting information: done

mount upper: mount /usr/local/pocker/data/containers/f5212791-e818-4fe2-b040-e7f779473e23/disk.img /usr/local/pocker/data/containers/f5212791-e818-4fe2-b040-e7f779473e23/disk
do: mount -t overlay overlay -olowerdir=/usr/local/pocker/data/images/busybox,upperdir=/usr/local/pocker/data/containers/f5212791-e818-4fe2-b040-e7f779473e23/disk/upper,workdir=/usr/local/pocker/data/containers/f5212791-e818-4fe2-b040-e7f779473e23/disk/tmp /usr/local/pocker/data/containers/f5212791-e818-4fe2-b040-e7f779473e23/disk/overlay
container begin: sh
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # mkdir abc
/ # ls
abc   bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # exit
container exited, status=0,  thanks for using pocker
[root@e070166a0ba7 /]# pocker run -it busybox sh
RunParam:: tty: 1, interactive: 1, detach: 0, memory: 10485760, memorySwap: 10485760, cpus: 1.000000, image: busybox,containerId: 276d1b7b-72de-4538-8b39-fbfb469ae930,containerName: 
20+0 records in
20+0 records out
20971520 bytes (21 MB, 20 MiB) copied, 0.0629004 s, 333 MB/s
mke2fs 1.45.6 (20-Mar-2020)
Discarding device blocks: done                            
Creating filesystem with 20480 1k blocks and 5136 inodes
Filesystem UUID: 4231f410-3e32-4486-9d09-2de705563eb8
Superblock backups stored on blocks: 
    8193

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (1024 blocks): done
Writing superblocks and filesystem accounting information: done

mount upper: mount /usr/local/pocker/data/containers/276d1b7b-72de-4538-8b39-fbfb469ae930/disk.img /usr/local/pocker/data/containers/276d1b7b-72de-4538-8b39-fbfb469ae930/disk
do: mount -t overlay overlay -olowerdir=/usr/local/pocker/data/images/busybox,upperdir=/usr/local/pocker/data/containers/276d1b7b-72de-4538-8b39-fbfb469ae930/disk/upper,workdir=/usr/local/pocker/data/containers/276d1b7b-72de-4538-8b39-fbfb469ae930/disk/tmp /usr/local/pocker/data/containers/276d1b7b-72de-4538-8b39-fbfb469ae930/disk/overlay
container begin: sh
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # exit
container exited, status=0,  thanks for using pocker
[root@e070166a0ba7 /]# exit
exit
s@s pocker % 

4.3-volumes版本

今天去拍毕业照了,累了一整天,哎,真累,无聊中得到了两个点:

  • 仔细想了想,4.2中让pocker去适配操作系统的文件系统,有点不太合理,我觉得应该pocker还是不应该管操作系统的文件系统是哪个具体的类型,让用户自己去挂载ext4就可以了,没必要在程序中搞。
  • Java代码写C++真是shit到家了,我全给他改成了下划线命名。

然后步入正题,在4.2版本中我们注意到换根以后,卸载privot以前,可以做一些文件映射,于是笔者直接使用mount指令将其挂载,实现了容器数据的持久化,但是由于笔者使用的命令行框架似乎不支持数组格式的flag,所以目前只能映射一个目录。这个以后应该会修复,新增代码如下,非常简单。

void pre_umount_privot(run_param *arg_run_param, string privot_root_name) {
  if (!arg_run_param->volumes.empty()) {
    auto v = arg_run_param->volumes[0];
    string src = "/" + privot_root_name + "/" + v.from;
    system(("mkdir -p " + v.to).data());
    if (mount(src.data(), v.to.data(), NULL, MS_BIND, NULL)) {
      cerr << "mount " << v.from << " to " << v.to << " failed" << getErr()
           << endl;
      exit(-1);
    }
  }
}

当然笔者还是准备了一个非常nice的docker版本,专门给懒人用的,下面是例子。

s@s ~ % docker run -it --rm --privileged fightinggg/pocker:4.3-volumes 
Unable to find image 'fightinggg/pocker:4.3-volumes' locally
4.3-volumes: Pulling from fightinggg/pocker
7a0437f04f83: Already exists 
00386041dce7: Already exists 
06435815e90c: Pull complete 
7e9a89da8fed: Pull complete 
16d9b87459ec: Pull complete 
Digest: sha256:f6314e114d6bd6bed21c6749c38b4636d1db51b22f8ab575bbdf72ccd8307dd2
Status: Downloaded newer image for fightinggg/pocker:4.3-volumes
[root@1e558fec41d2 /]# pocker run -it -v /:/hostdir/root busybox sh
run_param:: tty: 1, interactive: 1, detach: 0, memory: 10485760, memory_swap: 10485760, cpus: 1.000000, image: busybox,volumes: [ (from: /, to: /hostdir/root)]id: 744a37f3-a390-4faf-8fbb-3baa43b62988,name: 
20+0 records in
20+0 records out
20971520 bytes (21 MB, 20 MiB) copied, 0.0395407 s, 530 MB/s
mke2fs 1.45.6 (20-Mar-2020)
Discarding device blocks: done                            
Creating filesystem with 20480 1k blocks and 5136 inodes
Filesystem UUID: d46262a1-0cc4-46a0-ac1b-1fd908c8fa28
Superblock backups stored on blocks: 
    8193

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (1024 blocks): done
Writing superblocks and filesystem accounting information: done

mount upper: mount /usr/local/pocker/data/containers/744a37f3-a390-4faf-8fbb-3baa43b62988/disk.img /usr/local/pocker/data/containers/744a37f3-a390-4faf-8fbb-3baa43b62988/disk
do: mount -t overlay overlay -olowerdir=/usr/local/pocker/data/images/busybox,upperdir=/usr/local/pocker/data/containers/744a37f3-a390-4faf-8fbb-3baa43b62988/disk/upper,workdir=/usr/local/pocker/data/containers/744a37f3-a390-4faf-8fbb-3baa43b62988/disk/tmp /usr/local/pocker/data/containers/744a37f3-a390-4faf-8fbb-3baa43b62988/disk/overlay
container begin: sh
/ # ls
bin      dev      etc      home     hostdir  proc     root     sys      tmp      usr      var
/ # cd hostdir
/hostdir # ls
root
/hostdir # cd root
/hostdir/root # ls
bin         etc         lib         lost+found  mnt         proc        run         srv         tmp         var
dev         home        lib64       media       opt         root        sbin        sys         usr
/hostdir/root # mkdir containerCreateDir
/hostdir/root # exit
container exited, status=0,  thanks for using pocker
[root@1e558fec41d2 /]# ls
bin  containerCreateDir  dev  etc  home  lib  lib64  lost+found  media    mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@1e558fec41d2 /]# 

容器进阶

笔者不准备实现这部分代码了。

5.1实现后台运行只需要将容器变为守护进程,让init收管即可

5.2实现查看运行中的容器是crud,把本地json文件当做了数据库

5.3实现查看容器日志只需要重定向容器的输出到文件中即可

5.4实现进入容器还是有点意思的,需要使用系统调用setns进入和容器相同的名称空间即可

5.5停止容器实际上只是发了一个kill信号

5.6删除容器清理一下就好了

5.7通过容器制作镜像只需要提前保留容器的upper文件系统,之后自己合并即可

5.8配置环境变量也只需要简简单单的在宿主进程进入容器时配置即可

容器网络

这部分过于偏向于计算机网络,对于这部分,笔者后面会专门出一篇Blog进行介绍。

这部分代码笔者也不准备实现。关于容器网络部分,笔者第一次接触到是在《Kubernetes权威指南:从Docker到Kubernetes实践全接触》(第2版)-2016.10-电工-P519-龚正,吴治辉,王伟 等 的第三章第七节网络原理中,这两本书的这两个地方中有很多重复的地方。

至此,笔者的Pocker项目也将会告一段落了。

高级实践

这部分介绍一些名词,希望可以体会到其中的设计理念。

OCI

Open Container Initiative 是一个组织,是Linux基金会在2015年成立的,是一个定义容器标准的组织。

runC

runC是一个容器引擎,他主要用于构造、运行容器。

runC是个轻量级的容器运行引擎, 包括所有 Docker 使用的和容器相关的系统调用的代码。

runC的目标就是去构造到处都可以运行的标准容器。

引用: 原书177页

runC的流程和pocker的流程是比较类似的,pocker只是个小demo,毕竟全部加起来也就几百行代码,他没有使用任何设计模式。

代码读到这里,应该可以大概理解runC创建容器的整个过程了,如下

1.读取配置文件。

2.设置 rootfilesystem

3.使用 factory创建容器,各个系统平台均有不同实现。

4.创建容器的初始化进程 process

5.设置容器的输出管道,主要是Go的 pipes

6.执行 Container. Start)启动物理的容器

7.回调init方法重新初始化容器进程。

runC父进程等待子进程初始化成功后退出。

可以看到,具体的执行流程涉及3个概念: process、 container、 factory。 factory用来创建容器, process 负责进程之间的通信和启动容器

引用: 原书185页

Containerd

containerd是一个守护进程,专注于容器的生命周期管理,方便容器编排。当然containerd只是docker的一部分,他不负责镜像的构造。

每个 contained只负责一台机器,Pul镜像、对容器的操作(启动、停止等)、网络、存储都 是由 container完成的。具体运行容器由runC负责。

CRI容器引擎

CRI是 Container Runtime Interface ,即容器运行时,众所周知,容器不只docker这一个软件支持,还有很多其他的支持容器的软件,k8s是负责容器编排的,k8s定义了一套CRI标准,只要你的容器支持CRI,那么k8s就可以帮助你管理他。

classDiagram
  CRI <|.. docker
  CRI <|.. podman
  CRI <|.. LXC
  CRI <|.. others
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《自己动手Docker》是一本由架构师嵩天和Docker社区联合编的开源技术书籍。书中介绍了Docker的基本概念、背景知识和原理,并且通过实践的方式帮助读者了解Docker的具体应用。以下是对这本书的回答: 《自己动手Docker》这本书是作者嵩天和Docker社区共同编的一本开源技术书籍。通过这本书,读者可以了解到Docker的基本概念、背景知识和工作原理。 首先,书中介绍了Docker的概念和由来。Docker是一个开源的容器化技术,通过容器化的方式,能够将应用程序和其依赖的资源打包在一起,以便于在任何地方都能够运行。Docker的诞生解决了传统虚拟化技术的运行效率问题,并且能够提供更好的资源利用率和应用程序的可移植性。 其次,书中详细介绍了Docker的工作原理和组成部分。Docker的核心是Docker引擎,它负责创建、运行和管理容器。Docker镜像是Docker的核心概念之一,它是一个只读的模板,包含了运行应用程序所需的全部依赖项。通过Docker镜像,我们可以快速创建和部署应用程序。此外,书中还介绍了容器和镜像的关系、Docker的网络和存储管理方式等重要内容。 最后,书中通过实际操作引导读者理解和使用Docker。通过一些具体的例子和实践,读者可以学习如何构建自己的Docker镜像、创建和运行容器、使用Docker网络和存储等技术。通过这些实践,读者不仅能够清晰地了解Docker的工作机制,还能够掌握一些实际应用场景下的技巧和经验。 总而言之,《自己动手Docker》这本书通过理论和实践相结合的方式,以简洁明了的语言和实例详细解释了Docker的基本概念、工作原理和实际应用。是一本适合开发者、运维人员和对Docker感兴趣的读者了解和学习Docker的好书籍。 ### 回答2: 《自己动手docker》是一本以Docker为重点的技术书籍,通过学习本书可以了解Docker的原理和使用方法,掌握构建、部署和管理容器化应用的技巧。 这本书首先介绍了Docker的背景和发展,解释了为什么要使用Docker以及它的优势。接着,书中详细讲解了Docker的核心概念,包括镜像、容器、仓库等,帮助读者理解Docker的基本原理和组成部分。 随后,本书介绍了如何在本地环境搭建Docker,并通过实例演示了如何构建自定义镜像、运行容器以及管理容器的生命周期。这些实例涵盖了常见的应用场景,如部署Web应用、数据库容器化等。 此外,本书还介绍了Docker Swarm和Kubernetes等容器编排工具,让读者能够了解如何使用这些工具高效地管理和扩展容器化应用。 总体来说,《自己动手docker》是一本很实用的技术书籍,适合那些想要系统学习和掌握Docker技术的开发者和运维人员。无论是初学者还是有一定经验的人,都可以从这本书中获得价值。通过亲自动手实践各种实例,读者能够深入理解Docker的原理和使用方法,提升自己的技术水平。 ### 回答3: 《自己动手docker》是一本讲述如何编Docker的书籍。Docker是一种流行的容器化技术,可以将应用程序及其依赖项打包到一个可移植的容器中,从而实现应用程序在不同环境中的快速部署和扩展。 这本书主要讲解了Docker的核心原理和技术实现。首先,它介绍了Linux容器的基本概念和原理,包括命名空间、控制组、镜像和容器等。然后,它详细解释了Docker的架构和基本组件,例如Docker引擎、镜像仓库和网络管理等。 接下来,这本书逐步引导读者编自己的Docker。它从编一个简单的容器隔离工具开始,逐渐引入更多功能,例如容器的生命周期管理、网络隔离和卷管理等。这样,读者能够逐步了解容器化技术的实现细节。 此外,这本书还介绍了一些与Docker相关的技术和工具,例如容器编排工具Docker Compose和容器编排平台Kubernetes。这些技术可以帮助读者更好地管理和扩展自己的Docker应用程序。 总而言之,《自己动手docker》是一本编Docker容器的实践指南。通过学习这本书,读者可以深入了解Docker的原理和实现,并掌握使用Docker构建和管理容器的技能。无论是对于开发人员还是系统管理员来说,这本书都是一本不可多得的学习资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值