当前位置: 首页 > news >正文

从零开始写 Docker(七)---实现 mydocker commit 打包容器成镜像

mydocker-commit.png

本文为从零开始写 Docker 系列第七篇,实现类似 docker commit 的功能,把运行状态的容器存储成镜像保存下来。


完整代码见: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. 概述

由于之前使用 pivotRoot + overlayfs 技术 将 /root/merged 目录作为容器的 rootfs,因此容器中的所有改动都发生在该目录下。

这里我们的 mydocker commit 命令只需要把该目录保存下来即可,因此简单实现为 使用 tar 命令将/root/merged 目录打成 tar 包

2. 实现

具体流程

整个打包流程如下图所示:

mydocker-commit-process.png

commitCommand

在 main_ command.go 文件中实现 commitCommand 命令,从用户的输入获取image name。

var commitCommand = cli.Command{Name:  "commit",Usage: "commit container to image",Action: func(context *cli.Context) error {if len(context.Args()) < 1 {return fmt.Errorf("missing image name")}imageName := context.Args().Get(0)commitContainer(imageName)return nil},
}

然后在 main 方法中添加 commit 命令:

func main() {app := cli.NewApp()app.Name = "mydocker"app.Usage = usageapp.Commands = []cli.Command{initCommand,runCommand,commitCommand,}// 省略其他
}

commitContainer

添加 commit.go文件,通过 commitContainer 函数实现将容器文件系统打包成$ {imagename}.tar 文件。

func commitContainer(imageName string) {mntPath := "/root/merged"imageTar := "/root/" + imageName + ".tar"fmt.Println("commitContainer imageTar:", imageTar)if _, err := exec.Command("tar", "-czf", imageTar, "-C", mntPath, ".").CombinedOutput(); err != nil {log.Errorf("tar folder %s error %v", mntPath, err)}
}

如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。

搜索公众号【探索云原生】即可订阅


3. 测试

测试流程如下:

  • 1)启动容器
  • 2)创建新文件
  • 3)新终端中将容器打包为镜像
  • 4)解压该镜像,查看 2 中的内容是否存在

首先,启动容器

root@mydocker:~/feat-commit/mydocker# ./mydocker run -it /bin/sh
{"level":"info","msg":"resConf:\u0026{ 0  }","time":"2024-01-19T16:18:24+08:00"}
{"level":"info","msg":"busybox:/root/busybox busybox.tar:/root/busybox.tar","time":"2024-01-19T16:18:24+08:00"}
{"level":"info","msg":"mount overlayfs: [/usr/bin/mount -t overlay overlay -o lowerdir=/root/busybox,upperdir=/root/upper,workdir=/root/work /root/merged]","time":"2024-01-19T16:18:24+08:00"}
{"level":"info","msg":"command all is /bin/sh","time":"2024-01-19T16:18:24+08:00"}
{"level":"info","msg":"init come on","time":"2024-01-19T16:18:24+08:00"}
{"level":"info","msg":"Current location is /root/merged","time":"2024-01-19T16:18:24+08:00"}
{"level":"info","msg":"Find path /bin/sh","time":"2024-01-19T16:18:24+08:00"}

创建一个文件

/ # echo KubeExplorer > tmp/hello.txt
/ # cat /tmp/hello.txt
KubeExplorer

接着另外打开 terminal 执行 mydocker commit 命令,将当前容器提交为镜像

root@mydocker:~/feat-commit/mydocker# ./mydocker commit myimage
commitContainer imageTar: /root/myimage.tar

再次查看/root 目录的内容,多了 myimage.tar 文件,这个就是我们的镜像文件了

root@mydocker:~# ls
busybox  busybox.tar merged  myimage.tar  upper  volume  work

查看 myimage.tar 中内容:

root@mydocker:~# tar -tf myimage.tar |grep hello.txt
./tmp/hello.txt

可以看到,前面在容器中创建的 hello.txt 是存在的。

4. 总结

本篇 mydocker commit 比较简单,就是使用 tar 命令将 rootfs 直接进行打包,没有太多需要注意的地方。

镜像构造部分到此就基本完成了,总结一下:

  • 1)首先使用 busybox 作为基础镜像创建了一个容器,理解了什么是 rootfs,以及如何使用 rootfs 来打造容器的基本运行环境。

  • 2)然后,使用 overlayfs 来构建了一个拥有二层模式的镜像,对于最上层可写层的修改不会影响到基础层。这里就基本解释了镜像分层存储的原理。

  • 3)之后使用 -v 参数做了一个 volume 挂载的例子,介绍了如何将容器外部的文件系统挂载到容器中,并且让它可以访问。

  • 4)最后(本文)实现了一个简单版本的容器镜像打包。

这一章主要针对镜像的存储及文件系统做了基本的原理性介绍,通过这几个例子,可以很好地理解镜像是如何构建的,后续会基于这些基础做更多的扩展。


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



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

相关代码见 feat-volume 分支,测试脚本如下:

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

# 克隆代码
git clone -b feat-commit https://github.com/lixd/mydocker.git
cd mydocker
# 拉取依赖并编译
go mod tidy
go build .
# 测试 
./mydocker run -it  /bin/ls
./mydocker commit

相关文章:

  • 【C语言】指针基础知识(一)
  • Excel使用VLOOKUP函数
  • C# 使用OpenCvSharp4将Bitmap合成为MP4视频的环境
  • Android和IOS Flutter应用开发使用 Provider.of 时,可以使用 listen: false 来避免不必要的重建
  • 算法---二分查找练习-2(寻找旋转排序数组中的最小值)
  • 稀碎从零算法笔记Day22-LeetCode:
  • 【类脑智能】脑网络通信模型分类及量化指标(附思维导图)
  • Spark-Scala语言实战(3)
  • Spring Boot:筑基
  • 【滑动窗口】长度最小的子数组|无重复字符的最长子串|最大连续1的个数 III|将 x 减到 0 的最小操作数
  • EPSON XV4001BC陀螺仪传感器汽车导航系统的应用
  • LabVIEW NV色心频率扫描
  • C#,图论与图算法,计算无向连通图中长度为n环的算法与源代码
  • 热插拔技术详解(中)
  • Python分析无人驾驶汽车在桂林市文旅行业推广的问卷
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • Git的一些常用操作
  • go语言学习初探(一)
  • Javascript 原型链
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • linux安装openssl、swoole等扩展的具体步骤
  • MQ框架的比较
  • PAT A1017 优先队列
  • Python学习笔记 字符串拼接
  • Redux 中间件分析
  • Spring核心 Bean的高级装配
  • Web设计流程优化:网页效果图设计新思路
  • 分布式任务队列Celery
  • 服务器从安装到部署全过程(二)
  • 高性能JavaScript阅读简记(三)
  • 检测对象或数组
  • 前端性能优化--懒加载和预加载
  • 微信支付JSAPI,实测!终极方案
  • 用mpvue开发微信小程序
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • ​Base64转换成图片,android studio build乱码,找不到okio.ByteString接腾讯人脸识别
  • (1)(1.11) SiK Radio v2(一)
  • (4.10~4.16)
  • (Matlab)使用竞争神经网络实现数据聚类
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (四)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)
  • (原創) 如何讓IE7按第二次Ctrl + Tab時,回到原來的索引標籤? (Web) (IE) (OS) (Windows)...
  • (转)Linux整合apache和tomcat构建Web服务器
  • (转载)在C#用WM_COPYDATA消息来实现两个进程之间传递数据
  • *** 2003
  • .NET 8.0 中有哪些新的变化?
  • .Net Core webapi RestFul 统一接口数据返回格式
  • .NET Core中的去虚
  • .NET 中创建支持集合初始化器的类型
  • .net对接阿里云CSB服务
  • .NET委托:一个关于C#的睡前故事
  • .NET下的多线程编程—1-线程机制概述
  • :not(:first-child)和:not(:last-child)的用法
  • [ vulhub漏洞复现篇 ] JBOSS AS 4.x以下反序列化远程代码执行漏洞CVE-2017-7504