前言

简单介绍一下一些容器的操作原理。

正文

docker exec 是怎么做到进入容器里的呢。

比如说:

这里有一个容器,我们可以exec 进去:

 docker exec -it b265 /bin/sh

我们为什么能看到和容器内部一样的场景呢?

首先我们知道了为什么容器进程只能看到规定的namespace了,那么如果我们能拿到这个namespace的信息,那么我们就能看到容器进程一样的场景了。

docker inspect --format '{{ .State.Pid }}' b26585ee826b

上面可以查看容器进程。

可以查看namespace信息。

#define _GNU_SOURCE
#include <fcntl.h>
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h> #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE);} while (0) int main(int argc, char *argv[]) {
int fd; fd = open(argv[1], O_RDONLY);
if (setns(fd, 0) == -1) {
errExit("setns");
}
execvp(argv[2], &argv[2]);
errExit("execvp");
}

写入到文件中,然后编译:

tee exectest.c <<- 'EOF'
> #define _GNU_SOURCE
> #include <fcntl.h>
> #include <sched.h>
> #include <unistd.h>
> #include <stdlib.h>
> #include <stdio.h>
>
> #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE);} while (0)
>
> int main(int argc, char *argv[]) {
> int fd;
>
> fd = open(argv[1], O_RDONLY);
> if (setns(fd, 0) == -1) {
> errExit("setns");
> }
> execvp(argv[2], &argv[2]);
> errExit("execvp");
> }
> EOF
gcc -o exectest exectest.c

然后运行./exectest:

./exectest /proc/17240/ns/mnt /bin/sh

那么看下这个进程看到的信息是啥:

./exectest /proc/17240/ns/mnt /bin/sh

看到的信息:

这段代码的的核心操作,则是通过 open() 系统调用打开了指定的 Namespace 文件,并把

这个文件的描述符 fd 交给 setns() 使用。在 setns() 执行后,当前进程就加入了这个文件

对应的 Linux Namespace 当中了

你也可以将网络带进去:

正如上所示,当我们执行 ifconfig 命令查看网络设备时,我会发现能看到的网卡“变

少”了:只有两个。而我的宿主机则至少有四个网卡。这是怎么回事呢?

实际上,在 setns() 之后我看到的这两个网卡,正是我在前面启动的 Docker 容器里的网

卡。也就是说,我新创建的这个 /bin/bash 进程,由于加入了该容器进程(PID=25686)

的 Network Namepace,它看到的网络设备与这个容器里是一样的,即:/bin/bash 进程

的网络设备视图,也被修改了。

而一旦一个进程加入到了另一个 Namespace 当中,在宿主机的 Namespace 文件上,也

会有所体现。

其实就是将自己的namespace 指向了原来容器进程的namespace。

查看进程号:

发现和原来的17240进程的net一样。

此外,Docker 还专门提供了一个参数,可以让你启动一个容器并“加入”到另一个容器的

Network Namespace 里,这个参数就是 -net,比如:

docker run -it --net container:4ddf4638572d busybox ifconfig

而如果我指定–net=host,就意味着这个容器不会为进程启用 Network Namespace。这

就意味着,这个容器拆除了 Network Namespace 的“隔离墙”,所以,它会和宿主机上

的其他普通进程一样,直接共享宿主机的网络栈。这就为容器直接操作和使用宿主机网络提

供了一个渠道。

docker commit 干了啥? 比如我们提交一个容器。

docker commit,实际上就是在容器运行起来后,把最上层的“可读写层”,加上原先容

器镜像的只读层,打包组成了一个新的镜像。当然,下面这些只读层在宿主机上是共享的,

不会占用额外的空间。

而由于使用了联合文件系统,你在容器里对镜像 rootfs 所做的任何修改,都会被操作系统

先复制到这个可读写层,然后再修改。这就是所谓的:Copy-on-Write。

而正如前所说,Init 层的存在,就是为了避免你执行 docker commit 时,把 Docker 自己

对 /etc/hosts 等文件做的修改,也一起提交掉。

前面我已经介绍过,容器技术使用了 rootfs 机制和 Mount Namespace,构建出了一个同

宿主机完全隔离开的文件系统环境。这时候,我们就需要考虑这样两个问题:

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

    这正是 Docker Volume 要解决的问题:Volume 机制,允许你将宿主机上指定的目录或

    者文件,挂载到容器里面进行读取和修改操作。

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

目录当中:

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

而这两种声明方式的本质,实际上是相同的:都是把一个宿主机的目录挂载进了容器的/test 目录。

只不过,在第一种情况下,由于你并没有显示声明宿主机目录,那么 Docker 就会默认在宿

主机上创建一个临时目录 /var/lib/docker/volumes/[VOLUME_ID]/_data,然后把它挂载

到容器的 /test 目录上。而在第二种情况下,Docker 就直接把宿主机的 /home 目录挂载

到容器的 /test 目录上

当容器进程被创建之后,尽管开启了 Mount Namespace,但是在它执行 chroot(或者 pivot_root)之前,容器进程一直可以看到宿主机上的整个文件系统

而宿主机上的文件系统,也自然包括了我们要使用的容器镜像。这个镜像的各个层,保存在

/var/lib/docker/aufs/diff 目录下,在容器进程启动后,它们会被联合挂载在

/var/lib/docker/aufs/mnt/ 目录中,这样容器所需的 rootfs 就准备好了。

所以,我们只需要在 rootfs 准备好之后,在执行 chroot 之前,把 Volume 指定的宿主机

目录(比如 /home 目录),挂载到指定的容器目录(比如 /test 目录)在宿主机上对应的

目录(即 /var/lib/docker/aufs/mnt/[可读写层 ID]/test)上,这个 Volume 的挂载工作

就完成了。

更重要的是,由于执行这个挂载操作时,“容器进程”已经创建了,也就意味着此时

Mount Namespace 已经开启了。所以,这个挂载事件只在这个容器里可见。你在宿主机

上,是看不见容器内部的这个挂载点的。这就保证了容器的隔离性不会被 Volume 打破。

注意:这里提到的 " 容器进程 ",是 Docker 创建的一个容器初始化进程
(dockerinit),而不是应用进程 (ENTRYPOINT + CMD)。dockerinit 会负责
完成根目录的准备、挂载设备和目录、配置 hostname 等一系列需要在容器
内进行的初始化操作。最后,它通过 execv() 系统调用,让应用进程取代自
己,成为容器里的 PID=1 的进程。

而这里要使用到的挂载技术,就是 Linux 的绑定挂载(bind mount)机制。它的主要作

用就是,允许你将一个目录或者文件,而不是整个设备,挂载到一个指定的目录上。并且,

这时你在该挂载点上进行的任何操作,只是发生在被挂载的目录或者文件上,而原挂载点的

内容则会被隐藏起来且不受影响

所以,在一个正确的时机,进行一次绑定挂载,Docker 就可以成功地将一个宿主机上的目

录或文件,不动声色地挂载到容器中。

这样,进程在容器里对这个 /test 目录进行的所有操作,都实际发生在宿主机的对应目录

(比如,/home,或者 /var/lib/docker/volumes/[VOLUME_ID]/_data)里,而不会影响

容器镜像的内容。

那么,这个 /test 目录里的内容,既然挂载在容器 rootfs 的可读写层,它会不会被 docker

commit 提交掉呢?

也不会。

这个原因其实我们前面已经提到过。容器的镜像操作,比如 docker commit,都是发生在

宿主机空间的。而由于 Mount Namespace 的隔离作用,宿主机并不知道这个绑定挂载的

存在。所以,在宿主机看来,容器中可读写层的 /test 目录

(/var/lib/docker/aufs/mnt/[可读写层 ID]/test),始终是空的。

不过,由于 Docker 一开始还是要创建 /test 这个目录作为挂载点,所以执行了 docker

commit 之后,你会发现新产生的镜像里,会多出来一个空的 /test 目录。毕竟,新建目录

操作,又不是挂载操作,Mount Namespace 对它可起不到“障眼法”的作用。

实验:

docker run -d -v /test nginx

查看挂载信息:

那么是哪一个呢?

docker inspect a9c79

直接看这个好了。

docker exec -it a9c79 /bin/bash

然后运行一下:

在里面写了一下文件.

然后看下这个目录里面有不。

然后我们看下读写层有不:

有一个test 目录,但是是空的。

可以确认,容器 Volume 里的信息,并不会被 docker commit 提交掉;但这个挂载点目录 /test 本身,则会出现在新的镜像当中。

如果你执行 docker run -v /home:/test 的时候,容器镜像里的 /test 目录下本来就有内容的话,你会发现,在宿主机的 /home 目录下,也会出现这些内容。这是怎么回事?为什么它们没有被绑定挂载隐藏起来呢?(提示:Docker 的“copyData”功能)

下一节k8s的本质。

k8s 深入篇———— 一些容器操作的原理[三]的更多相关文章

  1. Minikube之Win10单机部署Kubernetes(k8s)自动化容器操作的开源平台

    Minikube之Win10单机部署 Kubernetes(k8s)是自动化容器操作的开源平台,基于这个平台,你可以进行容器部署,资源调度和集群扩容等操作.如果你曾经用过Docker部署容器,那么可以 ...

  2. 《两地书》--Kubernetes(K8s)基础知识(docker容器技术)

    大家都知道历史上有段佳话叫“司马相如和卓文君”.“皑如山上雪,皎若云间月”.卓文君这么美,却也抵不过多情女儿薄情郎. 司马相如因一首<子虚赋>得汉武帝赏识,飞黄腾达之后便要与卓文君“故来相 ...

  3. Kubernetes(K8s)基础知识(docker容器技术)

    今天谈谈K8s基础知识关键词: 一个目标:容器操作:两地三中心:四层服务发现:五种Pod共享资源:六个CNI常用插件:七层负载均衡:八种隔离维度:九个网络模型原则:十类IP地址:百级产品线:千级物理机 ...

  4. docker+k8s基础篇一

    Docker+K8s基础篇(一) docker的介绍 A:为什么是docker B:k8s介绍 docker的使用 A:docker的安装 B:docker的常用命令 C:docker容器的启动和操作 ...

  5. docker+k8s基础篇四

    Docker+K8s基础篇(四) pod控制器 A:pod控制器类型 ReplicaSet控制器 A:ReplicaSet控制器介绍 B:ReplicaSet控制器的使用 Deployment控制器 ...

  6. docker+k8s基础篇三

    Docker+K8s基础篇(三) kubernetes上的资源 A:k8s上的常用资源 Pod的配置清单 A:Pod上的清单定义 B:Pod创建资源的方法 C:spec下其它字段的介绍 Pod的生命周 ...

  7. docker+k8s基础篇二

    Docker+K8s基础篇(二) docker的资源控制 A:docker的资源限制 Kubernetes的基础篇 A:DevOps的介绍 B:Kubernetes的架构概述 C:Kubernetes ...

  8. Stream常用操作以及原理探索

    Stream常用操作以及原理 Stream是什么? Stream是一个高级迭代器,它不是数据结构,不能存储数据.它可以用来实现内部迭代,内部迭代相比平常的外部迭代,它可以实现并行求值(高效,外部迭代要 ...

  9. Docker:镜像操作和容器操作

    镜像操作 列出镜像: $ sudo docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE hello-world latest 0a6b ...

  10. ApplicationContext容器的设计原理

    1.在ApplicationContext容器中,我们以常用的FileSystemXmlApplicationContext的实现为例来说明ApplicationContext容器的设计原理. 2.在 ...

随机推荐

  1. 使用内网nginx代理rancher

    需求: rancher 部署在 192.168.188.167服务器上 控制台访问地址 https://192.168.188.167:8443 在本地只有192.168.80.111有权限访问, 需 ...

  2. minio通过docker方式部署

    MinIO 是在 GNU Affero 通用公共许可证 v3.0 下发布的高性能对象存储. 它是与 Amazon S3 云存储服务兼容的 API 官方文档http://docs.minio.org.c ...

  3. TypeScript实践总结

    下文将TypeScript简称ts 一.为什么要学 1.1 减少bug,提高质量 强语言,语法等方面异常,编译阶段"提前"报错 支持面向对象,软件设计与工程化更为成熟,更容易做单元 ...

  4. 清除 gitee.io 页面强缓存 Chrome浏览器 F12 找到页面 右键 Clear browser cache

    清除 gitee.io 页面强缓存 Chrome浏览器 F12 找到页面 右键 Clear browser cache

  5. 【大语言模型基础】GPT(Generative Pre-training )生成式无监督预训练模型原理

    GPT,GPT-2,GPT-3 论文精读[论文精读]_哔哩哔哩_bilibili   ELMo:将上下文当作特征,但是无监督的语料和我们真实的语料还是有区别的,不一定符合我们特定的任务,是一种双向的特 ...

  6. day22--Java集合05

    Java集合05 11.HashSet课堂练习 11.1课堂练习1 定义一个Employee类,该类包括:private成员属性name,age 要求: 创建3个Employee对象放入HashSet ...

  7. day23-服务器端渲染技术01

    服务器端渲染技术01 为什么需要jsp? 在之前的开发过程中,我们可以发现servlet做界面非常不方便: 引出jsp技术=> jsp=html+java代码+标签+javascript+css ...

  8. 一种OSD 简单实现 (文字反色---opencv、字体切换---freetype2(中文、空格))

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明   本文作为本人csdn blog的主站的备份.(Bl ...

  9. TomCat 的 Jenkins 提示:你的容器没有使用UTF-8解码URL地址

    1.编辑 Linux 系统的 Tomcat 安装目录的 conf 目录的 server.xml 文件 2.在 <Connector> 追加内容 URIEncoding="UTF- ...

  10. 记录--a标签跳转新地址无法访问,但手动输入新地址可以访问

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 问题描述 最近遇到一个有意思的问题,项目中有一个地方,点击需要跳转到一个新的域名地址 笔者使用a标签做跳转,跳是跳过去了,可是跳过去以后, ...