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