docker 1.13中docker system prune的浅析
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 运行情况
- 下图为运行帮助命令,
docker system prune -h
后的截图:有两个选项:--all
,意思是删除所有unused镜像,而不单单是dangling状态的镜像--f
,意思是跳过确认选择,直接删除
- 下图为执行
docker system prune
并确认删除后的截图:可以看到罗列出4种会被prune的对象以及其他信息,包括:- stopped containers, 不是运行状态的container
- unused volumes,不被任何container引用的volume,所谓dangling volume,一般删除了某个container后,可能会产生这样的volume,可以通过
docker rm -v
避免这种dangling volume - unused network,不被任何container引用的network
- dangling images,不被任何container引用的image
- 每个被删除的对象都能看到它的ID,比如container ID、volume ID
- 最后有个释放空间大小的summary
跟踪container prune功能,解析docker system prune 代码
相对docker system df
的实现,由于prune的目的明确,所以它的代码实现逻辑应该也比较简单:利用df实现的相关逻辑找出目标对象,然后删除它们。
查看了相关的代码,感觉整个调用逻辑很有趣,所以梳理了一个脑图出来,帮助将来理解其他功能模块:
大家有兴趣,可以自己去看看代码,我就简单说明下:
- 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的过程,其他维度类似:
- docker client端发起API请求,调用
/containers/prune
POST方法,参见https://github.com/docker/docker/blob/master/client/container_prune.go#L13 - 找到docker API server端,container prune相关的API 路由信息,参见https://github.com/docker/docker/blob/master/api/server/router/container/container.go#L71和https://github.com/docker/docker/blob/master/api/server/router/container/container_routes.go#L551
- 在docker daemon中,找到真正执行container prune这个操作的方法,参见https://github.com/docker/docker/blob/master/daemon/prune.go#L23,其实这个文件里也包含了volume、network以及image的prune实现。选取container 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项目里的测试代码来了解。