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 }