- 后端
- 虚拟化
- 容器运行时
【免费下载链接】incus
Powerful system container and virtual machine manager
导读:
disk是 Incus 中最核心的设备类型之一,负责为容器和虚拟机实例提供附加存储。本文以 doc/reference/devices_disk.md 为骨架,系统讲解其九类source来源(存储卷、主机路径、Ceph、ISO、tmpfs 等)的挂载方法、initial.*初始配置的三种应用场景,以及全部设备选项的取值与默认值,并结合 internal/server/device/disk.go 的实现细节说明底层行为。读完本文,你将能够熟练为实例配置根磁盘、自定义卷、VM 云配置盘与内存文件系统。
一、disk设备是什么
disk设备为实例提供附加存储。对于容器,它本质上是实例内部的一个挂载点——既可以是宿主机上已有文件或目录的 bind-mount,也可以是对块设备的常规挂载。对于虚拟机,文件系统类挂载通过9p或virtiofs(可用时)共享宿主侧的挂载点或目录,块设备则作为 VirtIO 磁盘挂载。
`disk` 设备类型同时支持容器和虚拟机,且两者都支持热插拔(hotplugging)。这里有一个值得注意的细节:设备名会影响设备生成的序列号(serial)。当设备名超过 14 个字符(nvme和virtio-blk总线)或 30 个字符(virtio-scsi总线)时,Incus 会对设备值进行哈希,以保证生成的序列号不超过长度约束,而设备名本身保持不变。
从源码看,disk设备在 internal/server/device/disk.go 中定义为一个disk结构体,实现了设备接口的Start、Stop、Add、Remove、Update等全部生命周期方法,并通过 CanHotPlug() 决定是否支持热插拔。
二、disk设备的九种来源(source类型)
source选项的值决定了磁盘设备的类型。创建磁盘设备的基础命令是:
incus config device add <实例名> <设备名> disk [key=value...]1. 存储卷(Storage volume)——最常用
指定存储卷的名称作为source:
incus config device add <实例名> <设备名> disk pool=<存储池名> source=<卷名> [path=<实例内路径>]path对文件系统卷是必需的,对块卷则不需要。- 也可以使用
incus storage volume attach命令挂载存储卷,两条命令底层使用同一机制。 - 子路径挂载:
source=<卷名>/<子路径>语法可以将自定义卷的某个子路径挂载进实例。如果该子路径在自定义卷中不存在,设备启动时会根据initial.XYZ配置键自动创建,并设置对应的所有权与权限。
2. 主机路径(Path on the host)
共享宿主机上的文件系统或块设备:
incus config device add <实例名> <设备名> disk source=<宿主机路径> [path=<实例内路径>]同样,path对文件系统是必需的,对块设备则不需要。源码中通过 sourceIsLocalPath() 判断source是否为宿主机本地路径——空值、cloud-init:config、agent:config、tmpfs:、tmpfs-overlay:以及 Ceph/CephFS 前缀都不视为本地路径。
3. Ceph RBD
如果已有外部托管的 Ceph RBD 卷,可以这样挂载:
incus config device add <实例名> <设备名> disk source=ceph:<存储池名>/<卷名> ceph.user_name=<用户名> ceph.cluster_name=<集群名> [path=<实例内路径>]path对文件系统必需,对块设备不需要。
4. CephFS
挂载外部托管的 Ceph 文件系统:
incus config device add <实例名> <设备名> disk source=cephfs:<fs_name>/<路径> ceph.user_name=<用户名> ceph.cluster_name=<集群名> path=<实例内路径>注意 CephFS 是文件系统,因此path始终必需。源码中以strings.HasPrefix(d.config["source"], "cephfs:")识别该来源(sourceIsCephFs())。
5. ISO 文件(仅 VM)
将 ISO 文件作为虚拟机内部的 ROM 设备添加:
incus config device add <实例名> <设备名> disk source=<宿主机文件路径>该来源仅适用于虚拟机,不需要path。
6. VMcloud-init配置盘(仅 VM)
由cloud-init.vendor-data与cloud-init.user-data配置键生成一个 cloud-init 配置 ISO,挂载到虚拟机上,VM 内的 cloud-init 在启动时会检测该驱动器并应用配置:
incus config device add <实例名> <设备名> disk source=cloud-init:config源码中该值对应常量diskSourceCloudInit = "cloud-init:config"(disk.go),ISO 的生成实现在 generateVMConfigDrive()。
7. VMagent配置盘(仅 VM)
生成一个包含 agent 二进制、配置文件和安装脚本的 ISO。这在9p不受支持的环境中加载 agent 的替代方案:
incus config device add <实例名> <设备名> disk source=agent:config对应常量diskSourceAgent = "agent:config",生成逻辑见 generateVMAgentDrive()。
8. Tmpfs(内存文件系统)
incus config device add <实例名> <设备名> disk source=tmpfs: path=<实例内路径> [size=<大小>] [initial.uid=<uid>] [initial.gid=<gid>] [initial.mode=<模式>]source与path都必需。它会在实例内创建一个tmpfs挂载,支持通过可选属性设置大小、所有权与权限。注意源码中 CanHotPlug() 对tmpfs:与tmpfs-overlay:返回false,即这两种来源不支持热插拔。
9. Tmpfs with overlayfs(tmpfs-overlay:)
需要 tmpfs 行为又叠加 overlayfs 语义时使用:
incus config device add <实例名> <设备名> disk source=tmpfs-overlay: path=<实例内路径> [size=<大小>] [initial.uid=<uid>] [initial.gid=<gid>] [initial.mode=<模式>]source与path都必需,且目标path必须已存在于容器内部。它提供带 overlayfs 处理的临时内存文件系统。
三、根磁盘设备的初始卷配置(initial.*)
initial.*前缀的配置用于新建实例的根磁盘设备,只在实例创建时生效,因此可以为实例创建独立于存储池默认设置的独特配置。
例如,给现有 profile 添加zfs.block_mode初始配置,此后使用该 profile 创建的每个新实例都会生效:
incus profile device set <profile_name> <设备名> initial.zfs.block_mode=true也可以直接在创建实例时设置:
incus init <镜像> <实例名> --device <设备名>,initial.zfs.block_mode=true注意:initial.*不能与自定义卷选项一起使用,也不能用于设置卷的大小。
四、initial.uid、initial.gid与initial.mode的三种应用场景
这三个配置项在三种不同场景下发挥作用:
- 根磁盘设备:传递给存储驱动,在创建时设置实例根卷的所有权与模式(受驱动支持性限制)。
tmpfs:与tmpfs-overlay:设备:被转换为底层tmpfs挂载的uid=、gid=、mode=挂载选项。- 带子路径的自定义卷(如
source=myvol/sub/path):作为设备启动时自动创建子目录的所有权与模式。
在所有场景中,initial.uid和initial.gid默认值为0,initial.mode默认值为0711(八进制)。
五、initial.copy:首次启动时向空卷复制容器内容
initial.copy适用于挂载到容器的自定义卷磁盘。当设置为true时,容器启动时若卷(或其子路径)为空,会将容器自身文件系统中path处的现有内容复制进卷。这正好符合应用容器将数据随镜像打包在挂载目录中的使用习惯(典型场景如path=/var/lib/mysql的数据库目录)。
关键行为细节:
- 每个卷只复制一次,是否已复制记录在卷的
volatile.initial.copied键中。已有内容的卷不会被触碰,复制后又被清空的卷也不会再次复制。 - 复制时保留所有权、权限、时间戳与扩展属性,并按容器的 ID map 转换;符号链接原样复制;设备节点会被跳过。
- 复制只在启动已停止的容器时执行,因此不能把带
initial.copy的设备添加到运行中的容器上。
完整示例:
incus storage volume create <存储池名> <卷名> incus config device add <实例名> <设备名> disk pool=<存储池名> source=<卷名> path=/var/lib/mysql initial.copy=true从实现看,disk_initial_copy.go 中的initialCopy()会先通过OperationLockName("InitialCopy", ...)与其他挂载同一卷的实例做串行化,再检查volatile.initial.copied,确认卷为空(跳过lost+found)后才执行复制,最后写入volatile.initial.copied = "true"。这也印证了"仅复制一次"的语义是有数据库层面保证的。
六、disk设备全部选项参考
以下选项由 doc/config_options.txt 的devices-disk配置组生成,同时可以在 validateConfig() 的校验规则中找到对应实现。
| 选项 | 类型 | 默认值 | 必填 | 说明 |
|---|---|---|---|---|
source | string | — | 是 | 文件系统或块设备的来源(见上文的类型说明) |
path | string | — | 是(仅文件系统设备) | 实例内挂载路径。容器支持挂载文件系统设备、其中路径与单个文件;VM 支持文件系统设备与其中路径,不支持单个文件 |
pool | string | — | 否 | 磁盘设备所属的存储池(仅适用于 Incus 托管的存储卷) |
readonly | bool | false | 否 | 是否只读挂载 |
recursive | bool | false | 否 | 是否递归挂载源路径 |
required | bool | true | 否 | 源不存在时是否报错(optional为已废弃的旧名) |
shift | bool | false | 否 | 建立 shifting overlay,将源 UID/GID 转换为与实例匹配(仅容器) |
propagation | string | private | 否 | bind-mount 在实例与宿主机之间的共享方式,取值:private、shared、slave、unbindable、rshared、rslave、runbindable、rprivate |
raw.mount.options | string | — | 否 | 文件系统特定的挂载选项 |
size | string | — | 否 | 磁盘大小(支持多种后缀,见单位说明),仅支持根文件系统/ |
size.state | string | — | 否 | 同size,但用于 VM 保存运行状态的文件系统卷 |
ceph.cluster_name | string | ceph | 否 | Ceph 集群名(Ceph/CephFS 来源必需) |
ceph.user_name | string | admin | 否 | Ceph 集群用户名(Ceph/CephFS 来源必需) |
attached | bool | true | 否 | 仅 VM:磁盘是挂载状态还是弹出状态 |
boot.priority | string | 0 | 否 | 仅 VM:启动优先级(数值越大越先启动),skip表示跳过 |
dependent | bool | false | 否 | 磁盘是否依赖实例 |
initial.* | string | — | 否 | 根磁盘设备的初始卷配置,覆盖存储池默认卷配置 |
io.bus | string | 块:virtio-scsi;文件系统:auto | 否 | 仅 VM:覆盖设备总线。块设备可选nvme、virtio-blk、virtio-scsi(默认)、usb;文件系统可选9p、auto(默认,优先virtiofs否则9p)、virtiofs |
io.cache | string | none | 否 | 仅 VM:覆盖缓存模式。块设备可选none(默认)、writeback、unsafe;文件系统可选none(默认)、metadata、unsafe |
wwn | bool | 空 | 否 | 仅 VM:设置磁盘 World Wide Name(仅在virtio-scsi总线上支持) |
limits.max | string | — | 否 | 读写双向 I/O 限额(byte/s 和/或 IOPS),等价于同时设置limits.read与limits.write |
limits.read | string | — | 否 | 读 I/O 限额(byte/s 与/或iops后缀),两者同时指定用逗号分隔 |
limits.write | string | — | 否 | 写 I/O 限额,规则同limits.read |
limits.max.burst | string | — | 否 | 读写双向的突发上限 |
limits.max.burst.length | string | 1s | 否 | 突发上限可持续的时间 |
limits.read.burst | string | — | 否 | 读突发上限(仅 VM),可持续limits.read.burst.length |
limits.read.burst.length | string | 1s | 否 | 读突发在回落到持续限额前可维持的时间 |
limits.write.burst | string | — | 否 | 写突发上限(仅 VM) |
limits.write.burst.length | string | 1s | 否 | 写突发可维持的时间 |
关于io.bus的注意点:9p不支持热插拔,virtiofs不支持在线迁移(live migration)。auto模式会在migration.stateful未设为true且宿主机支持virtiofsd时优先使用virtiofs,否则回退到9p。
在实现层面,I/O 限额配置被解析为diskBlockLimit结构(disk.go),并通过limits.*系列键(diskLimitsConfigKeys)检测设备是否配置了限额;容器场景下最终会转换为 cgroup 的块设备读写限制。
七、迁移与热插拔行为
disk设备的迁移能力由 CanMigrate() 决定:
- 根磁盘(
path=/)总是可迁移的; - 远程存储池上的磁盘(如 Ceph)可迁移;
- 特殊来源(cloud-init、agent、tmpfs 等)可迁移;
- 其余本地路径磁盘不可迁移。
热插拔则由 CanHotPlug() 控制:tmpfs:/tmpfs-overlay:不可热插拔;io.bus为空或auto时,会读取 volatile 键中记录的io.bus是否为9p来判断——因为auto在启动时无法预知挂载类型,选择尽早失败更安全。
八、典型实战组合
以下命令组合覆盖了最常见的磁盘使用场景:
场景一:挂载自定义存储卷并自动填充容器数据(数据库目录)
incus storage volume create default mydb incus config device add c1 data disk pool=default source=mydb path=/var/lib/mysql initial.copy=true场景二:共享宿主目录(只读)
incus config device add c1 share disk source=/srv/data path=/mnt/data readonly=true场景三:给 VM 添加 ISO 安装镜像并设置启动优先级
incus config device add vm1 iso disk source=/home/iso/installer.iso boot.priority=10场景四:临时内存盘
incus config device add c1 scratch disk source=tmpfs: path=/mnt/scratch size=1GiB结合 doc/reference/devices_disk.md、配置组 doc/config_options.txt 与设备实现 internal/server/device/disk.go 一同查阅,可以覆盖从命令行挂载到内核级挂载语义的完整链路。
- 后端
- 虚拟化
- 容器运行时
【免费下载链接】incus
Powerful system container and virtual machine manager
相关推荐
Incus 存储卷管理实战指南:创建、挂载、配置与扩容自定义存储卷
Incus 存储卷管理实战指南:创建、挂载、配置与扩容自定义存储卷 本指南围绕 Incus 的 incus storage volume 命令族,系统讲解自定义
后端虚拟化容器运行时大麦自动抢票工具 ticket-purchase:双端方案、关键配置与上手路径
大麦自动抢票工具 ticket purchase:双端方案、关键配置与上手路径 大麦自动抢票项目 ticket purchase 是一个基于 Selenium
后端虚拟化容器运行时Orchard Core 临时文件存储深入指南:ITempDirectoryProvider 架构、配置与共享卷挂载实战
Orchard Core 临时文件存储深入指南:ITempDirectoryProvider 架构、配置与共享卷挂载实战 导读 本文围绕 Orchard Cor
CMS后端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考