如何在不启动 livecd 的情况下缩小根文件系统

Tom*_*unt 111 centos lvm

我发现自己需要重新排列系统分区,以将之前根文件系统下的数据移动到专用挂载点。卷都在 LVM 中,所以这相对容易:创建新卷,将数据移入其中,缩小根文件系统,然后在适当的点挂载新卷。

问题是第 3 步,缩小根文件系统。涉及的文件系统是ext4,所以支持在线调整大小;然而,在挂载时,文件系统只能增长。缩小分区需要卸载它,这对于正常运行的根分区当然是不可能的。

网络上的答案似乎围绕着启动 LiveCD 或其他应急媒体、执行压缩操作,然后启动回已安装的系统。但是,有问题的系统是远程的,我只能通过 SSH 访问。我可以重新启动,但无法启动救援光盘并从控制台执行操作。

如何在保持远程 shell 访问的同时卸载根文件系统?

Tom*_*unt 203

在解决这个问题时,http: //www.ivarch.com/blogs/oss/2007/01/resize-a-live-root-fs-a-howto.shtml提供的信息是关键。但是,该指南适用于非常旧的 RHEL 版本,并且各种信息已经过时。

下面的说明是为在 CentOS 7 上工作而设计的,但它们应该很容易转移到任何运行 systemd 的发行版。所有命令都以 root 身份运行。

  1. 确保系统处于稳定状态

    确保没有其他人在使用它,并且没有其他重要的事情发生。停止提供服务的单元(如 httpd 或 ftpd)可能是个好主意,以确保外部连接不会在中间中断。

    systemctl stop httpd
    systemctl stop nfs-server
    # and so on....
    
    Run Code Online (Sandbox Code Playgroud)
  2. 卸载所有未使用的文件系统

    umount -a
    
    Run Code Online (Sandbox Code Playgroud)

    这将为根卷本身和各种临时/系统 FS 打印许多“目标正忙”警告。这些暂时可以忽略。重要的是,除了根文件系统本身之外,没有任何磁盘上的文件系统保持挂载。验证这一点:

    # mount alone provides the info, but column makes it possible to read
    mount | column -t
    
    Run Code Online (Sandbox Code Playgroud)

    如果您看到任何磁盘上的文件系统仍然挂载,那么一些不应该的东西仍在运行。检查它正在使用什么fuser

    # if necessary:
    yum install psmisc
    # then:
    fuser -vm <mountpoint>
    systemctl stop <whatever>
    umount -a
    # repeat as required...
    
    Run Code Online (Sandbox Code Playgroud)
  3. 制作临时根

    mkdir /tmp/tmproot
    mount -t tmpfs none /tmp/tmproot
    mkdir /tmp/tmproot/{proc,sys,dev,run,usr,var,tmp,oldroot}
    cp -ax /{bin,etc,mnt,sbin,lib,lib64} /tmp/tmproot/
    cp -ax /usr/{bin,sbin,lib,lib64} /tmp/tmproot/usr/
    cp -ax /var/{account,empty,lib,local,lock,nis,opt,preserve,run,spool,tmp,yp} /tmp/tmproot/var/
    
    Run Code Online (Sandbox Code Playgroud)

    这会创建一个非常小的根系统,它会破坏(除其他外)联机帮助页查看(no /usr/share)、用户级自定义(no/root/home)等等。这是故意的,因为它鼓励不要在这种陪审团操纵的根系统中停留超过必要的时间。

    此时您还应该确保安装了所有必要的软件,因为它也肯定会破坏包管理器。浏览所有步骤,并确保您拥有必要的可执行文件。

  4. 转入根部

    mount --make-rprivate / # necessary for pivot_root to work
    pivot_root /tmp/tmproot /tmp/tmproot/oldroot
    for i in dev proc sys run; do mount --move /oldroot/$i /$i; done
    
    Run Code Online (Sandbox Code Playgroud)

    systemd 导致挂载默认允许子树共享(与mount --make-shared),这会导致pivot_root失败。因此,我们使用mount --make-rprivate /. 系统和临时文件系统被批量移动到新的根目录中。这是让它工作所必需的;用于与 systemd 等通信的套接字位于 中/run,因此无法让正在运行的进程关闭它。

  5. 确保远程访问在转换后继续存在

    systemctl restart sshd
    systemctl status sshd
    
    Run Code Online (Sandbox Code Playgroud)

    重新启动 sshd 后,通过打开另一个终端并通过 ssh 再次连接到机器,确保您可以进入。如果不能,请在继续之前解决问题。

    确认可以再次连接后,退出当前使用的 shell 并重新连接。这允许剩余的分叉sshd退出并确保新的不持有/oldroot.

  6. 仍然使用旧根关闭所有内容

    fuser -vm /oldroot
    
    Run Code Online (Sandbox Code Playgroud)

    这将打印仍保留在旧根目录中的进程列表。在我的系统上,它看起来像这样:

                 USER        PID ACCESS COMMAND
    /oldroot:    root     kernel mount /oldroot
                 root          1 ...e. systemd
                 root        549 ...e. systemd-journal
                 root        563 ...e. lvmetad
                 root        581 f..e. systemd-udevd
                 root        700 F..e. auditd
                 root        723 ...e. NetworkManager
                 root        727 ...e. irqbalance
                 root        730 F..e. tuned
                 root        736 ...e. smartd
                 root        737 F..e. rsyslogd
                 root        741 ...e. abrtd
                 chrony      742 ...e. chronyd
                 root        743 ...e. abrt-watch-log
                 libstoragemgmt    745 ...e. lsmd
                 root        746 ...e. systemd-logind
                 dbus        747 ...e. dbus-daemon
                 root        753 ..ce. atd
                 root        754 ...e. crond
                 root        770 ...e. agetty
                 polkitd     782 ...e. polkitd
                 root       1682 F.ce. master
                 postfix    1714 ..ce. qmgr
                 postfix   12658 ..ce. pickup
    
    Run Code Online (Sandbox Code Playgroud)

    在卸载之前,您需要处理这些进程中的每一个/oldroot。蛮力方法只是kill $PID针对每个人,但这可能会破坏事情。要做得更轻柔:

    systemctl | grep running
    
    Run Code Online (Sandbox Code Playgroud)

    这将创建一个正在运行的服务列表。您应该能够将其与持有的进程列表相关联/oldroot,然后systemctl restart为每个进程发出问题。有些服务会在临时根目录中拒绝出现并进入失败状态;这些暂时不重要。

    如果您要调整大小的根驱动器是 LVM 驱动器,您可能还需要重新启动其他一些正在运行的服务,即使它们没有出现在由fuser -vm /oldroot. 如果您发现无法在步骤 7 下调整 LVM 驱动器的大小,请尝试systemctl restart systemd-udevd

    有些进程不能通过简单的systemctl restart. 对我来说,这些包括auditd(它不喜欢通过 杀死systemctl,所以只想要一个kill -15)。这些可以单独处理。

    您会发现的最后一个过程通常是systemd它本身。为此,运行systemctl daemon-reexec.

    完成后,表格应如下所示:

                 USER        PID ACCESS COMMAND
    /oldroot:    root     kernel mount /oldroot
    
    Run Code Online (Sandbox Code Playgroud)
  7. 卸载旧根

    umount /oldroot
    
    Run Code Online (Sandbox Code Playgroud)

    此时,您可以执行所需的任何操作。最初的问题需要一个简单的resize2fs调用,但你可以在这里做任何你想做的事情;另一个用例是将根文件系统从一个简单的分区转移到 LVM/RAID/任何东西。

  8. 将根旋转回来

    mount <blockdev> /oldroot
    mount --make-rprivate / # again
    pivot_root /oldroot /oldroot/tmp/tmproot
    for i in dev proc sys run; do mount --move /tmp/tmproot/$i /$i; done
    
    Run Code Online (Sandbox Code Playgroud)

    这是步骤 4 的直接逆转。

  9. 处理临时根

    重复步骤 5 和 6,除了使用/tmp/tmproot代替/oldroot。然后:

    umount /tmp/tmproot
    rmdir /tmp/tmproot
    
    Run Code Online (Sandbox Code Playgroud)

    由于它是一个 tmpfs,此时临时根溶解在以太中,再也不会出现。

  10. 把东西放回原处

    再次挂载文件系统:

    mount -a
    
    Run Code Online (Sandbox Code Playgroud)

    在这一点上,你也应该更新/etc/fstab,并grub.cfg按照你的步骤7中所做的任何调整。

    重新启动任何失败的服务:

    systemctl | grep failed
    systemctl restart <whatever>
    
    Run Code Online (Sandbox Code Playgroud)

    再次允许共享子树:

    mount --make-rshared /
    
    Run Code Online (Sandbox Code Playgroud)

    启动已停止的服务单元 - 您可以使用此单个命令:

    systemctl isolate default.target
    
    Run Code Online (Sandbox Code Playgroud)

你已经完成了。

非常感谢 Andrew Wood,他在 RHEL4 上制定了这个演变,以及 steve,他为我提供了前者的链接。

  • 很棒的答案。近乎神奇,而且非常清晰和直接。将它与 debian VPS 一起使用,没有任何问题(当然,在第 6 阶段只需“卸载 /oldroot/boot”)。我将您的答案与其他没有回应或否定答案的 SE 问题联系起来。 (14认同)
  • 我遇到的另一个问题:/tmp 是我系统上的一个 ramdisk,所以我最终在 `/oldroot/tmp` 安装了一个 ramdisk,这阻止我卸载 `/oldroot`,但没有出现在 `/oldroot` 中fuser` 或 `lsof` 输出。花了一些时间盯着 systemd 来解决这个问题...... (4认同)
  • 并已解决,问题如@vaab 所示;你必须先`umount /oldroot/boot`,然后再`umount /oldroot` (3认同)
  • 关键是在不需要物理控制台的情况下卸载和操作根文件系统。据我所知,在卸载该分区时无法保持打开从分区读取的服务。如果您的服务不涉及根 FS,您可以使用 `mount --move` 进入 tmpfs 使其保持打开状态,但这是不受支持的。 (3认同)
  • 您需要使用操作系统工具来重新启动 init 守护进程。我从未使用过暴发户,但 https://wiki.ubuntu.com/FoundationsTeam/Specs/QuantalUpstartStatefulReexec 建议 `telinit u` 可以做你想做的事。 (2认同)
  • 谢谢,@TomHunt,`telinit u` 不断地使我的盒子崩溃,并显示一条消息“内核恐慌未同步试图杀死 init”。我决定尝试一种不同的方法:我修改了 /boot 中的 ramdisk 并在启动期间挂载之前调整了我的文件系统的大小。感谢您的入住和最初的答复。 (2认同)
  • 使用`umount -r /oldroot`,递归卸载。 (2认同)

小智 13

如果您确定自己在做什么 - 因此不进行实验,您可以连接到 initrd,这是一种非交互式且快速的方式。

在基于 Debian 的系统上,方法如下。

查看代码:https : //github.com/szepeviktor/debian-server-tools/blob/master/debian-setup/debian-resizefs.sh

还有一个例子:https : //github.com/szepeviktor/debian-server-tools/blob/master/debian-setup/debian-convert-ext3-ext4.sh