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

本文为从零开始写 Docker 系列第七篇,实现类似 docker commit 的功能,把运行状态的容器存储成镜像保存下来。
完整代码见: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. 概述
由于之前使用 pivotRoot + overlayfs 技术 将 /root/merged 目录作为容器的 rootfs,因此容器中的所有改动都发生在该目录下。
这里我们的 mydocker commit 命令只需要把该目录保存下来即可,因此简单实现为 使用 tar 命令将/root/merged 目录打成 tar 包
2. 实现
具体流程
整个打包流程如下图所示:

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 = usage
app.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
从零开始写 Docker(七)---实现 mydocker commit 打包容器成镜像的更多相关文章
- Docker创建支持ssh服务的容器和镜像
原文链接:Docker创建支持ssh服务的容器和镜像 1. 这里使用的centos作为容器,所以首先下载centos的images # sudo docker pull centos 2. 下载后执行 ...
- docker容器和镜像区别
这篇文章希望能够帮助读者深入理解Docker的命令,还有容器(container)和镜像(image)之间的区别,并深入探讨容器和运行中的容器之间的区别. 当我对Docker技术还是一知半解的时候,我 ...
- docker入门(二)容器与镜像的理解
10张图带你深入理解Docker容器和镜像 申明:此篇文章是转载的(原文地址http://dockone.io/article/783),今天意外发现已经有人转载了(复制了),希望大家关注原创 原本打 ...
- docker入门(二)容器与镜像的关系
[编者的话]本文用图文并茂的方式介绍了容器.镜像的区别和Docker每个命令后面的技术细节,能够很好的帮助读者深入理解Docker. 这篇文章希望能够帮助读者深入理解Docker的命令,还有容器(co ...
- [转]图解Docker容器和镜像
本文转自:https://www.cnblogs.com/wangqiaomei/p/5818636.html 图解Docker容器和镜像 这篇文章希望能够帮助读者深入理解Docker的命令,还有容器 ...
- 10张图带你深入理解Docker容器和镜像
http://dockone.io/article/783 [编者的话]本文用图文并茂的方式介绍了容器.镜像的区别和Docker每个命令后面的技术细节,能够很好的帮助读者深入理解Docker. Doc ...
- 图解Docker容器和镜像
图解Docker容器和镜像 这篇文章希望能够帮助读者深入理解Docker的命令,还有容器(container)和镜像(image)之间的区别,并深入探讨容器和运行中的容器之间的区别. 当我对Docke ...
- docker容器和镜像
这篇文章希望能够帮助读者深入理解Docker的命令,还有容器(container)和镜像(image)之间的区别,并深入探讨容器和运行中的容器之间的区别. 当我对Docker技术还是一知半解的时候,我 ...
- docker中容器和镜像的区别
自学docker过程中一直搞不明白 镜像容器的关系,网上查阅看到一篇文章觉得讲的很好,转载记录. 转自 http://blog.csdn.net/chszs/article/details/48252 ...
- 10 张图带你深入理解 Docker 容器和镜像
这篇文章希望能够帮助读者深入理解 Docker 的命令,还有容器(container)和镜像(image)之间的区别,并深入探讨容器和运行中的容器之间的区别. 当我对 Docker 技术还是一知半解的 ...
随机推荐
- 《ASP.NET Core 微服务实战》-- 读书笔记(第11章)
第 11 章 开发实时应用和服务 在本章,我们将讨论"实时"的准确含义,以及在大部分消费者看来应该属于这一范畴的应用类型 接着,我们将探讨 WebSocket,并分析为什么传统的 ...
- CF1833F Ira and Flamenco
题目链接 题解 知识点:组合数学,枚举,双指针. 注意到,长度为 \(m\) 且数字各不相同的子序列,那么最大值与最小值的差至少为 \(m-1\) .因此,对于任意子序列,它是合法的,当且仅当,将其从 ...
- 石子合并(区间dp+记忆化搜索)
经典例题:石子合并 题目链接 N 堆石子排成一行,现要将石子有次序地合并成一堆,规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分.计算合并最小得分. 方法一.区间dp ...
- win32 - 基于hwnd获取进程名字(GetModuleFileNameEx)
#include <Windows.h> #include <psapi.h> int main() { DWORD process_ID = 0; WCHAR process ...
- etcd实现分布式锁分析
3篇关于分布式锁的文章,可以结合看: consul实现分布式锁:https://www.cnblogs.com/jiujuan/p/10527786.html redis实现分布式锁:https:// ...
- 学习go语言编程之数据类型
数据类型概述 Golang语言内置了如下基础数据类型: 布尔类型:bool 整型:int8,unit8,int16,uint16,int32,uint32,int64,uint64,int,uint, ...
- jq中的正则
正则匹配表达式 \w \s \d \b . 匹配除换行符以外的任意字符 \w 匹配字母或数字或下划线或汉字 等价于 '[A-Za-z0-9_]'. \s 匹配任意的空白符 \d 匹配数字 \b 匹配单 ...
- abc模块的用法
首先需要了解的是一个基类(父类),abc.ABCMeta.这个是用于实现抽象类的一个基础类 抽象方法的使用,在相应的方法之前一行加上@abstractmethod之后,从新的一行开始定义相应的方法.实 ...
- Html飞机大战(十六): 完成"清除"敌机奖励类
好家伙, 我们先来尝试完成一个最简单的功能 正面buff: 1.消灭全图敌机 我们要先找一个好看一点的素材 把背景弄成透明的(搞了好久),感谢度娘的技术支持Photoshop中如何把图 ...
- 【Azure Developer】解答《美丽的数学》一书中P120页的一道谜题:寻找第四个阶乘和数
一道谜题 在观看<美丽的数学>一书中,在120页中有一道谜题: 数字145被称为一个阶乘和数, 因为它具有以下有趣的属性,如果我们将它的各位数字的阶乘相加,会得到该数字本身 1! +4! ...