通俗的来说容器其实是一种沙盒技术。顾名思义,沙盒就是能够像一个集装箱一样,把你的应用“装”起来的技术。这样,应用与应用之间,就因为有了边界而不至于相互干扰;而被装进集装箱的应用,也可以被方便地搬来搬去。不过,这两个能力说起来简单,但要用技术手段去实现它们,确并不是很容易。所以,本篇文章就来剖析一下容器的实现方式
我们知道一个程序被执行起来之后,它就会从磁盘上的二进制文件,变成了计算机内存中的数据、寄存器里的值、堆栈中的指令、被打开的文件,以及各种设备的状态信息的一个集合也就是一个进程
所以,对于进程来说,它的静态表现就是程序,平常都安安静静地待在磁盘上;而一旦运行起来,它就变成了计算机里的数据和状态的总和,这就是它的动态表现。
而容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”。正是因为这个边界才会让容器里面的程序看不到宿主机上其他的程序从而给程序一种它就是在一个独立的操作系统上的假象
对于 Docker 等大多数 Linux 容器来说,Cgroups和Namespace 技术就是它们实现的关键
接下来的内容是以你已经初步了解docker的使用为基础的
Namespace
<figure class="highlight applescript"><table><tr>
<td class="gutter"><pre><span class="line">1</span><br></pre></td>
<td class="code"><pre><span class="line">docker <span class="built_in">run</span> -<span class="keyword">it</span> holloword /bin/sh</span><br></pre></td>
</tr></table></figure>
我们知道使用如上命令就可以直接启动一个holloword容器
如果这个时候我们在容器里执行一下 ps 指令,就会发现一些比较有趣的事情:
<figure class="highlight routeros"><table><tr>
<td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td>
<td class="code"><pre><span class="line">PID <span class="built_in"> USER </span> TIME COMMANDine </span><br><span class="line">1 root 0:00 /bin/sh </span><br><span class="line">10 root 0:00 ps</span><br></pre></td>
</tr></table></figure>
可以看到,我们在 Docker 里最开始执行的 /bin/sh,就是这个容器内部的第 1 号进程(PID=1),而这个容器里一共只有两个进程在运行。这就意味着,
前面执行的 /bin/sh,以及我们刚刚执行的 ps,已经被 Docker 隔离在了一个跟宿主机完全不同的世界当中
本来,每当我们在宿主机上运行了一个 /bin/sh 程序,操作系统都会给它分配一个进程编号,比如 PID=100。这个编号是进程的唯一标识,就像员工的工牌一样。所以 PID=100,可以粗略地理解为这个 /bin/sh 是我们公司里的第 100 号员工,而第 1 号员工就自然是比尔 · 盖茨这样统领全局的人物
而现在,我们要通过 Docker 把这个 /bin/sh 程序运行在一个容器当中。这时候,Docker 就会在这个第 100 号员工入职时给他施一个“障眼法”,让他永远看不到前面的其他 99 个员工,更看不到比尔 · 盖茨。这样,他就会错误地以为自己就是公司里的第 1 号员工
这种机制,其实就是对被隔离应用的进程空间做了手脚,使得这些进程只能看到重新计算过的进程编号,比如 PID=1。可实际上,他们在宿主机的操作系统里,还是原来的第 100 号进程
这种技术,就是 Linux 里面的 Namespace 机制。而 Namespace 的使用方式也非常有意思:它其实只是 Linux 创建新进程的一个可选参数。我们知道,
在 Linux 系统中创建线程的系统调用是 clone(),比如:
<figure class="highlight zephir"><table><tr>
<td class="gutter"><pre><span class="line">1</span><br></pre></td>
<td class="code"><pre><span class="line"><span class="keyword">int</span> pid = <span class="keyword">clone</span>(main_function, stack_size, SIGCHLD, <span class="keyword">NULL</span>);</span><br></pre></td>
</tr></table></figure>
这个系统调用就会为我们创建一个新的进程,并且返回它的进程号 pid。
而当我们用 clone() 系统调用创建一个新进程时,就可以在参数中指定 CLONE_NEWPID 参数,比如:
<figure class="highlight objectivec"><table><tr>
<td class="gutter"><pre><span class="line">1</span><br></pre></td>
<td class="code"><pre><span class="line"><span class="keyword">int</span> pid = clone(main_function, stack_size, <span class="built_in">CLONE_NEWPID</span> | SIGCHLD, <span class="literal">NULL</span>);</span><br></pre></td>
</tr></table></figure>
这时,新创建的这个进程将会“看到”一个全新的进程空间,在这个进程空间里,它的 PID 是 1。之所以说“看到”,是因为这只是一个“障眼法”,
在宿主机真实的进程空间里,这个进程的 PID 还是真实的数值,比如 100。
当然,我们还可以多次执行上面的 clone() 调用,这样就会创建多个 PID Namespace,而每个 Namespace 里的应用进程,都会认为自己是当前容器里的
第 1 号进程,它们既看不到宿主机里真正的进程空间,也看不到其他 PID Namespace 里的具体情况
而除了我们刚刚用到的 PID Namespace,
Linux 操作系统还提供了 Mount、UTS、IPC、Network 和 User 这些 Namespace,用来对各种不同的进程上下文进行“障眼法”操作
比如:
- Mount Namespace,用于让被隔离进程只看到当前 Namespace 里的挂载点信息;
- Network Namespace,用于让被隔离进程看到当前 Namespace 里的网络设备和配置。
这,就是 Linux 容器实现的第一个机制了
所以,Docker 容器这个听起来玄而又玄的概念,实际上是在创建容器进程时,指定了这个进程所需要启用的一组 Namespace 参数。这样,容器就只能“看”到当前 Namespace 所限定的资源、文件、设备、状态,或者配置。而对于宿主机以及其他不相关的程序,它就完全看不到了
说到这里,其实你就明白了,跟真实存在的虚拟机不同,在使用 Docker 的时候,并没有一个真正的“Docker 容器”运行在宿主机里面。Docker 项目帮助用户启动的,还是原来的应用进程,只不过在创建这些进程时,Docker 为它们加上了各种各样的 Namespace 参数。
这时,这些进程就会觉得自己是各自 PID Namespace 里的第 1 号进程,只能看到各自 Mount Namespace 里挂载的目录和文件,只能访问到各自 Network Namespace 里的网络设备,就仿佛运行在一个个“容器”里面,与世隔绝
Cgroups
实际上只是使用Namespace隔离了进程还不能称之为容器,例如:
虽然容器内的第 1 号进程在“障眼法”的干扰下只能看到容器里的情况,但是宿主机上,它作为第 100 号进程与其他所有进程之间依然是平等的竞争关系。
这就意味着,虽然第 100 号进程表面上被隔离了起来,
但是它所能够使用到的资源(比如 CPU、内存),却是可以随时被宿主机上的其他进程(或者其他容器)占用的。
当然,这个 100 号进程自己也可能把所有资源吃光。这些情况,显然都不是一个“沙盒”应该表现出来的合理行为。
而Linux Cgroups 就是 Linux 内核中用来为进程设置资源限制的一个重要功能它最主要的作用是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽以及对进程进行优先级设置、审计,以及将进程挂起和恢复等操作
除 CPU 子系统外,Cgroups 的每一项子系统都有其独有的资源限制能力,比如:
- blkio,为块设备设定I/O 限制,一般用于磁盘等设备
- cpuset,为进程分配单独的 CPU 核和对应的内存节点
- memory,为进程设定内存使用的限制
Linux Cgroups 的设计还是比较易用的,简单粗暴地理解呢,它就是一个子系统目录加上一组资源限制文件的组合。
而对于 Docker 等 Linux 容器项目来说,它们只需要在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录),然后在启动容器进程之后,把这个进程的 PID 填写到对应控制组的 tasks 文件中就可以了。
而至于在这些控制组下面的资源文件里填上什么值,就靠用户执行 docker run 时的参数指定了,比如这样一条命令:
<figure class="highlight routeros"><table><tr>
<td class="gutter"><pre><span class="line">1</span><br></pre></td>
<td class="code"><pre><span class="line">docker <span class="builtin-name">run</span> -it <span class="attribute">--cpu-period</span>=100000 <span class="attribute">--cpu-quota</span>=20000 ubuntu /bin/sh</span><br></pre></td>
</tr></table></figure>
在启动这个容器后,我们可以通过查看 Cgroups 文件系统下,CPU 子系统中,“docker”这个控制组里的资源限制文件的内容来确认:
<figure class="highlight dts"><table><tr>
<td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td>
<td class="code"><pre><span class="line">$ cat <span class="meta-keyword">/sys/</span>fs<span class="meta-keyword">/cgroup/</span>cpu<span class="meta-keyword">/docker/</span><span class="number">5</span>d5c9f67d/cpu.cfs_period_us </span><br><span class="line"><span class="number">100000</span></span><br><span class="line">$ cat <span class="meta-keyword">/sys/</span>fs<span class="meta-keyword">/cgroup/</span>cpu<span class="meta-keyword">/docker/</span><span class="number">5</span>d5c9f67d/cpu.cfs_quota_us </span><br><span class="line"><span class="number">20000</span></span><br></pre></td>
</tr></table></figure>
这就意味着这个 Docker 容器,只能使用到 20% 的 CPU 带宽。
一个正在运行的 Docker 容器,其实就是一个启用了多个 Linux Namespace 的应用进程,而这个进程能够使用的资源量,则受 Cgroups 配置的限制。
这也是容器技术中一个非常重要的概念,即:容器是一个“单进程”模型
推荐阅读
- SpringCloud学习系列汇总
- 为什么一线大厂面试必问redis,有啥好问的?
- 多线程面试必备基础知识汇总
- Java集合源码分析汇总-JDK1.8
- Linux常用命令速查-汇总篇
- JVM系列文章汇总
- MySQL系列文章汇总
- RabbitMQ系列文章汇总
博客所有文章首发于公众号《Java学习录》转载请保留
扫码关注公众号即可领取2000GJava学习资源
分享到:
相关推荐
本文主要简单介绍 docker 容器与前置进程的关系,以及如何编写 Dockerfile/docker-compose.yml 优雅的让容器可以常驻运行。 docker 容器的生命周期是同容器中的前置进程相关在一起的,这也是我们平时可能会遇到一些...
ocker 是一个开源的应用容器引擎,它允许开发者打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。...Docker容器可以包含任何东西,从后端服务到前端应用。
docker容器与容器云的第二版,高清晰版本,非常适合docker学习和kubernetes的学习。讲解很好,很实用,了解了以前没有学到的东西。
用于监视容器和其他东西的全堆栈工具
IoC指的是应用程序的控制权由原本的组件自身转移到了外部容器,而DI则是IoC的一种具体实现方式,即通过容器来管理和注入组件所需依赖。 依赖关系通常存在于一个对象(组件)需要另一个对象来完成其功能的情况。例如...
也就是我的Spring容器(也就是IoC容器,以下我们都称为Spring容器)要管的东西是什么?那肯定是对应的Bean。也就是你原来造对象的那些类,是不是我要管完以后把它放到我们的Spring容器中变成Bean。所以说我们就知道管...
这里核心关注两个东西:@SpringBootApplication 注解和 SpringApplication.run() 静态方法。 2.1 @SpringBootApplication 注解 @SpringBootApplication 注解是 Spring Boot 的核心注解之一。它的源码如下: ``` @...
内部包含gpmall容器商城部署(单节点)文档,镜像资源,镜像制作文件,制作幸苦,谢绝外传
list 容器 迭代 真是练手的好东西哦!
我自己写了一个vector,写了一个这样子的东西,收获挺大的,新手多打打代码
2. Docker 给整个IT 领域输出一个集装箱的概念,正如集装箱在运输领域的作用,它可以把原来一些非标准的东西统一放在一个集装箱里,让它能够加速整个运输的效率。 3. Docker 容器云将会给整个虚拟化开发运维、微服务...
- 微服务架构增加了服务间的通信复杂性,东西向流量增加,安全控制需要扩展到服务间的交互,防止潜在攻击。API网关可以作为安全控制点,但直接的服务间交互也需要相应的安全措施。 6. **金融行业的特定挑战** - ...
只需一次点击,就可以在一...不用再每次都弹出安装程序窗口 好东西,如果你机器上同时安装了OFFICE 2003和OFFICE 2007, 不要错过啊,既可以体验2003的快捷,又可以使用2007超过65535行的处理能力和优秀的图文特性!
标题中的“视镜图纸非常好的东西”表明这是一份关于视镜设计或工程的图纸资源,而描述中的“视镜资料非常好的东西,可以下载使用”进一步确认了这是一个实用的、供学习或参考的视镜相关资料。标签“压力容器视镜”则...
工作过程中,手部如有破损,不得接触食品,严禁在工作场所吃东西、吸烟、随地吐痰等。 【食品、食品原料、食品添加剂采购、验收和仓库管理卫生制度】 原料采购时应遵循国家质量卫生标准,向供应商索取检验合格证和...
prometheus docker 容器监控 k8s kubernetes 好东西 岗岗的
我们不需要在这个容器里运行任何东西,只需要这个文件(或者关联的文件系统)存在。为了演示,我们会在这个容器里运行 df ,来看一下根文件系统的大小。 $ docker run -d ubuntu df -h / 4ab0bdde0a0dd663d35993e...
随着C++0x标准的确立,C++的标准库中也终于有了hash table这个东西。 很久以来,STL中都只提供作为存放对应关系的容器,内部通常用红黑树实现,据说原因是二叉平衡树(如红黑树)的各种操作,插入、删除、查找等,都...
基础设施要求的Python 3 本地配置的SSH配置(最好通过部署) 安装在系统上的ansible安装./scripts/ansible/setup.sh cd terraform/ && ./scripts/terraform/terraform.sh init私人设定ansible Vault密码需要在...