一种给PVE虚拟机磁盘扩容的方法
PVE(Proxmox Virtual Environment )是一个基于QEMU/KVM和LXC的开源服务器虚拟化管理解决方案,可以使用集成的 Web 界面或通过 CLI 管理虚拟机、容器、高可用性集群、存储和网络。和ESXi相比,易用性不相上下,性能略差(客户操作系统为Windows时迟滞感更明显),基于AGPL协议,可以免费使用,对于需要跑Linux Server的场景是个不错的虚拟化方案。
1. 背景和思路
我在家里的低功耗服务器上用PVE作为虚拟化平台,在它上面再跑Linux、Windows的各客户操作系统,已经运行了两年了,稳定可靠,个人体验不是以Windows为核心的虚拟化业务完全可以替代ESXi,ESXi安装时有些硬件驱动还不好找,安装PVE我还没有遇到类似问题,感觉硬件兼容性比ESXi强。

最近,我在PVE上架设的OMV(OpenMediaVault)的操作系统磁盘空间已经吃光了,于是我花了一些时间研究PVE虚拟机磁盘扩容的方案,一个晚上两遍实操下来,感觉我用的方法是一种快速、低风险且向后兼容的方案。
按传统思路,磁盘扩容操作就是对“磁盘”分区进行扩大,但是如果要扩容的分区紧邻分区存在数据就需要移动紧邻分区的数据,才能执行扩容操作,这是高风险且非常耗时的操作。我要扩容的分区是主分区,后面紧跟交换分区和其他分区,如下图所示:

如果操作系统是默认安装,大家普遍会使用这种分区结构,那就得先收缩后面的分区且向后挪动数据,再扩大主分区。我最早也是打算采用这个方案,但考虑到风险犹豫了一段时间。后来看PVE的“磁盘”其实是操作系统概念里的文件,并不是直接在物理磁盘上划分的分区,如下图所示:

那要增加磁盘在PVE里只是多个文件而已,结合我的需求,我OMV系统磁盘上最耗空间的是Docker,则我只要增加一块磁盘,再把Docker文件移动到这块新磁盘上,最后把磁盘挂载进去覆盖Docker目录,就是实现了Docker已用空间的回收,解决了系统盘空间耗光的问题,对系统整体来说增加了一块磁盘(虽然它只是定向给Docker使用),也是间接的实现了扩容。

2. 动手实操
2.1 备份
操作前一定要做备份。PVE的备份就是复制磁盘文件,我在家里千兆网复制文件平均速度超过100M/秒,不拆磁盘对拷,通过网络复制,吃个晚饭它就拷完了,还是比较快的。
在Windows shell / Linux termial上执行类似这样的命令:
scp -r root@192.168.x.x:/var/lib/vz/images/ .
2.2 增加一块磁盘
PVE和OMV支持热拨插(可能还和磁盘阵列/Raid卡相关),OMV在运行状态也可以直接操作。

知识点:在PVE上添加磁盘,它并不会立即分配空间,因此可以指定一个足够大的磁盘容量而不用管PVE磁盘上的实际剩余空间,这样做的好处是以后迁到更大的物理盘的时候不用再做逻辑扩容。

添加完成后进OMV,可以查到新磁盘:

创建分区:
# fdisk /dev/sde
Command (m for help): n
Partition type
p primary (0 primary, 0 extended, 4 free)
e extended (container for logical partitions)
Select (default p):
Using default response p.
Partition number (1-4, default 1):
First sector (2048-2147483647, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-2147483647, default 2147483647):
Created a new partition 1 of type 'Linux' and of size 1024 GiB.
Command (m for help): p
Disk /dev/sde: 1 TiB, 1099511627776 bytes, 2147483648 sectors
Disk model: QEMU HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x81f4463c
Device Boot Start End Sectors Size Id Type
/dev/sde1 2048 2147483647 2147481600 1024G 83 Linux
Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
格式化:
# mkfs -t ext4 /dev/sde1
mke2fs 1.46.2 (28-Feb-2021)
Discarding device blocks: done
Creating filesystem with 268435200 4k blocks and 67108864 inodes
Filesystem UUID: 5b09f5c7-cae6-4a64-afc4-19a12691f0a6
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968,
102400000, 214990848
Allocating group tables: done
Writing inode tables: done
Creating journal (262144 blocks): done
Writing superblocks and filesystem accounting information: done
2.3 挂载和复制
挂载到临时路径:
# mount /dev/sde1 /mnt/hd2
停掉Docker服务:
# systemctl stop docker
移动Docker文件夹进新磁盘:
#mv /var/lib/docker/* /mnt/hd2/
知识点:我这里用了mv命令,这其实是个高危操作,如果中间被中断,数据被破坏的概率很大,建议的操作是先cp,确认成功后再rm。
卸载:
# umount /mnt/hd2
重新挂载,以覆盖Docker目录:
# mount /dev/sde1 /var/lib/docker/
# df -h
Filesystem Size Used Avail Use% Mounted on
udev 9.8G 0 9.8G 0% /dev
tmpfs 2.0G 155M 1.9G 8% /run
/dev/sda1 46G 29G 15G 66% /
tmpfs 9.8G 0 9.8G 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 9.8G 0 9.8G 0% /sys/fs/cgroup
tmpfs 9.8G 0 9.8G 0% /tmp
/dev/sdd2 414G 4.0G 389G 2% /srv/dev-disk-by-label-data2
/dev/sdc1 2.7T 1.1T 1.5T 43% /srv/dev-disk-by-label-data1
/dev/sdb1 2.7T 904G 1.9T 33% /srv/dev-disk-by-label-parity1
tmpfs 2.0G 0 2.0G 0% /run/user/0
/dev/sde1 1007G 17G 940G 2% /var/lib/docker
启动Docker服务:
# systemctl start docker
检查Docker运行是否正常:
systemctl status docker
docker ps -a
2.4 实现开机挂载
前面是手工挂载,系统重启后就没有了,要实现开机挂载,需要改写/etc/fstab。
首先要查UUID:
# blkid
/dev/sde1: UUID="5b09f5c7-cae6-4a64-afc4-12345" TYPE="ext4" PARTUUID="81f4463c-01"
写fstab配置:
# cat /etc/fstab
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point> <type> <options> <dump> <pass>
# / was on /dev/sda1 during installation
...
UUID=5b09f5c7-cae6-4a64-afc4-12345 /var/lib/docker ext4 defaults 0 0
知识点:fstab对磁盘挂载支持多种写法,用“/dev/sde1 /var/lib/docker”这种写法更直观,但这里有一个大坑,就是Linux的磁盘序号是动态的。我操作时新磁盘在/dev/sde,重启后变成/dev/sda了,原来操作系统磁盘由/dev/sda变成了/dev/sde,和新磁盘调了个个,Docker服务启动后往/dev/sde写数据,直接破坏操作系统,这也是我开篇说一晚上实操两遍的原因。同时也是前面强调一定要备份的原因!一定要备份!我上面给出的是用UUID的写法,这样不受磁盘序号影响,可以锁定具体的磁盘。
最后重启检查是否正常,有异常检查前面的步骤,没有就可以收工了。
操作前后的空间占用情况对比:
# df -h
Filesystem Size Used Avail Use% Mounted on
udev 9.8G 0 9.8G 0% /dev
tmpfs 2.0G 155M 1.9G 8% /run
/dev/sda1 46G 46G 0 100% /
...
# df -h
Filesystem Size Used Avail Use% Mounted on
udev 9.8G 0 9.8G 0% /dev
tmpfs 2.0G 155M 1.9G 8% /run
/dev/sda1 46G 29G 15G 66% /
...
/dev/sde1 1007G 17G 940G 2% /var/lib/docker
...
3. 总结和建议
用这个方案操作,相对传统的调整分区大小的方案,操作难度小很多,操作时间短很多(相应的服务中断时间就短很多),风险小了很多,在有冗余空间的情况下没有增加硬件成本,且以后PVE增加新的大磁盘后,迁移成本小(使用PVE的预划定足够大的容量,避免以后的二次扩容、迭代扩容)。
早期我在做我的空间规划的时候,计划OMV只是放个操作系统,数据全部放在挂载磁盘上,没有扩容需求,但是在使用过程中把OMV作为核心系统使用,Docker服务基本放在上面,导致空间消耗殆尽,这是当时没有想到的(包括我的台式机Windows、笔记本MacOS都有遇到系统盘不够用的情况),因此建议1:系统盘划分时尽可能的大。
创建虚拟磁盘时我习惯以当前宿主物理磁盘大小来做规划,其实很多虚拟化平台,也包括桌面版本的VMWare 、VirtualBox、Parallels Desktop等,都有指定非预分配的选项,即时分配,用多少分配多少,因此建议2:根据业务而不是当前物理磁盘剩余空间来指定磁盘大小。
不仅是磁盘扩容,在运维过程中总有一些不确定因素和意外发生,因此建议3:做好备份!