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