Docker实战06|深入剖析Docker Run命令

前几篇文章中,重点讲解了Linux Namespace、Cgroups、AUFS的核心原理,同样也是Docker的底层原理实现。目录如下:

  • • 《Docker实战01|容器与开发语言》

  • • 《Docker实战02|Namespace》

  • • 《Docker实战03|Cgroups》

  • • 《Docker实战04|Union File System》

  • • 《Docker实战05|Docker构建流程分析》

有需要的小伙伴可以回顾一下。

核心原理讲完,接下来的内容就是如何构造容器、构造镜像了。首先,先从Docker run命令开始深入剖析。

深入剖析Docker Run命令

获取代码

git clone https://gitee.com/mjreams/docker.git
git checkout code3-1

本章即将开始真正踏上构造自己的容器的道路。我们会基于当前的操作系统创 建一个与宿主机隔离的容器环境,下面就开始吧。

Linux /proc文件介绍

Linux下的/proc文件系统是由内核提供的,它其实不是一个真正的文件系统,只包含了系统运行时的信息(比如系统内存、mount设备信息、一些硬件配直等),它只存在于内存中,而不占用外存空间。它以文件系统的形式,为访问内核数据的操作提供接口。实际上,很多系统工具都是简单地去读取这个文件系统的某个文件内容,比如lsmod,其实就是cat /proc/modules。

当遍历这个目录的时候,会发现很多数字,这些都是为每个进程创建的空间,数字就是它们的PID。

图片

下面介绍几个比较重要的部分:

图片

 run命令实现

首先,实现一个简单的run命令,类似docker run -it [command] 。后续会继续添加network等功能。

目前代码目录结构如下:

图片

  • • main.go 作为项目入口

  • • main_command.go 中包含了所有的 command

  • • run.go 则是 run 命令核心逻辑

  • • container 目录则是一些 container 的核心实现

再来看一下main.go

图片

使用github.com/urfave/cli命令行工具,提供了几个基本的命令。包括initCommand、runCommand。然后在app.Before内初始化一下log的配置。

再来看一下main_command.go中runCommand的具体实现:

图片

Action这里是run命令执行的真正函数:

  1. 1. 判断参数是否包含command

  2. 2. 获取用户制定的command

  3. 3. 调用Run方法去启动容器

Run(createTty, cmdArray, resConf, containerName, volume, imageName, envSlice, network, portmapping)

再来深入看一下Run方法具体做了哪些事情:

图片

NewParentProcess 启动一个新进程

这里是父进程,也就是当前进程执行的内容。

  1. 1. 这里的/proc/se1f/exe调用中,/proc/self/ 指的是当前运行进程自己的环境,exec 其实就是自己调用了自己,使用这种方式对创建出来的进程进行初始化

  2. 2. 后面的args是参数,其中init是传递给本进程的第一个参数,在本例中,其实就是会去调用initCommand去初始化进程的一些环境和资源

  3. 3. 下面的clone参数就是去fork出来一个新进程,并且使用了namespace隔离新创建的进程和外部环境。

  4. 4. 如果用户指定了-it参数,就需要把当前进程的输入输出导入到标准输入输出上

图片

那么,init函数里面做了些什么呢 ?

图片

RunContainerInitProcess 启动容器的init进程

  1. 1. 这里的init函数是在容器内部执行的,也就是说,代码执行到这里后,容器所在的进程其实就已经创建出来了,这是本容器执行的第一个进程。

  2. 2. 使用mount先去挂载proc文件系统,以便后面通过ps等系统命令去查看当前进程资源的情况。

这里 Mount 意思如下:

  • • MS_NOEXEC 在本文件系统 许运行其 程序。

  • • MS_NOSUID 在本系统中运行程序的时候, 允许 set-user-ID set-group-ID

  • • MS_NOD 这个参数是自 Linux 2.4 ,所有 mount 的系统都会默认设定的参数。

本函数最后的syscall.Exec是最为重要的一句黑魔法,正是这个系统调用实现了完成初始化动作并将用户进程运行起来的操作。

首先,使用 Docker 创建起来一个容器之后,会发现容器内的第一个程序,也就是 PID 为 1 的那个进程,是指定的前台进程。但是,我们知道容器创建之后,执行的第一个进程并不是用户的进程,而是 init 初始化的进程。这时候,如果通过 ps 命令查看就会发现,容器内第一个进程变成了自己的 init,这和预想的是不一样的。

有没有什么办法把自己的进程变成 PID 为 1 的进程呢?

这里 execve 系统调用就是用来做这件事情的。

syscall.Exec这个方法,其实最终调用了 Kernel 的 int execve(const char *filename, char *const argv[], char *const envp[]);这个系统函数。

它的作用是执行当前 filename 对应的程序,它会覆盖当前进程的镜像、数据和堆栈等信息,包括 PID,这些都会被将要运行的进程覆盖掉。

也就是说,调用这个方法,将用户指定的进程运行起来,把最初的 init 进程给替换掉,这样当进入到容器内部的时候,就会发现容器内的第一个程序就是我们指定的进程了。

具体流程如下:

图片

测试

root@mydocker:~/mydocker# go build .
root@mydocker:~/mydocker# ./mydocker run -it /bin/sh
{"level":"info","msg":"init come on","time":"2024-01-07T14:18:35+08:00"}
{"level":"info","msg":"command: /bin/sh","time":"2024-01-07T14:18+08:00"}
{"level":"info","msg":"command:/bin/sh","time":"2024-01-07T14:18:35+08:00"}
# ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 09:47 pts/1    00:00:00 /bin/sh
root           5       1  0 09:47 pts/1    00:00:00 ps -ef

在看一下ubuntu的

[root@docker ~]# docker run -it ubuntu /bin/sh
# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 01:49 pts/0    00:00:00 /bin/sh
root         7     1  0 01:49 pts/0    00:00:00 ps -ef

几乎是一模一样。

关注「程序员溪昂」带你学习更多云原生知识。

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

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

相关文章

【Web】forward 和 redirect 的区别

🍎个人博客:个人主页 🏆个人专栏:Web ⛳️ 功不唐捐,玉汝于成 目录 前言 正文 Forward(转发): Redirect(重定向): 区别总结: …

ES索引原理

ES在检索时底层使用的就是倒排索引,正向索引是通过key找value,反向索引则是通过value找key。 索引会分为两个区域:索引区和元数据区。数据是这样存储在里面的: 简单理解就是:当要录入一条数据时,首先会将完…

68.网游逆向分析与插件开发-角色数据的获取-利用蓝量属性分析角色数据基址

内容参考于:易道云信息技术研究院VIP课 上一个内容:67.网游逆向分析与插件开发-角色数据的获取-分析角色数据基址-CSDN博客 然后分析任何一个东西,逆向分析的本质就是找东西的意思,找东西核心的观念是内存里得有,就是…

在机械行业中,直线导轨和弧形导轨哪个应用范围更广泛?

弧形导轨和直线导轨是两种常见的导轨类型,直线导轨主要被用于高精度或快速直线往复运动场所,而弧形导轨是一种专门设计用于曲线运动的导轨系统,那么在机械行业中,直线导轨和弧形导轨哪个应用范围更加广泛呢? 直线导轨主…

Redis的设计、实现

数据结构和内部编码 type命令实际返回的就是当前键的数据结构类型,它们分别是:string(字符串)hash(哈希)、list(列表)、set(集合)、zset (有序集合),但这些只是Redis对外的数据结构。 实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,这样Redis会在合适的…

11.3、信赖域策略优化算法TRPO强化学习-运用实践

基于LunarLander登陆器的TRPO强化学习(含PYTHON工程) TRPO强化学习算法主要分为3个部分,分别介绍其理论、细节、实现 本文主要介绍TRPO的理论和代码的对应、实践 TRPO系列(TRPO是真的复杂,全部理解花费了我半个月的…

计算机组成原理-计算机的发展(计算机系统 硬件发展 软件发展 微处理器和微计算机的发展 摩尔定律 发展趋势)

文章目录 总览什么是计算机系统软件硬件的发展第一代第二代第三代第四代微处理器的发展相关人物摩尔定律 软件的发展目前的发展趋势小结 总览 什么是计算机系统 软件 语言处理程序就是编译程序之类的 调试代码就是服务程序 硬件的发展 第一代 逻辑元件:处理电信…

【Leetcode】2085. 统计出现过一次的公共字符串

文章目录 题目思路代码 题目 2085. 统计出现过一次的公共字符串 思路 使用两个哈希表 words1Count 和 words2Count 分别统计两个数组中每个单词的出现次数。然后遍历 words1Count 中的每个单词,如果该单词在 words1 中出现了一次,且在 words2 中也出…

知识库软件有很多,这几个最好用

时代进步的同时,逐渐优化的企业知识库已经成为企业优化工作效率、提升企业竞争力的重要工具。随着云计算和大数据技术的快速发展,知识库软件如雨后春笋般出现在人们的视野中。下面,我从寻宝者的角度,向大家稳稳地推荐三款最优秀的…

DHCP与时间同步

目录 一、DHCP 1、DHCP定义 1.什么是DHCP 2.DHCP的好处 3.DHCP的分配方式 4.为什么使用DHCP 5.DHCP模式 2、DHCP的工作过程 3、DHCP动态配置主机地址 1.DHCP服务的优点 2.可分配的地址信息 3.动态分配IP地址 二、时间同步 1、ntp 2、chrony 1、搭建本地本地时间…

C++学习笔记——类继承

目录 一、一个简单的基类 1.1封装性 1.2继承性 1.3虚函数 1.4多态性 二、基类 2.1一个简单的C基类的示例 2.2 Animal是一个基类。 三、继承 3.1概念 3.2is-a关系 3.3多态公有继承 3.4静态联编和动态联编 3.5访问控制 3.6ABC理念 一、一个简单的基类 C中的基类是一…

从vue小白到高手,从一个内容管理网站开始实战开发第七天,登录功能后台功能设计--通用分页、枚举以及相关工具类

上一篇实现了数据库访问层的相关功能,还没有了解的小伙伴可以去看前面文章实现的内容,因为每一篇内容都是连贯的,不学习的话可能下面的内容学习起来会有点摸不着头脑 从vue小白到高手,从一个内容管理网站开始实战开发第六天,登录功能后台功能设计--API项目中的登录实现(二…