UDN-企业互联网技术人气社区

板块导航

浏览  : 635
回复  : 1

[技术交流] 动手创建一个容器

[复制链接]
哥屋恩的头像 楼主
发表于 2016-5-27 20:54:19 | 显示全部楼层 |阅读模式
  自己动手,透彻理解。

  Docker如火如荼,引发容器(container)与虚拟机(vm)的激烈碰撞,基于容器的虚拟化技术

  大行其道,大家都玩儿的热火朝天,你怎能隔岸观火呢?!

  然后容器到底是个啥,稀里糊涂搞不明白。其实容器并不是多新鲜的技术,也不是什么高深的

  技术,让我们通过100行C代码自己动手创建一个容器出来,看看它到底是个啥!

  容器 v.s. 虚拟机

  这两者到底什么区别,我们不细说,只上个总结:

  容器是软件虚拟化,而虚拟机是硬件虚拟化

  容器是利用操作系统(linux kernel)特性创建的一个 进程

  容器是什么

  本质上来说,容器其实就是一个特殊的进程。相比于普通进程,这个进程(容器)之所以特殊,

  是因为它有自己独立的

  UTS(Unix Time-sharing System):可以拥有独立的主机名和域名

  PID:可以拥有独立的进程ID

  NET: 可以拥有独立的网络设备

  IPC:可以拥有独立的进程间通讯

  NS:可以拥有独立的文件系统(rootfs)

  每个方面都是一种namespace,如果一个进程拥有这些独立的namespace,便可以跟其他进程隔离开来,

  那么该进程看起来是不是就像一个独立的主机呢?对,这就达到我们的目的啦!

  拥有这些独立的namespace难不难呢,一点儿都不难,这些namespace都是linux kernel支持的,

  只需要在clone进程的时候设置相应的flag,那么新的进程就有拥有这些独立的namespace了。

  创建容器

  容器进程之所以特殊,就是因为clone时给它增加了一堆flag,这些flag让它可以拥有自己独立的namespace,

  所以创建容器首先就是要通过clone函数创建这个特殊的进程。

  创建进程

  通过clone函数创建进程,

  1.   static char c_stack[STACK_SIZE];

  2. static int c_main(void *args) {
  3.     // 在子进程里运行
  4. }

  5. int main()
  6. {
  7.     int pid = clone(c_main, c_stack + STACK_SIZE,
  8.                     SIGCHLD | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS,
  9.                     &config);
  10. }
复制代码


  这对flag让新创建的进程拥有魔力: CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS。

  进程设置

  新创建的进程虽然拥有自己独立的namespace了,但是它的很多参数默认仍然使用父进程的,

  如hostname,rootfs,procfs等(有很多,需要一个个重新配置),所以要想让它成为一个

  独立的环境,还需要进行配置,或者叫做初始化,我们这里只初始化一部分,

  set new host name

  mount procfs,向ps, top等命令会从procfs里读取信息

  挂载新的rootfs,这样该进程就拥有自己的/bin等标准目录了

  准备新的rootfs

  rootfs就是ls /所看到的内容,我们要让新进程拥有自己的rootfs,这只需调用chroot()函数就能实现,

  但是请注意,一旦chroot了,所有命令,如ls,就会去新的rootfs里面寻找,因为系统原来的rootfs对于新进程

  是不可见的了。所以在chroot之前需要做好充分的准备,否则把rootfs切换到一个空白目录,一穷二白,

  啥事儿都干不了。

  所以,我们需要准备一个新的rootfs,你可能会想,我把Ubuntu啊,Fedora啊这些发行版本的rootfs复制一份

  不就好了?的确,但是工作量太大而不可行。好在我们有busybox,神器啊!

  busybox是一个静态链接的工具库,它把常见的命令,如ls, ps, top, grep啥的,统统打包到一个可执行文件

  里了,而且这个文件只有1M左右,也就是说我们只用一个busybox就可以弄成一个rootfs!

  当然,最简单的rootfs是空白目录,啥都没有最简单。

  所谓的rootfs就是一个普通的目录,里面创建一个bin目录,然后把busybox文件放进去,大功告成。

  抽象成镜像

  这就完了吗?我的rootfs可以是一个空白目录,可以是一个用busybox做成的简易目录,可以是用Ubuntu这样的

  发行版本做成的复杂目录,也可以是只有一个应用(如Nodejs)及其依赖做的的目录,也可以是MySQL及其依赖

  做成的一个目录,发散下去,我们可以做出各种各样的rootfs来啊!我们把rootfs打包一下,然后给他起一个

  响亮的名字:镜像(image)。现在你知道什么叫做 镜像 了吧。

  分层镜像

  我们的rootfs当然是可以读写的,我们的作为容器的进程可以在rootfs里面随意读写,写入一些内容我们就可以重新

  打包rootfs,这样就创建了一个新的镜像,但是等等,新旧镜像冗余了啊!我们能否让镜像差异化增加呢?

  当然可以!

  为此,我们引入一个新的。。。额,新的文件系统(filesystem),叫做UnionFS。它的作用就是:

  把多个目录的内容联合起来形成一个新的目录,而且可以指定各个目录的读写权限。

  比如,mount -t aufs -o dirs=dir1=rw:dir2=ro:dir3=ro none rootfs/,

  这有,rootfs目录里面就同时有dir1, dir2, dir3的内容了,而且rootfs目录里面可以创建新文件、

  删除文件等操作(完全跟正常目录一样),但是新增加的文件会写入到dir1里面,因为我们指定dir1是

  可读写的,其他目录都是只读的。

  利用unionfs我们就可以把镜像目录设置为只读的,然后新建一个可读写目录存放我们新建的文件,这样

  我们在rootfs里面创建一堆文件之后,只需打包可读写目录就可以形成一个新的镜像,旧的镜像作为新镜像

  的parent,使用的时候把他们一并联合起来,

  
  1. mount -t aufs -o dirs=rwdir=rw:img=ro:pimg=ro:ppimg=ro:pppimg=ro none rootfs
复制代码


  img, pimg, ppimg, pppimg分别是祖孙四代镜像。习惯上来说,可读写目录就叫做容器(container)。

  至此,让我们再把Docker的配图来一张,是不是就很清晰了:

a.png


  代码

  因为代码比较少,直接贴上来,

  1. // container.c
  2. #define _GNU_SOURCE

  3. #include <sys/types.h>
  4. #include <sys/wait.h>
  5. #include <sys/mount.h>
  6. #include <linux/fs.h>
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <string.h>
  10. #include <sched.h>
  11. #include <unistd.h>
  12. #include <errno.h>

  13. #define BUFFER_SIZE 1024
  14. #define STACK_SIZE (1024*1024)
  15. static char c_stack[STACK_SIZE];

  16. struct c_config {
  17.     char *hostname;
  18.     char *container;
  19.     char *rootfs;
  20.     char *image;
  21.     char **args;
  22. };

  23. static char *args[] = {
  24.     "/bin/sh",
  25.     NULL
  26. };

  27. static struct c_config config = {
  28.     .hostname = "my-container",
  29.     .container = "tmp/container",
  30.     .rootfs = "tmp/rootfs",
  31.     .image = "busybox-img",
  32.     .args = args
  33. };

  34. static int c_init(const struct c_config *config) {
  35.     /* set host name */
  36.     sethostname(config->hostname, strlen(config->hostname));
  37.    
  38.     /* mount rootfs */
  39.     char *cmd = malloc(BUFFER_SIZE);
  40.     if (cmd == NULL) {
  41.         perror("mount cmd string alloc err.");
  42.         return -1;
  43.     }
  44.     snprintf(cmd, BUFFER_SIZE, "mount -t aufs -o dirs=%s=rw:%s=ro none %s",
  45.              config->container, config->image, config->rootfs);
  46.     if (system(cmd)) {
  47.         perror(strerror(errno));
  48.         free(cmd);
  49.         return errno;
  50.     }
  51.     free(cmd);
  52.    
  53.     /* change root dir */
  54.     if (chdir(config->rootfs) != 0 || chroot("./") != 0) {
  55.         perror("chdir/chroot err.");
  56.         return -1;
  57.     }
  58.    
  59.     /* mount procfs */
  60.     if (mount("proc", "/proc", "proc", 0, NULL)) {
  61.         perror("mount proc err.");
  62.         return -1;
  63.     }
  64.    
  65.     return 0;
  66. }

  67. static int c_main(void *args) {
  68.     struct c_config *config = args;
  69.     c_init(config);
  70.     execv(config->args[0], config->args);
  71.    
  72.     perror("Oops!");
  73.     return -1;
  74. }

  75. int main()
  76. {
  77.     int pid = clone(c_main, c_stack + STACK_SIZE,
  78.                     SIGCHLD | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS,
  79.                     &config);
  80.     if (pid < 0) {
  81.         perror("clone err, no permission ?!");
  82.         return -1;
  83.     }
  84.     waitpid(pid, NULL, 0);
  85.     return 0;
  86. }
复制代码


  编译: gcc container.c

  运行:

  准备一个空目录

  在其中创建镜像目录并命名为:busybox-img

  创建tmp/container/, tmp/rootfs/目录

  整个工作目录看起来是这个样子的:

  1.   +---busybox-img
  2. |   +---bin
  3. |   |       busybox
  4. |   |       sh
  5. |   |
  6. |   \---proc
  7. \---tmp
  8.     +---container
  9.     \---rootfs
复制代码


  busybox文件需要自行下载,sh是busybox的符号链接,然后sudo ./a.out就可以了。

原文作者:佚名 来源:开发者头条
发表于 2016-5-27 22:45:15 | 显示全部楼层
赞一个
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部