从零开始写 Docker(十五)---实现 mydocker run -e 支持环境变量传递
本文为从零开始写 Docker 系列第十五篇,实现 mydocker run -e
, 支持在启动容器时指定环境变量,让容器内运行的程序可以使用外部传递的环境变量。
完整代码见:https://github.com/lixd/mydocker
欢迎 Star
推荐阅读以下文章对 docker 基本实现有一个大致认识:
- 核心原理:深入理解 Docker 核心原理:Namespace、Cgroups 和 Rootfs
- 基于 namespace 的视图隔离:探索 Linux Namespace:Docker 隔离的神奇背后
- 基于 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=17x
和name=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
从零开始写 Docker(十五)---实现 mydocker run -e 支持环境变量传递的更多相关文章
- CentOS 7下设置Docker代理(Linux下Systemd服务的环境变量配置)
Docker守护程序使用HTTP_PROXY,HTTPS_PROXY以及NO_PROXY环境变量在其启动环境来配置HTTP或HTTPS代理的行为.无法使用daemon.json文件配置这些环境变量. ...
- Docker(十五)-Docker的数据管理(volume/bind mount/tmpfs)
Docker提供了三种不同的方式用于将宿主的数据挂载到容器中:volumes,bind mounts,tmpfs volumes.当你不知道该选择哪种方式时,记住,volumes总是正确的选择. vo ...
- MIS2000 Lab,我的IT人生与职场--从零开始的前十五年 与 我的微创业
http://www.dotblogs.com.tw/mis2000lab/archive/2014/09/16/ithome_2014_ironman.aspx [IT邦幫忙]鐵人賽 -- MIS2 ...
- Java从零开始学四十五(Socket编程基础)
一.网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可 ...
- Java从零开始学三十五(JAVA IO- 字节流)
一.字节流 FileOutputStream是OutputStream 的直接子类 FileInputStream也是InputStream的直接子类 二.文本文件的读写 2.1.字节输入流 Test ...
- 从零开始学安全(十五)●DHCP服务
DHCP,全名为:Dynamic Host Configuration Protocol,动态主机配置协议,它是一种基于UDP的局域网协议,其作用主要有:给主机自动分配IP地址,管理员通过该协议管理内 ...
- Java从零开始学二十五(枚举定义和简单使用)
一.枚举 枚举是指由一组固定的常量组成的类型,表示特定的数据集合,只是在这个数据集合定义时,所有可能的值都是已知的. 枚举常量的名称建议大写. 枚举常量就是枚举的静态字段,枚举常量之间使用逗号隔开. ...
- salesforce 零基础学习(六十五)VF页面应善于使用变量和函数(一)常用变量的使用
我们在使用formula或者validation rules等的时候通常会接触到很多function,这些函数很便捷的解决了我们很多问题.其实很多函数也可以应用在VF页面中,VF页面有时候应该善于使用 ...
- 性能测试十六:liunx下jmete配置环境变量
修改环境变量后就不用每次手动输入路径,省时省事,减少命令长度和出错率 按Ctrl+L可进行翻页,翻页到最后一行,此处有java的环境变量 添加jmeter的目录和bin目录 此时,虽修改成功,但是并未 ...
- 从零开始学习 Docker
这篇文章是我学习 Docker 的记录,大部分内容摘抄自 <<Docker - 从入门到实践>> 一书,并非本人原创.学习过程中整理成适合我自己的笔记,其中也包含了我自己的 ...
随机推荐
- SQL 中的运算符与别名:使用示例和语法详解
SQL中的IN运算符 IN运算符允许您在WHERE子句中指定多个值,它是多个OR条件的简写. 示例:获取您自己的SQL Server 返回所有来自'Germany'.'France'或'UK'的客户: ...
- Python生成测试数据--Faker的使用方法
# 官方文档:https://faker.readthedocs.io/en/master/index.html # 安装:pip install Faker from faker import Fa ...
- k8s之operator
背景 数字经济的兴起推动了云计算.物联网.大数据行业的快速蓬勃发展,对数据中心提出了更高的要求,同时,用户对于数据库运维自动化的需求越来越高,数据库即服务的需求越来越强烈. 随着k8s的普及以及云原生 ...
- spring boot 学习前提
前言 总结spring boot的学习前提. 正文 1.spring 程度(可以基本使用,我在学习spring boot的时候,感觉到需要一些spring的基础) 2.maven (这个是包管理,一定 ...
- P7177 [COCI2014-2015#4] MRAVI 题解
思路. 我们知道最初添加的液体越多,那么每个蚂蚁得到的液体也就越多,又因为标签里有深搜,所以可以用 DFS+二分解决(感觉说了一通废话),算是比较常规的一种解法了. 在此题中我们需要魔改一下建树,需在 ...
- axios请求时获取不到错误提示问题。
前端方面使用axios请求,由于新增时,有的条件格式填写错误.后端返回412状态码. ,axios可能封装不完善,他获取数据使状态码为4开头的统统不暴露出去,导致请求时,412这样的状态码,获取不到里 ...
- Llama3-8B到底能不能打?实测对比
前几天Meta开源发布了新的Llama大语言模型:Llama-3系列,本次一共发布了两个版本:Llama-3-8B和Llama-3-70B,根据Meta发布的测评报告,Llama-3-8B的性能吊打之 ...
- HarmonyOS NEXT应用开发之多层嵌套类对象监听
介绍 本示例介绍使用@Observed装饰器和@ObjectLink装饰器来实现多层嵌套类对象属性变化的监听. 效果图预览 使用说明 加载完成后显示商品列表,点击刷新按钮可以刷新商品图片和价格. 实现 ...
- 提质增效,安全灵活,阿里云EDA上云方案让芯片设计驶入高速路
简介: 今天下午14点,直播间等你 导语:随着芯片工艺的跃升,EDA 需要越来越大的计算能力,处理高达PB级的海量数据.传统的算力交付模式已无法跟上快速发展的芯片设计行业,云的快速交付与强大生态提供了 ...
- 全链路灰度新功能:MSE上线配置标签推送
简介: 微服务场景下,全链路灰度作为一种低成本的新功能验证方式,得到了越来越广泛的应用.除了微服务实例和流量的灰度,微服务应用中的配置项也应该具备相应的灰度能力,以应对灰度应用对特殊配置的诉求. 为什 ...