如何调整虚拟机磁盘大小?

有没有办法调整虚拟机的磁盘大小?比如将磁盘从32GB增加到64GB。我在运行Ubuntu server 11.10 64位上的KVM/Qemu。谢谢。
5个回答

在基于Debian的发行版上,你应该使用virt-resize。现在它几乎可以处理所有事情了。假设你的镜像叫做Win7(为什么不呢?)。首先确保你的虚拟机已经关闭:
安装这个工具:
# apt-get install libguestfs-tools

获取您的虚拟机磁盘位置:
# virsh dumpxml Win7 | xpath -e /domain/devices/disk/source
Found 2 nodes in stdin:
-- NODE --
<source file="/var/lib/libvirt/images/Win7.img" />
-- NODE --
<source file="/var/lib/libvirt/images/Win7.iso" />

你可能需要对以下路径进行调整:/var/lib/libvirt/images/Win7.img
# virt-filesystems --long --parts --blkdevs -h -a /var/lib/libvirt/images/Win7.img
Name       Type       MBR  Size  Parent
/dev/sda1  partition  07   100M  /dev/sda
/dev/sda2  partition  07   32G   /dev/sda
/dev/sda   device     -    32G   -

创建您的64G磁盘:
# truncate -s 64G /var/lib/libvirt/images/outdisk

你需要扩展/dev/sda2(而不是引导分区):
# virt-resize --expand /dev/sda2 /var/lib/libvirt/images/Win7.img /var/lib/libvirt/images/outdisk
Examining /var/lib/libvirt/images/Win7.img ...
 100% [progress bar] --:--
**********

Summary of changes:

/dev/sda1: This partition will be left alone.

/dev/sda2: This partition will be resized from 32G to 64G.  The 
    filesystem ntfs on /dev/sda2 will be expanded using the 
    'ntfsresize' method.

**********
Setting up initial partition table on outdisk ...
Copying /dev/sda1 ...
Copying /dev/sda2 ...
 100% [progress bar] 00:00
 100% [progress bar] 00:00
Expanding /dev/sda2 using the 'ntfsresize' method ...

Resize operation completed with no errors.  Before deleting the old 
disk, carefully check that the resized disk boots and works correctly.

为了以防万一,请备份(或者如果您不想备份,可以使用mv命令):
# cp /var/lib/libvirt/images/Win7.img /var/lib/libvirt/images/Win7.img.old
# mv /var/lib/libvirt/images/outdisk /var/lib/libvirt/images/Win7.img

现在启动! 更多信息请参考:man virt-resize

3不必在最后一步创建备份(如果镜像太大,您不想/没有额外的磁盘空间),您可以只需执行 virsh edit virt_name 并将磁盘源的路径更改为新路径。然后,如果它不起作用,请将其更改回来并重试。使用2个镜像就可以了,使用3个镜像没有太大意义。 - Mike
1或者你可以在第一个命令中使用mv代替cp。除了节省磁盘空间外,这样做还会显著提高速度。 - Kevin Keane
6有一件重要的事情需要注意,即使原始镜像具有某种类型,目标实例将是“raw”类型。如果您想保持例如“qcow2”类型,您应该进行转换,如下所示:qemu-img convert -O qcow2 /var/lib/libvirt/images/outdisk /var/lib/libvirt/images/outdisk.qcow2 - logoff
1而且virt-resize甚至还有一个可爱的ncurses进度旋转器。 - David McNeill
请注意,由于Ubuntu的一个错误,您需要使用sudo来运行libguestfs-tools命令:https://askubuntu.com/questions/1046828/how-to-run-libguestfs-tools-tools-such-as-virt-make-fs-without-sudo - Ciro Santilli OurBigBook.com
我在命令提示符中使用了#来表示(而不是普通用户通常使用的$)。 - malat

在进行任何操作之前,我建议您先完整复制磁盘映像,这样当一切出问题时,您可以将其复制回来重新开始。
有三件事情您需要做:
1)扩大磁盘映像。在您的主机中:
qemu-img resize foo.qcow2 +32G

现在您的客人可以看到一个更大的磁盘,但仍然有旧的分区和文件系统。

2)使磁盘映像内的分区变大。您需要在客人中使用LiveCD引导此操作,因为您无法干扰已挂载的分区。这是相当复杂和可能是最危险的部分。这里要复制的内容很多,所以我现在只会提供链接。您需要执行类似于以下操作:

http://www.howtoforge.com/linux_resizing_ext3_partitions_p2

或者2b)如果你只是想要更多的存储空间,创建一个新分区会更简单(和更安全)。 使用fdisk或cfdisk,或者你觉得舒服的任何工具 - 现在你应该可以看到你的虚拟磁盘上一大堆未分配的空间。

3)最后,如果你调整了现有的分区大小,请将新的更大分区内的文件系统也调整为更大(实际上,在上面提到的指南中已经有解释了)。 在你的虚拟机中:

resize2fs /dev/sda1

谢谢你的快速回答。看起来这是一个可行的解决方案。我将离开城市几天,但下周回到办公室后,我会尝试一下并报告结果是否有效。 - Tong Wang
1再问一个问题:如果我想缩小磁盘,我需要按相反的顺序执行步骤吗?比如:1. 缩小文件系统;2. 缩小分区;3. 缩小磁盘镜像。对吗? - Tong Wang
是的,resize2fs可以收缩和扩展分区,但显然需要足够的文件系统空间。在收缩分区时,我建议留下比实际所需稍多一点的空间,以免意外截断文件系统的末尾 :) - Caesium
你绝对可以对已挂载的分区进行操作。 - etherous

我认为Caesium的回答很好,我只是想写下一些其他命令来实现同样的目标。
假设你有一个名为disk.img的文件,其中包含一个磁盘镜像,即它具有分区表和一个或多个分区,并且你想要扩大最后一个分区。你需要做的是:
1)将整个文件变大,比如说4GiB。一个快速的方法是使用dd命令。
dd if=/dev/zero of=disk.img bs=1c seek=4G count=0

2)使用fdisk将分区调大(我希望我能够在parted或者一些更友好的工具上完成这个任务...有没有人知道?)
fdisk disk.img

输入p以打印分区表并查找分区的起始扇区,例如分区2从扇区106496开始。
您需要做的是从分区表中删除该分区,并创建一个新的分区,其起始扇区与之前相同,但结束于稍后的扇区。然后该分区将包含一个有效的文件系统。
输入d并给出要删除的分区号码。(咽口水!)
输入n并给出所需的分区号码,然后输入起始扇区。您必须使用与之前相同的起始扇区。最后,给出结束扇区或让fdisk选择您可以使用的最高扇区。
输入w以将更改写回磁盘映像文件,并退出fdisk。
3)现在您需要调整文件系统的大小。为此,您需要知道磁盘映像中文件系统的偏移量(即位置)。如果您知道扇区大小(通常为512),则可以根据扇区号计算出来,或者您可以使用parted工具。
parted disk.img u b p

parted可以接受命令作为命令行参数,这意味着它会打印分区表并使用字节作为大小单位。)
这将打印出分区的起始和结束位置。假设您的分区起始位置是54525952,然后您可以使用losetup创建一个回环块设备。
losetup -f --show -o 54525952 disk.img 

losetup 告诉你它选择了哪个设备,例如 /dev/loop0。现在你可以使用 resize2fs

resize2fs /dev/loop0

最后移除循环设备

losetup -d /dev/loop0


谢谢你的简单回答。在Ubuntu 16.04上对我有用。 - phil

#include <stdio.h>

static unsigned long auxfilesize(FILE* fp) {

    unsigned long len=0;
    int c=0;


    while ( (c = fgetc(fp)) != -1 ) {
        len++;
    }

    return len;
}


static unsigned long aux_copyNBytesFromTo(FILE* from, FILE* to,
                                    unsigned long fromSize, 
                                    unsigned long bytes) {

    unsigned long iter = 0;
    while ( iter++ < fromSize ) {
        int c = fgetc(from);
        fputc(c, to);

    }
    iter-=1; 
    if ( fromSize < bytes ) {
        while ( iter++ < bytes ) {
            fputc(0, to);
        }
    }
    return iter;
}


int main(int argc, char **argv) {
    FILE *from = fopen(argv[1], "rb");
    FILE *to = fopen(argv[2], "wb" );
    unsigned long l = auxfilesize(from);

    rewind(from); 

    aux_copyNBytesFromTo(from, to, l, (l + l/2)) ;

    fclose(from);
    fclose(to);

}

这个简单的程序在虚拟图像的末尾添加了N个字节。我使用Paragon分区映像工具来合并新创建的图像。在这里运行得很好。

未使用的静态函数'auxfilesize','aux_copyNBytesFromTo'。那有什么帮助呢? - A.B.
未使用?我不明白这个问题。它们两者都被使用了。 - Ilian Zapryanov
结果是未分配的字节和一个新的硬盘给操作系统。你需要格式化它。如果你问为什么foo-s是静态的。那只是为了保存原型,为什么不呢。这只是一个我在闲暇时间里用两分钟写的简单程序 :) 没有什么特别或最优的。 - Ilian Zapryanov
4你本可以使用现有的工具,比如dd或者truncate... - rudimeier