從零開始寫 Docker(十三)---實現 mydocker rm 刪除容器

探索云原生發表於2024-05-09

mydocker-rm.png

本文為從零開始寫 Docker 系列第十三篇,實現類似 docker rm 的功能,使得我們能夠刪除容器。


完整程式碼見: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 stop 能夠停止後臺執行的容器,那麼,對於已經處於停止狀態的容器,還剩餘一個刪除操作來補全容器的整個生命週期。

本篇就會完成這最後一步的清理工作,實現mydocker rm 命令,讓我們能夠直接刪除已經停止的容器。

2. 實現

mydocker rm 實現起來很簡單,主要是檔案操作,因為容器對應的程序已經被停止,所以只需要將對應記錄檔案資訊的目錄刪除即可。

docker 可以透過 -f 強制刪除執行中的容器,具體見 moby/delete.go#L92,這裡也加一下,指定 force 時先 stop 再刪除即可。

removeCommand

同樣是先定義 removeCommand,然後再新增到 main 函式中。

var removeCommand = cli.Command{
	Name:  "rm",
	Usage: "remove unused containers,e.g. mydocker rm 1234567890",
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "f", // 強制刪除
			Usage: "force delete running container,",
		}},
	Action: func(context *cli.Context) error {
		if len(context.Args()) < 1 {
			return fmt.Errorf("missing container id")
		}
		containerId := context.Args().Get(0)
		force := context.Bool("f")
		removeContainer(containerId, force)
		return nil
	},
}

這裡只做引數解析,拿到 containerId 傳遞給 removeContainer 即可。

removeContainer

removeContainer 則是 rm 命令的真正實現,根據 Id 拿到容器資訊,然後先判斷狀態:

  • STOP 狀態,則直接刪除
  • RUNNING 狀態,如果帶了 force flag 則先 Stop 然後再刪除,否則列印錯誤資訊
func removeContainer(containerId string, force bool) {
	containerInfo, err := getInfoByContainerId(containerId)
	if err != nil {
		log.Errorf("Get container %s info error %v", containerId, err)
		return
	}

	switch containerInfo.Status {
	case container.STOP: // STOP 狀態容器直接刪除即可
		dirPath := fmt.Sprintf(container.InfoLocFormat, containerId)
		if err = os.RemoveAll(dirPath); err != nil {
			log.Errorf("Remove file %s error %v", dirPath, err)
			return
		}
	case container.RUNNING: // RUNNING 狀態容器如果指定了 force 則先 stop 然後再刪除
		if !force {
			log.Errorf("Couldn't remove running container [%s], Stop the container before attempting removal or"+
				" force remove", containerId)
			return
		}
		stopContainer(containerId)
		removeContainer(containerId, force)
	default:
		log.Errorf("Couldn't remove container,invalid status %s", containerInfo.Status)
		return
	}
}

3. 測試

刪除 STOP 狀態容器

首先建立一個 detach 容器

root@mydocker:~/feat-rm/mydocker# go build .
root@mydocker:~/feat-rm/mydocker# ./mydocker run -d -name rm1 top
{"level":"info","msg":"createTty false","time":"2024-01-30T15:11:20+08:00"}
{"level":"info","msg":"resConf:\u0026{ 0  }","time":"2024-01-30T15:11:20+08:00"}
{"level":"info","msg":"busybox:/root/busybox busybox.tar:/root/busybox.tar","time":"2024-01-30T15:11:20+08:00"}
{"level":"error","msg":"mkdir dir /root/merged error. mkdir /root/merged: file exists","time":"2024-01-30T15:11:20+08:00"}
{"level":"error","msg":"mkdir dir /root/upper error. mkdir /root/upper: file exists","time":"2024-01-30T15:11:20+08:00"}
{"level":"error","msg":"mkdir dir /root/work error. mkdir /root/work: file exists","time":"2024-01-30T15:11:20+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-30T15:11:20+08:00"}
{"level":"info","msg":"command all is top","time":"2024-01-30T15:11:20+08:00"}

mydocker ps 檢視一下:

root@mydocker:~/feat-rm/mydocker# ./mydocker ps
ID           NAME        PID         STATUS      COMMAND     CREATED
0394026801   rm1         181151      running     top         2024-01-30 15:11:20

可以看到,容器正處於 running 狀態。

嘗試直接刪除容器:

root@mydocker:~/feat-rm/mydocker# ./mydocker rm 0394026801
{"level":"error","msg":"Couldn't remove running container [0394026801], Stop the container before attempting removal or force remove","time":"2024-01-30T15:12:12+08:00"}

根據錯誤資訊可知,不能直接刪除執行中的容器

於是先把容器 stop 掉:

root@mydocker:~/feat-rm/mydocker# ./mydocker stop 0394026801
root@mydocker:~/feat-rm/mydocker# ./mydocker ps
ID           NAME        PID         STATUS      COMMAND     CREATED
0394026801   rm1                     stopped     top         2024-01-30 15:11:20

此時已經是 stopped 狀態,可以執行刪除了。

root@mydocker:~/feat-rm/mydocker# ./mydocker rm 0394026801
root@mydocker:~/feat-rm/mydocker# ./mydocker ps
ID          NAME        PID         STATUS      COMMAND     CREATED

可以看到,容器資訊已經不見了,說明刪除成功。

強制刪除 RUNNING 狀態容器

再測試一下指定 -f 時能否刪除 RUNNING 狀態的容器。

首先,也是啟動一個 detach 容器

root@mydocker:~/feat-rm/mydocker# ./mydocker run -d -name rm2 top
{"level":"info","msg":"createTty false","time":"2024-01-30T15:13:44+08:00"}
{"level":"info","msg":"resConf:\u0026{ 0  }","time":"2024-01-30T15:13:44+08:00"}
{"level":"info","msg":"busybox:/root/busybox busybox.tar:/root/busybox.tar","time":"2024-01-30T15:13:44+08:00"}
{"level":"error","msg":"mkdir dir /root/merged error. mkdir /root/merged: file exists","time":"2024-01-30T15:13:44+08:00"}
{"level":"error","msg":"mkdir dir /root/upper error. mkdir /root/upper: file exists","time":"2024-01-30T15:13:44+08:00"}
{"level":"error","msg":"mkdir dir /root/work error. mkdir /root/work: file exists","time":"2024-01-30T15:13:44+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-30T15:13:44+08:00"}
{"level":"info","msg":"command all is top","time":"2024-01-30T15:13:44+08:00"}

檢視容器資訊

root@mydocker:~/feat-rm/mydocker# ./mydocker ps
ID           NAME        PID         STATUS      COMMAND     CREATED
9293725578   rm2         181202      running     top         2024-01-30 15:13:44
root@mydocker:~/feat-rm/mydocker# ps -ef|grep top
root      181202       1  0 15:13 pts/10   00:00:00 top

普通刪除和強制刪除

root@mydocker:~/feat-rm/mydocker# ./mydocker rm 9293725578
{"level":"error","msg":"Couldn't remove running container [9293725578], Stop the container before attempting removal or force remove","time":"2024-01-30T15:15:10+08:00"}
root@mydocker:~/feat-rm/mydocker# ./mydocker rm -f 9293725578

普通刪除提示失敗,強制刪除則成功了,看下是否真的刪掉了

root@mydocker:~/feat-rm/mydocker# ./mydocker ps
ID          NAME        PID         STATUS      COMMAND     CREATED
root@mydocker:~/feat-rm/mydocker# ps -ef|grep top
root      181231  177607  0 15:15 pts/10   00:00:00 grep --color=auto top

容器資訊刪除了,程序也消失了,說明刪除是成功的。

4. 小結

本篇主要實現 mydocker rm 命令,根據 containerId 找到記錄容器資訊的目錄,然後刪除該目錄以實現刪除容器的效果。

對於 RUNNING 狀態容器可以指定-f 強制刪除,或者先執行 stop 命令停止容器。


【從零開始寫 Docker 系列】持續更新中,搜尋公眾號【探索雲原生】訂閱,閱讀更多文章。



完整程式碼見:https://github.com/lixd/mydocker
歡迎關注~

相關程式碼見 feat-rm 分支,測試指令碼如下:

需要提前在 /root 目錄準備好 busybox.tar 檔案,具體見第四篇第二節。

# 克隆程式碼
git clone -b feat-rm https://github.com/lixd/mydocker.git
cd mydocker
# 拉取依賴並編譯
go mod tidy
go build .
# 測試 
./mydocker run -d -name c1 top
# 檢視容器 Id
./mydocker ps
# stop 停止指定容器
./mydocker stop ${containerId}

相關文章