前言

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

正文

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. 解决网页无法复制粘贴选中的问题 显示vip无法复制解决方案

    方法:先是按F12打开控制台点击console输入以下代码!!!! 解决网页禁止鼠标右键,无法被选中的 第一种: javascript:(function() { function R(a){ona ...

  2. elementPlus配合vue-router搭建后台系统菜单模块

    设置menuType来区分菜单类型 /** * @params menuType * -1 一般为首页 / -> /home 只显示第一个子项 * -2 为无子菜单的菜单项 /config -& ...

  3. C++入门编程----C++运算符(8)

    什么是运算符 运算符是让程序执行特定的数学或逻辑操作的符号,用来表示针对数据的特定操作,也称之为操作符.C++运算符分别有算术运算符.关系运算符.逻辑运算符.赋值运算符.位运算符.移位运算符.size ...

  4. Elasticsearch 如何保证写入过程中不丢失数据的

    丢失数据的本质 在本文开始前,首先明白一个点,平时我们说的组件数据不丢失究竟是在指什么,如果你往ES写入数据,ES返回给你写入错误,这个不算数据丢失.如果你往ES写入数据,ES返回给你成功,但是后续因 ...

  5. K8S容器环境下资源限制与jvm内存回收

    一.k8s中的java资源限制与可能的问题 与以前单机跑单服务的情况相比,在k8s.docker容器化环境下的宿主机内存.cpu相对更大,所以当运行java类程序的时候,就必然有必要对容器进行内存限制 ...

  6. 将Maven和Plugins的源都改为阿里镜像的Setting.xml

    <?xml version="1.0" encoding="UTF-8"?> <!-- Licensed to the Apache Soft ...

  7. UDP、IMCP、ARP协议通过netmap解析的实现。

    上一篇文章我们讲了一个异步的线程池大概需要如何去实现,现在的话,我们如何来解析一个UDP的包. 环境的搭配 这个环境的问题困扰了很久,这个netmap已经不再更新了,支持Ubuntu16.04-Ubu ...

  8. 通达信金融终端解锁Level-2功能 续(202307)

    外挂方式,不修改原程序.解锁Level-2 逐笔分析.对"非法访问"Say NO! LEVEL2逐笔分析破解后,仍然被防调试. 竞价分析,实时资金示例. 逆向通达信Level-2 ...

  9. Justep X5 Studio,业界公认第一的快速开发平台

    Justep X5 Studio,业界公认第一的快速开发平台,提供完全可视化.组件化开发环境,具备超强的工作流.组织机构和权限.复杂图表和报表.丰富的业务规则定制能力,以及各种浏览器环境下的复杂业务展 ...

  10. 面试官:小伙子,能聊明白JMM给你SSP!我:嘚吧嘚吧一万字,直接征服面试官!

    写在开头 面试官:小伙子,JMM了解吗? 我:JMM(Java Memory Model),Java内存模型呀,学过的! 面试官:那能给我详细的聊一聊吗,越详细越好! 我:嗯~,确定越详细越好?起码得 ...