Skip to content

Commit

Permalink
bugfix: should add snapshot gc label to config
Browse files Browse the repository at this point in the history
For `containerd.io/gc.root` label means the content/snapshot cannot be
removed during gc. If we use it for snapshot, the snapshot cannot be
removed during removing image.

By default, we should add
`containerd.io/gc.ref.snapshot.{snapshot-name}` gc label to the image
config so that gc can remove the snapshot if user tries to remove the
image.

close #2556

Signed-off-by: Wei Fu <fuweid89@gmail.com>
  • Loading branch information
fuweid authored and rudyfly committed Jan 9, 2019
1 parent 55c24e2 commit cb450a4
Showing 1 changed file with 52 additions and 55 deletions.
107 changes: 52 additions & 55 deletions ctrd/image_commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ func (c *Client) Commit(ctx context.Context, config *CommitConfig) (_ digest.Dig
}
client := wrapperCli.client

// NOTE: make sure that gc scheduler doesn't remove content/snapshot during commmit
ctx, done, err := client.WithLease(ctx)
if err != nil {
return "", errors.Wrapf(err, "failed to create lease for commit")
}
defer done()

var (
sn = client.SnapshotService(CurrentSnapshotterName())
cs = client.ContentStore()
Expand All @@ -85,33 +92,26 @@ func (c *Client) Commit(ctx context.Context, config *CommitConfig) (_ digest.Dig
// export new layer
snapshot, err := c.GetSnapshot(ctx, config.ContainerID)
if err != nil {
return "", fmt.Errorf("failed to get snapshot: %s", err)
}
layer, diffIDStr, err := exportLayer(ctx, snapshot.Name, sn, cs, differ)
if err != nil {
return "", err
return "", errors.Wrap(err, "failed to get snapshot")
}

// create child image
diffIDDigest, err := digest.Parse(diffIDStr)
layer, diffID, err := exportLayer(ctx, snapshot.Name, sn, cs, differ)
if err != nil {
return "", err
return "", errors.Wrap(err, "failed to export layer")
}

childImg, err := newChildImage(ctx, config, diffIDDigest)
if err != nil {
return "", err
}
childImg := newChildImage(ctx, config, diffID)

// create new snapshot for new layer
snapshotKey := identity.ChainID(childImg.RootFS.DiffIDs).String()
if err = newSnapshot(ctx, config.Image, sn, differ, layer, snapshotKey, diffIDStr); err != nil {
rootfsID := identity.ChainID(childImg.RootFS.DiffIDs).String()
if err = newSnapshot(ctx, rootfsID, config.Image, sn, differ, layer); err != nil {
return "", err
}

defer func() {
if err0 != nil {
logrus.Warnf("remove snapshot %s cause commit image failed", snapshotKey)
client.SnapshotService(CurrentSnapshotterName()).Remove(ctx, snapshotKey)
logrus.Warnf("remove snapshot %s cause commit image failed", rootfsID)
client.SnapshotService(CurrentSnapshotterName()).Remove(ctx, rootfsID)
}
}()

Expand Down Expand Up @@ -180,18 +180,24 @@ func (c *Client) Commit(ctx context.Context, config *CommitConfig) (_ digest.Dig
if !errdefs.IsNotFound(err) {
return "", fmt.Errorf("failed to cover exist image %s", err)
}

if _, err := client.ImageService().Create(ctx, img); err != nil {
return "", fmt.Errorf("failed to create new image %s", err)
}
}

// write manifest content
if err := content.WriteBlob(ctx, cs, mfstDigest.String(), bytes.NewReader(mfstJSON), mfstDesc.Size, mfstDesc.Digest, content.WithLabels(labels)); err != nil {
ref := mfstDigest.String()
if err := content.WriteBlob(ctx, cs, ref, bytes.NewReader(mfstJSON), mfstDesc.Size, mfstDesc.Digest, content.WithLabels(labels)); err != nil {
return "", errors.Wrapf(err, "error writing manifest blob %s", mfstDigest)
}

// write config content
if err := content.WriteBlob(ctx, cs, configDesc.Digest.String(), bytes.NewReader(imgJSON), configDesc.Size, configDesc.Digest); err != nil {
ref = configDesc.Digest.String()
labelOpt := content.WithLabels(map[string]string{
fmt.Sprintf("containerd.io/gc.ref.snapshot.%s", CurrentSnapshotterName()): rootfsID,
})
if err := content.WriteBlob(ctx, cs, ref, bytes.NewReader(imgJSON), configDesc.Size, configDesc.Digest, labelOpt); err != nil {
return "", errors.Wrap(err, "error writing config blob")
}

Expand All @@ -200,37 +206,39 @@ func (c *Client) Commit(ctx context.Context, config *CommitConfig) (_ digest.Dig
}

// export a new layer from a container
func exportLayer(ctx context.Context, name string, sn snapshots.Snapshotter, cs content.Store, differ diff.Differ) (ocispec.Descriptor, string, error) {
// export new layer
rwDesc, err := rootfs.Diff(ctx, name, sn, differ, diff.WithLabels(map[string]string{
"containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339Nano),
}))
func exportLayer(ctx context.Context, name string, sn snapshots.Snapshotter, cs content.Store, differ diff.Differ) (ocispec.Descriptor, digest.Digest, error) {
rwDesc, err := rootfs.Diff(ctx, name, sn, differ)
if err != nil {
return ocispec.Descriptor{}, "", fmt.Errorf("failed to diff: %s", err)
return ocispec.Descriptor{}, digest.Digest(""), fmt.Errorf("failed to diff: %s", err)
}

info, err := cs.Info(ctx, rwDesc.Digest)
if err != nil {
return ocispec.Descriptor{}, "", fmt.Errorf("failed to get exported layer info: %s", err)
return ocispec.Descriptor{}, digest.Digest(""), fmt.Errorf("failed to get exported layer info: %s", err)
}

diffIDStr, ok := info.Labels[containerdUncompressed]
if !ok {
return ocispec.Descriptor{}, "", fmt.Errorf("invalid differ response with no diffID")
return ocispec.Descriptor{}, digest.Digest(""), fmt.Errorf("invalid differ response with no diffID")
}

diffID, err := digest.Parse(diffIDStr)
if err != nil {
return ocispec.Descriptor{}, digest.Digest(""), err
}

layer := ocispec.Descriptor{
MediaType: layerType,
Digest: rwDesc.Digest,
Size: info.Size,
}

return layer, diffIDStr, nil
return layer, diffID, nil
}

// create a new child image descriptor
func newChildImage(ctx context.Context, config *CommitConfig, diffIDDigest digest.Digest) (ocispec.Image, error) {
func newChildImage(ctx context.Context, config *CommitConfig, diffID digest.Digest) ocispec.Image {
createdTime := time.Now()
emptyLayer := (diffIDDigest == emptyGZLayer)
emptyLayer := (diffID == emptyGZLayer)
history := ocispec.History{
Created: &createdTime,
CreatedBy: strings.Join(config.ContainerConfig.Cmd, " "),
Expand All @@ -241,34 +249,32 @@ func newChildImage(ctx context.Context, config *CommitConfig, diffIDDigest diges

// new child image
pImg := config.Image
image := ocispec.Image{
return ocispec.Image{
Architecture: runtime.GOARCH,
OS: runtime.GOOS,
Created: &createdTime,
Author: config.Author,
Config: newImageConfig(config.ContainerConfig),
RootFS: ocispec.RootFS{
Type: "layers",
DiffIDs: append(pImg.RootFS.DiffIDs, diffIDDigest),
DiffIDs: append(pImg.RootFS.DiffIDs, diffID),
},
History: append(pImg.History, history),
}

return image, nil
}

// create a new snapshot for exported layer
func newSnapshot(ctx context.Context, pImg ocispec.Image, sn snapshots.Snapshotter, differ diff.Differ, layer ocispec.Descriptor, name, diffIDStr string) error {
diffIDs := pImg.RootFS.DiffIDs
parent := identity.ChainID(diffIDs).String()

key := randomid.Generate()
// avoid active snapshots cleaned by containerd 1.0.3 gc
opt := snapshots.WithLabels(map[string]string{
"containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339),
snapshots.TypeLabelKey: snapshots.ImageType,
})
mount, err := sn.Prepare(ctx, key, parent, opt)
func newSnapshot(ctx context.Context, name string, pImg ocispec.Image, sn snapshots.Snapshotter, differ diff.Differ, layer ocispec.Descriptor) error {
var (
key = randomid.Generate()
parent = identity.ChainID(pImg.RootFS.DiffIDs).String()
)

mount, err := sn.Prepare(ctx, key, parent,
snapshots.WithLabels(map[string]string{
snapshots.TypeLabelKey: snapshots.ImageType,
}),
)
if err != nil {
return err
}
Expand All @@ -278,15 +284,7 @@ func newSnapshot(ctx context.Context, pImg ocispec.Image, sn snapshots.Snapshott
return fmt.Errorf("failed to apply layer: %s", err)
}

withLabels := func(info *snapshots.Info) error {
info.Labels = map[string]string{
containerdUncompressed: diffIDStr,
"containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339Nano),
}
return nil
}

if err = sn.Commit(ctx, name, key, withLabels); err != nil {
if err = sn.Commit(ctx, name, key); err != nil {
if !errdefs.IsAlreadyExists(err) {
return fmt.Errorf("failed to commit snapshot %s: %s", key, err)
}
Expand All @@ -296,7 +294,6 @@ func newSnapshot(ctx context.Context, pImg ocispec.Image, sn snapshots.Snapshott
return fmt.Errorf("failed to cleanup aborted apply %s: %s", key, err)
}
}

return nil
}

Expand Down

0 comments on commit cb450a4

Please sign in to comment.