容器基础3:容器镜像

1.容器概念回溯

容器本质是一种特殊的进程
namespace作用是视觉隔离,cgroups作用是限制,给沙箱围了已圈墙

2.容器内看到的文件系统是什么样子?

联想Mount namespace问题
容器里的应用进程,按理应该看到一份完全独立的文件系统,这样就可以在自己容器目录(/tmp)下操作
不受宿主机及其他容器影响

3.拿c的代码去验证一下

伪代码


#define _GNU_SOURCE
#include <sys/mount.h> 
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#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!
");
  execv(container_args[0], container_args);
  printf("Something's wrong!
");
  return 1;
}

int main()
{
  printf("Parent - start a container!
");
  int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWNS | SIGCHLD , NULL);
  waitpid(container_pid, NULL, 0);
  printf("Parent - container stopped!
");
  return 0;
}

功能说明:main函数里,clone系统调用创建了新的子进程container_main,声明要为它启用Mount Namespace(即: CLONE_NEWNS标记)

子进程执行的是/bin/bash。这个shell运行在了Mount Namespace隔离环境中

编译代码


$ gcc -o ns ns.c
$ ./ns
Parent - start a container!
Container - inside the container!

进入容器中,执行ls命令

ls /tmp
...

发现展示的内容和宿主机的内容是一样的

发现即使开启了MountNamespace,容器进程总看到的文件系统还是和宿主机一毛一样

4.重新认知一下Mount Namespace

Mount Namespace修改的,是容器进程对文件系统“挂载点”视觉的认知。
只有在“挂载”操作后,视觉才会被改变。在挂载之前,新容器会直接继承宿主机的挂载点

如何修复呢?
在容器执行前,先挂载


int container_main(void* arg)
{
  printf("Container - inside the container!
");
  // 如果你的机器的根目录的挂载类型是shared,那必须先重新挂载根目录
  // mount("", "/", NULL, MS_PRIVATE, "");
  mount("none", "/tmp", "tmpfs", 0, "");
  execv(container_args[0], container_args);
  printf("Something's wrong!
");
  return 1;
}

验证


$ gcc -o ns ns.c
$ ./ns
Parent - start a container!
Container - inside the container!
$ ls /tmp

发现变为空目录了,重新挂载生效了,容器内可以用mount -l检查


$ mount -l | grep tmpfs
none on /tmp type tmpfs (rw,relatime)

挂载操作+mount namespace的操作,重新挂载的操作只在容器内的mount namespace中有效。

而在宿主机上执行mount -l 可以发现没有tmpfs的挂载信息

5.更优化的环境,容器中文件系统独立隔离环境,容器镜像

换句话说,就是/分区下是独立的文件系统

chroot帮忙了。change root system 改变进程的根目录到你指定的位置

实际mount namespace就是基于chroot 改良发明的,也是linux 操作系统里的第一个namespace

容器根目录更真实,一版会在根目录下挂载一个完整的文件系统,比如centos的iso,容器启动后,查看ls / 展示的就是centos的所有目录和文件

挂载根目录,用来为容器提供隔离后的执行文件系统,就是容器镜像(rootfs)

一般容器镜像,会包含如下内容


$ ls /
bin dev etc home lib lib64 mnt opt proc root run sbin sys tmp usr var

进入容器后执行的/bin/bash,与宿主机的/bin/bash完全不同

6.docker的核心原理

为待创建的用户进程:
1.启用Linux Namespace配置
2.设置指定的Cgroups参数
3.切换进程的根目录

容器就诞生了。docker项目最后一步切换根目录上,优先使用pivot_root系统调用,如果不支持,才会使用chroot。这2个系统调用功能类似,但有细微区别

7.rootfs的特殊性

rootfs是操作系统包含的文件,配置,目录,不包括系统内核。linux中这2部分是分开的。操作系统只有开机启动才会加载指定版本的内核镜像

同一个宿主机上的n个容器共享宿主机的系统内核

8.镜像带来的强一致性

之前:云端环境与本地服务器环境不同,环境不同,非常痛苦

rootfs打包的不只是应用,而是操作系统层面的打包,应用以及所需要的所有依赖,都被封装一起

应用的依赖,认知不能局限在语言层面,比如golang的godeps.json。实际上操作系统本身才是应用程序最完整的“依赖库”

深入到操作系统层级的环境一致性

9.镜像的分层

用到了什么技术?
Union File System联合文件系统能力,UnionFS

将多个不同位置的目录联合挂载到同一个目录下


$ docker image inspect ubuntu:latest
...
     "RootFS": {
      "Type": "layers",
      "Layers": [
        "sha256:f49017d4d5ce9c0f544c...",
        "sha256:8f2b771487e9d6354080...",
        "sha256:ccd4d61916aaa2159429...",
        "sha256:c01d74f99de40e097c73...",
        "sha256:268a067217b5fe78e000..."
      ]
    }

rootfs的层次结构
image

  • 只读层
    最下面的5层,挂载方式是只读
    这些层的内容。增量的方式分别包含了centos操作系统的一部分

$ ls /var/lib/docker/aufs/diff/72b0744e06247c7d0...
etc sbin usr var
$ ls /var/lib/docker/aufs/diff/32e8e20064858c0f2...
run
$ ls /var/lib/docker/aufs/diff/a524a729adadedb900...
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
  • 可读写层
    最上面一层,rw方式挂载,没有写入文件前,整个目录空的,一旦容器里做了写操作,产生的内容增量出现在这个层里
    如果是删除只读层里的一个文件呢?
    通过在读写层创建一个whiteout文件,白名单文件,把你要删除的文件遮挡起来,其实并没有真正删除
    比如,你要删除只读层里一个名叫 foo 的文件,那么这个删除操作实际上是在可读写层创建了一个名叫.wh.foo 的文件。这样,当这两个层被联合挂载之后,foo 文件就会被.wh.foo 文件“遮挡”起来,“消失”了。这个功能,就是“ro+wh”的挂载方式,即只读 +whiteout 的含义。我喜欢把 whiteout 形象地翻译为:“白障”。
  • Init层
    只读层和读写层之间,docker的内部曾,专门存放/etc/hosts,/etc/resolv.conf等信息
    用户往往需要启动容器时写入一些特定的值hostname,就需要在读写层对他们进行修改
    这个修改支队当前容器有效,不希望docker commit 这些也提交
    所以单独抽了一个init层出来,用户docker commit提交的是读写层,不包含这些配置文件内容

10.总结

原创:做时间的朋友
原文地址:https://www.cnblogs.com/PythonOrg/p/14900388.html