Improve documentation for mount manager

Signed-off-by: Derek McGowan <derek@mcg.dev>
This commit is contained in:
Derek McGowan
2025-10-27 17:25:48 -07:00
parent 49a177d65b
commit 8ef2cfb7eb

View File

@@ -35,14 +35,55 @@ handle custom mount types.
The interface for a custom mount handler is very simple.
```.go
```go
type Handler interface {
Mount(context.Context, Mount, string, []ActiveMount) (ActiveMount, error)
Unmount(context.Context, string) error
}
```
### Mount formatting
#### Built-in Mount Handlers
##### Loopback Handler
The loopback handler (`loop`) allows mounting files as loopback devices. This is useful
for mounting disk images or filesystem images without requiring a pre-configured loopback device.
It is preferable to use the `loop` option on mounts when supported by other mount types to allow
other mount types to optimize when a loopback is needed, when this handler is used with
another mount type it may force a loopback to be used even when not necessary.
```go
// Example mount using loopback
mount.Mount{
Type: "loop",
Source: "/path/to/disk.img",
Options: []string{},
}
```
The handler automatically:
- Sets up the loopback device using the first available loop device
- Makes device available at mount point
- Handles cleanup on unmount
### Mount Transformers
Mount transformers are interfaces that can modify mounts based on previous mount state.
Transformers are useful for preparing mounts before they are activated, such as creating
directories or formatting filesystems.
```go
type Transformer interface {
Transform(context.Context, Mount, []ActiveMount) (Mount, error)
}
```
Transformers are specified in the mount type using a prefix pattern: `<transformer>/<mount-type>`.
Multiple transformers can be chained: `<transformer1>/<transformer2>/<mount-type>`.
#### Built-in Transformers
##### Format Transformer (`format/`)
In order to chain mounts together, the results from a previous mount may be
needed for subsequent mounts. Some of these mount parameters may not be
@@ -51,7 +92,7 @@ mount values. Formatted mounts allow providing templated values for mount
parameters to be filled in at mount activation time using the previous
mounts' results.
Formatted mounts have a type that starts with `format/` and following by the
Formatted mounts have a type that starts with `format/` followed by the
intended mount type after filling in format values.
Uses templating based on [go templates](https://pkg.go.dev/text/template) to
fill in values.
@@ -70,11 +111,266 @@ Formatted mounts are handled differently than other custom mounts. If the
resulting mount after formatting is a supported system mount, it does not
need to be mounted by the mount handlers like custom mounts do.
**Example:**
```go
// First mount provides the lower layer
mount.Mount{
Type: "bind",
Source: "/var/lib/containerd/snapshots/1",
Options: []string{"ro"},
},
// Second mount uses formatting to reference the first mount
mount.Mount{
Type: "format/overlay",
Source: "overlay",
Options: []string{
"lowerdir={{ mount 0 }}",
"upperdir=/upper",
"workdir=/work",
},
}
```
##### Mkfs Transformer (`mkfs/`)
The mkfs transformer creates and formats filesystem images. It supports creating
ext2, ext3, ext4, and xfs filesystems in files that can then be mounted as loopback devices.
Mount options with the `X-containerd.mkfs.` prefix are consumed by the transformer:
| Option | Description | Example |
|--------|-------------|---------|
| `X-containerd.mkfs.size` | Size of the filesystem image (supports units like MiB, GiB) | `X-containerd.mkfs.size=100MiB` |
| `X-containerd.mkfs.fs` | Filesystem type (ext2, ext3, ext4, xfs) | `X-containerd.mkfs.fs=ext4` |
| `X-containerd.mkfs.uuid` | UUID for the filesystem | `X-containerd.mkfs.uuid=550e8400-e29b-41d4-a716-446655440000` |
**Example:**
```go
mount.Mount{
Type: "mkfs/loop",
Source: "/path/to/disk.img",
Options: []string{
"X-containerd.mkfs.size=1GiB",
"X-containerd.mkfs.fs=ext4",
},
}
```
This will:
1. Create a 1GiB file at `/path/to/disk.img`
2. Format it as ext4
3. Set up a loopback device for the file
4. Return the loop device for subsequent mounting
##### Mkdir Transformer (`mkdir/`)
The mkdir transformer creates directories before mounting. This is useful for
ensuring overlay upperdir and workdir directories exist, or for creating mount points.
Mount options with the `X-containerd.mkdir.` prefix are consumed by the transformer:
| Option Format | Description |
|--------------|-------------|
| `X-containerd.mkdir.path=<dir>` | Create directory with default permissions (0700) |
| `X-containerd.mkdir.path=<dir>:<mode>` | Create directory with specified octal mode |
| `X-containerd.mkdir.path=<dir>:<mode>:<uid>:<gid>` | Create directory with mode and ownership |
**Example:**
```go
mount.Mount{
Type: "format/mkdir/overlay",
Source: "overlay",
Options: []string{
"X-containerd.mkdir.path={{ mount 0 }}/upper:0755",
"X-containerd.mkdir.path={{ mount 0 }}/work:0755",
"lowerdir={{ mount 1 }}",
"upperdir={{ mount 0 }}/upper",
"workdir={{ mount 0 }}/work",
},
}
```
#### Chaining Transformers
Transformers can be chained together to perform multiple operations in sequence:
```go
mount.Mount{
Type: "mkfs/loop",
Source: "/data/fs.img",
Options: []string{
"X-containerd.mkfs.size=500MiB",
"X-containerd.mkfs.fs=xfs",
},
},
mount.Mount{
Type: "xfs",
Source: "{{ source 0 }}", // Loop device from previous mount
Options: []string{},
},
mount.Mount{
Type: "format/mkdir/overlay",
Source: "overlay",
Options: []string{
"X-containerd.mkdir.path={{ mount 1 }}/upper:0755",
"X-containerd.mkdir.path={{ mount 1 }}/work:0755",
"lowerdir=/lower",
"upperdir={{ mount 1 }}/upper",
"workdir={{ mount 1 }}/work",
},
}
```
This example:
1. Creates and formats a 500MiB XFS image
2. Sets up a loop device and mounts the XFS filesystem
3. Creates directories on the XFS filesystem and sets up an overlay
### Garbage Collection and Backreferences
The mount manager integrates with containerd's garbage collection system to ensure
mounts are properly tracked and cleaned up. Mounts can reference other resources
using special labels:
| Label | Description |
|-------|-------------|
| `containerd.io/gc.bref.container.*` | Back reference to a container |
| `containerd.io/gc.bref.content.*` | Back reference to content in the content store |
| `containerd.io/gc.bref.image.*` | Back reference to an image |
| `containerd.io/gc.bref.snapshot.*` | Back reference to a snapshot |
The `.*` suffix allows for named backreferences separated by `.` or `/`.
**Example:**
```go
info, err := mountManager.Activate(ctx, "my-mount", mounts,
mount.WithLabels(map[string]string{
"containerd.io/gc.bref.container": "container-id-123",
"containerd.io/gc.bref.snapshot.overlayfs": "active-snapshot-key",
}),
)
```
These labels ensure that the mount won't be garbage collected while the
referenced resources still exist, and the mount will be automatically cleaned
up when the references are removed.
### Relationship with runtimes
The runtime should use the mount manager to initiate activation of the mounts
before setting up the rootfs for a container. The runtime name should be passed
along to the activation call so that the mount manager may be configured for
runtime specific behavior. For example, a runtime is able to understand
formatting or specific mount types, then the mount manager can avoid performing
those mounts and let the runtime handle it.
runtime specific behavior.
The `ActivateOptions` allow runtimes to indicate which mount types they can handle:
```go
// Runtime can handle formatting, so don't let mount manager do it
info, err := mountManager.Activate(ctx, name, mounts,
mount.WithAllowMountType("format/*"),
)
// Runtime can handle loop devices
info, err := mountManager.Activate(ctx, name, mounts,
mount.WithAllowMountType("loop"),
)
```
#### Support with containerd shims
By default, the containerd runtime will call the mount manager to activate mounts,
which will perform any transformations and custom mounts. However, a runtime shim may
choose to handle some mount types or transformations itself in order to optimize
performance based on the runtime environment. For example, a VM based runtime may
choose to handle loopback mounts itself by passing the disk image file directly to
the VM instead of setting up a loop device on the host. The runtime shim may export
the annotation `containerd.io/runtime-allow-mounts` in its runtime info to indicate
which mount types the shim can handle. The values are comma separated and passed to
via the `mount.WithAllowMountType` option when activating mounts.
### Mount Manager Interface
The complete mount manager interface:
```go
type Manager interface {
Activate(context.Context, string, []Mount, ...ActivateOpt) (ActivationInfo, error)
Deactivate(context.Context, string) error
Info(context.Context, string) (ActivationInfo, error)
Update(context.Context, ActivationInfo, ...string) (ActivationInfo, error)
List(context.Context, ...string) ([]ActivationInfo, error)
}
```
**Methods:**
- `Activate`: Activate a set of mounts with a unique name
- `Deactivate`: Unmount and cleanup an activation
- `Info`: Get information about an active mount
- `Update`: Update an active mount (not yet implemented)
- `List`: List all active mounts, optionally filtered
**ActivationInfo** contains:
- `Name`: Unique identifier for the activation
- `Active`: Mounts that were handled by the mount manager
- `System`: Remaining mounts that must be performed by the system/runtime
- `Labels`: Labels associated with the activation
### Storage and Persistence
The mount manager stores activation state in a BoltDB database and maintains
mount targets in a dedicated directory. This provides:
- Crash recovery: Mounts can be tracked and cleaned up after daemon restart
- Garbage collection: Integration with containerd's GC system
- Lease support: Mounts can be associated with leases for lifecycle management
### Example Usage
```go
// Initialize mount manager
mm, err := manager.NewManager(
db,
targetDir,
manager.WithMountHandler("loop", mount.LoopbackHandler()),
)
// Create mounts for a writable overlay with custom block device
mounts := []mount.Mount{
{
Type: "mkfs/loop",
Source: "/data/writable.img",
Options: []string{
"X-containerd.mkfs.size=1GiB",
"X-containerd.mkfs.fs=ext4",
},
},
{
Type: "ext4",
Source: "{{ source 0 }}",
},
{
Type: "mkdir/format/overlay",
Source: "overlay",
Options: []string{
"X-containerd.mkdir.path={{ mount 1 }}/upper:0755",
"X-containerd.mkdir.path={{ mount 1 }}/work:0755",
"lowerdir=/snapshots/base",
"upperdir={{ mount 1 }}/upper",
"workdir={{ mount 1 }}/work",
},
},
}
// Activate with lease and backreference
info, err := mm.Activate(ctx, "container-123-rootfs", mounts,
mount.WithLabels(map[string]string{
"containerd.io/gc.bref.container": "container-123",
}),
)
// info.Active contains mounts handled by mount manager
// info.System contains remaining mounts to perform in container namespace
// Later, cleanup
err = mm.Deactivate(ctx, "container-123-rootfs")
```