码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 从零开始写 Docker(十五)---实现 mydocker run -e 支持环境变量传递


    合集 - Docker(22)
    1.深入理解 Docker 核心原理:Namespace、Cgroups 和 Rootfs2023-12-262.探索 Linux Namespace:Docker 隔离的神奇背后01-043.初探 Linux Cgroups:资源控制的奇妙世界01-094.深入剖析 Linux Cgroups 子系统:资源精细管理01-125.Docker 与 Linux Cgroups:资源隔离的魔法之旅01-166.Docker 魔法解密:探索 UnionFS 与 OverlayFS01-197.从零开始写 Docker(一)---实现 mydocker run 命令02-228.从零开始写 Docker(二)---优化:使用匿名管道传递参数02-269.从零开始写 Docker(三)---基于 cgroups 实现资源限制03-0110.从零开始写 Docker(四)---使用 pivotRoot 切换 rootfs 实现文件系统隔离03-0511.从零开始写 Docker(五)---基于 overlayfs 实现写操作隔离03-1212.从零开始写 Docker(六)---实现 mydocker run -v 支持数据卷挂载03-1413.从零开始写 Docker(七)---实现 mydocker commit 打包容器成镜像03-1914.从零开始写 Docker(八)---实现 mydocker run -d 支持后台运行容器03-2115.从零开始写 Docker(九)---实现 mydocker ps 查看运行中的容器03-2616.从零开始写 Docker(十)---实现 mydocker logs 查看容器日志04-0917.从零开始写 Docker(十一)---实现 mydocker exec 进入容器内部04-1618.从零开始写 Docker(十二)---实现 mydocker stop 停止容器04-2519.从零开始写 Docker(十三)---实现 mydocker rm 删除容器05-0920.从零开始写 Docker(十四)---重构:实现容器间 rootfs 隔离05-10
    21.从零开始写 Docker(十五)---实现 mydocker run -e 支持环境变量传递05-24
    22.从零开始写 Docker(十六)---容器网络实现(上):为容器插上”网线”05-28
    收起

    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=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//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
    
  • 相关阅读:
    从0开始学go第六天
    基于Python网络爬虫的小说网站数据分析
    Vue样式绑定
    idea2023启动springboot项目如何指定配置文件
    算法题解记录29+++全排列(百日筑基)
    栈和队列相关的一些问题
    Python的一些基础实操练习题
    【Redis】6.Feed流
    交互式shell和非交互式shell、登录shell和非登录shell
    MES系统看板管理,助力企业实现车间可视化!
  • 原文地址:https://www.cnblogs.com/KubeExplorer/p/18210047
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号