从零开始写 Docker(十五)---实现 mydocker run -e 支持环境变量传递

news2024/10/6 8:38:53

mydocker-run-e.png

本文为从零开始写 Docker 系列第十五篇,实现 mydocker run -e, 支持在启动容器时指定环境变量,让容器内运行的程序可以使用外部传递的环境变量。


完整代码见:https://github.com/lixd/mydocker
欢迎 Star


推荐阅读以下文章对 docker 基本实现有一个大致认识:

  • 核心原理:深入理解 Docker 核心原理:Namespace、Cgroups 和 Rootfs
  • 基于 namespace 的视图隔离:探索 Linux Namespace:Docker 隔离的神奇背后
  • 基于 cgroups 的资源限制
    • 初探 Linux Cgroups:资源控制的奇妙世界
    • 深入剖析 Linux Cgroups 子系统:资源精细管理
    • Docker 与 Linux Cgroups:资源隔离的魔法之旅
  • 基于 overlayfs 的文件系统:Docker 魔法解密:探索 UnionFS 与 OverlayFS
  • 基于 veth pair、bridge、iptables 等等技术的 Docker 网络:揭秘 Docker 网络:手动实现 Docker 桥接网络

开发环境如下:

root@mydocker:~# lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 20.04.2 LTS
Release:	20.04
Codename:	focal
root@mydocker:~# uname -r
5.4.0-74-generic

注意:需要使用 root 用户

1. 概述

本节实现mydocker run -e flag,支持在启动容器时指定环境变量,让容器内运行的程序可以使用外部传递的环境变量。

2. 实现

实现也比较简单,就是在构建 cmd 的时候指定 Env 参数。

  • 1)run 命令增加 -e 参数
  • 2)cmd 中指定 Env 参数

run 命令增加 -e flag

在原来的基础上,增加 -e 选项指定环境变量,由于可能存在多个环境变量,因此允许用户通过多次使用 -e 选项来传递多个环境变量。

var runCommand = cli.Command{
	Name: "run",
	Usage: `Create a container with namespace and cgroups limit
			mydocker run -it [command]
			mydocker run -d -name [containerName] [imageName] [command]`,
	Flags: []cli.Flag{
    // 省略其他内容
		cli.StringSliceFlag{ // 增加 -e flag
			Name:  "e",
			Usage: "set environment,e.g. -e name=mydocker",
		},
	},

	Action: func(context *cli.Context) error {
		if len(context.Args()) < 1 {
			return fmt.Errorf("missing container command")
		}

		envSlice := context.StringSlice("e") // 获取 env 并传递
		Run(tty, cmdArray, envSlice, resConf, volume, containerName, imageName)
		return nil
	},
}

注意到这里的类型是cli. StringSliceFlag,即字符串数组参数,因为这是针对传入多个环境变量的情况。

然后增加对环境变量的解析,并且传递给 Run 函数。

cmd 对象指定 Env 参数

由于原来的 command 实际就是容器启动的进程,所以只需要在原来的基础上,增加一下环境变量的配置即可。

默认情况下,新启动进程的环境变量都是继承于原来父进程的环境变量,但是如果手动指定了环境变量,那么这里就会覆盖掉原来继承自父进程的变量

由于在容器的进程中,有时候还需要使用原来父进程的环境变量,比如 PATH 等,因此这里会使用 os.Environ() 来获取宿主机的环境变量,然后把自定义的变量加进去。

func NewParentProcess(tty bool, volume, containerId, imageName string, envSlice []string) (*exec.Cmd, *os.File) {
    // 省略其他内容
	cmd.Env = append(os.Environ(), envSlice...)
	return cmd, writePipe
}

到此,环境变量的实现就完成了。

3. 测试

通过 -e 注入两个环境变量测试一下

$ go build .
./mydocker run -it -name c1 -e user=17x -e name=mydocker busybox sh

然后在容器中查看环境变量

/ # env|grep user
user=17x
/ # env|grep name
name=mydocker

这里可以看到,手动指定的环境变量 user=17xname=mydocker 都已经可以在容器内可见了。

说明,-e flag 基本 ok。

下面创建一个后台运行的容器,查看一下是否可以。

root@mydocker:~/feat-run-e/mydocker# ./mydocker run -d -name c2 -e user=17x -e name=mydocker busybox top

查看 ID

root@mydocker:~/feat-run-e/mydocker# ./mydocker ps
ID           NAME        PID         STATUS      COMMAND     CREATED
9250006592   c2          228185      running     top         2024-02-27 10:37:09

然后通过 exec 命令进入容器,查看环境变量

root@mydocker:~/feat-run-e/mydocker# ./mydocker exec 9250006592 sh
# 容器内
/ # env|grep user
/ #

可以发现,并没有看到创建时指定的环境变量。

这里看不到环境变量的原因是:exec 命令其实是 mydocker 创建
的另外一个进程,这个进程的父进程其实是宿主机的的进程,并不是容器进程的。

因为在 Cgo 里面使用了 setns 系统调用,才使得这个进程进入到了容器内的命名空间

由于环境变量是继承自父进程的,因此这个 exec 进程的环境变量其实是继承自宿主机的,所以在 exec 进程内看到的环境变量其实是宿主机的环境变量

因此需要修改一下 exec 命令实现,使其能够看到容器中的环境变量。

4. 修改 mydocker exec 命令

首先提供了一个函数,可以根据指定的 PID 来获取对应进程的环境变量。

// getEnvsByPid 读取指定PID进程的环境变量
func getEnvsByPid(pid string) []string {
	path := fmt.Sprintf("/proc/%s/environ", pid)
	contentBytes, err := os.ReadFile(path)
	if err != nil {
		log.Errorf("Read file %s error %v", path, err)
		return nil
	}
	// env split by \u0000
	envs := strings.Split(string(contentBytes), "\u0000")
	return envs
}

由于进程存放环境变量的位置是/proc/<PID>/environ,因此根据给定的 PID 去读取这个文件,便可以获取环境变量。

在文件的内容中,每个环境变量之间是通过\u0000分割的,因此以此为标记来获取环境变量数组。

然后再启动 exec 进程时把容器中的环境变量也一并带上:

func ExecContainer(containerName string, comArray []string) {
	// 省略其他内容
	// 把指定PID进程的环境变量传递给新启动的进程,实现通过exec命令也能查询到容器的环境变量
	containerEnvs := getEnvsByPid(pid)
	cmd.Env = append(os.Environ(), containerEnvs...)
}

这样,exec 到容器内之后就可以看到所有的环境变量了。

再次测试一下,使用通用的 exec 命令进入容器,查看能否看到环境变量

root@mydocker:~/feat-run-e/mydocker# ./mydocker exec 9250006592 sh
/ # env|grep user
user=17x
/ # env|grep name
name=mydocker

ok,mydocker exec 已经可以获取到容器中的环境变量了。

5. 小结

本章实现了mydocker run -e flag 的添加,支持启动容器时传递环境变量到容器中。

核心实现就是启动 cmd 时指定 Env 参数,具体如下:

	cmd := exec.Command("/proc/self/exe", "init")
	cmd.Env = append(os.Environ(), envSlice...)

同时修改了 exec 命令,将容器中的环境变量append 到 exec 进程,便于查看。


**【从零开始写 Docker 系列】**持续更新中,搜索公众号【探索云原生】订阅,文章。


完整代码见:https://github.com/lixd/mydocker
欢迎关注~

相关代码见 refactor-isolate-rootfs 分支,测试脚本如下:

需要提前在 /var/lib/mydocker/image 目录准备好 busybox.tar 文件,具体见第四篇第二节。

# 克隆代码
git clone -b feat-run-e https://github.com/lixd/mydocker.git
cd mydocker
# 拉取依赖并编译
go mod tidy
go build .
# 测试 
./mydocker run -d -name c1 -e user=17x -e name=mydocker busybox top
# 查看容器 Id
./mydocker ps
# stop 停止指定容器
./mydocker exec ${containerId} sh

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

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

相关文章

《我的阿勒泰》观后感(一、什么叫做有用)

通过央视热播短剧《我的阿勒泰》&#xff0c;认识了李娟老师。同时也认识了她的作品&#xff0c;值得推荐。 生命并不荒凉&#xff0c;它是一种安静的绝美。 生活&#xff0c;如同一个巨大的迷宫&#xff0c;充满了未知和变数。有时&#xff0c;我们会在其中迷失方向&#xf…

STM32通用定时器的应用实例(基于STM32F103)

目录 概述 1 STM32Cube配置项目 1.1 准备环境 1.2 配置项目参数 1.3 生成Project 2 HAL函数 2.1 初始化函数&#xff1a;HAL_TIM_Base_Init 2.2 中断模式启动定时器函数&#xff1a;HAL_TIM_Base_Start 2.3 定时器回调函数&#xff1a; HAL_TIM_PeriodElapsedCallback…

信息系统项目管理师0128:输出(8项目整合管理—8.6管理项目知识—8.6.3输出)

点击查看专栏目录 文章目录 8.6.3 输出 8.6.3 输出 经验教训登记册 经验教训登记册可以包含执行情况的类别和详细的描述&#xff0c;还可包括与执行情况相关的影响、建议和行动方案。经验教训登记册可以记录遇到的挑战、问题、意识到的风险和机会以及其他适用的内容。经验教训…

「云渲染课堂」3dmax地砖材质参数怎么让画面更加真实?

在3DMAX中&#xff0c;地砖材质的渲染需要细致的调整&#xff0c;因为不同材质的地砖在反射和折射参数上各不相同。为了使地砖材质更加逼真&#xff0c;以下简要说明了一些设置方法&#xff0c;希望对大家有所帮助&#xff01; 3dmax地砖材质参数如何设置 1、打开材质编辑器&a…

C语言指针指针和数组笔试题(必看)

前言&#xff1a; 前面介绍了指针的大体内容&#xff0c;如果接下来能够把这些代码的含义搞得清清楚楚&#xff0c;那么你就是代码king&#xff01; 一维数组&#xff1a; int a[] {1,2,3,4}; printf("%d\n",sizeof(a)); printf("%d\n",sizeof(a0)); pr…

推荐系统学习笔记(三)

swing召回通道 Q&#xff1a;假如重合的用户是一个小圈子&#xff1a;在一个群里&#xff0c;毫无关联的笔记也会被同时交互 solve&#xff1a;降低小圈子权重--------------swing的主要目的------------给用户加权 相似度&#xff1a; a是人工参数&#xff0c;overlap降低小…

基于Python+KNN神经网络手写数字识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 手写数字识别是机器学习领域的一个基础问题&#xff0c;也是许多实际应用的基石&#xff0c;如邮政编码识别、银行表…

STM32F1之SPI通信·软件SPI代码编写

目录 1. 简介 2. 硬件电路 移位示意图 3. SPI时序基本单元 3.1 起始条件 3.2 终止条件 3.3 交换一个字节&#xff08;模式0&#xff09; 3.4 交换一个字节&#xff08;模式1&#xff09; 3.5 交换一个字节&#xff08;模式2&#xff09; 3.6 交换一个字节&a…

网络信息安全

目录 引言 网络信息安全的基本概念 定义 主要目标 网络信息安全的范围 主要威胁 恶意软件 黑客攻击 拒绝服务攻击 社交工程 内部威胁 常用技术和防护措施 加密技术 防火墙 入侵检测和防御系统 访问控制 多因素认证 安全审计和监控 安全培训和意识提升 未来发…

panic对defer语句的执行的影响

1.主线程中的panic会直接导致所有正在运行的go协程无法执行,还会导致声明在它之后的defer语句无法执行。 package mainimport ("fmt""time" )func main() {defer fmt.Println("defer1") //声明在panic之前的defer会执行go func() {defer fmt.Pri…

npm介绍、常用命令详解以及什么是全局目录

目录 npm介绍、常用命令详解以及什么是全局目录一、介绍npm的主要功能npm仓库npm的配置npm的版本控制 二、命令1. npm init: 初始化一个新的Node.js项目&#xff0c;创建package.json文件。package.json是一个描述项目信息和依赖关系的文件。2. npm install <package_name&g…

Java入门基础学习笔记42——常用API

API&#xff08;全称&#xff1a;Application Programming Interface&#xff1a;应用程序编程接口&#xff09; 就是Java自己写好的程序&#xff0c;给程序员调用&#xff0c;方便完成一些功能的。 为什么要学别人写好的程序&#xff1f; 不要重复造轮子。 开发效率高。 面…

MySQL主从复制(一):主备一致

MySQL主备的基本原理 如图所示就是基本的主备切换流程&#xff1a; 在状态1中&#xff0c; 客户端的读写都直接访问节点A&#xff0c; 而节点B是A的备库&#xff0c; 只是将A的更新都同步过来&#xff0c; 到本地执行。 这样可以保持节点B和A的数据是相同的。 当需要切换的时候…

基于C#开发web网页管理系统模板流程-主界面管理员录入和编辑功能完善

前言 紧接上篇->基于C#开发web网页管理系统模板流程-登录界面和主界面_c#的网页编程-CSDN博客 已经完成了登录界面和主界面&#xff0c;本篇将完善主界面的管理员录入和编辑功能&#xff0c;事实上管理员录入和编辑的设计套路适用于所有静态表的录入和编辑 首先还是介绍一下…

uniapp中使用mockjs模拟接口测试总结(swiper轮播图示例)

完整总结下在uni-app中如何使用Mock.js模拟接口测试&#xff0c;这在后台接口未就绪的情况下非常有用。同时也给出个首页swiper轮播图的mock接口使用。网上的文章都不太完整&#xff0c;这里总结下完整的使用示例&#xff0c;同时也支持h5和小程序平台&#xff0c;分享给需要的…

基于Arduino的电梯超载报警系统

企鹅&#xff1a;2583550535 项目和论文都有 第1章 绪论.............................................................................................................................. 1 1.1 项目背景及意义........................................................…

【教学类-56-03】数感训练——数字03(寻找自己的学号数字,15-20个)

背景需求&#xff1a; 在实际操作中&#xff0c;孩子们把数字当做了自己的学好&#xff0c;这个提示老师可以给每位孩子做一份“学号数感训练 【教学类-56-02】数感训练——数字02&#xff08;控制指定数字出现的数量&#xff09;-CSDN博客文章浏览阅读341次&#xff0c;点赞…

TypeScript(持续更新中...)

1.TypeScript是什么&#xff1f; TypeScript是javaScript的超集。 2.使用TypeScript 1&#xff09;全局安装nodejs 2&#xff09;安装TypeScript编译器 npm i -g typescript 3.编译ts文件 //注意&#xff1a;需要在ts文件同级目录执行此命令&#xff0c;否则会报找不到…

AI爆文写作:关注热点,提前埋伏好关键词,吃系统的热点推荐,吃搜索流量,让你的文章直接爆了!

做内容&#xff0c;要对热点敏感。 小米汽车的发布会时间&#xff0c;我们是不是提前就知道&#xff0c;发布会前&#xff0c;大家最关注的就是价格。 你看这个相关关键词搜索&#xff0c;10W太多了。 我看到有博主在发布会前&#xff0c;埋伏了一篇&#xff0c;公众号也有推…

FunSound: 基于FunASR-onnx 的高精度离线转写

​ 基于funasr的高精度离线语音转写网页 www.funsound.cn 精度和速度表现不错&#xff0c;提供给大家免费测试 ​