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