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 }