看穿容器的外表,Linux容器实现原理演示

来源 | 多选参数

责编 | 程序锅

头图 | 下载于视觉中国

容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”也就是独立的“运行环境”。下面我们使用 C 语言和 Namespace 技术来手动创建一个容器,演示 Linux 容器最基本的实现原理。

什么是容器?容器其实是一种特殊的进程而已,只是这个进程运行在自己的 “运行环境” 中,比如有自己的文件系统而不是使用主机的文件系统(文件系统这个对我来说印象是最深刻的,也是让人对容器很更好理解的一个切入点)。

有一个计算数值总和的小程序,这个程序的输入来自一个文件,计算完成后的结果则输出到另一个文件中。为了让这个程序可以正常运行,除了程序本身的二进制文件之外还需要数据,而这两个东西放在磁盘上,就是我们平常所说的一个“程序”,也就是代码的可执行镜像。

当“程序”被执行起来之后,它就从磁盘上的二进制文件变成了计算机内存中的数据、寄存器里的值、堆栈中的指令、被打开的文件,以及各种设备的状态信息的一个集合。像这样一个程序运行起来后的计算机执行环境的总和,就是进程,而计算机执行环境的总和就是它的动态表现。

而容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”也就是独立的“运行环境”。那么怎么去造成这个边界呢?

  • 对于 Docker 等大多数 Linux 容器来说,Cgroups 技术是用来制造约束的主要手段;

  • Namespace 技术则是用来修改进程视图的主要方法;

下面我们使用 C 语言和 Namespace 技术来手动创建一个容器,演示 Linux 容器最基本的实现原理。

自己实现一个容器


Linux 中关于 Namespace 的系统调用主要有这么三个:

  • clone()---实现线程的系统调用,用来创建一个新的进程,同时可以设置 Namespace 的一些参数。

  • unshare()---使某个进程脱离某个 namespace。

  • setns()---把某进程加入到某个 namespace。

我们使用 clone 来创建一个子进程,通过创建出来的效果可以看到,子进程的 PID 是跟在父亲节点后面的,而不是 1。

 1#define _GNU_SOURCE2#include <sys/types.h>3#include <sys/wait.h>4#include <sys/mount.h>5#include <stdio.h>6#include <sched.h>7#include <signal.h>8#include <unistd.h>9
10#define STACK_SIZE (1024 * 1024)
11static char container_stack[STACK_SIZE];
12
13char* const container_args[] = {
14    "/bin/bash",
15    NULL
16};
17
18int container_main(void* arg) {
19    printf("Container [%5d] - inside the container!\n", getpid());
20    execv(container_args[0], container_args);
21    printf("Something's wrong!\n");
22    return 1;
23}
24
25int main() {
26    printf("Parent [%5d] - start a container!\n", getpid());
27    int container_id = clone(container_main, container_stack + STACK_SIZE, SIGCHLD, NULL);
28    waitpid(container_id, NULL, 0);
29    printf("Parent - container stopped!\n");
30    return 0;
31}

接下去这段代码我们给创建出来的进程设置 PID namespace 和 UTS namespace。从实际的效果我们可以看到子进程的 pid 为 1,而子进程中打开的 bash shell 显示的主机名为 container_dawn。是不是有点容器那味了?这里子进程在自己的 PID Namespace 中的 PID 为 1,因为 Namespace 的隔离机制,让这个子进程误以为自己是第 1 号进程,相当于整了个障眼法。但是,实际上这个进程在宿主机的进程空间中的编号不为 1,是一个真实的数值,比如 14624。

 1int container_main(void* arg) {2    printf("Container [%5d] - inside the container!\n", getpid());3    sethostname("container_dawn", 15);4    execv(container_args[0], container_args);5    printf("Something's wrong!\n");6    return 1;7}89int main() {
10    printf("Parent [%5d] - start a container!\n", getpid());
11    int container_id = clone(container_main, container_stack + STACK_SIZE, 
12                                CLONE_NEWUTS | CLONE_NEWPID | SIGCHLD, NULL);
13    waitpid(container_id, NULL, 0);
14    printf("Parent - container stopped!\n");
15    return 0;
16}

最后我们改变一下这个进程可以看到的文件系统,我们首先使用 docker export 将 busybox 镜像导出成一个 rootfs 目录,这个 rootfs 目录的情况如图所示,已经包含了 /proc、/sys 等特殊的目录。

接下去,我们在代码中使用 chroot() 函数将创建出来的子进程的根目录改变成上述的 rootfs 目录。从实现的效果来看,创建出来的子进程的 PID 为 1,并且这个子进程将上述提到的 rootfs 目录当成了自己的根目录。

 1char* const container_args[] = {2    "/bin/sh",3    NULL4};56int container_main(void* arg) {7    printf("Container [%5d] - inside the container!\n", getpid());89    if (chdir("./rootfs") || chroot("./") != 0) {
10        perror("chdir/chroot");
11    }
12
13    execv(container_args[0], container_args);
14    printf("Something's wrong!\n");
15    return 1;
16}
17
18int main() {
19    printf("Parent [%5d] - start a container!\n", getpid());
20    int container_id = clone(container_main, container_stack + STACK_SIZE, 
21                                CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
22    waitpid(container_id, NULL, 0);
23    printf("Parent - container stopped!\n");
24    return 0;
25}

需要注意的是所使用的 shell 需要改一下,因为 busybox 中没有 /bin/bash,假如还是 /bin/bash 的话是会报错的,因为 chroot 改变子进程的根目录视图之后,最终是从 rootfs/bin/  中找 bash 这个程序的。

上面其实已经基本实现了一个容器,接下去我们实现一下 Docker 卷的基本原理(假设你已经知道卷是什么了)。在代码中,我们将 /tmp/t1 这个目录挂载到 rootfs/mnt 这个目录中,并采用 MS_BIND 的方式,这种方式使得 rootfs/mnt (进入容器之后就是 mnt 目录)的视图其实就是 /tmp/t1 的视图,你对 rootfs/mnt 的修改其实就是对 /tmp/t1 修改,rootfs/mnt 相当于 /tmp/t1 的另一个入口而已。当然,在实验之前,你先确保 /tmp/t1 和 rootfs/mnt 这两个目录都已经被创建好了。实验效果见代码之后的那张图。

 1char* const container_args[] = {2    "/bin/sh",3    NULL4};56int container_main(void* arg) {7    printf("Container [%5d] - inside the container!\n", getpid());89    /*模仿 docker 中的 volume*/
10    if (mount("/tmp/t1", "rootfs/mnt", "none", MS_BIND, NULL)!=0) {
11        perror("mnt");
12    }
13
14    /* 隔离目录 */
15    if (chdir("./rootfs") || chroot("./") != 0) {
16        perror("chdir/chroot");
17    }
18
19    execv(container_args[0], container_args);
20    printf("Something's wrong!\n");
21    return 1;
22}
23
24int main() {
25    printf("Parent [%5d] - start a container!\n", getpid());
26    int container_id = clone(container_main, container_stack + STACK_SIZE, 
27                                CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
28    waitpid(container_id, NULL, 0);
29    printf("Parent - container stopped!\n");
30    return 0;
31}

除了上述所使用的 PID、UTS、Mount namespace,Linux 操作系统还提供了 IPC、Network 和 User 这些 Namespace。

总结


通过上面我们可以看到,容器的创建和普通进程创建没什么区别。都是父进程先创建一个子进程,只是对于容器来说,这个子进程接下去通过内核提供的隔离机制再给自己创建一个独立的资源环境。

同理,在使用 Docker 的时候,其实也并没有一个真正的 Docker 容器运行在宿主机里面。Docker 项目启动还是用户原来的应用进程,只是在创建进程的时候,Docker 为这个进程指定了它所需要启用的一组 Namespace 参数。这样,这个进程只能“看”到当前 Namespace 所限定的资源、文件、设备、状态或者配置。而对于宿主机以及其他不相关的程序,这个进程就完全看不到了。这时,进程就会以为自己是 PID Namespace 里面 1 号进程,只能看到各自 Mount Namespace 里面挂载的目录和文件,只能访问 Network Namespace 里的网络设备。这种就使得进程运行在一个独立的“运行环境”里,也就是容器里面。

因此,对接一开始所说的,还想再唠叨一句:容器其实就是一种特殊的进程而已。**只是这个进程和它运行所需的所有资源都打包在了一起,进程执行时所使用的资源也都是打包中的。相比虚拟机的方式,本质是进程的容器则仅仅是在操作系统上划分出了不同的“运行环境”,从而使得占用资源更少,部署速度更快。

更多阅读推荐

  •  用三国杀讲分布式算法,舒适了吧?

  •  云原生体系下的技海浮沉

  • 如何通过 Serverless 轻松识别验证码?

  • 5G与金融行业融合应用的场景探索

  • 打破“打工人”魔咒,RPA 来狙击!

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

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

相关文章

小时候都想当科学家后来只有他做到了——对话阿里云MVP朱祺

云栖号资讯&#xff1a;【点击查看更多行业资讯】 在这里您可以找到不同行业的第一手的上云资讯&#xff0c;还在等什么&#xff0c;快来&#xff01; 简介&#xff1a; 对朱祺我是好奇的。初次交流时&#xff0c;仅限于一个积极活跃、涉猎广泛的印象&#xff0c;拥抱新技术的传…

视频豪横时代,应用如何快速构建视频点播能力?

QuestMobile2020数据显示&#xff0c;疫情发生以来&#xff0c;每个网民每天花在移动互联网的时长比年初增加了21.5%&#xff0c;对于视频类应用增长尤为突出。而短视频用户规模已超8.5亿&#xff0c;用户使用时长在移动互联网用户使用总时长占比已达10.5%&#xff0c;仅次于社…

大数据的下一站是什么?服务/分析一体化

作者&#xff1a;蒋晓伟&#xff08;量仔&#xff09; 阿里巴巴研究员 因为侧重点的不同&#xff0c;传统的数据库可以分为交易型的 OLTP 系统和分析型的 OLAP 系统。随着互联网的发展&#xff0c;数据量出现了指数型的增长&#xff0c;单机的数据库已经不能满足业务的需求。特…

No outgoing sequence flow of the exclusive gateway ‘XXX‘ could be selected for continuing the proces

不满足流程图的排他网关设置了【条件表达式】的条件&#xff0c;注意设置条件表达式值时&#xff0c;内容不要出现空格。 No outgoing sequence flow of the exclusive gateway sid-9B4912C2-EEA4-4076-886E-D185AB4CBDBB could be selected for continuing the process检查流…

阿里云发布OAMKubernetes标准实现与核心依赖库

作者 | 张磊 阿里云高级技术专家、CNCF 官方大使&#xff0c;CNCF 应用交付领域 co-chair&#xff0c;Kubernetes 项目资深维护者 美国西部时间 2020 年 5 月 27 日&#xff0c;阿里云和微软云共同宣布&#xff0c;Open Application Model &#xff08;OAM&#xff09; 社区携…

Serverless 在大规模数据处理中的实践

来源 | Serverless作者 | 西流头图 | 下载于视觉中国前言当您第一次接触 Serverless 的时候&#xff0c;有一个不那么明显的新使用方式&#xff1a;与传统的基于服务器的方法相比&#xff0c;Serverless 服务平台可以使您的应用快速水平扩展&#xff0c;并行处理的工作更加有效…

阿里宜搭重磅发布专有云版本、精品应用市场,助力政企数字化转型

6月9日&#xff0c;在2020阿里云线上峰会上&#xff0c;“宜搭”重磅发布专有云版本和精品应用市场&#xff0c;为政企数字化转型提供高效、安全、可靠的服务。宜搭是阿里巴巴集团企业智能事业部自研的低代码应用开发PaaS平台&#xff0c;通过可视化拖拽的方式&#xff0c;传统…

信息如何实现病毒式传播?一文看懂Gossip协议

来源 | 架构之美责编 | 寇雪芹头图 | 下载于视觉中国起源Gossip protocol 也叫 Epidemic Protocol &#xff08;流行病协议&#xff09;。Gossip protocol在1987年8月由施乐-帕洛阿尔托研究中心发表ACM上的论文《Epidemic Algorithms for Replicated Database Maintenance》中被…

勇于尝鲜,感受世界——对话阿里云 MVP黄坤

云栖号资讯&#xff1a;【点击查看更多行业资讯】 在这里您可以找到不同行业的第一手的上云资讯&#xff0c;还在等什么&#xff0c;快来&#xff01; 简介&#xff1a; 阿里云 MVP黄坤是个言简意赅的实干家&#xff0c;言谈中显示出的知识广度和技术深度令我钦佩折服&#xff…

阿里云混合云管理平台发布帮您管好云

6月9日&#xff0c; 在2020阿里云线上峰会上阿里云混合云战略正式发布&#xff1a;全栈建云、智能管云、极致用云。同步发布专有云敏捷版&#xff08;Apsara Stack Agility&#xff09;、 混合云管理平台&#xff08;Apsara Uni-manager&#xff09;以及下一代企业级一站式DevO…

ApiPost6/Postman发送POST请求及日期格式的参数

Postman在发送POST请求的时候&#xff0c;所有参数写在Request Body&#xff08;请求体&#xff09;中&#xff0c;如果需要的参数类型的日期格式的&#xff0c;只需要将日期格式写为2000/01/01即可&#xff0c;Postman会自动识别为日期格式的数据。 如果是2000-01-01格式&…

Mendix入局中国低代码,开发者们你准备好了吗

作者 | 宋慧 出品 | CSDN云计算 头图 | 付费下载于视觉中国 在企业级软件与技术领域鼎鼎大名的Gartner魔力象限&#xff0c;几乎是全球公认的IT厂商实力的重要背书&#xff0c;进入魔力象限右上部分的领导者&#xff08;leaders&#xff09;更是能力全面的最优质厂商。 2021年…

OpenYurt开箱测评|一键让原生K8s集群具备边缘计算能力

作者| 郑超 阿里云高级开发工程师 随着物联网技术以及 5G 技术的高速发展&#xff0c;将云计算的能力延伸至边缘设备端&#xff0c;并通过中心进行统一交付、管控&#xff0c;已成为云计算的重要发展趋势。为服务更多开发者把握这一趋势&#xff0c;5 月 29 日&#xff0c;阿里…

技术运维的经营大法——对话阿里云MVP熊昌伟

云栖号资讯&#xff1a;【点击查看更多行业资讯】 在这里您可以找到不同行业的第一手的上云资讯&#xff0c;还在等什么&#xff0c;快来&#xff01; 简介&#xff1a; 不同于其他技术人的进阶经历&#xff0c;熊昌伟毕业后从未跳槽&#xff0c;在用友网络潜心修炼14年至今。从…

我同事狠心用 Python 3 ,刚开始就直接崩溃!你们试试......

Python 近两年一直霸占编程语言排行榜 Top3&#xff0c;火热程度有目共睹。这也让刚入行的程序员&#xff0c;甚至 BATJ 的技术大牛&#xff0c;都意识到 Python 对于一个程序员职业发展的重要性&#xff0c;将其作为第一/第二开发语言去学习。我同事前些日子说要学Python&…

阿里宜搭助力服务中枢升级,提升10倍开发效率

“真没想到&#xff0c;我在小程序上申请了‘要一双拖鞋’&#xff0c;不到半分钟功夫&#xff0c;机器人就把拖鞋送来了&#xff01;”住在菲住布渴的王女士感叹到。在酒店管理和服务能力的建设中&#xff0c;菲住布渴始终致力于通过数字化、智能化手段&#xff0c;让每一位入…

系统架构设计师 - 第三方认证服务

文章目录1.基于非对称密钥体系的 KPI/CA2.基于对称密钥体系的 Kerberos1.基于非对称密钥体系的 KPI/CA PKI&#xff1a;公钥基础设施 CA&#xff1a;认证中心 加密相关&#xff08;对称加密、非对称加密、信息摘要、数字签名、CA数字证书&#xff09; 2.基于对称密钥体系的 K…

MyBatisplus分页插件

文章目录一、后台分页配置1. 配置分页插件2. 编写分页代码3. 测试二、自定义查询2.1. 自定义接口2.2. 自定义查询2.3. 测试自定义分页一、后台分页配置 MyBatis Plus自带分页插件&#xff08;即BaseMapper接口中的selectPage()方法&#xff09;&#xff0c;只要简单的配置即可…

一个半月快速、低成本上云,云数据库专属集群解决方案看过来

在6月9日的“全速重构”2020阿里云线上峰会&#xff08;点击可查看数据库专场亮点&#xff09;中&#xff0c;阿里云智能数据库事业部的资深产品专家斗佛开启了全球首发4款云数据库新产品——云数据库专属集群、图数据库GDB、云数据库Cassandra版、云数据库ClickHouse。今天小编…