深入刨析容器(四):深入理解容器镜像

news2024/10/5 16:30:03

容器通过Namespace和Cgroups将自己与宿主机隔离,那么容器里的进程看到文件系统又是什么样子的呢?容器里的程序应该看到完全独立的文件系统,这样它就可以在自己的容器目录(比如 /tmp)下进行操作,而完全不会受宿主机以及其他容器的影响。

但真的是这样情况吗?

一听到文件系统的隔离我们就能想到Mount Namespace。那我们接下来就来讲讲它。

1.Mount Namespace

Mount Namespace:用来隔离各个进程看到的挂载点视图,在不同的Namespace进程中,看到的文件系统层次是不一样的。

我们来个小验证对这个问题进行验证下:在创建子进程时开启指定的 Namespace。

#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!\n");
  execv(container_args[0], container_args);
  printf("Something's wrong!\n");
  return 1;
}

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

这段代码的功能非常简单:在 main 函数里,我们通过 clone() 系统调用创建了一个新的子进程 container_main,并且声明要为它启用 Mount Namespace(即:CLONE_NEWNS 标志)。

而这个子进程执行的,是一个“/bin/bash”程序,也就是一个 shell。所以这个 shell 就运行在了 Mount Namespace 的隔离环境中。

来一起编译下程序:

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

这样我们就进入了容器的内部,这时我们执行ls查看文件,发现/tmp目录下的内容和宿主机完全一样,

$ ls /tmp
# 你会看到好多宿主机的文件

即使开启了Mount Namespace。容器进程看到的文件也和宿主机的文件一样。

原因是:我们需要真正的“挂载”操作后才,进程的视图才会被改变,在此之前,新创建的容器直接继承宿主机的各个挂载点,解决办法是,在创建进程时除了声明要启用 Mount Namespace 之外,我们需要先告诉容器进程,我们要挂载哪个目录。


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

可以看到,在修改后的代码里,我在容器进程启动之前,加上了一句 mount(“none”, “/tmp”, “tmpfs”, 0, “”) 语句。就这样,我告诉了容器以 tmpfs(内存盘)格式,重新挂载了 /tmp 目录。

看一下编译执行的结果:

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

可以看到这次ls /tmp目录则是个空文件,证明挂载生效了,可以用mount -l检查下:

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

可以看到,容器里的 /tmp 目录是以 tmpfs 方式单独挂载的。

tmpfs:是Linux/Unix系统上的一种基于内存的文件系统。tmpfs可以使用您的内存或swap分区来存储文件。

Mount Namespace对进程的改变一定伴随着挂载(mount)操作才能生效,可不可以有更方便的方式,我们可以在容器启动之前重新挂载它的整个根目录,那么引出了chroot(change root file system)。

2.chroot

chroot:针对正在运行的软件进程和子进程,改变它外显的根目录,

为了让容器看起来更“真实”,一般在根目录挂载一个完整的操作系统的文件系统。比如 Ubuntu16.04 的 ISO,这样容器启动后,在容器里执行ls,查看根目录,则就是Ubuntu的文件目录。

而这个挂载在容器根目录,用来在容器进程提供隔离后执行环境文件的系统,就是所谓的“容器镜像”。他有什么更专业的名字,叫做rootfs(跟文件系统)。

你可能会想怎么又扯到rootfs去了?

chroot是一种在Unix-like系统中改变进程的根目录的方法,它可以将进程的文件系统隔离开,使得进程只能访问到指定的目录及其子目录。而rootfs是一种虚拟文件系统,它创建了一个独立的根目录,并将进程的文件系统限定在这个根目录下。通过使用rootfs,可以实现容器化技术,将应用程序及其依赖项隔离开来,提供更高的安全性和可移植性。因此,可以说rootfs是基于chroot思想的一种扩展和应用。

3.rootfs

一个最常见的rootfs,或者容器镜像,包括了如下所示的一些目录和文件,比如/bin,/etc,/proc等,

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

而你进入容器之后执行的 /bin/bash,就是 /bin 目录下的可执行文件,与宿主机的 /bin/bash 完全不同。

需要注意的是,rootfs只是包含运行的文件,其实并不包含宿主机的内核,容器还是运用的宿主机共享操作系统内核,这样的坏处是,要处理内核全局的配置则需要多加小心,更改一处则宿主机的也会被更改,这也是容器比虚拟机主要缺陷之一,虚拟机是模拟出一整个内核系统使用,随便怎么折腾都行。

但也就是这一点,也是容器的优点,由于 rootfs 里打包的不只是应用,而是整个操作系统的文件和目录,也就意味着,应用以及它运行所需要的所有依赖,都被封装在了一起,这就赋予了容器所谓的一致性:无论在本地、云端,还是在一台任何地方的机器上,用户只需要解压打包好的容器镜像,那么这个应用运行所需要的完整的执行环境就被重现出来了。

现在有一个问题,难道我每开发一个应用,或者升级一下现有的应用,都要重复制作一次 rootfs 吗

比如,我在Ubuntu操作系统的 ISO做了一个rootfs,然后又在里面安装了一个java环境,用来部署我的Java应用,那么我另一个同事在发布java应用时显然是想用我的环境的java的rootfs,而不是重复这个流程。

那么假设如果可以每做有意义的操作,则就保存一个rootfs出来,但是这样的话就会碎片化,新旧两个rootfs没有任何的关联,这样的话会产生很多重复的文件。

我们希望更改在一个rootfs里并且以增量的方式进行修改,所以docker在使用rootfs里添加了自己的创新。

Docker 在镜像的设计中,引入了层(layer)的概念。也就是说,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量 rootfs,这个想法用到了联合文件系统。

4.联合文件系统介绍

联合文件系统 Union File System 也叫 UnionFS,将多个不同位置的目录联合挂载(union mount)到同一个目录下,比如,我现在有两个目录 A 和 B,它们分别有两个文件:

$ tree
.
├── A
│  ├── a
│  └── x
└── B
  ├── b
  └── x

使用联合挂载,将这两个目录挂载到一个公共的目录 C 上:

$ mkdir C
$ mount -t aufs -o dirs=./A:./B none ./C

这时,再查看目录 C 的内容,就能看到目录 A 和 B 下的文件被合并到了一起。

$ tree ./C
./C
├── a
├── b
└── x

如果你对目录C里有修改,这些修改也会在对应的目录 A、B 中生效。

而在Docker中使用的是AuFS,你可能会有些疑问,那么我们让ChartGpt帮忙回答下吧!

如提问:UnionFS和 AuFS区别,也是Docker容器借鉴的UnionFS?

 如提问:UnionFS和 AuFS都是linux上的功能吗?

 5.AuFS

在Docker中使用的是AuFS,对于 AuFS 来说,它最关键的目录结构在 /var/lib/docker 路径下的 diff 目录:

/var/lib/docker/aufs/diff/<layer_id>

看一下这个目录的作用:

启动一下容器

docker run -d ubuntu:latest sleep 3600

这时候,Docker 就会从 Docker Hub 上拉取一个 Ubuntu 镜像到本地。

这个镜像就是ubuntu操作系统的rootfs,它的内容是 Ubuntu 操作系统的所有文件和目录。不过,与之前我们讲述的 rootfs 稍微不同的是,Docker 镜像使用的 rootfs往往由多个“层”组成:

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

这个Ubuntu镜像实际上是由五个层组成,这五个层就是5个增量rootfs,每一层都是 Ubuntu 操作系统文件与目录的一部分;而在使用镜像时,Docker 会把这些增量联合挂载在一个统一的挂载点上(等价于前面例子里的“/C”目录)为/var/lib/docker/aufs/mnt/,如:

/var/lib/docker/aufs/mnt/6e3be5d2ecccae7cc0fcfa2a2f5c89dc21ee30e166be823ceaeba15dce645b3e

那看这个目录肯定有完整的操作系统了,

$ ls /var/lib/docker/aufs/mnt/6e3be5d2ecccae7cc0fcfa2a2f5c89dc21ee30e166be823ceaeba15dce645b3e
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

前面提到的五个镜像层,又是如何被联合挂载成这样一个完整的 Ubuntu 文件系统的呢?这个信息记录在AuFS的系统目录sys/fs/aufs/下面。

首先,通过查看 AuFS 的挂载信息,我们可以找到这个目录对应的 AuFS 的内部 ID(也叫:si):

$ cat /proc/mounts| grep aufs
none /var/lib/docker/aufs/mnt/6e3be5d2ecccae7cc0fc... aufs rw,relatime,si=972c6d361e6b32ba,dio,dirperm1 0 0

si=972c6d361e6b32ba,然后使用这个 ID,你就可以在 /sys/fs/aufs 下查看被联合挂载在一起的各个层的信息:

$ cat /sys/fs/aufs/si_972c6d361e6b32ba/br[0-9]*
/var/lib/docker/aufs/diff/6e3be5d2ecccae7cc...=rw
/var/lib/docker/aufs/diff/6e3be5d2ecccae7cc...-init=ro+wh
/var/lib/docker/aufs/diff/32e8e20064858c0f2...=ro+wh
/var/lib/docker/aufs/diff/2b8858809bce62e62...=ro+wh
/var/lib/docker/aufs/diff/20707dce8efc0d267...=ro+wh
/var/lib/docker/aufs/diff/72b0744e06247c7d0...=ro+wh
/var/lib/docker/aufs/diff/a524a729adadedb90...=ro+wh

我们可以看到,镜像的层都放置在 /var/lib/docker/aufs/diff 目录下,然后被联合挂载在 /var/lib/docker/aufs/mnt 里面。

 总共分为3个部分,只读层、可读写层、初始化层、只读层。

5.1 只读层

第一个部分只读层:

是这个容器的 rootfs 最下面的五层,对应的正是 ubuntu:latest 镜像的五层。可以看到,它们的挂载方式都是只读的(ro+wh,即 readonly+whiteout,至于什么是 whiteout,我下面马上会讲到)

我们分别可以查看这几个层内容:

这些层,都以增量的方式分别包含了 Ubuntu 操作系统的一部分

$ 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

5.2 可读写层

第二部分:可读写层

这个容器的 rootfs 最上面的一层(6e3be5d2ecccae7cc),它的挂载方式为:rw,即 read write。在没有写入文件之前,这个目录是空的。而一旦在容器里做了写操作,你修改产生的内容就会以增量的方式出现在这个层中。

你有没有想到这样一个问题:如果我现在要做的,是删除只读层里的一个文件呢?为了实现这样的删除操作,AuFS 会在可读写层创建一个 whiteout 文件,把只读层里的文件“遮挡”起来。比如,你要删除只读层里一个名叫 foo 的文件,那么这个删除操作实际上是在可读写层创建了一个名叫.wh.foo 的文件。这样,当这两个层被联合挂载之后,foo 文件就会被.wh.foo 文件“遮挡”起来,“消失”了。这个功能,就是“ro+wh”的挂载方式,即只读 +whiteout 的含义。我喜欢把 whiteout 形象地翻译为:“白障”。

容器中的可读写层的作用专门用来存放增删改查的rootfs 后产生的增量,这个被修改容器的内容,也可以通过命令推送到 Docker hub上,

5.3 init层

它是一个以“-init”结尾的层,夹在只读层和读写层之间。Init 层是 Docker 项目单独生成的一个内部层,专门用来存放 /etc/hosts、/etc/resolv.conf 等信息。需要这样一层的原因是,这些文件本来属于只读的 Ubuntu 镜像的一部分,但是用户往往需要在启动容器时写入一些指定的值比如 hostname,所以就需要在可读写层对它们进行修改。可是,这些修改往往只对当前的容器有效,我们并不希望执行 docker commit 时,把这些信息连同可读写层一起提交掉。所以,Docker 做法是,在修改了这些文件之后,以一个单独的层挂载了出来。而用户执行 docker commit 只会提交可读写层,所以是不包含这些内容的。

最终,这 7 个层都被联合挂载到 /var/lib/docker/aufs/mnt 目录下,表现为一个完整的 Ubuntu 操作系统供容器使用。

6.Volume

1.容器里进程新建的文件,怎么让宿主机获取到?

2.宿主机上的文件和目录,怎么才能让容器里的进程访问到?

这正是 Docker Volume 要解决的问题:Volume 机制,允许你将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作。

Docker它支持两种 Volume 声明方式,可以把宿主机目录挂载进容器的 /test 目录当中:

$ docker run -v /test ...
$ docker run -v /home:/test ...

这两种声明方式,本质是一样的,都是将目录挂载到容器/test下,第一种的话没有指定宿主机目录,所以默认会在宿主机创造临时目录/var/lib/docker/volumes/[VOLUME_ID]/_data,然后把它挂载到容器的/test上,而第二种情况则直接把宿主机的目录/home挂载到容器/test上。

Docker的Volume实际上实现并不复杂,在容器启动后在rootfs准备好后,在执行 chroot 之前,Volume 指定的宿主机目录(比如 /home 目录),挂载到指定的容器目录(比如 /test 目录)在宿主机上对应的目录(即 /var/lib/docker/aufs/mnt/[可读写层 ID]/test)上,这个 Volume 的挂载工作就完成了。

而这里使用到的挂载技术,就是linux的绑定挂载(bind mount)机制,它的主要作用就是,允许你将一个目录或者文件,而不是整个设备,挂载到一个指定的目录上。并且,这时你在该挂载点上进行的任何操作,只是发生在被挂载的目录或者文件上,而原挂载点的内容则会被隐藏起来且不受影响。

其实,这个挂载过程就是inode替换的过程,

inode:可以l理解为是存放文件的“对象”,而dentry也叫目录项,就是访问这个inode所使用的“指针”,

正如上图所示,mount --bind /home /test,会将 /home 挂载到 /test 上。其实相当于将 /test 的 dentry,重定向到了 /home 的 inode。这样当我们修改 /test 目录时,实际修改的是 /home 目录的 inode。这也就是为何,一旦执行 umount 命令,/test 目录原先的内容就会恢复:因为修改真正发生在的,是 /home 目录里。

今天这一讲我们就理解了容器镜像是是什么,以及采用了哪些思想以及工具搭建了现在的容器,虽然今天的内容比较多,但是掌握了受益非浅,看待容器的角度也不同了,也不会觉得它那么神秘了。

本章节根据张磊老师的《深入剖析 Kubernetes》课程来整理的笔记,希望能够给你带来更多的成长!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/727987.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

贝莱德CEO力挺比特币!币圈嘲讽:传统金融从嘲笑到开始入场了!

资产管理巨头贝莱德&#xff08;BlackRock&#xff09;首席执行官Larry Fink公开喊话&#xff0c;希望监管者以民主化方式&#xff0c;来看待现货ETF申请。将与监管积极配合&#xff0c;解除他们对现货比特币ETF的疑虑。 六月中旬&#xff0c;贝莱德向美国证券交易委员会&#…

vue3中Cron表达式的使用

效果&#xff1a; <a-form-item label"Cron表达式" name"cron" required><a-input v-show"false" v-model:value"setForm.cron"></a-input><a-button type"primary" size"small" click"…

使用NVCleanstall导致显卡功率被锁至115W问题解决

以拯救者Y9000K为例&#xff0c;显卡功耗最大可以达到165W&#xff0c;但最近更新至最新的显卡驱动后&#xff0c;发现显卡功率被限制到了115W。一度怀疑是老黄做了手脚。 经过一系列测试后发现&#xff0c;是自己操作姿势不对。 NVIDIA Platform Controllers and Framework这…

leetcode极速复习版-第四章字符串

目录 344. 反转字符串 541. 反转字符串II 剑指Offer 05.替换空格 151.翻转字符串里的单词 剑指Offer58-II.左旋转字符串 28.实现 strStr() 459.重复的子字符串 字符串总结 344. 反转字符串 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 char…

JAVA jfreechart生成柱状图

JAVA jfreechart生成柱状图 在项目资源评估中&#xff0c;也就是生成word文档里需要根据数据生成柱状图&#xff0c;在网上找到了jfreechart工具包&#xff0c;来生成柱状图&#xff0c;当然他不仅仅只能生成柱状图&#xff0c;还支持折线图、饼状图等等… 过程 导入依赖 &l…

快速创建剪映草稿

实现原理 : JianYingPro 项目文件是 json 的形式存储的,只需要创建draft_content.json,draft_mate_info.json 打开软件后会自动补全。添加一个媒体到轨道顺序 草稿媒体库 -> 内容媒体库-> 轨道片段add_media_to_track 会识别媒体类型,加入到对应轨道。当没有视频轨道时…

哈希表 基础理论

什么是哈希表&#xff1f; 哈希表英文名hash table&#xff0c;国内有一些书籍也翻译为散列表。哈希表是根据关键码的值而直接进行访问的数据结构。 直白来讲&#xff0c;其实数组就是一张哈希表&#xff0c;哈希表中关键码就是数组的索引下标&#xff0c;然后通过下标直接访…

华为云编译构建CodeArts Build新手操作指南

华为云编译构建&#xff08;CodeArts Build&#xff09;基于云端大规模并发加速&#xff0c;为客户提供高速、低成本、配置简单的混合语言构建能力&#xff0c;帮助客户缩短构建时间&#xff0c;提升构建效率。 本文将给各位开发者带来华为云CodeArts Pipeline的手把手初级教学…

亚马逊买家账号被封的原因

亚马逊封号原因有很多种情况&#xff0c;以下是一些可能导致账号被封的常见原因&#xff1a; 1、违反亚马逊的服务条款&#xff1a;亚马逊有一系列的服务条款和规定&#xff0c;如果您违反了这些规定&#xff0c;比如多次提交虚假评价、涉及欺诈行为、滥用退货政策等&#xff…

【深度学习】日常笔记9

泛化误差&#xff08;generalization error&#xff09;是指&#xff0c;模型应⽤在同样从原始样本的分布中 抽取的⽆限多数据样本时&#xff0c;模型误差的期望。考虑对掷硬币的结果&#xff08;类别0&#xff1a;正⾯&#xff0c;类别1&#xff1a;反⾯&#xff09;进⾏分类的…

AIGC - Stable Diffusion 图像控制插件 ControlNet (OpenPose) 配置与使用

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/131591887 论文&#xff1a;Adding Conditional Control to Text-to-Image Diffusion Models ControlNet 是神经网络结构&#xff0c;用于控制预…

CentOS7安装详细安装

CentOS 7镜像下载 官网下载链接&#xff1a;http://isoredirect.centos.org/centos/7/isos/x86_64/ step1: 进入下载页&#xff0c;选择阿里云站点进行下载 Actual Country 国内资源 Nearby Countries 周边国家资源 阿里云站点&#xff1a;http://mirrors.aliyun.com/cento…

开源微服务框架是什么?看完这篇文章就知道了

随着低代码开发平台的快速发展&#xff0c;企业实现流程化管理的愿望指日可待。开源微服务框架是什么&#xff1f;都有哪些特点和优势&#xff1f;作为企业&#xff0c;想要提高办公协作效率&#xff0c;做好数据管理&#xff0c;应用专用的开发平台可以少走弯路&#xff0c;创…

【电子量产工具】6. 业务系统

文章目录 前言一、业务系统分析二、处理配置文件三、生成界面四、根据输入事件找到按钮五、业务系统总流程测试测试效果&#xff1a;总结 前言 最近看了 电子量产工具 这个项目&#xff0c;本专栏是对该项目的一个总结。 一、业务系统分析 前面实现了各个子系统&#xff1a;显…

【Java项目】Vue+ElementUI+Ceph实现多类型文件上传功能并实现文件预览功能

文章目录 效果演示前端后端Java 效果演示 先说一下我们的需求&#xff0c;我们的需求就是文件上传&#xff0c;之前的接口是只支持上传图片的&#xff0c;之后需求是需要支持上传pdf&#xff0c;所以我就得换接口&#xff0c;把原先图片上传的接口换为后端ceph&#xff0c;但是…

MV-Map论文研读

MV-Map MV-Map: Offboard HD-Map Generation with Multi-view Consistency 论文&#xff1a;https://arxiv.org/pdf/2305.08851.pdf code&#xff1a;https://github.com/ZiYang-xie/MV-Map 代码未开源 总体网络结构 简述 论文首次提出以非车载的方式产生高精度地图。可以…

基于QT使用7z压缩与解压总结

1. 概述 本文主要讲述使用7z第三方工具对文件或文件夹进行加密压缩和解密解压相关方法。7z的全称7-Zip&#xff0c;是一款开源软件。&#xff08;资源主页&#xff1a;https://7-zip.org/&#xff09;2. 设计原理 本文主要使用7z.exe通过命令行来实现压缩与解压功能&…

数据库之MySQL字符集与数据库操作

目录 字符集 CHRARCTER SET 与COLLATION的关联 CHRARCTER SET 定义 基础操作 查看当前MySQL Server支持的 CHARACTER SET 查看特定字符集信息&#xff08;主要包含默认的COLLATION 与 MAXLEN&#xff09; COLLATION 定义 COLLATION后缀 基础操作 查看MySQL Server支持的…

C++教程(一)开发环境visual studio的安装——图文详细

一、visual studio下载地址&#xff1a; 1、百度网盘 链接&#xff1a;https://pan.baidu.com/s/1QJosSoAT7EumuvyjtC_1Iw?pwdwuqz 提取码&#xff1a;wuqz 2、官网下载 Visual Studio: 面向软件开发人员和 Teams 的 IDE 和代码编辑器 (microsoft.com)https://visualstudio.…

【Linux】vi编辑器的使用,要求能新建、编辑、保存一个文本文件。

&#xff08;1&#xff09;点击”应用程序”→ “附件”→“终端”&#xff0c;打开终端&#xff0c;在终端输入命令&#xff1a; [rootlocalhost root]#vi kk.c按 i 键&#xff0c;进入插入状态。 &#xff08;2&#xff09;输入以下C程序 #include<stdio.h>int main( …