zoukankan      html  css  js  c++  java
  • 容器基础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.总结

    原创:做时间的朋友
  • 相关阅读:
    oracle 查找或删除重复记录的语句
    多线程案例
    JAVA调用增删改的存储过程
    设计中最常用的CSS选择器
    ORACLE多表查询优化
    oracle存储过程的事务处理
    oracle函数调用存储过程
    oracle存储过程的基本语法
    java.lang.OutOfMemoryError: Java heap space解决方法
    文件操作工具类
  • 原文地址:https://www.cnblogs.com/PythonOrg/p/14900388.html
Copyright © 2011-2022 走看看