ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

go-containerregistry `empty` 包实战:以 “FROM scratch“ 为起点构建 v1.Image 与 v1.ImageIndex

go-containerregistry `empty` 包实战:以 “FROM scratch“ 为起点构建 v1.Image 与 v1.ImageIndex 人工智能AI AgentAgent 沙箱云原生容器运行时零信任【免费下载链接】substrateAgent Substrate: the core system项目地址https://gitcode.com/GitHub_Trending/substrate7/substrate点击查看免费下载导读empty是 go-containerregistry 中一个精巧的基础包它提供了两个开箱即用的空对象——empty.Image与empty.Index分别等价于 Dockerfile 中的FROM scratch空镜像与空 OCI 索引Manifest List。在 substrate 项目中它被大量用于测试镜像的构造先以empty.Image为基底再通过mutate.AppendLayers或mutate.ConfigFile追加层与配置最后推送到临时 registry 验证镜像卷image volume解析、镜像缓存等链路。读完本文你将掌握 empty 包的设计动机、源码实现细节以及它与mutate包配合构造镜像/索引的标准用法并能直接照搬到自己的测试或构建工具中。empty包的定位从零构造一个v1.Image或v1.ImageIndex在 go-containerregistry 的v1接口体系中v1.Image、v1.ImageIndex与v1.Layer都是只读访问器accessor-only本质上不可变——你无法像修改对象字段那样去改动一个镜像。因此任何变更都必须通过mutate包产出新的接口实例。而empty包提供的正是这条变更链的起点一个不包含任何层的空镜像和一个不包含任何子清单的空索引。正如其在 README.md 中所写它最适合与 mutate 包 配合使用典型场景是mutate.Append向空镜像追加层与构建历史mutate.AppendManifests向空索引追加子镜像/子索引描述符。从代码结构看empty包总共只有三个文件doc.go、image.go、index.go且全部是单例 空实现几乎没有任何运行时开销。empty.Image源码层面的FROM scratch在 image.go 中empty.Image被定义为一个包级单例// Image is a singleton empty image, think: FROM scratch. var Image, _ partial.UncompressedToImage(emptyImage{})它通过partial.UncompressedToImage将emptyImage{}适配为完整的v1.Image接口。其关键实现点有三个MediaType 为 Docker 清单格式image.go#L31-L33func (i emptyImage) MediaType() (types.MediaType, error) { return types.DockerManifestSchema2, nil }即空镜像的清单manifest默认是application/vnd.docker.distribution.manifest.v2json而非 OCI 格式。ConfigFile 只含一个空 RootFSimage.go#L41-L48func (i emptyImage) ConfigFile() (*v1.ConfigFile, error) { return v1.ConfigFile{ RootFS: v1.RootFS{ // Some clients check this. Type: layers, }, }, nil }注释明确说明一些客户端会检查这个字段所以即便没有层也必须把RootFS.Type置为layers否则某些严格校验的注册表或客户端可能拒绝该镜像。ConfigFile中不包含任何History、Config.Entrypoint等内容全部为空。任何层查询都会报错image.go#L50-L52func (i emptyImage) LayerByDiffID(h v1.Hash) (partial.UncompressedLayer, error) { return nil, fmt.Errorf(LayerByDiffID(%s): empty image, h) }因为空镜像本来就没有层按 diffID 查找层必然失败——这也是空语义的一部分调用方应把该错误视为没有该层而非程序异常。empty.Index源码层面的空 OCI 索引与empty.Image相对index.go 中的empty.Index是一个空的多架构镜像索引// Index is a singleton empty index, think: FROM scratch. var Index emptyIndex{}其核心实现func base() *v1.IndexManifest { return v1.IndexManifest{ SchemaVersion: 2, MediaType: types.OCIImageIndex, Manifests: []v1.Descriptor{}, } }注意两点与empty.Image的差异MediaType 是 OCI 索引格式types.OCIImageIndex即application/vnd.oci.image.index.v1json而非 Docker 格式Manifests是显式初始化的空切片[]v1.Descriptor{}保证序列化后是[]而不是null这一点对严格校验 JSON 的客户端很重要。同时emptyIndex的Image(hash)与ImageIndex(hash)方法都直接返回errors.New(empty index)与empty.Image.LayerByDiffID一样体现了空的语义边界。与mutate配合从空镜像构造出真实可用的镜像empty本身空无一物真正有价值的是它与 mutate 包 的组合。在 mutate/mutate.go 中可以看到它直接引用了empty作为类型载体mutate.AppendLayers(base v1.Image, layers ...v1.Layer)mutate.go#L50-L58把若干层直接包装成Addendum后调用Append是最常用的追加方式mutate.Append(base, adds ...Addendum)mutate.go#L60-L73Addendum除了Layer还可携带History、URLs、Annotations、MediaType适合需要同时写入构建历史的场景mutate.AppendManifests(base v1.ImageIndex, adds ...IndexAddendum)mutate.go#L91-L97向空索引追加描述符常用于构造多架构镜像列表mutate.ConfigFile/mutate.Configmutate.go#L107-L117修改镜像配置entrypoint、环境变量、作者等。mutate 的 README.md 也专门注明For constructing an imageFROM scratch, see theemptypackage再次印证了两者的配套关系。此外mutate/rebase.go 与 remote/referrers.go 内部同样以empty为基座后者用empty.Index作为空 referrers 索引的占位实现。substrate 中的真实用法测试镜像的快速构造在 substrate 仓库中empty包被实际用于测试基础设施——先用它构造镜像再配合内存 registry 完成端到端验证。典型例子是 cmd/atelet/imagevolume_test.gofunc singleFileLayer(t *testing.T, path, body string) v1.Layer { // 把单个文件打包成 tar 流再用 tarball.LayerFromOpener 转成 v1.Layer ... } func pushTestImage(t *testing.T, ref string, layers ...v1.Layer) { t.Helper() img, err : mutate.AppendLayers(empty.Image, layers...) if err ! nil { t.Fatalf(mutate.AppendLayers: %v, err) } tag, err : name.ParseReference(ref, name.Insecure) if err ! nil { t.Fatalf(name.ParseReference(%q): %v, ref, err) } if err : remote.Write(tag, img); err ! nil { t.Fatalf(remote.Write(%q): %v, ref, err) } }该测试通过empty.Imagemutate.AppendLayers构造单文件镜像推送到registry.New创建的内存 OCI registry 后再验证resolveImageVolumes能否正确解析出镜像卷的层目录与sha256:摘要TestResolveImageVolumes_MultiLayer则用两个singleFileLayer验证多层的自底向上顺序。同样的模式还出现在internal/imagecache/imagecache_test.go#L70mutate.ConfigFile(empty.Image, v1.ConfigFile{...})构造带配置的空镜像测试镜像缓存对配置文件的处理cmd/atelet/sandbox_prewarm_test.go#L52mutate.AppendLayers(empty.Image, ...)构造包含pause文件的镜像测试沙箱预热链路internal/e2e/suites/combinedvolumes/combinedvolumes_test.go#L115e2e 套件中同样以空镜像为基底组合多卷场景。可见empty mutate 已成为 substrate 测试代码中构造任意内容镜像的通用范式想要什么文件就把文件打成 tar 层追加上去想要什么配置就用mutate.ConfigFile覆盖整个过程不依赖任何外部基础镜像。完整实战示例从空镜像到可推送镜像综合上面内容一个完整可运行的构造流程如下可直接用于本地测试或构建工具package main import ( archive/tar bytes io github.com/google/go-containerregistry/pkg/name github.com/google/go-containerregistry/pkg/registry v1 github.com/google/go-containerregistry/pkg/v1 github.com/google/go-containerregistry/pkg/v1/empty github.com/google/go-containerregistry/pkg/v1/mutate github.com/google/go-containerregistry/pkg/v1/remote github.com/google/go-containerregistry/pkg/v1/tarball ) func layerFromFile(path, content string) (v1.Layer, error) { var buf bytes.Buffer tw : tar.NewWriter(buf) if err : tw.WriteHeader(tar.Header{ Name: path, Mode: 0o755, Size: int64(len(content)), }); err ! nil { return nil, err } if _, err : tw.Write([]byte(content)); err ! nil { return nil, err } if err : tw.Close(); err ! nil { return nil, err } return tarball.LayerFromOpener(func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(buf.Bytes())), nil }) } func main() { // 1. 以空镜像为基底等价于 FROM scratch img : empty.Image // 2. 追加层可多次调用顺序即层序 payload, _ : layerFromFile(/ate/payload, hello) img, _ mutate.AppendLayers(img, payload) // 3. 可选修改配置例如设置入口命令 cfg, _ : img.ConfigFile() cfg.Config.Entrypoint []string{/ate/payload} img, _ mutate.ConfigFile(img, cfg) // 4. 推送到本地内存 registry srv : registry.New() ref, _ : name.ParseReference(127.0.0.1:5000/agent:v1, name.Insecure) _ remote.Write(ref, img) // 5. 若要构建多架构索引从 empty.Index 开始 // idx : mutate.AppendManifests(empty.Index, // mutate.IndexAddendum{Add: img}, // ) }注意事项与易错点不要对空镜像做层查询empty.Image.LayerByDiffID与empty.Index.Image/ImageIndex必然返回错误这是设计使然调用时应将其视为空的语义而非故障。两种 MediaType 不同空镜像默认是 Docker manifest schema 2空索引默认是 OCI image index。若注册表对格式有严格要求例如只接受 OCI需要通过mutate.MediaType/mutate.IndexMediaType显式转换。RootFS.Type: layers不可省略这是emptyImage.ConfigFile显式设置的字段部分客户端依赖它做校验自定义空镜像实现时也应保留。空索引的Manifests要初始化为空切片empty.Index返回[]v1.Descriptor{}而非nil保证 JSON 序列化结果是[]避免null导致严格校验失败。小结empty包虽小却是 go-containerregistry 镜像构造链中从零开始的关键一环它以极简的单例实现提供了FROM scratch语义的v1.Image与空v1.ImageIndex通过与mutate包的组合可以快速构造任意内容与配置的镜像。在 substrate 仓库中这一组合被广泛应用于镜像卷解析、镜像缓存、沙箱预热与 e2e 测试是从空镜像到可推送镜像的标准化起点。赞分享人工智能AI AgentAgent 沙箱云原生容器运行时零信任【免费下载链接】substrateAgent Substrate: the core system项目地址https://gitcode.com/GitHub_Trending/substrate7/substrate点击查看免费下载相关推荐深入 go-containerregistry 的 partial 包以最小“部分实现”构建完整 v1.Image深入 go containerregistry 的 partial 包以最小“部分实现”构建完整 v1.Image 在 KubeSphere 等依赖容器镜像处后端云原生容器编排微服务kOps 仓库中的 go-containerregistry empty 包从 scratch 构建容器镜像的零起点底座kOps 仓库中的 go containerregistry empty 包从 scratch 构建容器镜像的零起点底座 导读 本篇文章聚焦于 kOps 仓库云原生集群管理运维IaCgo-containerregistry partial 包深度解析用最小接口快速构建完整的 v1.Image 镜像表示go containerregistry partial 包深度解析用最小接口快速构建完整的 v1.Image 镜像表示 导读 partial 是 go co云原生CLI应用安全上一篇推荐一款强大的JavaFX开发工具xJavaFxTool下一篇终极指南如何用Catapult构建高性能网络应用的完整教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表