github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/rkt/gc.go (about)

     1  // Copyright 2014 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //+build linux
    16  
    17  package main
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"syscall"
    25  	"time"
    26  
    27  	"github.com/hashicorp/errwrap"
    28  	"github.com/rkt/rkt/common"
    29  	"github.com/rkt/rkt/pkg/mountinfo"
    30  	pkgPod "github.com/rkt/rkt/pkg/pod"
    31  	"github.com/rkt/rkt/stage0"
    32  	"github.com/rkt/rkt/store/imagestore"
    33  	"github.com/rkt/rkt/store/treestore"
    34  	"github.com/spf13/cobra"
    35  )
    36  
    37  const (
    38  	defaultGracePeriod        = 30 * time.Minute
    39  	defaultPreparedExpiration = 24 * time.Hour
    40  )
    41  
    42  var (
    43  	cmdGC = &cobra.Command{
    44  		Use:   "gc [--grace-period=duration] [--expire-prepared=duration]",
    45  		Short: "Garbage collect rkt pods no longer in use",
    46  		Long: `This is intended to be run periodically from a timer or cron job.
    47  
    48  Garbage collection is a 2-step process. First, stopped pods are moved to the
    49  garbage by one invocation of the gc command. A subsequent invocation will clean
    50  up the pod, assuming the pod has been in the garbage for more time than the
    51  specified grace period.
    52  
    53  Use --grace-period=0s to effectively disable the grace-period.`,
    54  		Run: ensureSuperuser(runWrapper(runGC)),
    55  	}
    56  	flagGracePeriod        time.Duration
    57  	flagPreparedExpiration time.Duration
    58  	flagMarkOnly           bool
    59  )
    60  
    61  func init() {
    62  	cmdRkt.AddCommand(cmdGC)
    63  	cmdGC.Flags().DurationVar(&flagGracePeriod, "grace-period", defaultGracePeriod, "duration to wait before discarding inactive pods from garbage")
    64  	cmdGC.Flags().DurationVar(&flagPreparedExpiration, "expire-prepared", defaultPreparedExpiration, "duration to wait before expiring prepared pods")
    65  	cmdGC.Flags().BoolVar(&flagMarkOnly, "mark-only", false, "if set to true, then the exited/aborted pods will be moved to the garbage directories without actually deleting them, this is useful for marking the exit time of a pod")
    66  }
    67  
    68  func runGC(cmd *cobra.Command, args []string) (exit int) {
    69  	if err := renameExited(); err != nil {
    70  		stderr.PrintE("failed to rename exited pods", err)
    71  		return 254
    72  	}
    73  
    74  	if err := renameAborted(); err != nil {
    75  		stderr.PrintE("failed to rename aborted pods", err)
    76  		return 254
    77  	}
    78  
    79  	if flagMarkOnly {
    80  		return
    81  	}
    82  
    83  	if err := renameExpired(flagPreparedExpiration); err != nil {
    84  		stderr.PrintE("failed to rename expired prepared pods", err)
    85  		return 254
    86  	}
    87  
    88  	if err := emptyExitedGarbage(flagGracePeriod); err != nil {
    89  		stderr.PrintE("failed to empty exitedGarbage", err)
    90  		return 254
    91  	}
    92  
    93  	if err := emptyGarbage(); err != nil {
    94  		stderr.PrintE("failed to empty garbage", err)
    95  		return 254
    96  	}
    97  
    98  	return
    99  }
   100  
   101  // renameExited renames exited pods to the exitedGarbage directory
   102  func renameExited() error {
   103  	if err := pkgPod.WalkPods(getDataDir(), pkgPod.IncludeRunDir, func(p *pkgPod.Pod) {
   104  		if p.State() == pkgPod.Exited {
   105  			stderr.Printf("moving pod %q to garbage", p.UUID)
   106  			if err := p.ToExitedGarbage(); err != nil && err != os.ErrNotExist {
   107  				stderr.PrintE("rename error", err)
   108  			}
   109  		}
   110  	}); err != nil {
   111  		return err
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  // emptyExitedGarbage discards sufficiently aged pods from exitedGarbageDir()
   118  func emptyExitedGarbage(gracePeriod time.Duration) error {
   119  	if err := pkgPod.WalkPods(getDataDir(), pkgPod.IncludeExitedGarbageDir, func(p *pkgPod.Pod) {
   120  		gp := p.Path()
   121  		st := &syscall.Stat_t{}
   122  		if err := syscall.Lstat(gp, st); err != nil {
   123  			if err != syscall.ENOENT {
   124  				stderr.PrintE(fmt.Sprintf("unable to stat %q, ignoring", gp), err)
   125  			}
   126  			return
   127  		}
   128  
   129  		if expiration := time.Unix(st.Ctim.Unix()).Add(gracePeriod); time.Now().After(expiration) {
   130  			if err := p.ExclusiveLock(); err != nil {
   131  				return
   132  			}
   133  			stdout.Printf("Garbage collecting pod %q", p.UUID)
   134  
   135  			deletePod(p)
   136  		} else {
   137  			stderr.Printf("pod %q not removed: still within grace period (%s)", p.UUID, gracePeriod)
   138  		}
   139  	}); err != nil {
   140  		return err
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  // renameAborted renames failed prepares to the garbage directory
   147  func renameAborted() error {
   148  	if err := pkgPod.WalkPods(getDataDir(), pkgPod.IncludePrepareDir, func(p *pkgPod.Pod) {
   149  		if p.State() == pkgPod.AbortedPrepare {
   150  			stderr.Printf("moving failed prepare %q to garbage", p.UUID)
   151  			if err := p.ToGarbage(); err != nil && err != os.ErrNotExist {
   152  				stderr.PrintE("rename error", err)
   153  			}
   154  		}
   155  	}); err != nil {
   156  		return err
   157  	}
   158  	return nil
   159  }
   160  
   161  // renameExpired renames expired prepared pods to the garbage directory
   162  func renameExpired(preparedExpiration time.Duration) error {
   163  	if err := pkgPod.WalkPods(getDataDir(), pkgPod.IncludePreparedDir, func(p *pkgPod.Pod) {
   164  		st := &syscall.Stat_t{}
   165  		pp := p.Path()
   166  		if err := syscall.Lstat(pp, st); err != nil {
   167  			if err != syscall.ENOENT {
   168  				stderr.PrintE(fmt.Sprintf("unable to stat %q, ignoring", pp), err)
   169  			}
   170  			return
   171  		}
   172  
   173  		if expiration := time.Unix(st.Ctim.Unix()).Add(preparedExpiration); time.Now().After(expiration) {
   174  			stderr.Printf("moving expired prepared pod %q to garbage", p.UUID)
   175  			if err := p.ToGarbage(); err != nil && err != os.ErrNotExist {
   176  				stderr.PrintE("rename error", err)
   177  			}
   178  		}
   179  	}); err != nil {
   180  		return err
   181  	}
   182  	return nil
   183  }
   184  
   185  // emptyGarbage discards everything from garbageDir()
   186  func emptyGarbage() error {
   187  	if err := pkgPod.WalkPods(getDataDir(), pkgPod.IncludeGarbageDir, func(p *pkgPod.Pod) {
   188  		if err := p.ExclusiveLock(); err != nil {
   189  			return
   190  		}
   191  		stdout.Printf("Garbage collecting pod %q", p.UUID)
   192  
   193  		deletePod(p)
   194  	}); err != nil {
   195  		return err
   196  	}
   197  
   198  	return nil
   199  }
   200  
   201  // mountPodStage1 tries to remount stage1 image overlay in case
   202  // it is not anymore available in place (e.g. a reboot happened
   203  // in-between). If an overlay mount is already there, we assume
   204  // stage1 image is properly in place and we don't perform any
   205  // further actions. A boolean is returned to signal whether a
   206  // a new mount was performed.
   207  func mountPodStage1(ts *treestore.Store, p *pkgPod.Pod) (bool, error) {
   208  	if !p.UsesOverlay() {
   209  		return false, nil
   210  	}
   211  
   212  	s1Id, err := p.GetStage1TreeStoreID()
   213  	if err != nil {
   214  		return false, errwrap.Wrap(errors.New("error getting stage1 treeStoreID"), err)
   215  	}
   216  	s1rootfs := ts.GetRootFS(s1Id)
   217  	stage1Dir := common.Stage1RootfsPath(p.Path())
   218  
   219  	if mounts, err := mountinfo.ParseMounts(0); err == nil {
   220  		for _, m := range mounts {
   221  			if m.MountPoint == stage1Dir {
   222  				// stage1 image overlay already in place
   223  				return false, nil
   224  			}
   225  		}
   226  	}
   227  
   228  	overlayDir := filepath.Join(p.Path(), "overlay")
   229  	imgDir := filepath.Join(overlayDir, s1Id)
   230  	upperDir := filepath.Join(imgDir, "upper")
   231  	workDir := filepath.Join(imgDir, "work")
   232  
   233  	opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", s1rootfs, upperDir, workDir)
   234  	err = syscall.Mount("overlay", stage1Dir, "overlay", 0, opts)
   235  	if err != nil {
   236  		// -EBUSY means the overlay mount is already present.
   237  		// This behavior was introduced in Linux 4.13, double-mounts were allowed
   238  		// in older kernels.
   239  		if err == syscall.EBUSY {
   240  			stderr.Println("mount detected on stage1 target, skipping overlay mount")
   241  			return false, nil
   242  		}
   243  		return false, errwrap.Wrap(errors.New("error mounting stage1"), err)
   244  	}
   245  	return true, nil
   246  }
   247  
   248  // deletePod cleans up files and resource associated with the pod
   249  // pod must be under exclusive lock and be in either ExitedGarbage
   250  // or Garbage state
   251  func deletePod(p *pkgPod.Pod) bool {
   252  	podState := p.State()
   253  	if podState != pkgPod.ExitedGarbage && podState != pkgPod.Garbage && podState != pkgPod.ExitedDeleting {
   254  		stderr.Errorf("non-garbage pod %q (status %q), skipped", p.UUID, p.State())
   255  		return false
   256  	}
   257  
   258  	if podState == pkgPod.ExitedGarbage {
   259  		s, err := imagestore.NewStore(storeDir())
   260  		if err != nil {
   261  			stderr.PrintE("cannot open store", err)
   262  			return false
   263  		}
   264  		defer s.Close()
   265  
   266  		ts, err := treestore.NewStore(treeStoreDir(), s)
   267  		if err != nil {
   268  			stderr.PrintE("cannot open store", err)
   269  			return false
   270  		}
   271  
   272  		if globalFlags.Debug {
   273  			stage0.InitDebug()
   274  		}
   275  
   276  		if newMount, err := mountPodStage1(ts, p); err == nil {
   277  			if err = stage0.GC(p.Path(), p.UUID, globalFlags.LocalConfigDir); err != nil {
   278  				stderr.PrintE(fmt.Sprintf("problem performing stage1 GC on %q", p.UUID), err)
   279  			}
   280  			// Linux <4.13 allows an overlay fs to be mounted over itself, so let's
   281  			// unmount it here to avoid problems when running stage0.MountGC
   282  			if p.UsesOverlay() && newMount {
   283  				stage1Mnt := common.Stage1RootfsPath(p.Path())
   284  				if err := syscall.Unmount(stage1Mnt, 0); err != nil && err != syscall.EBUSY {
   285  					stderr.PrintE("error unmounting stage1", err)
   286  				}
   287  			}
   288  		} else {
   289  			stderr.PrintE("skipping stage1 GC", err)
   290  		}
   291  
   292  		// unmount all leftover mounts
   293  		if err := stage0.MountGC(p.Path(), p.UUID.String()); err != nil {
   294  			stderr.PrintE(fmt.Sprintf("GC of leftover mounts for pod %q failed", p.UUID), err)
   295  			return false
   296  		}
   297  	}
   298  
   299  	// remove the rootfs first; if this fails (eg. due to busy mountpoints), pod manifest
   300  	// is left in place and clean-up can be re-tried later.
   301  	rootfsPath, err := p.Stage1RootfsPath()
   302  	if err == nil {
   303  		if e := os.RemoveAll(rootfsPath); e != nil {
   304  			stderr.PrintE(fmt.Sprintf("unable to remove pod rootfs %q", p.UUID), e)
   305  			return false
   306  		}
   307  	}
   308  
   309  	// finally remove all remaining pieces
   310  	if err := os.RemoveAll(p.Path()); err != nil {
   311  		stderr.PrintE(fmt.Sprintf("unable to remove pod %q", p.UUID), err)
   312  		return false
   313  	}
   314  
   315  	return true
   316  }