Golang中解决使用github.com/docker/docker/client包时Docker镜像大小不一致的问题

Golang中解决使用github.com/docker/docker/client包时Docker镜像大小不一致的问题 我正在使用Go构建自己的Docker客户端,但出于某种原因,在显示/计算镜像大小时出现了不一致的情况。

使用标准的Docker客户端,我得到:

[12:59:18|jfgratton@zenika:~]: docker images |egrep "certbui|postgres"
nexus:9820/postgresql14   latest-arm64     53e6d093a323   3 months ago   87.2MB
nexus:9820/certbuilder    2.02.00-arm64    6fb52fc0da42   3 months ago   226MB

而我的工具得到的是:

[12:49:25|jfgratton@zenika:~]: dtools lsi |egrep "certbui|postgres"
┃ nexus:9820 ┃ certbuilder  ┃ 2.02.00-arm64  ┃ 6fb52fc0da42 ┃ 2022.09.03 15:50:41 ┃ 215MB       ┃
┃ nexus:9820 ┃ postgresql14 ┃ latest-arm64   ┃ 53e6d093a323 ┃ 2022.09.08 18:07:51 ┃ 83MB        ┃

我使用的来自docker/docker/client包的代码非常简单直接:

images, err := cli.ImageList(ctx, types.ImageListOptions{All: true})
	if err != nil {
		errmsg := fmt.Sprintf("%v", err)
		if errmsg == "Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?" {
			fmt.Println(errmsg)
			os.Exit(-1)
		} else {
			panic(err)
		}
	}

	for _, image := range images {
		//imgSpec := getImageTag(image.ID, image.RepoTags, image.Created, image.Size)
		imgspecSlice = append(imgspecSlice, getImageTag(image.ID, image.RepoTags, image.Created, image.Size)...)
	}

根据types包 - github.com/docker/docker/api/types - Go Packages,我期望镜像大小在Size(int64)结构体中(顺便问一下:VirtualSize是做什么用的?)。

我理解这本身不是一个Go问题,而更多是关于Docker的API…


更多关于Golang中解决使用github.com/docker/docker/client包时Docker镜像大小不一致的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

问题已解决……顺便说一句,我提问时疏忽了,漏掉了一段计算报告大小并将其格式化为MB的代码片段。

基本上,我之前是这样做的:

imgspec.size = (float32)(size / 1024.0 / 1024.0)
		imgspecSlice = append(imgspecSlice, imgspec)

而不是:

imgspec.size = (float32)(size / 1000.0 / 1000.0)
		imgspecSlice = append(imgspecSlice, imgspec)

我错误地将数值除以了“实际”的单位大小(1024),而不是人类可读的大小(1000)。

已解决。

更多关于Golang中解决使用github.com/docker/docker/client包时Docker镜像大小不一致的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Docker API中,镜像大小计算确实存在一些需要注意的细节。主要问题在于Docker使用不同的单位来计算和显示大小。

docker images命令显示的是可读格式的大小,而API返回的是字节数。你需要手动进行转换。以下是修正后的代码:

package main

import (
    "context"
    "fmt"
    "github.com/docker/docker/api/types"
    "github.com/docker/docker/client"
    "math"
)

func formatSize(size int64) string {
    // Docker CLI使用的单位转换
    const unit = 1024
    if size < unit {
        return fmt.Sprintf("%dB", size)
    }
    div, exp := int64(unit), 0
    for n := size / unit; n >= unit; n /= unit {
        div *= unit
        exp++
    }
    // Docker使用1000作为MB的除数,而不是1024
    if exp >= 2 { // 从MB开始使用1000
        div = int64(math.Pow(1000, float64(exp-1))) * 1024
    }
    sizeStr := fmt.Sprintf("%.1f", float64(size)/float64(div))
    
    // 移除不必要的.0
    if sizeStr[len(sizeStr)-2:] == ".0" {
        sizeStr = sizeStr[:len(sizeStr)-2]
    }
    
    // 添加单位
    units := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}
    return fmt.Sprintf("%s%s", sizeStr, units[exp])
}

func main() {
    ctx := context.Background()
    cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
    if err != nil {
        panic(err)
    }

    images, err := cli.ImageList(ctx, types.ImageListOptions{All: true})
    if err != nil {
        panic(err)
    }

    for _, image := range images {
        // 使用VirtualSize而不是Size
        // VirtualSize包含所有分层的大小,而Size可能只包含顶层
        size := image.VirtualSize
        if size == 0 {
            size = image.Size
        }
        
        formattedSize := formatSize(size)
        fmt.Printf("Image: %v, Size: %s\n", image.RepoTags, formattedSize)
    }
}

关于VirtualSize字段:在Docker的早期版本中,VirtualSize表示包含所有分层的总大小,而Size可能只表示顶层的大小。但在现代Docker版本中,这两个值通常是相同的。建议优先使用VirtualSize,如果为0则回退到Size

大小不一致的原因:

  1. Docker CLI使用1000作为MB/GB的除数(SI单位),而Go的fmt包默认使用1024(二进制单位)
  2. 舍入差异:Docker CLI显示一位小数,你的代码可能使用了不同的舍入方式
  3. 单位标签:Docker使用"MB"、“GB"等,而标准库可能使用"MiB”、“GiB”

要完全匹配docker images的输出,你需要实现与Docker CLI完全相同的格式化逻辑。上面的formatSize函数模拟了Docker CLI的行为。

回到顶部