You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Go的磁盘私有容器Registry重启后无法读取已推送镜像的解决方法

解决基于go-containerregistry的磁盘存储Registry重启后丢失镜像的问题

核心问题在于你参考的测试代码仅实现了blob的磁盘写入,但没有持久化镜像的元数据(manifest、tag映射、仓库信息),也没有在Registry启动时加载磁盘上已有的blob和元数据来重建服务状态。以下是具体的解决思路和实现方案:

1. 扩展存储层:持久化元数据

默认的mem.NewRegistry使用内存存储manifest和tag信息,重启后会丢失。你需要自定义实现ManifestStore和TagStore接口,将元数据持久化到磁盘(可选择文件系统、轻量数据库如BoltDB/SQLite):

  • 为每个镜像仓库创建独立目录,存储tag与manifest digest的映射文件(如tags.json)
  • 将manifest内容单独存储为文件,文件名使用其digest值
  • 确保写入元数据时同时更新内存缓存和磁盘文件

2. 启动时加载已有数据

在Registry初始化阶段,添加扫描逻辑:

加载已存在的Blob

遍历磁盘上的blob存储目录(默认结构为blobs/<算法>/<摘要值>),将所有有效blob的digest注册到blob存储的索引中:

func loadExistingBlobs(blobRoot string) ([]v1.Hash, error) {
    var digests []v1.Hash
    algoDirs, err := os.ReadDir(blobRoot)
    if err != nil {
        return nil, err
    }

    for _, algoDir := range algoDirs {
        if !algoDir.IsDir() {
            continue
        }
        algo := algoDir.Name()
        digestDirs, err := os.ReadDir(filepath.Join(blobRoot, algo))
        if err != nil {
            continue
        }

        for _, dDir := range digestDirs {
            if !dDir.IsDir() {
                continue
            }
            digestStr := fmt.Sprintf("%s:%s", algo, dDir.Name())
            digest, err := v1.NewHash(digestStr)
            if err != nil {
                continue
            }
            digests = append(digests, digest)
        }
    }
    return digests, nil
}

将加载的digest注入到磁盘blob存储中(需扩展原diskBlobStore,添加注册已存在blob的方法):

blobStore, err := disk.NewBlobStore(blobRoot)
if err != nil {
    log.Fatal(err)
}

existingBlobs, err := loadExistingBlobs(blobRoot)
if err != nil {
    log.Fatal(err)
}
for _, digest := range existingBlobs {
    // 假设diskBlobStore内部有一个map维护已存在的blob,直接添加
    blobStore.blobs[digest] = struct{}{}
}

加载已存在的Manifest和Tag

自定义磁盘版ManifestStore,初始化时扫描仓库目录加载元数据:

type diskManifestStore struct {
    rootDir string
    cache   map[string]map[string]v1.Manifest // repo -> tag -> manifest
}

func NewDiskManifestStore(rootDir string) (*diskManifestStore, error) {
    ms := &diskManifestStore{
        rootDir: rootDir,
        cache:   make(map[string]map[string]v1.Manifest),
    }

    // 扫描所有仓库目录
    repos, err := os.ReadDir(rootDir)
    if err != nil {
        return nil, err
    }

    for _, repoEntry := range repos {
        if !repoEntry.IsDir() {
            continue
        }
        repoName := repoEntry.Name()
        repoPath := filepath.Join(rootDir, repoName)

        // 加载tag映射
        tagMap := make(map[string]v1.Manifest)
        tagFiles, err := os.ReadDir(repoPath)
        if err != nil {
            continue
        }

        for _, tagFile := range tagFiles {
            tagName := tagFile.Name()
            manifestBytes, err := os.ReadFile(filepath.Join(repoPath, tagName))
            if err != nil {
                continue
            }
            manifest, err := v1.ParseManifest(manifestBytes)
            if err != nil {
                continue
            }
            tagMap[tagName] = manifest
        }
        ms.cache[repoName] = tagMap
    }
    return ms, nil
}

// 实现ManifestStore接口的Put方法,同时写入磁盘和缓存
func (ms *diskManifestStore) Put(ctx context.Context, repo, tag string, m v1.Manifest) error {
    repoPath := filepath.Join(ms.rootDir, repo)
    if err := os.MkdirAll(repoPath, 0755); err != nil {
        return err
    }

    // 写入磁盘
    manifestBytes, err := m.RawManifest()
    if err != nil {
        return err
    }
    if err := os.WriteFile(filepath.Join(repoPath, tag), manifestBytes, 0644); err != nil {
        return err
    }

    // 更新缓存
    if _, ok := ms.cache[repo]; !ok {
        ms.cache[repo] = make(map[string]v1.Manifest)
    }
    ms.cache[repo][tag] = m
    return nil
}

// 实现Get、Delete等其他ManifestStore接口方法(略)

3. 组装自定义Registry

使用自定义的BlobStore和ManifestStore创建Registry:

func main() {
    blobRoot := "./registry/blobs"
    manifestRoot := "./registry/manifests"

    // 初始化磁盘BlobStore并加载已有blob
    blobStore, err := disk.NewBlobStore(blobRoot)
    // ... 加载已有blob的代码 ...

    // 初始化磁盘ManifestStore并加载已有元数据
    manifestStore, err := NewDiskManifestStore(manifestRoot)
    if err != nil {
        log.Fatal(err)
    }

    // 创建自定义Registry
    reg := registry.New(
        registry.WithBlobStore(blobStore),
        registry.WithManifestStore(manifestStore),
    )

    // 启动HTTP服务
    http.Handle("/", reg)
    log.Fatal(http.ListenAndServe(":5000", nil))
}

注意事项

  • 处理并发读写:使用文件锁或事务型数据库(如BoltDB)避免元数据损坏
  • 一致性维护:删除镜像时需同时删除对应的manifest文件和blob文件
  • 错误处理:加载过程中跳过无效的文件或目录,避免服务启动失败

内容的提问来源于stack exchange,提问作者Astin Gengo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 02:35:04