從零開始寫 Docker(七)---實現 mydocker commit 打包容器成映象

探索云原生發表於2024-03-19

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 = 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

相關文章