实现流程可控的镜像下载和存储(二)

基于containers/storage的镜像存储

镜像结构

完整镜像Image主要由Layer、Blob和Manifest组成
Layer:通过链式结构组成,存储镜像的数据,每个Layer依赖其Parent Layer,即使两个Layer的内容完全相同,Parent Layer不同也会导致Layer不同
Blob:压缩后的Layer数据,解压后即是Layer,通过Digest区分不同Blob,Digest即Blob文件的sha256sum值
Manifest:存有镜像信息以及每个Layer的信息,通过Manifest可以将Blob组成Layer并合成Image

镜像存储

增加additional image store用于存储需要管理的镜像

/etc/containers/storage.conf
[storage]
    driver = "overlay"
    runroot = "/run/containers/storage"
    graphroot = "/var/lib/containers/storage"
[storage.options]
    additionalimagestores=["/var/cache/image"]
    mount_program = "/usr/bin/fuse-overlayfs"

初始化store并获取镜像列表

import (
	"github.com/containers/storage"
	"github.com/containers/storage/types"
)

func Images() error {
	store, err := storage.GetStore(
		types.StoreOptions{
			GraphDriverName: "overlay",
			GraphRoot:       "/var/cache/image",
		})
	if err != nil {
		return err
	}
	images, err := store.Images()
	if err != nil {
		return err
	}
	for _, image := range images {
	// fmt.Println(image.Names)
	}
	return nil
}

计算当前blob文件的解压后生成layer的ID和Diff ID

func GenerateDiffID(blobPath, parentID string) (string, digest.Digest, error) {
	// ID: layer 's ID
	// ParentID: ID of current layer 's parent layer
	// Diff ID: Used to create layer
	file, err := os.OpenFile(blobPath, os.O_RDONLY, 0666)
	if err != nil {
		return "", "", err
	}
	decompressed, err := archive.DecompressStream(file)
	if err != nil {
		return "", "", err
	}
	diff := digest.Canonical.Digester()
	_, err = io.Copy(diff.Hash(), decompressed)
	decompressed.Close()
	if err != nil {
		return "", "", err
	}
	diffID := diff.Digest()
	id := ""
	if parentID == "" {
		id = diffID.Hex()
	} else {
		id = digest.Canonical.FromBytes([]byte(parentID + "+" + diffID.Hex())).Hex()
	}
	return id, diffID, nil
}

基于blob文件生成layer

其中digest为blob文件的sha256sum值,可在manifest中获取,也可以直接计算获取,其余参数GenerateDiffID可计算得到

import (
	"os"

	"github.com/containers/storage"
	"github.com/containers/storage/types"
	"github.com/opencontainers/go-digest"
)

func CreateLayer(id, parent, path string, digest, diffID digest.Digest) error {
	store, err := storage.GetStore(
		types.StoreOptions{
			GraphDriverName: "overlay",
			GraphRoot:       "/var/cache/image",
		})
	if err != nil {
		return err
	}
	// Layer exist
	if store.Exists(id) {
		return nil
	}
	file, err := os.Open(path)
	if err != nil {
		return err
	}
	defer file.Close()
	_, _, err = store.PutLayer(id, parent, nil, "", false,
		&storage.LayerOptions{
			OriginalDigest:     digest,
			UncompressedDigest: diffID,
		}, file)
	return err
}

解析Manifest,获取镜像的部分参数

import (
	"encoding/json"
	"time"

	"github.com/containers/image/v5/manifest"
	"github.com/containers/storage"
	"github.com/opencontainers/go-digest"
)

func ParseManifest(manifestData []byte) error {
	// Find the list of layer blobs
	man, err := manifest.FromBlob(manifestData, manifest.GuessMIMEType(manifestData))
	if err != nil {
		return err
	}
	// Get image info
	imageInfoDigest := man.ConfigInfo().Digest
	imageInfo, err := GetMetaData(imageInfoDigest.String())
	if err != nil {
		return err
	}
	imageOptions := storage.ImageOptions{}
	imageInfoData := make(map[string]interface{})
	// Get image create time
	err = json.Unmarshal(imageInfo, &imageInfoData)
	if err != nil {
		imageOptions.CreationDate = time.Now()
	} else {
		created := imageInfoData["created"].(string)
		t, err := time.Parse(time.RFC3339, created)
		if err != nil {
			imageOptions.CreationDate = time.Now()
		} else {
			imageOptions.CreationDate = t
		}
	}
	intendedID := imageInfoDigest.String()[7:]
	imageOptions.BigData = append(imageOptions.BigData, storage.ImageBigDataOption{
		Key:    "manifest",
		Data:   manifestData,
		Digest: digest.Digest(imageInfoDigest.String()),
	})

	imageOptions.BigData = append(imageOptions.BigData, storage.ImageBigDataOption{
		Key:    "manifest-" + imageInfoDigest.String(),
		Data:   manifestData,
		Digest: digest.Digest(imageInfoDigest.String()),
	})

	imageOptions.BigData = append(imageOptions.BigData, storage.ImageBigDataOption{
		Key:    imageInfoDigest.String(),
		Data:   imageInfo,
		Digest: imageInfoDigest,
	})
	return nil
}

创建镜像

其中intendedID为镜像Mainfest的Digest,options在解析Manifest时可生成,lastLayer为镜像最后一个Layer的ID

import (
	"github.com/containers/storage"
	"github.com/containers/storage/types"
)

func CreateImage(intendedID, lastLayer string, options *storage.ImageOptions) (*storage.Image, error) {
	store, err := storage.GetStore(
		types.StoreOptions{
			GraphDriverName: "overlay",
			GraphRoot:       "/var/cache/image",
		})
	if err != nil {
		return nil, err
	}
	return store.CreateImage(intendedID, nil, lastLayer, "", options)
}
posted on 2024-02-06 16:32  umichan  阅读(13)  评论(0)    收藏  举报