github.com/lalkh/containerd@v1.4.3/snapshots/overlay/overlay.go (about)

     1  // +build linux
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package overlay
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  	"syscall"
    29  
    30  	"github.com/containerd/containerd/log"
    31  	"github.com/containerd/containerd/mount"
    32  	"github.com/containerd/containerd/platforms"
    33  	"github.com/containerd/containerd/plugin"
    34  	"github.com/containerd/containerd/snapshots"
    35  	"github.com/containerd/containerd/snapshots/storage"
    36  	"github.com/containerd/continuity/fs"
    37  	"github.com/pkg/errors"
    38  )
    39  
    40  func init() {
    41  	plugin.Register(&plugin.Registration{
    42  		Type: plugin.SnapshotPlugin,
    43  		ID:   "overlayfs",
    44  		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
    45  			ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec())
    46  			ic.Meta.Exports["root"] = ic.Root
    47  			return NewSnapshotter(ic.Root, AsynchronousRemove)
    48  		},
    49  	})
    50  }
    51  
    52  // SnapshotterConfig is used to configure the overlay snapshotter instance
    53  type SnapshotterConfig struct {
    54  	asyncRemove bool
    55  }
    56  
    57  // Opt is an option to configure the overlay snapshotter
    58  type Opt func(config *SnapshotterConfig) error
    59  
    60  // AsynchronousRemove defers removal of filesystem content until
    61  // the Cleanup method is called. Removals will make the snapshot
    62  // referred to by the key unavailable and make the key immediately
    63  // available for re-use.
    64  func AsynchronousRemove(config *SnapshotterConfig) error {
    65  	config.asyncRemove = true
    66  	return nil
    67  }
    68  
    69  type snapshotter struct {
    70  	root        string
    71  	ms          *storage.MetaStore
    72  	asyncRemove bool
    73  	indexOff    bool
    74  }
    75  
    76  // NewSnapshotter returns a Snapshotter which uses overlayfs. The overlayfs
    77  // diffs are stored under the provided root. A metadata file is stored under
    78  // the root.
    79  func NewSnapshotter(root string, opts ...Opt) (snapshots.Snapshotter, error) {
    80  	var config SnapshotterConfig
    81  	for _, opt := range opts {
    82  		if err := opt(&config); err != nil {
    83  			return nil, err
    84  		}
    85  	}
    86  
    87  	if err := os.MkdirAll(root, 0700); err != nil {
    88  		return nil, err
    89  	}
    90  	supportsDType, err := fs.SupportsDType(root)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  	if !supportsDType {
    95  		return nil, fmt.Errorf("%s does not support d_type. If the backing filesystem is xfs, please reformat with ftype=1 to enable d_type support", root)
    96  	}
    97  	ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db"))
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) {
   103  		return nil, err
   104  	}
   105  
   106  	// figure out whether "index=off" option is recognized by the kernel
   107  	var indexOff bool
   108  	if _, err = os.Stat("/sys/module/overlay/parameters/index"); err == nil {
   109  		indexOff = true
   110  	}
   111  
   112  	return &snapshotter{
   113  		root:        root,
   114  		ms:          ms,
   115  		asyncRemove: config.asyncRemove,
   116  		indexOff:    indexOff,
   117  	}, nil
   118  }
   119  
   120  // Stat returns the info for an active or committed snapshot by name or
   121  // key.
   122  //
   123  // Should be used for parent resolution, existence checks and to discern
   124  // the kind of snapshot.
   125  func (o *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) {
   126  	ctx, t, err := o.ms.TransactionContext(ctx, false)
   127  	if err != nil {
   128  		return snapshots.Info{}, err
   129  	}
   130  	defer t.Rollback()
   131  	_, info, _, err := storage.GetInfo(ctx, key)
   132  	if err != nil {
   133  		return snapshots.Info{}, err
   134  	}
   135  
   136  	return info, nil
   137  }
   138  
   139  func (o *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) {
   140  	ctx, t, err := o.ms.TransactionContext(ctx, true)
   141  	if err != nil {
   142  		return snapshots.Info{}, err
   143  	}
   144  
   145  	info, err = storage.UpdateInfo(ctx, info, fieldpaths...)
   146  	if err != nil {
   147  		t.Rollback()
   148  		return snapshots.Info{}, err
   149  	}
   150  
   151  	if err := t.Commit(); err != nil {
   152  		return snapshots.Info{}, err
   153  	}
   154  
   155  	return info, nil
   156  }
   157  
   158  // Usage returns the resources taken by the snapshot identified by key.
   159  //
   160  // For active snapshots, this will scan the usage of the overlay "diff" (aka
   161  // "upper") directory and may take some time.
   162  //
   163  // For committed snapshots, the value is returned from the metadata database.
   164  func (o *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
   165  	ctx, t, err := o.ms.TransactionContext(ctx, false)
   166  	if err != nil {
   167  		return snapshots.Usage{}, err
   168  	}
   169  	id, info, usage, err := storage.GetInfo(ctx, key)
   170  	t.Rollback() // transaction no longer needed at this point.
   171  
   172  	if err != nil {
   173  		return snapshots.Usage{}, err
   174  	}
   175  
   176  	if info.Kind == snapshots.KindActive {
   177  		upperPath := o.upperPath(id)
   178  		du, err := fs.DiskUsage(ctx, upperPath)
   179  		if err != nil {
   180  			// TODO(stevvooe): Consider not reporting an error in this case.
   181  			return snapshots.Usage{}, err
   182  		}
   183  
   184  		usage = snapshots.Usage(du)
   185  	}
   186  
   187  	return usage, nil
   188  }
   189  
   190  func (o *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
   191  	return o.createSnapshot(ctx, snapshots.KindActive, key, parent, opts)
   192  }
   193  
   194  func (o *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
   195  	return o.createSnapshot(ctx, snapshots.KindView, key, parent, opts)
   196  }
   197  
   198  // Mounts returns the mounts for the transaction identified by key. Can be
   199  // called on an read-write or readonly transaction.
   200  //
   201  // This can be used to recover mounts after calling View or Prepare.
   202  func (o *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) {
   203  	ctx, t, err := o.ms.TransactionContext(ctx, false)
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  	s, err := storage.GetSnapshot(ctx, key)
   208  	t.Rollback()
   209  	if err != nil {
   210  		return nil, errors.Wrap(err, "failed to get active mount")
   211  	}
   212  	return o.mounts(s), nil
   213  }
   214  
   215  func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error {
   216  	ctx, t, err := o.ms.TransactionContext(ctx, true)
   217  	if err != nil {
   218  		return err
   219  	}
   220  
   221  	defer func() {
   222  		if err != nil {
   223  			if rerr := t.Rollback(); rerr != nil {
   224  				log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
   225  			}
   226  		}
   227  	}()
   228  
   229  	// grab the existing id
   230  	id, _, _, err := storage.GetInfo(ctx, key)
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	usage, err := fs.DiskUsage(ctx, o.upperPath(id))
   236  	if err != nil {
   237  		return err
   238  	}
   239  
   240  	if _, err = storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil {
   241  		return errors.Wrap(err, "failed to commit snapshot")
   242  	}
   243  	return t.Commit()
   244  }
   245  
   246  // Remove abandons the snapshot identified by key. The snapshot will
   247  // immediately become unavailable and unrecoverable. Disk space will
   248  // be freed up on the next call to `Cleanup`.
   249  func (o *snapshotter) Remove(ctx context.Context, key string) (err error) {
   250  	ctx, t, err := o.ms.TransactionContext(ctx, true)
   251  	if err != nil {
   252  		return err
   253  	}
   254  	defer func() {
   255  		if err != nil {
   256  			if rerr := t.Rollback(); rerr != nil {
   257  				log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
   258  			}
   259  		}
   260  	}()
   261  
   262  	_, _, err = storage.Remove(ctx, key)
   263  	if err != nil {
   264  		return errors.Wrap(err, "failed to remove")
   265  	}
   266  
   267  	if !o.asyncRemove {
   268  		var removals []string
   269  		removals, err = o.getCleanupDirectories(ctx, t)
   270  		if err != nil {
   271  			return errors.Wrap(err, "unable to get directories for removal")
   272  		}
   273  
   274  		// Remove directories after the transaction is closed, failures must not
   275  		// return error since the transaction is committed with the removal
   276  		// key no longer available.
   277  		defer func() {
   278  			if err == nil {
   279  				for _, dir := range removals {
   280  					if err := os.RemoveAll(dir); err != nil {
   281  						log.G(ctx).WithError(err).WithField("path", dir).Warn("failed to remove directory")
   282  					}
   283  				}
   284  			}
   285  		}()
   286  
   287  	}
   288  
   289  	return t.Commit()
   290  }
   291  
   292  // Walk the snapshots.
   293  func (o *snapshotter) Walk(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error {
   294  	ctx, t, err := o.ms.TransactionContext(ctx, false)
   295  	if err != nil {
   296  		return err
   297  	}
   298  	defer t.Rollback()
   299  	return storage.WalkInfo(ctx, fn, fs...)
   300  }
   301  
   302  // Cleanup cleans up disk resources from removed or abandoned snapshots
   303  func (o *snapshotter) Cleanup(ctx context.Context) error {
   304  	cleanup, err := o.cleanupDirectories(ctx)
   305  	if err != nil {
   306  		return err
   307  	}
   308  
   309  	for _, dir := range cleanup {
   310  		if err := os.RemoveAll(dir); err != nil {
   311  			log.G(ctx).WithError(err).WithField("path", dir).Warn("failed to remove directory")
   312  		}
   313  	}
   314  
   315  	return nil
   316  }
   317  
   318  func (o *snapshotter) cleanupDirectories(ctx context.Context) ([]string, error) {
   319  	// Get a write transaction to ensure no other write transaction can be entered
   320  	// while the cleanup is scanning.
   321  	ctx, t, err := o.ms.TransactionContext(ctx, true)
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  
   326  	defer t.Rollback()
   327  	return o.getCleanupDirectories(ctx, t)
   328  }
   329  
   330  func (o *snapshotter) getCleanupDirectories(ctx context.Context, t storage.Transactor) ([]string, error) {
   331  	ids, err := storage.IDMap(ctx)
   332  	if err != nil {
   333  		return nil, err
   334  	}
   335  
   336  	snapshotDir := filepath.Join(o.root, "snapshots")
   337  	fd, err := os.Open(snapshotDir)
   338  	if err != nil {
   339  		return nil, err
   340  	}
   341  	defer fd.Close()
   342  
   343  	dirs, err := fd.Readdirnames(0)
   344  	if err != nil {
   345  		return nil, err
   346  	}
   347  
   348  	cleanup := []string{}
   349  	for _, d := range dirs {
   350  		if _, ok := ids[d]; ok {
   351  			continue
   352  		}
   353  
   354  		cleanup = append(cleanup, filepath.Join(snapshotDir, d))
   355  	}
   356  
   357  	return cleanup, nil
   358  }
   359  
   360  func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) (_ []mount.Mount, err error) {
   361  	ctx, t, err := o.ms.TransactionContext(ctx, true)
   362  	if err != nil {
   363  		return nil, err
   364  	}
   365  
   366  	var td, path string
   367  	defer func() {
   368  		if err != nil {
   369  			if td != "" {
   370  				if err1 := os.RemoveAll(td); err1 != nil {
   371  					log.G(ctx).WithError(err1).Warn("failed to cleanup temp snapshot directory")
   372  				}
   373  			}
   374  			if path != "" {
   375  				if err1 := os.RemoveAll(path); err1 != nil {
   376  					log.G(ctx).WithError(err1).WithField("path", path).Error("failed to reclaim snapshot directory, directory may need removal")
   377  					err = errors.Wrapf(err, "failed to remove path: %v", err1)
   378  				}
   379  			}
   380  		}
   381  	}()
   382  
   383  	snapshotDir := filepath.Join(o.root, "snapshots")
   384  	td, err = o.prepareDirectory(ctx, snapshotDir, kind)
   385  	if err != nil {
   386  		if rerr := t.Rollback(); rerr != nil {
   387  			log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
   388  		}
   389  		return nil, errors.Wrap(err, "failed to create prepare snapshot dir")
   390  	}
   391  	rollback := true
   392  	defer func() {
   393  		if rollback {
   394  			if rerr := t.Rollback(); rerr != nil {
   395  				log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
   396  			}
   397  		}
   398  	}()
   399  
   400  	s, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...)
   401  	if err != nil {
   402  		return nil, errors.Wrap(err, "failed to create snapshot")
   403  	}
   404  
   405  	if len(s.ParentIDs) > 0 {
   406  		st, err := os.Stat(o.upperPath(s.ParentIDs[0]))
   407  		if err != nil {
   408  			return nil, errors.Wrap(err, "failed to stat parent")
   409  		}
   410  
   411  		stat := st.Sys().(*syscall.Stat_t)
   412  
   413  		if err := os.Lchown(filepath.Join(td, "fs"), int(stat.Uid), int(stat.Gid)); err != nil {
   414  			if rerr := t.Rollback(); rerr != nil {
   415  				log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
   416  			}
   417  			return nil, errors.Wrap(err, "failed to chown")
   418  		}
   419  	}
   420  
   421  	path = filepath.Join(snapshotDir, s.ID)
   422  	if err = os.Rename(td, path); err != nil {
   423  		return nil, errors.Wrap(err, "failed to rename")
   424  	}
   425  	td = ""
   426  
   427  	rollback = false
   428  	if err = t.Commit(); err != nil {
   429  		return nil, errors.Wrap(err, "commit failed")
   430  	}
   431  
   432  	return o.mounts(s), nil
   433  }
   434  
   435  func (o *snapshotter) prepareDirectory(ctx context.Context, snapshotDir string, kind snapshots.Kind) (string, error) {
   436  	td, err := ioutil.TempDir(snapshotDir, "new-")
   437  	if err != nil {
   438  		return "", errors.Wrap(err, "failed to create temp dir")
   439  	}
   440  
   441  	if err := os.Mkdir(filepath.Join(td, "fs"), 0755); err != nil {
   442  		return td, err
   443  	}
   444  
   445  	if kind == snapshots.KindActive {
   446  		if err := os.Mkdir(filepath.Join(td, "work"), 0711); err != nil {
   447  			return td, err
   448  		}
   449  	}
   450  
   451  	return td, nil
   452  }
   453  
   454  func (o *snapshotter) mounts(s storage.Snapshot) []mount.Mount {
   455  	if len(s.ParentIDs) == 0 {
   456  		// if we only have one layer/no parents then just return a bind mount as overlay
   457  		// will not work
   458  		roFlag := "rw"
   459  		if s.Kind == snapshots.KindView {
   460  			roFlag = "ro"
   461  		}
   462  
   463  		return []mount.Mount{
   464  			{
   465  				Source: o.upperPath(s.ID),
   466  				Type:   "bind",
   467  				Options: []string{
   468  					roFlag,
   469  					"rbind",
   470  				},
   471  			},
   472  		}
   473  	}
   474  	var options []string
   475  
   476  	// set index=off when mount overlayfs
   477  	if o.indexOff {
   478  		options = append(options, "index=off")
   479  	}
   480  
   481  	if s.Kind == snapshots.KindActive {
   482  		options = append(options,
   483  			fmt.Sprintf("workdir=%s", o.workPath(s.ID)),
   484  			fmt.Sprintf("upperdir=%s", o.upperPath(s.ID)),
   485  		)
   486  	} else if len(s.ParentIDs) == 1 {
   487  		return []mount.Mount{
   488  			{
   489  				Source: o.upperPath(s.ParentIDs[0]),
   490  				Type:   "bind",
   491  				Options: []string{
   492  					"ro",
   493  					"rbind",
   494  				},
   495  			},
   496  		}
   497  	}
   498  
   499  	parentPaths := make([]string, len(s.ParentIDs))
   500  	for i := range s.ParentIDs {
   501  		parentPaths[i] = o.upperPath(s.ParentIDs[i])
   502  	}
   503  
   504  	options = append(options, fmt.Sprintf("lowerdir=%s", strings.Join(parentPaths, ":")))
   505  	return []mount.Mount{
   506  		{
   507  			Type:    "overlay",
   508  			Source:  "overlay",
   509  			Options: options,
   510  		},
   511  	}
   512  
   513  }
   514  
   515  func (o *snapshotter) upperPath(id string) string {
   516  	return filepath.Join(o.root, "snapshots", id, "fs")
   517  }
   518  
   519  func (o *snapshotter) workPath(id string) string {
   520  	return filepath.Join(o.root, "snapshots", id, "work")
   521  }
   522  
   523  // Close closes the snapshotter
   524  func (o *snapshotter) Close() error {
   525  	return o.ms.Close()
   526  }