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

     1  // Copyright 2015 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  package main
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"time"
    21  
    22  	"github.com/appc/spec/schema"
    23  	"github.com/hashicorp/errwrap"
    24  	"github.com/rkt/rkt/common"
    25  	"github.com/rkt/rkt/pkg/lock"
    26  	pkgPod "github.com/rkt/rkt/pkg/pod"
    27  	"github.com/rkt/rkt/store/imagestore"
    28  	"github.com/rkt/rkt/store/treestore"
    29  	"github.com/spf13/cobra"
    30  )
    31  
    32  const (
    33  	defaultImageGracePeriod = 24 * time.Hour
    34  )
    35  
    36  var (
    37  	cmdImageGC = &cobra.Command{
    38  		Use:   "gc",
    39  		Short: "Garbage collect local store",
    40  		Long: `This is intended to be run periodically from a timer or cron job.
    41  
    42  The default grace period is 24h. Use --grace-period=0s to effectively disable
    43  the grace-period.`,
    44  		Run: runWrapper(runGCImage),
    45  	}
    46  	flagImageGracePeriod time.Duration
    47  )
    48  
    49  func init() {
    50  	cmdImage.AddCommand(cmdImageGC)
    51  	cmdImageGC.Flags().DurationVar(&flagImageGracePeriod, "grace-period", defaultImageGracePeriod, "duration to wait since an image was last used before removing it")
    52  }
    53  
    54  func runGCImage(cmd *cobra.Command, args []string) (exit int) {
    55  	s, err := imagestore.NewStore(storeDir())
    56  	if err != nil {
    57  		stderr.PrintE("cannot open store", err)
    58  		return 254
    59  	}
    60  
    61  	ts, err := treestore.NewStore(treeStoreDir(), s)
    62  	if err != nil {
    63  		stderr.PrintE("cannot open store", err)
    64  		return
    65  	}
    66  
    67  	if err := gcTreeStore(ts); err != nil {
    68  		stderr.PrintE("failed to remove unreferenced treestores", err)
    69  		return 254
    70  	}
    71  
    72  	if err := gcStore(s, flagImageGracePeriod); err != nil {
    73  		stderr.Error(err)
    74  		return 254
    75  	}
    76  
    77  	return 0
    78  }
    79  
    80  // gcTreeStore removes all treeStoreIDs not referenced by any non garbage
    81  // collected pod from the store.
    82  func gcTreeStore(ts *treestore.Store) error {
    83  	// Take an exclusive lock to block other pods being created.
    84  	// This is needed to avoid races between the below steps (getting the
    85  	// list of referenced treeStoreIDs, getting the list of treeStoreIDs
    86  	// from the store, removal of unreferenced treeStoreIDs) and new
    87  	// pods/treeStores being created/referenced
    88  	keyLock, err := lock.ExclusiveKeyLock(lockDir(), common.PrepareLock)
    89  	if err != nil {
    90  		return errwrap.Wrap(errors.New("cannot get exclusive prepare lock"), err)
    91  	}
    92  	defer keyLock.Close()
    93  	referencedTreeStoreIDs, err := getReferencedTreeStoreIDs()
    94  	if err != nil {
    95  		return errwrap.Wrap(errors.New("cannot get referenced treestoreIDs"), err)
    96  	}
    97  	treeStoreIDs, err := ts.GetIDs()
    98  	if err != nil {
    99  		return errwrap.Wrap(errors.New("cannot get treestoreIDs from the store"), err)
   100  	}
   101  	for _, treeStoreID := range treeStoreIDs {
   102  		if _, ok := referencedTreeStoreIDs[treeStoreID]; !ok {
   103  			if err := ts.Remove(treeStoreID); err != nil {
   104  				stderr.PrintE(fmt.Sprintf("error removing treestore %q", treeStoreID), err)
   105  			} else {
   106  				stderr.Printf("removed treestore %q", treeStoreID)
   107  			}
   108  		}
   109  	}
   110  	return nil
   111  }
   112  
   113  func getReferencedTreeStoreIDs() (map[string]struct{}, error) {
   114  	treeStoreIDs := map[string]struct{}{}
   115  	// Consider pods in preparing, prepared, run, exitedgarbage state
   116  	if err := pkgPod.WalkPods(getDataDir(), pkgPod.IncludeMostDirs, func(p *pkgPod.Pod) {
   117  		stage1TreeStoreID, err := p.GetStage1TreeStoreID()
   118  		if err != nil {
   119  			stderr.PrintE(fmt.Sprintf("cannot get stage1 treestoreID for pod %s", p.UUID), err)
   120  			return
   121  		}
   122  		appsTreeStoreIDs, err := p.GetAppsTreeStoreIDs()
   123  		if err != nil {
   124  			stderr.PrintE(fmt.Sprintf("cannot get apps treestoreID for pod %s", p.UUID), err)
   125  			return
   126  		}
   127  		allTreeStoreIDs := append(appsTreeStoreIDs, stage1TreeStoreID)
   128  
   129  		for _, treeStoreID := range allTreeStoreIDs {
   130  			treeStoreIDs[treeStoreID] = struct{}{}
   131  		}
   132  	}); err != nil {
   133  		return nil, errwrap.Wrap(errors.New("failed to get pod handles"), err)
   134  	}
   135  	return treeStoreIDs, nil
   136  }
   137  
   138  func gcStore(s *imagestore.Store, gracePeriod time.Duration) error {
   139  	var imagesToRemove []string
   140  	aciinfos, err := s.GetAllACIInfos([]string{"lastused"}, true)
   141  	if err != nil {
   142  		return errwrap.Wrap(errors.New("failed to get aciinfos"), err)
   143  	}
   144  	runningImages, err := getRunningImages()
   145  	if err != nil {
   146  		return errwrap.Wrap(errors.New("failed to get list of images for running pods"), err)
   147  	}
   148  	for _, ai := range aciinfos {
   149  		if time.Now().Sub(ai.LastUsed) <= gracePeriod {
   150  			break
   151  		}
   152  		if isInSet(ai.BlobKey, runningImages) {
   153  			continue
   154  		}
   155  		imagesToRemove = append(imagesToRemove, ai.BlobKey)
   156  	}
   157  
   158  	if err := rmImages(s, imagesToRemove); err != nil {
   159  		return err
   160  	}
   161  
   162  	return nil
   163  }
   164  
   165  // getRunningImages will return the image IDs used to create any of the
   166  // currently running pods
   167  func getRunningImages() ([]string, error) {
   168  	var runningImages []string
   169  	var errors []error
   170  	err := pkgPod.WalkPods(getDataDir(), pkgPod.IncludeMostDirs, func(p *pkgPod.Pod) {
   171  		var pm schema.PodManifest
   172  		if p.State() != pkgPod.Running {
   173  			return
   174  		}
   175  		if !p.PodManifestAvailable() {
   176  			return
   177  		}
   178  		_, manifest, err := p.PodManifest()
   179  		if err != nil {
   180  			errors = append(errors, newPodListReadError(p, err))
   181  			return
   182  		}
   183  		pm = *manifest
   184  		for _, app := range pm.Apps {
   185  			runningImages = append(runningImages, app.Image.ID.String())
   186  		}
   187  	})
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	if len(errors) > 0 {
   192  		return nil, errors[0]
   193  	}
   194  	return runningImages, nil
   195  }
   196  
   197  func isInSet(item string, set []string) bool {
   198  	for _, elem := range set {
   199  		if item == elem {
   200  			return true
   201  		}
   202  	}
   203  	return false
   204  }