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