加载 Docker 镜像失败

loading docker image fails

提问人:BhanuKiran 提问时间:4/8/2023 最后编辑:BhanuKiran 更新时间:4/9/2023 访问量:158

问:

我正在使用 ,以格式加载 docker 映像。golangdocker client.tar

func loadImageFromTar(cli *client.Client, tarFilePath string) (string, error) {
    // Read tar file
    tarFile, err := os.Open(tarFilePath)
    if err != nil {
        return "", fmt.Errorf("failed to open tar file: %w", err)
    }
    defer tarFile.Close()

    // Create a pipe to stream data between tar reader and Docker client
    pr, pw := io.Pipe()

    // Set up a WaitGroup for synchronization
    var wg sync.WaitGroup
    wg.Add(2)

    // Load the Docker image in a separate goroutine
    var imageLoadResponse types.ImageLoadResponse
    go func() {
        defer wg.Done()
        imageLoadResponse, err = cli.ImageLoad(context.Background(), pr, false)
        if err != nil {
            err = fmt.Errorf("failed to load Docker image: %w", err)
        }
    }()

    // Read tar file metadata and copy the tar file to the pipe writer in a separate goroutine
    var repoTag string
    go func() {
        defer wg.Done()
        defer pw.Close()

        tarReader := tar.NewReader(tarFile)

        for {
            header, err := tarReader.Next()
            if err == io.EOF {
                break
            }
            if err != nil {
                err = fmt.Errorf("failed to read tar header: %w", err)
                fmt.Printf("Error: %v", err)
                return
            }

            // Extract the repository and tag from the manifest file
            if header.Name == "manifest.json" {
                data, err := io.ReadAll(tarReader)
                if err != nil {
                    err = fmt.Errorf("failed to read manifest file: %w", err)
                    fmt.Printf("Error: %v", err)
                    return
                }

                var manifest []map[string]interface{}
                err = json.Unmarshal(data, &manifest)
                if err != nil {
                    err = fmt.Errorf("failed to unmarshal manifest: %w", err)
                    fmt.Printf("Error: %v", err)
                    return
                }

                repoTag = manifest[0]["RepoTags"].([]interface{})[0].(string)
            }

            // Copy the tar file data to the pipe writer
            _, err = io.Copy(pw, tarReader)
            if err != nil {
                err = fmt.Errorf("failed to copy tar data: %w", err)
                fmt.Printf("Error: %v", err)
                return
            }
        }
    }()

    // Wait for both goroutines to finish
    wg.Wait()

    // Check if any error occurred in the goroutines
    if err != nil {
        return "", err
    }

    // Close the image load response body
    defer imageLoadResponse.Body.Close()

    // Get the image ID
    imageID, err := getImageIDByRepoTag(cli, repoTag)
    if err != nil {
        return "", fmt.Errorf("failed to get image ID: %w", err)
    }

    return imageID, nil
}

函数:getImageIDByRepoTag

func getImageIDByRepoTag(cli *client.Client, repoTag string) (string, error) {
    images, err := cli.ImageList(context.Background(), types.ImageListOptions{})
    if err != nil {
        return "", fmt.Errorf("failed to list images: %w", err)
    }

    for _, image := range images {
        for _, tag := range image.RepoTags {
            if tag == repoTag {
                return image.ID, nil
            }
        }
    }

    return "", fmt.Errorf("image ID not found for repo tag: %s", repoTag)
}

始终返回 . 此外,当我运行时,我没有看到正在加载的图像。看起来图像加载未完成。getImageIDByRepoTagfmt.Errorf("image ID not found for repo tag: %s", repoTag)docker images

在我的其他代码中,尽管 docker 客户端立即返回,但 docker 映像加载通常需要时间。我通常会在检查之前增加大约 30 秒的等待时间。在这种情况下,增加等待时间也无济于事。cli.ImageLoadgetImageIDByRepoTag

谢谢

docker go io waitgroup

评论

0赞 Burak Serdar 4/8/2023
错误是什么?
0赞 BhanuKiran 4/9/2023
@BurakSerdar .它总是在返回fmt.Errorf("image ID not found for repo tag: %s", repoTag)
0赞 Burak Serdar 4/9/2023
然后,相信它所说的。查看 ImageList 调用返回哪些图像,并查看您要查找的内容是否在那里。
0赞 colm.anseo 4/9/2023
管道的内容仅部分中继到客户端,导致客户端拒绝图像加载。docker

答:

1赞 colm.anseo 4/9/2023 #1

有几个问题:

  • 这两个 goroutines 共享,因此可能会丢失一些错误处理err
    • 您应该在此处为每个 goroutine 使用唯一的错误变量,并在wg.Wait()
  • 主要问题:您正在从阅读器中读取以查找清单文件并提取标记信息 - 这很好 - 但是在找到此内容后,您将字节流的其余部分复制到管道中。因此,您将丢失从未到达客户端的字节流块tardocker

为了避免两次读取 tar 字节流,您可以使用 io。Tee阅读器。 这允许您读取 tar 存档 - 扫描文件 - 但也在其他地方(即客户端)完整地写入此流。manifestdocker

创建 :TeeReader

tr := io.TeeReader(tarFile, pw)  // reading `tr` will read the tarFile - but simultaneously write to `pw`

图像加载现在将从中读取(而不是管道):

//imageLoadResponse, err = cli.ImageLoad(context.Background(), pr, false)
imageLoadResponse, err = cli.ImageLoad(context.Background(), tr, false)

然后更改您的阅读器以从管道中读取:archive/tar

//tarReader := tar.NewReader(tarFile) // direct from file
tarReader := tar.NewReader(pr) // read from pipe (indirectly from the file)

然后,您可以删除您的块:io.Copy

// no longer needed:
//
// _, err = io.Copy(pw, tarReader)
//

因为 tar-inspection 代码会将整个流读取到 EOF。

P.S. 当您检查来自任一 goroutines 的任何潜在错误时,您可能希望重置 to 以避免认为 a 是一个更严重的错误:io.EOFnilEOF

header, err = tarReader.Next()
if err == io.EOF {
    err = nil  //  EOF is a non-fatal error here
    break
}

评论

0赞 BhanuKiran 4/9/2023
按照建议修改后。图像按预期加载。但找不到 repoTag。在 .如果我添加 after 和 before 似乎工作正常并返回 imageID。有什么选择吗?getImageIDByRepoTagimageLoadtime.Sleep(5 * time.Second)imageLoadgetImageIDByRepoTag
0赞 colm.anseo 4/9/2023
我会将 2 个函数分开 - 您可以看到图像正文在函数返回时关闭 - 但在此之前,您正在尝试查询图像。可能存在 docker 客户端阻塞,因为一个请求仍处于待处理状态(因为正文尚未关闭) - 这可能解释了延迟。
0赞 BhanuKiran 4/10/2023
我试着把它们分开。但没有运气。当图像加载正常工作时,我将关闭此问题。并将提出一个关于getImageIDByRepoTag