docker system prune ,一个 should have 的功能

前一篇文章分析了docker system df的实现,这次分析下与它配套的docker system prune命令,之所以说配套,是因为既然已经检查出了可以被clean的unused data,那就需要do the clean stuff。这个功能,对于用docker时间比较久的同学,必定觉得“早就该出了”,他们肯定像我一样,在没有这个官方命令的时候,用

docker rm `docker ps -a | grep -i 'exited' | awk {'print $1'}`

类似这样的shell命令清理过没有运行的docker container。

docker system prune 运行情况

  1. 下图为运行帮助命令,docker system prune -h后的截图:有两个选项:
    • --all,意思是删除所有unused镜像,而不单单是dangling状态的镜像
    • --f,意思是跳过确认选择,直接删除
  2. 下图为执行docker system prune并确认删除后的截图:可以看到罗列出4种会被prune的对象以及其他信息,包括:
    1. stopped containers, 不是运行状态的container
    2. unused volumes,不被任何container引用的volume,所谓dangling volume,一般删除了某个container后,可能会产生这样的volume,可以通过docker rm -v避免这种dangling volume
    3. unused network,不被任何container引用的network
    4. dangling images,不被任何container引用的image
    5. 每个被删除的对象都能看到它的ID,比如container ID、volume ID
    6. 最后有个释放空间大小的summary

跟踪container prune功能,解析docker system prune 代码

相对docker system df的实现,由于prune的目的明确,所以它的代码实现逻辑应该也比较简单:利用df实现的相关逻辑找出目标对象,然后删除它们。

查看了相关的代码,感觉整个调用逻辑很有趣,所以梳理了一个脑图出来,帮助将来理解其他功能模块:

大家有兴趣,可以自己去看看代码,我就简单说明下:

  1. docker命令行是用cobra这个golang库,包括k8s在内的很多golang项目都用这个类库。docker system prune的命令行实现参见:https://github.com/docker/docker/blob/master/cli/command/system/prune.go,截取核心方法的源码,简单解读一下:
// docker system prune command handler
func runPrune(dockerCli *command.DockerCli, options pruneOptions) error {
	var message string

	// 这个选项针对image清理
	// 如果有all标识,就清理所有image,而不单单是dangling
	if options.all {
		message = fmt.Sprintf(warning, allImageDesc)
	} else {
		message = fmt.Sprintf(warning, danglingImageDesc)
	}

	if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), message) {
		return nil
	}

	var spaceReclaimed uint64

	/* 
	分析业务逻辑: 
		首先依次处理没有option的3个清理维度
			- container清理
			- volume清理
			- network清理
	*/   
	/* 
	分析代码写法:
		range后面跟的是初始化的数组,类型是func(看起来像是匿名函数),内容是填充dockerCli这个字段
	*/
	for _, pruneFn := range []func(dockerCli *command.DockerCli, filter opts.FilterOpt) (uint64, string, error){
		prune.RunContainerPrune,
		prune.RunVolumePrune,
		prune.RunNetworkPrune,
	} {
		// 循环体内,是依次执行这3个具有不同dockerCli值的函数
		spc, output, err := pruneFn(dockerCli, options.filter)
		if err != nil {
			return err
		}
		spaceReclaimed += spc
		if output != "" {
			fmt.Fprintln(dockerCli.Out(), output)
		}
	}

    // 由于接受的参数不同,image清理函数被独立执行
	spc, output, err := prune.RunImagePrune(dockerCli, options.all, options.filter)
	if err != nil {
		return err
	}
	if spc > 0 {
		spaceReclaimed += spc
		fmt.Fprintln(dockerCli.Out(), output)
	}

	fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))

	return nil
}

2. 通过跟踪container prune这个功能实现,来解读prune的过程,其他维度类似:

// actual function to do the prune operation for docker container
func (daemon *Daemon) ContainersPrune(pruneFilters filters.Args) (*types.ContainersPruneReport, error) {
	rep := &types.ContainersPruneReport{}

	// 这个方法目的是在于截取请求option当中until字段的值,并系列化成对象,后面会用到。
	// 这里对pruneFilters做的特殊处理,是为了docker image/container prune这些命令,因为他们可以有--filter until=这样的选项
        // 详细可参见:https://github.com/docker/docker/commit/58738cdee327f5de481dcf7d3d377374cbb5f13a
	until, err := getUntilFromPruneFilters(pruneFilters)
	if err != nil {
		return nil, err
	}
	/*
	分析业务逻辑:
		- 拿到本机所有container,包含运行和退出的
		- 遍历每个container是不是在运行,
		- 符合条件的container,就进行删除操作,并记录大小用于统计报告
	*/
	allContainers := daemon.List()
	for _, c := range allContainers {
		if !c.IsRunning() {
			// until字段,从字面意思就是说,会把这个字段指定的时间之后创建的container过滤掉,不做处理
			if !until.IsZero() && c.Created.After(until) {
				continue
			}
			// 获取container的大小
			cSize, _ := daemon.getSize(c)
			// TODO: sets RmLink to true?
			err := daemon.ContainerRm(c.ID, &types.ContainerRmConfig{})
			if err != nil {
				logrus.Warnf("failed to prune container %s: %v", c.ID, err)
				continue
			}
			// 把成功删除的container大小加到最终显示的清理值中去
			if cSize > 0 {
				rep.SpaceReclaimed += uint64(cSize)
			}
			rep.ContainersDeleted = append(rep.ContainersDeleted, c.ID)
		}
	}

	return rep, nil
}

总结

  • docker system prune包含了4种维度的清理
  • 解读了container prune的过程,其实image prune也是个有趣的过程,后续加以解读
  • 整理了某些docker command的前后端调用逻辑。很希望有方法可以截取docker client发出的API,看下它的request body,便于了解细节。目前是通过看docker项目里的测试代码来了解。

发表评论

邮箱地址不会被公开。 必填项已用*标注