在 Linux 系统上管理系统资源的推荐工具是 cgroups。虽然在可以调整的限制方面（CPU、内存、磁盘 I/O、网络等）非常强大，但配置 cgroups 并不简单。nice) 命令从 1973 年起就可以使用了。但它只是调整在一个处理器上竞争时间的进程之间的调度优先级。nice 命令不会限制一个进程在单位时间内所能消耗的 CPU 周期的百分比。cpulimit 命令提供了两个世界的最佳方案。它限制了一个进程在每单位时间内可以分配的 CPU 周期的百分比，而且相对容易调用。cpulimit 命令主要对长期运行的和 CPU 密集型的进程有用。编译软件和转换视频是长期运行的进程的常见例子，它们可以使计算机的 CPU 使用率达到最大。限制这类进程的 CPU 使用率将释放出处理器时间，供计算机上可能运行的其他任务使用。限制 CPU 密集型进程也将减少功耗及热输出，并可能减少系统的风扇噪音。限制一个进程的 CPU 使用率的代价是，它需要更多的时间来完成运行。安装 cpulimitcpulimit 命令在默认的 Fedora Linux 仓库中可用。运行下面的命令，在 Fedora Linux 系统上安装 cpulimit：$ sudo dnf install cpulimit 查看 cpulimit 的文档cpulimit 软件包并没有附带的手册页。使用下面的命令来查看 cpulimit 的内置文档。输出结果在下面提供。但你可能需要在你自己的系统上运行该命令，以防止自本文编写以来选项发生变化。$ cpulimit --help Usage: cpulimit [OPTIONS…] TARGET OPTIONS -l, --limit=N percentage of cpu allowed from 0 to 800 (required) -v, --verbose show control statistics -z, --lazy exit if there is no target process, or if it dies -i, --include-children limit also the children processes -h, --help display this help and exit TARGET must be exactly one of these: -p, --pid=N pid of the process (implies -z) -e, --exe=FILE name of the executable program file or path name COMMAND [ARGS] run this command and limit it (implies -z) 演示为了演示 cpulimit 命令的使用方式，下面提供了一个精心设计的、计算量很大的 Python 脚本。该脚本首先在没有限制的情况下运行，然后在限制为 50% 的情况下运行。它计算的是第 42 个 斐波那契数 的值。该脚本在这两种情况下都作为 time 命令的子进程运行，以显示计算答案所需的总时间。$ /bin/time -f '(computed in %e seconds)' /bin/python -c 'f = lambda n: n if n<2 else f(n-1)+f(n-2); print(f(42), end=" ")' 267914296 (computed in 51.80 seconds) $ /bin/cpulimit -i -l 50 /bin/time -f '(computed in %e seconds)' /bin/python -c 'f = lambda n: n if n<2 else f(n-1)+f(n-2); print(f(42), end=" ")' 267914296 (computed in 127.38 seconds) 当运行第一个版本的命令时，你可能会听到电脑上的 CPU 风扇转动起来。但在运行第二个版本时，你应该不会。第一个版本的命令不受 CPU 的限制，但它不应该导致你的电脑陷入瘫痪。它是以这样一种方式编写的：它最多只能使用一个 CPU 核心。大多数现代 PC 都有多个 CPU 核心，当其中一个 CPU 100% 繁忙时，可以毫无困难地同时运行其他任务。为了验证第一条命令是否使你的一个处理器达到最大，在一个单独的终端窗口中运行 top 命令并按下 1 键。要退出 top 命令可以按 Q 键。设置高于 100% 的限制只对能够进行 任务并行化 的程序有意义。对于这样的程序，高于 100% 的增量代表一个 CPU 的全部利用率（200%=2 个CPU，300%=3 个CPU，等等）。注意，在上面的例子中，-i 选项已经传递给 cpulimit 命令。这是必要的，因为要限制的命令不是 cpulimit 命令的直接子进程。相反，它是 time 命令的一个子进程，而后者又是 cpulimit 命令的一个子进程。如果没有 -i 选项，cpulimit 将只限制 time 命令。最后说明如果你想限制一个从桌面图标启动的图形程序，请将该程序的 .desktop 文件（通常位于 /usr/share/applications 目录下）复制到你的 ~/.local/share/applications 目录下，并相应修改 Exec 行。然后运行下面的命令来应用这些变化：$ update-desktop-database ~/.local/share/applications via: https://fedoramagazine.org/use-cpulimit-to-free-up-your-cpu/作者：Gregory Bartholomew 选题：lujun9972 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
Fedora 33 Workstation 是我们这个免费的、领先的操作系统的最新版本。你现在就可以从官方网站下载它。Fedora 33 Workstation 中有一些新的和值得注意的变化。请阅读如下更多细节。GNOME 3.38Fedora 33 Workstation 为各类用户提供了最新版本的 GNOME 桌面环境。在 Fedora 33 Workstation 中的 GNOME 3.38 包含了许多更新和改进，包括：一个新的 GNOME Tour 应用现在，新用户会看到一个“新的 Tour 应用，重点展示了桌面的主要功能，并为第一次使用 GNOME 的用户提供一个很好的欢迎页”。拖动重排序应用GNOME 3.38 用一个单一的可定制的、一致的视图取代了之前分开的“常用”和“所有”应用视图，这允许你重新排列应用并将它们组织到自定义文件夹中。只需点击并拖动即可移动应用。改进屏幕录制GNOME Shell 中的屏幕录制基础架构已被改进，以利用 PipeWire 和内核 API。这将有助于减少资源消耗并提高响应速度。GNOME 3.38 还提供了许多额外的功能和改进。查看 GNOME 3.38 发行说明以获得更多信息。B-tree 文件系统正如之前宣布的，新安装的 Fedora 33 将默认使用 Btrfs。每一个新的内核版本都会为 Btrfs 增加一些特性和增强功能。变更日志有一个完整的总结，它介绍了每个新内核版本给 Btrfs 带来的功能。Swap on ZRAMAnaconda 和 Fedora IoT 多年来一直默认使用 swap-on-zram。在 Fedora 33 中，将默认启用 swap-on-zram，而不是交换分区。查看 Fedora wiki 页面了解更多关于 swap-on-zram 的细节。默认使用 Nano新的 Fedora 33 将把 EDITOR 环境变量默认设置为 nano。这个变化影响了一些命令行工具，当它们需要用户输入时，会打开一个文本编辑器。在早期的版本中，这个环境变量的默认值并没有被指定，而是由各个应用程序来选择一个默认的编辑器。通常情况下，应用程序会使用 vi 作为它们的默认编辑器，因为它是一个小应用，通常在大多数 Unix/Linux 操作系统的基础安装中都可以使用。由于 Fedora 33 的基本安装中包含了 nano，而且 nano 对于初学者来说更加直观，所以 Fedora 33 将默认使用 nano。当然，想要使用 vi 的用户可以在自己的环境中覆盖 EDITOR 变量的值。详见Fedora 修改请求获取更多信息。via: https://fedoramagazine.org/whats-new-fedora-33-workstation/作者：Gregory Bartholomew 选题：lujun9972 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
Live CD 对于很多任务是很有用的，例如:将操作系统安装到一个硬盘驱动器修复一个启动加载程序或执行其它救援模式操作为 Web 浏览提供一个相适应的最小环境…以及更多的东西。作为使用 DVD 和 USB 驱动器来存储你的 Live CD 镜像是一个替代方案，你可以上传它们到一个不太可能丢失或损坏的 iSCSI 服务器中。这个指南向你展示如何加载你的 Live CD 镜像到一个 ISCSI 服务器上，并使用 iPXE 启动加载程序来访问它们。下载一个 Live CD 镜像$ MY_RLSE=27 $ MY_LIVE=$(wget -q -O - https://dl.fedoraproject.org/pub/archive/fedora/linux/releases/$MY_RLSE/Workstation/x86_64/iso | perl -ne '/(Fedora[^ ]*?-Live-[^ ]*?\.iso)(?{print $^N})/;') $ MY_NAME=fc$MY_RLSE $ wget -O $MY_NAME.iso https://dl.fedoraproject.org/pub/archive/fedora/linux/releases/$MY_RLSE/Workstation/x86_64/iso/$MY_LIVE上面的命令下载 Fedora-Workstation-Live-x86_64-27-1.6.iso Fedora Live 镜像，并保存为 fc27.iso。更改 MY_RLSE 的值来下载其它档案版本。或者，你可以浏览 https://getfedora.org/ 来下载最新的 Fedora live 镜像。在 21 之前的版本使用不同的命名约定，必需在这里手动下载。如果你手动下载一个 Live CD 镜像，设置 MY_NAME 变量为不带有扩展名的文件的基本名称。用此方法，下面部分中命令将引用正确的文件。转换 Live CD 镜像使用 livecd-iso-to-disk 工具来转换 ISO 文件为一个磁盘镜像，并添加 netroot 参数到嵌入的内核命令行：$ sudo dnf install -y livecd-tools $ MY_SIZE=$(du -ms $MY_NAME.iso | cut -f 1) $ dd if=/dev/zero of=$MY_NAME.img bs=1MiB count=0 seek=$(($MY_SIZE+512)) $ MY_SRVR=server-01.example.edu $ MY_RVRS=$(echo $MY_SRVR | tr '.' "\n" | tac | tr "\n" '.' | cut -b -${#MY_SRVR}) $ MY_LOOP=$(sudo losetup --show --nooverlap --find $MY_NAME.img) $ sudo livecd-iso-to-disk --format --extra-kernel-args netroot=iscsi:$MY_SRVR:::1:iqn.$MY_RVRS:$MY_NAME $MY_NAME.iso $MY_LOOP $ sudo losetup -d $MY_LOOP上传 Live 镜像到你的服务器在你的 ISCSI 服务器上创建一个目录来存储你的 live 镜像，随后上传你修改的镜像到其中。对于 21 及更高发布版本：$ MY_FLDR=/images $ scp $MY_NAME.img $MY_SRVR:$MY_FLDR/对于 21 以前发布版本：$ MY_FLDR=/images $ MY_LOOP=$(sudo losetup --show --nooverlap --find --partscan $MY_NAME.img) $ sudo tune2fs -O ^has_journal ${MY_LOOP}p1 $ sudo e2fsck ${MY_LOOP}p1 $ sudo dd status=none if=${MY_LOOP}p1 | ssh $MY_SRVR "dd of=$MY_FLDR/$MY_NAME.img" $ sudo losetup -d $MY_LOOP定义 iSCSI 目标在你的 iSCSI 服务器上运行下面的命令：$ sudo -i # MY_NAME=fc27 # MY_FLDR=/images # MY_SRVR=`hostname` # MY_RVRS=$(echo $MY_SRVR | tr '.' "\n" | tac | tr "\n" '.' | cut -b -${#MY_SRVR}) # cat << END > /etc/tgt/conf.d/$MY_NAME.conf <target iqn.$MY_RVRS:$MY_NAME> backing-store $MY_FLDR/$MY_NAME.img readonly 1 allow-in-use yes </target> END # tgt-admin --update ALL创建一个可启动 USB 驱动器iPXE 启动加载程序有一个 sanboot 命令，你可以使用它来连接并启动托管于你 ISCSI 服务器上运行的 live 镜像。它可以以很多不同的格式编译。最好的工作格式依赖于你正在运行的硬件。例如，下面的说明向你展示如何在一个 USB 驱动器上从 syslinux 中 链式加载 iPXE。首先，下载 iPXE，并以它的 lkrn 格式构建。这应该作为一个工作站上的普通用户完成：$ sudo dnf install -y git $ git clone http://git.ipxe.org/ipxe.git $HOME/ipxe $ sudo dnf groupinstall -y "C Development Tools and Libraries" $ cd $HOME/ipxe/src $ make clean $ make bin/ipxe.lkrn $ cp bin/ipxe.lkrn /tmp接下来，准备一个带有一个 MSDOS 分区表和一个 FAT32 文件系统的 USB 驱动器。下面的命令假设你已经连接将要格式化的 USB 驱动器。注意：你要格式正确的驱动器！$ sudo -i # dnf install -y parted util-linux dosfstools # echo; find /dev/disk/by-id ! -regex '.*-part.*' -name 'usb-*' -exec readlink -f {} \; | xargs -i bash -c "parted -s {} unit MiB print | perl -0 -ne '/^Model: ([^(]*).*\n.*?([0-9]*MiB)/i && print \"Found: {} = \$2 \$1\n\"'"; echo; read -e -i "$(find /dev/disk/by-id ! -regex '.*-part.*' -name 'usb-*' -exec readlink -f {} \; -quit)" -p "Drive to format: " MY_USB # umount $MY_USB? # wipefs -a $MY_USB # parted -s $MY_USB mklabel msdos mkpart primary fat32 1MiB 100% set 1 boot on # mkfs -t vfat -F 32 ${MY_USB}1最后，在 USB 驱动器上安装并配置 syslinux ，来链式加载 iPXE：# dnf install -y syslinux-nonlinux # syslinux -i ${MY_USB}1 # dd if=/usr/share/syslinux/mbr.bin of=${MY_USB} # MY_MNT=$(mktemp -d) # mount ${MY_USB}1 $MY_MNT # MY_NAME=fc27 # MY_SRVR=server-01.example.edu # MY_RVRS=$(echo $MY_SRVR | tr '.' "\n" | tac | tr "\n" '.' | cut -b -${#MY_SRVR}) # cat << END > $MY_MNT/syslinux.cfg ui menu.c32 default $MY_NAME timeout 100 menu title SYSLINUX label $MY_NAME menu label ${MY_NAME^^} kernel ipxe.lkrn append dhcp && sanboot iscsi:$MY_SRVR:::1:iqn.$MY_RVRS:$MY_NAME END # cp /usr/share/syslinux/menu.c32 $MY_MNT # cp /usr/share/syslinux/libutil.c32 $MY_MNT # cp /tmp/ipxe.lkrn $MY_MNT # umount ${MY_USB}1通过简单地编辑 syslinux.cfg 文件，并添加附加的菜单项，你应该能够使用这同一个 USB 驱动器来网络启动附加的 ISCSI 目标。这仅是加载 IPXE 的一种方法。你可以直接在你的工作站上安装 syslinux 。再一种选项是编译 iPXE 为一个 EFI 可执行文件，并直接放置它到你的 ESP 中。又一种选项是编译 iPXE 为一个 PXE 加载器，并放置它到你的能够被 DHCP 引用的 TFTP 服务器。最佳的选项依赖于的环境最后说明如果你以 IPXE 的 EFI 格式编译 IPXE ，你可能想添加 –filename \EFI\BOOT\grubx64.efi 参数到 sanboot 命令。能够创建自定义 live 镜像。更多信息参考创建和使用 live CD。可以添加 –overlay-size-mb 和 –home-size-mb 参数到 livecd-iso-to-disk 命令来创建永久存储的 live 镜像。然而，如果你有多个并发用户，你将需要设置你的 ISCSI 服务器来管理独立的每个用户的可写覆盖。这与 “如何构建一个网络启动服务器，部分 4” 一文所示类似。Live 镜像在它们的内核命令行中支持一个 persistenthome 选项（例如， persistenthome=LABEL=HOME）。与经过 CHAP 身份验证的 iSCSI 目标一起使用，对于中心控制主目录，persistenthome 选项为 NFS 提供一个有趣的替代方案。via: https://fedoramagazine.org/netboot-a-fedora-live-cd/作者：Gregory Bartholomew 选题：lujun9972 译者：robsean 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
交流是非常重要的。你的电脑可能正试图告诉你一些重要的事情。但是，如果你没有正确配置邮件传输代理（MTA），那么你可能不会收到通知。Postfix 是一个易于配置且以强大的安全记录而闻名)的 MTA。遵循以下步骤，以确保从本地服务发送的电子邮件通知将通过 Postfix MTA 路由到你的互联网电子邮件账户中。安装软件包使用 dnf 来安装一些必须软件包（你应该配置了 sudo，对吧？）：$ sudo -i # dnf install postfix mailx如果以前配置了不同的 MTA，那么你可能需要将 Postfix 设置为系统默认。使用 alternatives 命令设置系统默认 MTA：$ sudo alternatives --config mta There are 2 programs which provide 'mta'. Selection Command *+ 1 /usr/sbin/sendmail.sendmail 2 /usr/sbin/sendmail.postfix Enter to keep the current selection[+], or type selection number: 2创建一个 password\_maps 文件你需要创建一个 Postfix 查询表条目，其中包含你要用于发送电子邮件账户的地址和密码：# MY_EMAIL_ADDRESS=glb@gmail.com # MY_EMAIL_PASSWORD=abcdefghijklmnop # MY_SMTP_SERVER=smtp.gmail.com # MY_SMTP_SERVER_PORT=587 # echo "[$MY_SMTP_SERVER]:$MY_SMTP_SERVER_PORT $MY_EMAIL_ADDRESS:$MY_EMAIL_PASSWORD" >> /etc/postfix/password_maps # chmod 600 /etc/postfix/password_maps # unset MY_EMAIL_PASSWORD # history -c如果你使用的是 Gmail 账户，那么你需要为 Postfix 配置一个“应用程序密码”而不是使用你的 Gmail 密码。有关配置应用程序密码的说明，参阅“使用应用程序密码登录”。接下来，你必须对 Postfix 查询表运行 postmap 命令，以创建或更新 Postfix 实际使用的文件的散列版本：# postmap /etc/postfix/password_maps散列后的版本将具有相同的文件名，但后缀为 .db。更新 main.cf 文件更新 Postfix 的 main.cf 配置文件，以引用刚刚创建 Postfix 查询表。编辑该文件并添加以下行：relayhost = smtp.gmail.com:587 smtp_tls_security_level = verify smtp_tls_mandatory_ciphers = high smtp_tls_verify_cert_match = hostname smtp_sasl_auth_enable = yes smtp_sasl_security_options = noanonymous smtp_sasl_password_maps = hash:/etc/postfix/password_maps这里假设你使用 Gmail 作为 relayhost 设置，但是你可以用正确的主机名和端口替换系统应该将邮件发送到的邮件主机。有关上述配置选项的最新详细信息，参考 man 帮助：$ man postconf.5启用、启动和测试 Postfix更新 main.cf 文件后，启用并启动 Postfix 服务：# systemctl enable --now postfix.service然后，你可以使用 exit 命令或 Ctrl+D 以 root 身份退出 sudo 会话。你现在应该能够使用 mail 命令测试你的配置：$ echo 'It worked!' | mail -s "Test: $(date)" glb@gmail.com更新服务如果你安装了像 logwatch、mdadm、fail2ban、apcupsd 或 certwatch 这样的服务，你现在可以更新它们的配置，以便它们的电子邮件通知转到你的 Internet 电子邮件地址。另外，你可能希望将发送到本地系统 root 账户的所有电子邮件都转到互联网电子邮件地址中，将以下行添加到系统的 /etc/alises 文件中（你需要使用 sudo 编辑此文件，或首先切换到 root 账户）：root: glb+root@gmail.com现在运行此命令重新读取别名：# newaliases提示: 如果你使用的是 Gmail，那么你可以在用户名和 @ 符号之间添加字母数字标记，如上所示，以便更轻松地识别和过滤从计算机收到的电子邮件。常用命令查看邮件队列：$ mailq清除队列中的所有电子邮件：# postsuper -d ALL过滤设置，以获得感兴趣的值：$ postconf | grep "^relayhost\|^smtp_"查看 postfix/smtp 日志：$ journalctl --no-pager -t postfix/smtp进行配置更改后重新加载 postfix：$ systemctl reload postfixvia: https://fedoramagazine.org/use-postfix-to-get-email-from-your-fedora-system/作者：Gregory Bartholomew 选题：lujun9972 译者：MjSeven 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
本文包含一些示例命令，向你展示如何使用 dd 命令粗略估计硬盘驱动器和 RAID 阵列的性能。准确的测量必须考虑诸如写入放大和系统调用开销之类的事情，本指南不会考虑这些。对于可能提供更准确结果的工具，你可能需要考虑使用 hdparm。为了分解与文件系统相关的性能问题，这些示例显示了如何通过直接读取和写入块设备来在块级测试驱动器和阵列的性能。警告：写入测试将会销毁用来运行测试的块设备上的所有数据。不要对包含你想要保留的数据的任何设备运行这些测试！四个测试下面是四个示例 dd 命令，可用于测试块设备的性能：1、 从 $MY_DISK 读取的一个进程：# dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache2、写入到 $MY_DISK 的一个进程：# dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct3、从 $MY_DISK 并发读取的两个进程：# (dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache &); (dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache skip=200 &)4、 并发写入到 $MY_DISK 的两个进程：# (dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct &); (dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct skip=200 &)执行读写测试时，相应的 iflag=nocache 和 oflag=direct 参数非常重要，因为没有它们，dd 命令有时会显示从内存中传输数据的结果速度，而不是从硬盘。bs 和 count 参数的值有些随意，我选择的值应足够大，以便在大多数情况下为当前硬件提供合适的平均值。null 和 zero 设备在读写测试中分别用于目标和源，因为它们足够快，不会成为性能测试中的限制因素。并发读写测试中第二个 dd 命令的 skip=200 参数是为了确保 dd 的两个副本在硬盘驱动器的不同区域上运行。16 个示例下面是演示，显示针对以下四个块设备中之一运行上述四个测试中的各个结果：MY_DISK=/dev/sda2（用在示例 1-X 中）MY_DISK=/dev/sdb2（用在示例 2-X 中）MY_DISK=/dev/md/stripped（用在示例 3-X 中）MY_DISK=/dev/md/mirrored（用在示例 4-X 中）首先将计算机置于救援模式，以减少后台服务的磁盘 I/O 随机影响测试结果的可能性。警告：这将关闭所有非必要的程序和服务。在运行这些命令之前，请务必保存你的工作。你需要知道 root 密码才能进入救援模式。passwd 命令以 root 用户身份运行时，将提示你（重新）设置 root 帐户密码。$ sudo -i # passwd # setenforce 0 # systemctl rescue你可能还想暂时禁止将日志记录到磁盘：# sed -r -i.bak 's/^#?Storage=.*/Storage=none/' /etc/systemd/journald.conf # systemctl restart systemd-journald.service如果你有交换设备，可以暂时禁用它并用于执行后面的测试：# swapoff -a # MY_DEVS=$(mdadm --detail /dev/md/swap | grep active | grep -o "/dev/sd.*") # mdadm --stop /dev/md/swap # mdadm --zero-superblock $MY_DEVS示例 1-1 （从 sda 读取）# MY_DISK=$(echo $MY_DEVS | cut -d ' ' -f 1) # dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 1.7003 s, 123 MB/s示例 1-2 （写入到 sda）# MY_DISK=$(echo $MY_DEVS | cut -d ' ' -f 1) # dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 1.67117 s, 125 MB/s示例 1-3 （从 sda 并发读取）# MY_DISK=$(echo $MY_DEVS | cut -d ' ' -f 1) # (dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache &); (dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache skip=200 &)200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 3.42875 s, 61.2 MB/s 200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 3.52614 s, 59.5 MB/s示例 1-4 （并发写入到 sda）# MY_DISK=$(echo $MY_DEVS | cut -d ' ' -f 1) # (dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct &); (dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct skip=200 &)200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 3.2435 s, 64.7 MB/s 200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 3.60872 s, 58.1 MB/s示例 2-1 （从 sdb 读取）# MY_DISK=$(echo $MY_DEVS | cut -d ' ' -f 2) # dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 1.67285 s, 125 MB/s示例 2-2 （写入到 sdb）# MY_DISK=$(echo $MY_DEVS | cut -d ' ' -f 2) # dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 1.67198 s, 125 MB/s示例 2-3 （从 sdb 并发读取）# MY_DISK=$(echo $MY_DEVS | cut -d ' ' -f 2) # (dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache &); (dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache skip=200 &)200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 3.52808 s, 59.4 MB/s 200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 3.57736 s, 58.6 MB/s示例 2-4 （并发写入到 sdb）# MY_DISK=$(echo $MY_DEVS | cut -d ' ' -f 2) # (dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct &); (dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct skip=200 &)200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 3.7841 s, 55.4 MB/s 200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 3.81475 s, 55.0 MB/s示例 3-1 （从 RAID0 读取）# mdadm --create /dev/md/stripped --homehost=any --metadata=1.0 --level=0 --raid-devices=2 $MY_DEVS # MY_DISK=/dev/md/stripped # dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 0.837419 s, 250 MB/s示例 3-2 （写入到 RAID0）# MY_DISK=/dev/md/stripped # dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 0.823648 s, 255 MB/s示例 3-3 （从 RAID0 并发读取）# MY_DISK=/dev/md/stripped # (dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache &); (dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache skip=200 &)200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 1.31025 s, 160 MB/s 200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 1.80016 s, 116 MB/s示例 3-4 （并发写入到 RAID0）# MY_DISK=/dev/md/stripped # (dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct &); (dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct skip=200 &)200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 1.65026 s, 127 MB/s 200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 1.81323 s, 116 MB/s示例 4-1 （从 RAID1 读取）# mdadm --stop /dev/md/stripped # mdadm --create /dev/md/mirrored --homehost=any --metadata=1.0 --level=1 --raid-devices=2 --assume-clean $MY_DEVS # MY_DISK=/dev/md/mirrored # dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 1.74963 s, 120 MB/s示例 4-2 （写入到 RAID1）# MY_DISK=/dev/md/mirrored # dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 1.74625 s, 120 MB/s示例 4-3 （从 RAID1 并发读取）# MY_DISK=/dev/md/mirrored # (dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache &); (dd if=$MY_DISK of=/dev/null bs=1MiB count=200 iflag=nocache skip=200 &)200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 1.67171 s, 125 MB/s 200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 1.67685 s, 125 MB/s示例 4-4 （并发写入到 RAID1）# MY_DISK=/dev/md/mirrored # (dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct &); (dd if=/dev/zero of=$MY_DISK bs=1MiB count=200 oflag=direct skip=200 &)200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 4.09666 s, 51.2 MB/s 200+0 records in 200+0 records out 209715200 bytes (210 MB, 200 MiB) copied, 4.1067 s, 51.1 MB/s恢复交换设备和日志配置# mdadm --stop /dev/md/stripped /dev/md/mirrored # mdadm --create /dev/md/swap --homehost=any --metadata=1.0 --level=1 --raid-devices=2 $MY_DEVS # mkswap /dev/md/swap # swapon -a # mv /etc/systemd/journald.conf.bak /etc/systemd/journald.conf # systemctl restart systemd-journald.service # reboot结果解读示例 1-1、1-2、2-1 和 2-2 表明我的每个驱动器以大约 125 MB/s 的速度读写。示例 1-3、1-4、2-3 和 2-4 表明，当在同一驱动器上并行完成两次读取或写入时，每个进程的驱动器带宽大约为一半（60 MB/s）。3-X 示例显示了将两个驱动器放在 RAID0（数据条带化）阵列中的性能优势。在所有情况下，这些数字表明 RAID0 阵列的执行速度是任何一个驱动器能够独立提供的速度的两倍。相应的是，丢失所有内容的可能性也是两倍，因为每个驱动器只包含一半的数据。一个三个驱动器阵列的执行速度是单个驱动器的三倍（所有驱动器规格都相同），但遭受灾难性故障的可能也是三倍。4-X 示例显示 RAID1（数据镜像）阵列的性能类似于单个磁盘的性能，除了多个进程同时读取的情况（示例4-3）。在多个进程读取的情况下，RAID1 阵列的性能类似于 RAID0 阵列的性能。这意味着你将看到 RAID1 的性能优势，但仅限于进程同时读取时。例如，当你在前台使用 Web 浏览器或电子邮件客户端时，进程会尝试访问后台中的大量文件。RAID1 的主要好处是，如果驱动器出现故障，你的数据不太可能丢失。故障排除如果上述测试未按预期执行，则可能是驱动器坏了或出现故障。大多数现代硬盘都内置了自我监控、分析和报告技术（SMART）。如果你的驱动器支持它，smartctl 命令可用于查询你的硬盘驱动器的内部统计信息：# smartctl --health /dev/sda # smartctl --log=error /dev/sda # smartctl -x /dev/sda另一种可以调整 PC 以获得更好性能的方法是更改 I/O 调度程序。Linux 系统支持多个 I/O 调度程序，Fedora 系统的当前默认值是 deadline 调度程序的 multiqueue 变体。默认情况下它的整体性能非常好，并且对于具有许多处理器和大型磁盘阵列的大型服务器，其扩展性极为出色。但是，有一些更专业的调度程序在某些条件下可能表现更好。要查看驱动器正在使用的 I/O 调度程序，请运行以下命令：$ for i in /sys/block/sd?/queue/scheduler; do echo "$i: $(<$i)"; done你可以通过将所需调度程序的名称写入 /sys/block/<device name>/queue/scheduler 文件来更改驱动器的调度程序：# echo bfq > /sys/block/sda/queue/scheduler你可以通过为驱动器创建 udev 规则来永久更改它。以下示例显示了如何创建将所有的旋转式驱动器设置为使用 BFQ I/O 调度程序的 udev 规则：# cat << END > /etc/udev/rules.d/60-ioscheduler-rotational.rules ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="bfq" END这是另一个设置所有的固态驱动器使用 NOOP I/O 调度程序的示例：# cat << END > /etc/udev/rules.d/60-ioscheduler-solid-state.rules ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="none" END更改 I/O 调度程序不会影响设备的原始吞吐量，但通过优先考虑后台任务的带宽或消除不必要的块重新排序，可能会使你的 PC 看起来响应更快。via: https://fedoramagazine.org/check-storage-performance-with-dd/作者：Gregory Bartholomew 选题：lujun9972 译者：wxy 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
mdadm 是 多磁盘和设备管理 Multiple Disk and Device Administration 的缩写。它是一个命令行工具，可用于管理 Linux 上的软件 RAID 阵列。本文概述了使用它的基础知识。以下 5 个命令是你使用 mdadm 的基础功能：创建 RAID 阵列：mdadm --create /dev/md/test --homehost=any --metadata=1.0 --level=1 --raid-devices=2 /dev/sda1 /dev/sdb1组合（并启动）RAID 阵列：mdadm --assemble /dev/md/test /dev/sda1 /dev/sdb1停止 RAID 阵列：mdadm --stop /dev/md/test删除 RAID 阵列：mdadm --zero-superblock /dev/sda1 /dev/sdb1检查所有已组合的 RAID 阵列的状态：cat /proc/mdstat功能说明mdadm –create上面的创建命令除了 -create 参数自身和设备名之外，还包括了四个参数：1、–homehost：默认情况下，mdadm 将你的计算机名保存为 RAID 阵列的属性。如果你的计算机名与存储的名称不匹配，则阵列将不会自动组合。此功能在共享硬盘的服务器群集中很有用，因为如果多个服务器同时尝试访问同一驱动器，通常会发生文件系统损坏。名称 any 是保留字段，并禁用 -homehost 限制。2、 –metadata：-mdadm 保留每个 RAID 设备的一小部分空间，以存储有关 RAID 阵列本身的信息。 -metadata 参数指定信息的格式和位置。1.0 表示使用版本 1 格式，并将元数据存储在设备的末尾。3、–level：-level 参数指定数据应如何在底层设备之间分布。级别 1 表示每个设备应包含所有数据的完整副本。此级别也称为磁盘镜像。4、–raid-devices：-raid-devices 参数指定将用于创建 RAID 阵列的设备数。通过将 -level=1（镜像）与 -metadata=1.0 （将元数据存储在设备末尾）结合使用，可以创建一个 RAID1 阵列，如果不通过 mdadm 驱动访问，那么它的底层设备会正常显示。这在灾难恢复的情况下很有用，因为即使新系统不支持 mdadm 阵列，你也可以访问该设备。如果程序需要在 mdadm 可用之前以只读访问底层设备时也很有用。例如，计算机中的 UEFI 固件可能需要在启动 mdadm 之前从 ESP 读取引导加载程序。mdadm –assemble如果成员设备丢失或损坏，上面的组合命令将会失败。要强制 RAID 阵列在其中一个成员丢失时进行组合并启动，请使用以下命令：# mdadm --assemble --run /dev/md/test /dev/sda1其他重要说明避免直接写入底层是 RAID1 的设备。这导致设备不同步，并且 mdadm 不会知道它们不同步。如果你访问了在其他地方被修改了设备的某个 RAID1 阵列，则可能导致文件系统损坏。如果你在其他地方修改 RAID1 设备并需要强制阵列重新同步，请从要覆盖的设备中删除 mdadm 元数据，然后将其重新添加到阵列，如下所示：# mdadm --zero-superblock /dev/sdb1 # mdadm --assemble --run /dev/md/test /dev/sda1 # mdadm /dev/md/test --add /dev/sdb1以上用 sda1 的内容完全覆盖 sdb1 的内容。要指定在计算机启动时自动激活的 RAID 阵列，请创建 /etc/mdadm.conf 配置。有关最新和详细信息，请查看手册页：$ man mdadm $ man mdadm.conf本系列的下一篇文章将展示如何将现有的单磁盘 Linux 系统变为镜像磁盘安装，这意味着即使其中一个硬盘突然停止工作，系统仍将继续运行！via: https://fedoramagazine.org/managing-raid-arrays-with-mdadm/作者：Gregory Bartholomew 选题：lujun9972 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
Roderick W. Smith 的 sgdisk 命令可在命令行中管理硬盘的分区。下面将介绍使用它所需的基础知识。使用 sgdisk 的大多数基本功能只需要了解以下六个参数：1、-p 打印 分区表：# sgdisk -p /dev/sda2、 -d x 删除 分区 x：# sgdisk -d 1 /dev/sda3、 -n x:y:z 创建一个编号 x 的新分区，从 y 开始，从 z 结束：# sgdisk -n 1:1MiB:2MiB /dev/sda4、-c x:y 更改分区 x 的名称为 y：# sgdisk -c 1:grub /dev/sda5、-t x:y 将分区 x 的类型更改为 y：# sgdisk -t 1:ef02 /dev/sda6、–list-types 列出分区类型代码：# sgdisk --list-types如你在上面的例子中所见，大多数命令都要求将要操作的硬盘的设备文件名指定为最后一个参数。可以组合上面的参数，这样你可以一次定义所有分区：# sgdisk -n 1:1MiB:2MiB -t 1:ef02 -c 1:grub /dev/sda在值的前面加上 + 或 – 符号，可以为某些字段指定相对值。如果你使用相对值，sgdisk 会为你做数学运算。例如，上面的例子可以写成：# sgdisk -n 1:1MiB:+1MiB -t 1:ef02 -c 1:grub /dev/sda0 值对于以下几个字段有特殊意义：对于分区号字段，0 表示应使用下一个可用编号（编号从 1 开始）。对于起始地址字段，0 表示使用最大可用空闲块的头。硬盘开头的一些空间始终保留给分区表本身。对于结束地址字段，0 表示使用最大可用空闲块的末尾。通过在适当的字段中使用 0 和相对值，你可以创建一系列分区，而无需预先计算任何绝对值。例如，如果在一块空白硬盘中，以下 sgdisk 命令序列将创建典型 Linux 安装所需的所有基本分区：# sgdisk -n 0:0:+1MiB -t 0:ef02 -c 0:grub /dev/sda # sgdisk -n 0:0:+1GiB -t 0:ea00 -c 0:boot /dev/sda # sgdisk -n 0:0:+4GiB -t 0:8200 -c 0:swap /dev/sda # sgdisk -n 0:0:0 -t 0:8300 -c 0:root /dev/sda上面的例子展示了如何为基于 BIOS 的计算机分区硬盘。基于 UEFI 的计算机上不需要 grub 分区。由于 sgdisk 在上面的示例中为你计算了所有绝对值，因此你可以在基于 UEFI 的计算机上跳过第一个命令，并且可以无需修改即可运行其余命令。同样，你可以跳过创建交换分区，并且不需要修改其余命令。还有使用一个命令删除硬盘上所有分区的快捷方式：# sgdisk --zap-all /dev/sda关于最新和详细信息，请查看手册页：$ man sgdiskvia: https://fedoramagazine.org/managing-partitions-with-sgdisk/作者：Gregory Bartholomew 选题：lujun9972 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
在本系列教程中所构建的网络引导服务器有一个很重要的限制，那就是所提供的操作系统镜像是只读的。一些使用场景或许要求终端用户能够修改操作系统镜像。例如，一些教师或许希望学生能够安装和配置一些像 MariaDB 和 Node.js 这样的包来做为他们课程练习的一部分。可写镜像的另外的好处是，终端用户“私人定制”的操作系统，在下次不同的工作站上使用时能够“跟着”他们。修改 Bootmenu 应用程序以使用 HTTPS为 bootmenu 应用程序创建一个自签名的证书：$ sudo -i # MY_NAME=$(</etc/hostname) # MY_TLSD=/opt/bootmenu/tls # mkdir $MY_TLSD # openssl req -newkey rsa:2048 -nodes -keyout $MY_TLSD/$MY_NAME.key -x509 -days 3650 -out $MY_TLSD/$MY_NAME.pem验证你的证书的值。确保 Subject 行中 CN 的值与你的 iPXE 客户端连接你的网络引导服务器所使用的 DNS 名字是相匹配的：# openssl x509 -text -noout -in $MY_TLSD/$MY_NAME.pem接下来，更新 bootmenu 应用程序去监听 HTTPS 端口和新创建的证书及密钥：# sed -i "s#listen => .*#listen => ['https://$MY_NAME:443?cert=$MY_TLSD/$MY_NAME.pem\&key=$MY_TLSD/$MY_NAME.key\&ciphers=AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA'],#" /opt/bootmenu/bootmenu.conf注意 iPXE 当前支持的 加密算法是有限制的。GnuTLS 要求 “CAPDACREAD\_SEARCH” 能力，因此将它添加到 bootmenu 应用程序的 systemd 服务：# sed -i '/^AmbientCapabilities=/ s/$/ CAP_DAC_READ_SEARCH/' /etc/systemd/system/bootmenu.service # sed -i 's/Serves iPXE Menus over HTTP/Serves iPXE Menus over HTTPS/' /etc/systemd/system/bootmenu.service # systemctl daemon-reload现在，在防火墙中为 bootmenu 服务添加一个例外规则并重启动该服务：# MY_SUBNET=192.0.2.0 # MY_PREFIX=24 # firewall-cmd --add-rich-rule="rule family='ipv4' source address='$MY_SUBNET/$MY_PREFIX' service name='https' accept" # firewall-cmd --runtime-to-permanent # systemctl restart bootmenu.service使用 wget 去验证是否工作正常：$ MY_NAME=server-01.example.edu $ MY_TLSD=/opt/bootmenu/tls $ wget -q --ca-certificate=$MY_TLSD/$MY_NAME.pem -O - https://$MY_NAME/menu添加 HTTPS 到 iPXE更新 init.ipxe 去使用 HTTPS。接着使用选项重新编译 ipxe 引导加载器，以便它包含和信任你为 bootmenu 应用程序创建的自签名证书：$ echo '#define DOWNLOAD_PROTO_HTTPS' >> $HOME/ipxe/src/config/local/general.h $ sed -i 's/^chain http:/chain https:/' $HOME/ipxe/init.ipxe $ cp $MY_TLSD/$MY_NAME.pem $HOME/ipxe $ cd $HOME/ipxe/src $ make clean $ make bin-x86_64-efi/ipxe.efi EMBED=../init.ipxe CERT="../$MY_NAME.pem" TRUST="../$MY_NAME.pem"你现在可以将启用了 HTTPS 的 iPXE 引导加载器复制到你的客户端上，并测试它能否正常工作：$ cp $HOME/ipxe/src/bin-x86_64-efi/ipxe.efi $HOME/esp/efi/boot/bootx64.efi添加用户验证到 Mojolicious 中为 bootmenu 应用程序创建一个 PAM 服务定义：# dnf install -y pam_krb5 # echo 'auth required pam_krb5.so' > /etc/pam.d/bootmenu添加一个库到 bootmenu 应用程序中，它使用 Authen-PAM 的 Perl 模块去执行用户验证：# dnf install -y perl-Authen-PAM; # MY_MOJO=/opt/bootmenu # mkdir $MY_MOJO/lib # cat << 'END' > $MY_MOJO/lib/PAM.pm package PAM; use Authen::PAM; sub auth { my $success = 0; my $username = shift; my $password = shift; my $callback = sub { my @res; while (@_) { my $code = shift; my $msg = shift; my $ans = ""; $ans = $username if ($code == PAM_PROMPT_ECHO_ON()); $ans = $password if ($code == PAM_PROMPT_ECHO_OFF()); push @res, (PAM_SUCCESS(), $ans); } push @res, PAM_SUCCESS(); return @res; }; my $pamh = new Authen::PAM('bootmenu', $username, $callback); { last unless ref $pamh; last unless $pamh->pam_authenticate() == PAM_SUCCESS; $success = 1; } return $success; } return 1; END以上的代码是一字不差是从 Authen::PAM::FAQ 的 man 页面中复制来的。重定义 bootmenu 应用程序，以使它仅当提供了有效的用户名和密码之后返回一个网络引导模板：# cat << 'END' > $MY_MOJO/bootmenu.pl #!/usr/bin/env perl use lib 'lib'; use PAM; use Mojolicious::Lite; use Mojolicious::Plugins; use Mojo::Util ('url_unescape'); plugin 'Config'; get '/menu'; get '/boot' => sub { my $c = shift; my $instance = $c->param('instance'); my $username = $c->param('username'); my $password = $c->param('password'); my $template = 'menu'; { last unless $instance =~ /^fc[[:digit:]]{2}$/; last unless $username =~ /^[[:alnum:]]+$/; last unless PAM::auth($username, url_unescape($password)); $template = $instance; } return $c->render(template => $template); }; app->start; ENDbootmenu 应用程序现在查找 lib 命令去找到相应的 WorkingDirectory。但是，默认情况下，对于 systemd 单元它的工作目录设置为服务器的 root 目录。因此，你必须更新 systemd 单元去设置 WorkingDirectory 为 bootmenu 应用程序的根目录：# sed -i "/^RuntimeDirectory=/ a WorkingDirectory=$MY_MOJO" /etc/systemd/system/bootmenu.service # systemctl daemon-reload更新模块去使用重定义后的 bootmenu 应用程序：# cd $MY_MOJO/templates # MY_BOOTMENU_SERVER=$(</etc/hostname) # MY_FEDORA_RELEASES="28 29" # for i in $MY_FEDORA_RELEASES; do echo '#!ipxe' > fc$i.html.ep; grep "^kernel\|initrd" menu.html.ep | grep "fc$i" >> fc$i.html.ep; echo "boot || chain https://$MY_BOOTMENU_SERVER/menu" >> fc$i.html.ep; sed -i "/^:f$i$/,/^boot /c :f$i\nlogin\nchain https://$MY_BOOTMENU_SERVER/boot?instance=fc$i\&username=\${username}\&password=\${password:uristring} || goto failed" menu.html.ep; done上面的最后的命令将生成类似下面的三个文件：menu.html.ep：#!ipxe set timeout 5000 :menu menu iPXE Boot Menu item --key 1 lcl 1. Microsoft Windows 10 item --key 2 f29 2. RedHat Fedora 29 item --key 3 f28 3. RedHat Fedora 28 choose --timeout ${timeout} --default lcl selected || goto shell set timeout 0 goto ${selected} :failed echo boot failed, dropping to shell... goto shell :shell echo type 'exit' to get the back to the menu set timeout 0 shell goto menu :lcl exit :f29 login chain https://server-01.example.edu/boot?instance=fc29&username=${username}&password=${password:uristring} || goto failed :f28 login chain https://server-01.example.edu/boot?instance=fc28&username=${username}&password=${password:uristring} || goto failedfc29.html.ep：#!ipxe kernel --name kernel.efi ${prefix}/vmlinuz-4.19.5-300.fc29.x86_64 initrd=initrd.img ro ip=dhcp rd.peerdns=0 nameserver=192.0.2.91 nameserver=192.0.2.92 root=/dev/disk/by-path/ip-192.0.2.158:3260-iscsi-iqn.edu.example.server-01:fc29-lun-1 netroot=iscsi:192.0.2.158::::iqn.edu.example.server-01:fc29 console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet initrd --name initrd.img ${prefix}/initramfs-4.19.5-300.fc29.x86_64.img boot || chain https://server-01.example.edu/menufc28.html.ep：#!ipxe kernel --name kernel.efi ${prefix}/vmlinuz-4.19.3-200.fc28.x86_64 initrd=initrd.img ro ip=dhcp rd.peerdns=0 nameserver=192.0.2.91 nameserver=192.0.2.92 root=/dev/disk/by-path/ip-192.0.2.158:3260-iscsi-iqn.edu.example.server-01:fc28-lun-1 netroot=iscsi:192.0.2.158::::iqn.edu.example.server-01:fc28 console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet initrd --name initrd.img ${prefix}/initramfs-4.19.3-200.fc28.x86_64.img boot || chain https://server-01.example.edu/menu现在，重启动 bootmenu 应用程序，并验证用户认证是否正常工作：# systemctl restart bootmenu.service使得 iSCSI Target 可写现在，用户验证通过 iPXE 可以正常工作，在用户连接时，你可以按需在只读镜像的上面创建每用户可写的 overlay 叠加层 。使用一个 写时复制 的叠加层与简单地为每个用户复制原始镜像相比有三个好处：副本创建非常快。这样就可以按需创建。副本并不增加服务器上的磁盘使用。除了原始镜像之外，仅存储用户写入个人镜像的内容。由于每个副本的扇区大多都是服务器的存储器上的相同扇区，在随后的用户访问这些操作系统的副本时，它们可能已经加载到内存中，这样就提升了服务器的性能，因为对内存的访问速度要比磁盘 I/O 快得多。使用写时复制的一个潜在隐患是，一旦叠加层创建后，叠加层之下的镜像就不能再改变。如果它们改变，所有它们之上的叠加层将出错。因此，叠加层必须被删除并用新的、空白的进行替换。即便只是简单地以读写模式加载的镜像，也可能因为某些文件系统更新导致叠加层出错。由于这个隐患，如果原始镜像被修改将导致叠加层出错，因此运行下列的命令，将原始镜像标记为不可改变：# chattr +i </path/to/file>你可以使用 lsattr </path/to/file> 去查看不可改变标志，并可以使用 chattr -i </path/to/file> 取消设置不可改变标志。在设置了不可改变标志之后，即便是 root 用户或以 root 运行的系统进程也不修改或删除这个文件。停止 tgtd.service 之后，你就可以改变镜像文件：# systemctl stop tgtd.service当仍有连接打开的时候，运行这个命令一般需要一分钟或更长的时间。现在，移除只读的 iSCSI 出口。然后更新模板中的 readonly-root 配置文件，以使镜像不再是只读的：# MY_FC=fc29 # rm -f /etc/tgt/conf.d/$MY_FC.conf # TEMP_MNT=$(mktemp -d) # mount /$MY_FC.img $TEMP_MNT # sed -i 's/^READONLY=yes$/READONLY=no/' $TEMP_MNT/etc/sysconfig/readonly-root # sed -i 's/^Storage=volatile$/#Storage=auto/' $TEMP_MNT/etc/systemd/journald.conf # umount $TEMP_MNT将 journald 日志从发送到内存修改回缺省值（如果 /var/log/journal 存在的话记录到磁盘），因为一个用户报告说，他的客户端由于应用程序生成了大量的系统日志而产生内存溢出错误，导致它的客户端被卡住。而将日志记录到磁盘的负面影响是客户端产生了额外的写入流量，这将在你的网络引导服务器上可能增加一些没有必要的 I/O。你应该去决定到底使用哪个选择 —— 记录到内存还是记录到硬盘 —— 哪个更合适取决于你的环境。因为你的模板镜像在以后不能做任何的更改，因此在它上面设置不可更改标志，然后重启动 tgtd.service：# chattr +i /$MY_FC.img # systemctl start tgtd.service现在，更新 bootmenu 应用程序：# cat << 'END' > $MY_MOJO/bootmenu.pl #!/usr/bin/env perl use lib 'lib'; use PAM; use Mojolicious::Lite; use Mojolicious::Plugins; use Mojo::Util ('url_unescape'); plugin 'Config'; get '/menu'; get '/boot' => sub { my $c = shift; my $instance = $c->param('instance'); my $username = $c->param('username'); my $password = $c->param('password'); my $chapscrt; my $template = 'menu'; { last unless $instance =~ /^fc[[:digit:]]{2}$/; last unless $username =~ /^[[:alnum:]]+$/; last unless PAM::auth($username, url_unescape($password)); last unless $chapscrt = `sudo scripts/mktgt $instance $username`; $template = $instance; } return $c->render(template => $template, username => $username, chapscrt => $chapscrt); }; app->start; END新版本的 bootmenu 应用程序调用一个定制的 mktgt 脚本，如果成功，它将为每个它自己创建的新的 iSCSI 目标返回一个随机的 CHAP 密码。这个 CHAP 密码可以防止其它用户的 iSCSI 目标以间接方式挂载这个用户的目标。这个应用程序只有在用户密码认证成功之后才返回一个正确的 iSCSI 目标密码。mktgt 脚本要加 sudo 前缀来运行，因为它需要 root 权限去创建目标。$username 和 $chapscrt 变量也传递给 render 命令，因此在需要的时候，它们也能够被纳入到模板中返回给用户。接下来，更新我们的引导模板，以便于它们能够读取用户名和 chapscrt 变量，并传递它们到所属的终端用户。也要更新模板以 rw（读写）模式加载根文件系统：# cd $MY_MOJO/templates # sed -i "s/:$MY_FC/:$MY_FC-<%= \$username %>/g" $MY_FC.html.ep # sed -i "s/ netroot=iscsi:/ netroot=iscsi:<%= \$username %>:<%= \$chapscrt %>@/" $MY_FC.html.ep # sed -i "s/ ro / rw /" $MY_FC.html.ep运行上面的命令后，你应该会看到如下的引导模板：#!ipxe kernel --name kernel.efi ${prefix}/vmlinuz-4.19.5-300.fc29.x86_64 initrd=initrd.img rw ip=dhcp rd.peerdns=0 nameserver=192.0.2.91 nameserver=192.0.2.92 root=/dev/disk/by-path/ip-192.0.2.158:3260-iscsi-iqn.edu.example.server-01:fc29-<%= $username %>-lun-1 netroot=iscsi:<%= $username %>:<%= $chapscrt %>@192.0.2.158::::iqn.edu.example.server-01:fc29-<%= $username %> console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet initrd --name initrd.img ${prefix}/initramfs-4.19.5-300.fc29.x86_64.img boot || chain https://server-01.example.edu/menu注意：如果在 插入 变量后需要查看引导模板，你可以在 boot 命令之前，在它自己的行中插入 shell 命令。然后在你网络引导你的客户端时，iPXE 将在那里给你提供一个用于交互的 shell，你可以在 shell 中输入 imgstat 去查看传递到内核的参数。如果一切正确，你可以输入 exit 去退出 shell 并继续引导过程。现在，通过 sudo 允许 bootmenu 用户以 root 权限去运行 mktgt 脚本（仅这个脚本）：# echo "bootmenu ALL = NOPASSWD: $MY_MOJO/scripts/mktgt *" > /etc/sudoers.d/bootmenubootmenu 用户不应该写访问 mktgt 脚本或在它的家目录下的任何其它文件。在 /opt/bootmenu 目录下的所有文件的属主应该是 root，并且不应该被其它任何 root 以外的用户可写。sudo 在使用 systemd 的 DynamicUser 选项下不能正常工作，因此创建一个普通用户帐户，并设置 systemd 服务以那个用户运行：# useradd -r -c 'iPXE Boot Menu Service' -d /opt/bootmenu -s /sbin/nologin bootmenu # sed -i 's/^DynamicUser=true$/User=bootmenu/' /etc/systemd/system/bootmenu.service # systemctl daemon-reload最后，为写时复制覆盖创建一个目录，并创建管理 iSCSI 目标的 mktgt 脚本和它们的覆盖支持存储：# mkdir /$MY_FC.cow # mkdir $MY_MOJO/scripts # cat << 'END' > $MY_MOJO/scripts/mktgt #!/usr/bin/env perl # if another instance of this script is running, wait for it to finish "$ENV{FLOCKER}" eq 'MKTGT' or exec "env FLOCKER=MKTGT flock /tmp $0 @ARGV"; # use "RETURN" to print to STDOUT; everything else goes to STDERR by default open(RETURN, '>&', STDOUT); open(STDOUT, '>&', STDERR); my $instance = shift or die "instance not provided"; my $username = shift or die "username not provided"; my $img = "/$instance.img"; my $dir = "/$instance.cow"; my $top = "$dir/$username"; -f "$img" or die "'$img' is not a file"; -d "$dir" or die "'$dir' is not a directory"; my $base; die unless $base = `losetup --show --read-only --nooverlap --find $img`; chomp $base; my $size; die unless $size = `blockdev --getsz $base`; chomp $size; # create the per-user sparse file if it does not exist if (! -e "$top") { die unless system("dd if=/dev/zero of=$top status=none bs=512 count=0 seek=$size") == 0; } # create the copy-on-write overlay if it does not exist my $cow="$instance-$username"; my $dev="/dev/mapper/$cow"; if (! -e "$dev") { my $over; die unless $over = `losetup --show --nooverlap --find $top`; chomp $over; die unless system("echo 0 $size snapshot $base $over p 8 | dmsetup create $cow") == 0; } my $tgtadm = '/usr/sbin/tgtadm --lld iscsi'; # get textual representations of the iscsi targets my $text = `$tgtadm --op show --mode target`; my @targets = $text =~ /(?:^T.*\n)(?:^ .*\n)*/mg; # convert the textual representations into a hash table my $targets = {}; foreach (@targets) { my $tgt; my $sid; foreach (split /\n/) { /^Target (\d+)(?{ $tgt = $targets->{$^N} = [] })/; /I_T nexus: (\d+)(?{ $sid = $^N })/; /Connection: (\d+)(?{ push @{$tgt}, [ $sid, $^N ] })/; } } my $hostname; die unless $hostname = `hostname`; chomp $hostname; my $target = 'iqn.' . join('.', reverse split('\.', $hostname)) . ":$cow"; # find the target id corresponding to the provided target name and # close any existing connections to it my $tid = 0; foreach (@targets) { next unless /^Target (\d+)(?{ $tid = $^N }): $target$/m; foreach (@{$targets->{$tid}}) { die unless system("$tgtadm --op delete --mode conn --tid $tid --sid $_->[0] --cid $_->[1]") == 0; } } # create a new target if an existing one was not found if ($tid == 0) { # find an available target id my @ids = (0, sort keys %{$targets}); $tid = 1; while ($ids[$tid]==$tid) { $tid++ } # create the target die unless -e "$dev"; die unless system("$tgtadm --op new --mode target --tid $tid --targetname $target") == 0; die unless system("$tgtadm --op new --mode logicalunit --tid $tid --lun 1 --backing-store $dev") == 0; die unless system("$tgtadm --op bind --mode target --tid $tid --initiator-address ALL") == 0; } # (re)set the provided target's chap password my $password = join('', map(chr(int(rand(26))+65), 1..8)); my $accounts = `$tgtadm --op show --mode account`; if ($accounts =~ / $username$/m) { die unless system("$tgtadm --op delete --mode account --user $username") == 0; } die unless system("$tgtadm --op new --mode account --user $username --password $password") == 0; die unless system("$tgtadm --op bind --mode account --tid $tid --user $username") == 0; # return the new password to the iscsi target on stdout print RETURN $password; END # chmod +x $MY_MOJO/scripts/mktgt上面的脚本将做以下五件事情：创建 /<instance>.cow/<username> 稀疏文件（如果不存在的话）。创建 /dev/mapper/<instance>-<username> 设备节点作为 iSCSI 目标的写时复制支持存储（如果不存在的话）。创建 iqn.<reverse-hostname>:<instance>-<username> iSCSI 目标（如果不存在的话）。或者，如果已存在了，它将关闭任何已存在的连接，因为在任何时刻，镜像只能以只读模式从一个地方打开。它在 iqn.<reverse-hostname>:<instance>-<username> iSCSI 目标上（重新）设置 chap 密码为一个新的随机值。（如果前面的所有任务都成功的话）它在 标准输出 上显示新的 chap 密码。你应该可以在命令行上通过使用有效的测试参数来运行它，以测试 mktgt 脚本能否正常工作。例如：# echo `$MY_MOJO/scripts/mktgt fc29 jsmith`当你从命令行上运行时，mktgt 脚本应该会输出 iSCSI 目标的一个随意的八字符随机密码（如果成功的话）或者是出错位置的行号（如果失败的话）。有时候，你可能需要在不停止整个服务的情况下删除一个 iSCSI 目标。例如，一个用户可能无意中损坏了他的个人镜像，在那种情况下，你可能需要按步骤撤销上面的 mktgt 脚本所做的事情，以便于他下次登入时他将得到一个原始镜像。下面是用于撤销的 rmtgt 脚本，它以相反的顺序做了上面 mktgt 脚本所做的事情：# mkdir $HOME/bin # cat << 'END' > $HOME/bin/rmtgt #!/usr/bin/env perl @ARGV >= 2 or die "usage: $0 <instance> <username> [+d|+f]\n"; my $instance = shift; my $username = shift; my $rmd = ($ARGV[0] eq '+d'); #remove device node if +d flag is set my $rmf = ($ARGV[0] eq '+f'); #remove sparse file if +f flag is set my $cow = "$instance-$username"; my $hostname; die unless $hostname = `hostname`; chomp $hostname; my $tgtadm = '/usr/sbin/tgtadm'; my $target = 'iqn.' . join('.', reverse split('\.', $hostname)) . ":$cow"; my $text = `$tgtadm --op show --mode target`; my @targets = $text =~ /(?:^T.*\n)(?:^ .*\n)*/mg; my $targets = {}; foreach (@targets) { my $tgt; my $sid; foreach (split /\n/) { /^Target (\d+)(?{ $tgt = $targets->{$^N} = [] })/; /I_T nexus: (\d+)(?{ $sid = $^N })/; /Connection: (\d+)(?{ push @{$tgt}, [ $sid, $^N ] })/; } } my $tid = 0; foreach (@targets) { next unless /^Target (\d+)(?{ $tid = $^N }): $target$/m; foreach (@{$targets->{$tid}}) { die unless system("$tgtadm --op delete --mode conn --tid $tid --sid $_->[0] --cid $_->[1]") == 0; } die unless system("$tgtadm --op delete --mode target --tid $tid") == 0; print "target $tid deleted\n"; sleep 1; } my $dev = "/dev/mapper/$cow"; if ($rmd or ($rmf and -e $dev)) { die unless system("dmsetup remove $cow") == 0; print "device node $dev deleted\n"; } if ($rmf) { my $sf = "/$instance.cow/$username"; die "sparse file $sf not found" unless -e "$sf"; die unless system("rm -f $sf") == 0; die unless not -e "$sf"; print "sparse file $sf deleted\n"; } END # chmod +x $HOME/bin/rmtgt例如，使用上面的脚本去完全删除 fc29-jsmith 目标，包含它的支持存储设备节点和稀疏文件，可以按下列方式运行命令：# rmtgt fc29 jsmith +f一旦你验证 mktgt 脚本工作正常，你可以重启动 bootmenu 服务。下次有人从网络引导时，他们应该能够接收到一个他们可以写入的、可”私人定制“的网络引导镜像的副本：# systemctl restart bootmenu.service现在，就像下面的截屏示范的那样，用户应该可以修改根文件系统了：via: https://fedoramagazine.org/how-to-build-a-netboot-server-part-4/作者：Gregory Bartholomew 选题：lujun9972 译者：qhwdw 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
在 如何构建一台网络引导服务器（一） 中，我们提供了一个极简的 iPXE 引导脚本来引导你的网络引导镜像。许多用户除了使用网络引导镜像外，可能在机器本地也有一个操作系统。但是使用常见的工作站的 BIOS 去切换引导加载器是很笨拙的。在本系列文件的第三部分，我们将向你展示如何设置一个更复杂的 iPXE 配置。它将允许终端用户以更容易的方式去选择引导哪个操作系统。它也可以配置为让系统管理员从一台中央服务器来统一管理引导菜单。一个交互式 iPXE 引导菜单下面这些命令重定义了网络引导镜像的 boot.cfg 来作为一个交互式的 iPXE 引导菜单，并使用了一个 5 秒倒计时的定时器：$ MY_FVER=29 $ MY_KRNL=$(ls -c /fc$MY_FVER/lib/modules | head -n 1) $ MY_DNS1=192.0.2.91 $ MY_DNS2=192.0.2.92 $ MY_NAME=server-01.example.edu $ MY_EMAN=$(echo $MY_NAME | tr '.' "\n" | tac | tr "\n" '.' | cut -b -${#MY_NAME}) $ MY_ADDR=$(host -t A $MY_NAME | awk '{print $4}') $ cat << END > $HOME/esp/linux/boot.cfg #!ipxe set timeout 5000 :menu menu iPXE Boot Menu item --key 1 lcl 1. Microsoft Windows 10 item --key 2 f$MY_FVER 2. RedHat Fedora $MY_FVER choose --timeout \${timeout} --default lcl selected || goto shell set timeout 0 goto \${selected} :failed echo boot failed, dropping to shell... goto shell :shell echo type 'exit' to get the back to the menu set timeout 0 shell goto menu :lcl exit :f$MY_FVER kernel --name kernel.efi \${prefix}/vmlinuz-$MY_KRNL initrd=initrd.img ro ip=dhcp rd.peerdns=0 nameserver=$MY_DNS1 nameserver=$MY_DNS2 root=/dev/disk/by-path/ip-$MY_ADDR:3260-iscsi-iqn.$MY_EMAN:fc$MY_FVER-lun-1 netroot=iscsi:$MY_ADDR::::iqn.$MY_EMAN:fc$MY_FVER console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet initrd --name initrd.img \${prefix}/initramfs-$MY_KRNL.img boot || goto failed END上述菜单有五个节：menu 定义了显示在屏幕上的实际菜单内容。failed 提示用户发生了错误，并将用户带到 shell 以错误错误。shell 提供了交互式命令提示符。你可以在引导菜单出现时按下 Esc 键进入，或者是 boot 命令失败时也会进入到命令提示符。lcl 包含一个提供给 iPXE 退出的简单命令，以及返还控制权给 BIOS。在 iPXE 之后，无论你希望缺省引导的设备（即：工作站的本地硬件）是什么，都必须在你的工作站的 BIOS 中正确地作为下一个引导设备列出来。f29 包含前面文章提到同一个网络引导代码，但使用最终的退出代码来替换掉 goto failed。从你的 $HOME/esp/linux 目录中复制更新后的 boot.cfg 到所有客户端系统的 ESP 中。如果一切顺利，你应该会看到类似下面图片的结果：一个服务器托管的引导菜单你可以添加到网络引导服务器的另一个特性是，能够从一台中央位置去管理所有客户端的引导菜单。这个特性尤其适用于批量安装（升级）一个新版本的操作系统。在你将新内核和新的 initramfs 复制到所有客户端的 ESP 之后，这个特性可以让你执行一种 原子事务) 去切换所有客户端到新操作系统。安装 Mojolicious：$ sudo -i # dnf install -y perl-Mojolicious定义 “bootmenu” 应用程序：# mkdir /opt/bootmenu # cat << END > /opt/bootmenu/bootmenu.pl #!/usr/bin/env perl use Mojolicious::Lite; use Mojolicious::Plugins; plugin 'Config'; get '/menu'; app->start; END # chmod 755 /opt/bootmenu/bootmenu.pl为 “bootmenu” 应用程序定义配置文件：# cat << END > /opt/bootmenu/bootmenu.conf { hypnotoad => { listen => ['http://*:80'], pid_file => '/run/bootmenu/bootmenu.pid', } } END这是一个非常简单的 Mojolicious 应用程序，它监听 80 端口，并且只回应对 /menu 的请求。如果你想快速了解 Mojolicious 能做什么，运行 man Mojolicious::Guides::Growing 去查看手册。按 Q 键退出手册。将 boot.cfg 移到我们的网络引导应用程序中作为一个名为 menu.html.ep 的模板：# mkdir /opt/bootmenu/templates # mv $HOME/esp/linux/boot.cfg /opt/bootmenu/templates/menu.html.ep定义一个 systemd 服务去管理引导菜单应用程序：# cat << END > /etc/systemd/system/bootmenu.service [Unit] Description=Serves iPXE Menus over HTTP After=network-online.target [Service] Type=forking DynamicUser=true RuntimeDirectory=bootmenu PIDFile=/run/bootmenu/bootmenu.pid ExecStart=/usr/bin/hypnotoad /opt/bootmenu/bootmenu.pl ExecReload=/usr/bin/hypnotoad /opt/bootmenu/bootmenu.pl AmbientCapabilities=CAP_NET_BIND_SERVICE KillMode=process [Install] WantedBy=multi-user.target END在本地防火墙中为 HTTP 服务添加一个例外规则，并启动 bootmenu 服务：# firewall-cmd --add-service http # firewall-cmd --runtime-to-permanent # systemctl enable bootmenu.service # systemctl start bootmenu.service用 wget 测试它：$ sudo dnf install -y wget $ MY_BOOTMENU_SERVER=server-01.example.edu $ wget -q -O - http://$MY_BOOTMENU_SERVER/menu以上的命令应该会输出类似下面的内容：#!ipxe set timeout 5000 :menu menu iPXE Boot Menu item --key 1 lcl 1. Microsoft Windows 10 item --key 2 f29 2. RedHat Fedora 29 choose --timeout ${timeout} --default lcl selected || goto shell set timeout 0 goto ${selected} :failed echo boot failed, dropping to shell... goto shell :shell echo type 'exit' to get the back to the menu set timeout 0 shell goto menu :lcl exit :f29 kernel --name kernel.efi ${prefix}/vmlinuz-4.19.4-300.fc29.x86_64 initrd=initrd.img ro ip=dhcp rd.peerdns=0 nameserver=192.0.2.91 nameserver=192.0.2.92 root=/dev/disk/by-path/ip-192.0.2.158:3260-iscsi-iqn.edu.example.server-01:fc29-lun-1 netroot=iscsi:192.0.2.158::::iqn.edu.example.server-01:fc29 console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet initrd --name initrd.img ${prefix}/initramfs-4.19.4-300.fc29.x86_64.img boot || goto failed现在，引导菜单服务器已经正常工作了，重新构建 ipxe.efi 引导加载器，使用一个初始化脚本指向它。第一步，先更新我们在本系列文章的第一部分中创建的 init.ipxe 脚本：$ MY_BOOTMENU_SERVER=server-01.example.edu $ cat << END > $HOME/ipxe/init.ipxe #!ipxe dhcp || exit set prefix file:///linux chain http://$MY_BOOTMENU_SERVER/menu || exit END现在，重新构建引导加载器：$ cd $HOME/ipxe/src $ make clean $ make bin-x86_64-efi/ipxe.efi EMBED=../init.ipxe将更新后的引导加载器复制到你的 ESP 中：$ cp $HOME/ipxe/src/bin-x86_64-efi/ipxe.efi $HOME/esp/efi/boot/bootx64.efi将更新后的引导加载器复制到所有的客户端中之后，以后更新引导菜单只需要简单地编辑 /opt/bootmenu/templates/menu.html.ep 文件，然后再运行如下命令：$ sudo systemctl restart bootmenu.service做一步的改变如果引导菜单服务器工作正常，在你的客户端系统上的 boot.cfg 文件将更长。比如，重新添加 Fedora 28 镜像到引导菜单中：$ sudo -i # MY_FVER=28 # MY_KRNL=$(ls -c /fc$MY_FVER/lib/modules | head -n 1) # MY_DNS1=192.0.2.91 # MY_DNS2=192.0.2.92 # MY_NAME=$(</etc/hostname) # MY_EMAN=$(echo $MY_NAME | tr '.' "\n" | tac | tr "\n" '.' | cut -b -${#MY_NAME}) # MY_ADDR=$(host -t A $MY_NAME | awk '{print $4}') # cat << END >> /opt/bootmenu/templates/menu.html.ep :f$MY_FVER kernel --name kernel.efi \${prefix}/vmlinuz-$MY_KRNL initrd=initrd.img ro ip=dhcp rd.peerdns=0 nameserver=$MY_DNS1 nameserver=$MY_DNS2 root=/dev/disk/by-path/ip-$MY_ADDR:3260-iscsi-iqn.$MY_EMAN:fc$MY_FVER-lun-1 netroot=iscsi:$MY_ADDR::::iqn.$MY_EMAN:fc$MY_FVER console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet initrd --name initrd.img \${prefix}/initramfs-$MY_KRNL.img boot || goto failed END # sed -i "/item --key 2/a item --key 3 f$MY_FVER 3. RedHat Fedora $MY_FVER" /opt/bootmenu/templates/menu.html.ep # systemctl restart bootmenu.service如果一切顺利，你的客户端下次引导时，应该看到如下图所示的结果：via: https://fedoramagazine.org/how-to-build-a-netboot-server-part-3/作者：Gregory Bartholomew 选题：lujun9972 译者：qhwdw 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
在 如何构建一台网络引导服务器（一） 的文章中，我们展示了如何创建一个网络引导镜像，在那个镜像中使用了一个名为 liveuser 帐户，它的家目录位于内存中，重启后家目录中的内容将全部消失。然而很多用户都希望机器重启后保存他们的文件和设置。因此，在本系列的第二部分，我们将向你展示如何在第一部分的基础上，重新配置网络引导镜像，以便 活动目录 中的用户帐户可以进行登录，然后从一个 NFS 服务器上自动挂载他们的家目录。本系列的第三部分，我们将向你展示网络引导客户端如何与中心化配置的 iPXE 引导菜单进行交互。设置使用 KRB5 认证的 NFS4 Home 目录按以前的文章 “使用 Kerberos 强化共享的 NFS Home 目录安全性” 的指导来做这个设置。删除 Liveuser 帐户删除本系列文章第一部分中创建的 liveuser 帐户：$ sudo -i # sed -i '/automaticlogin/Id' /fc28/etc/gdm/custom.conf # rm -f /fc28/etc/sudoers.d/liveuser # for i in passwd shadow group gshadow; do sed -i '/^liveuser:/d' /fc28/etc/$i; done配置 NTP、KRB5 和 SSSD接下来，我们需要将 NTP、KRB5 和 SSSD 的配置文件复制进客户端使用的镜像中，以便于它们能够使用同一个帐户：# MY_HOSTNAME=$(</etc/hostname) # MY_DOMAIN=${MY_HOSTNAME#*.} # dnf -y --installroot=/fc28 install ntp krb5-workstation sssd # cp /etc/ntp.conf /fc28/etc # chroot /fc28 systemctl enable ntpd.service # cp /etc/krb5.conf.d/${MY_DOMAIN%%.*} /fc28/etc/krb5.conf.d # cp /etc/sssd/sssd.conf /fc28/etc/sssd在已配置的识别服务的基础上，重新配置 sssd 提供认证服务：# sed -i '/services =/s/$/, pam/' /fc28/etc/sssd/sssd.conf另外，配置成确保客户端不能更改这个帐户密码：# sed -i '/id_provider/a \ \ ad_maximum_machine_account_password_age = 0' /fc28/etc/sssd/sssd.conf另外，复制 nfsnobody 的定义：# for i in passwd shadow group gshadow; do grep "^nfsnobody:" /etc/$i >> /fc28/etc/$i; done加入活动目录接下来，你将执行一个 chroot 将客户端镜像加入到活动目录。从删除预置在网络引导镜像中同名的计算机帐户开始：# MY_USERNAME=jsmith # MY_CLIENT_HOSTNAME=$(</fc28/etc/hostname) # adcli delete-computer "${MY_CLIENT_HOSTNAME%%.*}" -U "$MY_USERNAME"在网络引导镜像中如果有 krb5.keytab 文件，也删除它：# rm -f /fc28/etc/krb5.keytabchroot 到网络引导镜像中：# for i in dev dev/pts dev/shm proc sys run; do mount -o bind /$i /fc28/$i; done # chroot /fc28 /usr/bin/bash --login执行一个加入操作：# MY_USERNAME=jsmith # MY_HOSTNAME=$(</etc/hostname) # MY_DOMAIN=${MY_HOSTNAME#*.} # MY_REALM=${MY_DOMAIN^^} # MY_OU="cn=computers,dc=${MY_DOMAIN//./,dc=}" # adcli join $MY_DOMAIN --login-user="$MY_USERNAME" --computer-name="${MY_HOSTNAME%%.*}" --host-fqdn="$MY_HOSTNAME" --user-principal="host/$MY_HOSTNAME@$MY_REALM" --domain-ou="$MY_OU"现在登出 chroot，并清除 root 用户的命令历史：# logout # for i in run sys proc dev/shm dev/pts dev; do umount /fc28/$i; done # > /fc28/root/.bash_history安装和配置 PAM 挂载我们希望客户端登入后自动挂载用户家目录。为实现这个目的，我们将要使用 pam_mount 模块。安装和配置 pam_mount：# dnf install -y --installroot=/fc28 pam_mount # cat << END > /fc28/etc/security/pam_mount.conf.xml <?xml version="1.0" encoding="utf-8" ?> <pam_mount> <debug enable="0" /> <volume uid="1400000000-1499999999" fstype="nfs4" server="$MY_HOSTNAME" path="/home/%(USER)" mountpoint="/home/%(USER)" options="sec=krb5" /> <mkmountpoint enable="1" remove="0" /> <msg-authpw>Password:</msg-authpw> </pam_mount> END重新配置 PAM 去使用 pam_mount：# dnf install -y patch # cp -r /fc28/usr/share/authselect/default/sssd /fc28/etc/authselect/custom # echo 'initgroups: files' >> /fc28/etc/authselect/custom/sssd/nsswitch.conf # patch /fc28/etc/authselect/custom/sssd/system-auth << END @@ -12 +12,2 @@ -auth sufficient pam_sss.so forward_pass +auth requisite pam_mount.so {include if "with-pammount"} +auth sufficient pam_sss.so {if "with-pammount":use_first_pass|forward_pass} @@ -35,2 +36,3 @@ session required pam_unix.so +session optional pam_mount.so {include if "with-pammount"} session optional pam_sss.so END # patch /fc28/etc/authselect/custom/sssd/password-auth << END @@ -9 +9,2 @@ -auth sufficient pam_sss.so forward_pass +auth requisite pam_mount.so {include if "with-pammount"} +auth sufficient pam_sss.so {if "with-pammount":use_first_pass|forward_pass} @@ -32,2 +33,3 @@ session required pam_unix.so +session optional pam_mount.so {include if "with-pammount"} session optional pam_sss.so END # chroot /fc28 authselect select custom/sssd with-pammount --force另外，要确保从客户端上总是可解析 NFS 服务器的主机名：# MY_IP=$(host -t A $MY_HOSTNAME | awk '{print $4}') # echo "$MY_IP $MY_HOSTNAME ${MY_HOSTNAME%%.*}" >> /fc28/etc/hosts可选，允许所有用户可以使用 sudo：# echo '%users ALL=(ALL) NOPASSWD: ALL' > /fc28/etc/sudoers.d/users转换 NFS 根目录到一个 iSCSI 后备存储器在一个 nfsroot 连接建立之后，目前版本的 nfs-utils 可能很难为家目录建立一个从客户端到 NFS 服务器的第二个连接。当尝试去访问家目录时，客户端将被挂起。因此，为了共享网络引导镜像，我们将使用一个不同的协议（iSCSI）来规避这个问题。首先 chroot 到镜像中，重新配置它的 initramfs，让它从一个 iSCSI 根目录中去引导：# for i in dev dev/pts dev/shm proc sys run; do mount -o bind /$i /fc28/$i; done # chroot /fc28 /usr/bin/bash --login # dnf install -y iscsi-initiator-utils # sed -i 's/nfs/iscsi/' /etc/dracut.conf.d/netboot.conf # echo 'omit_drivers+=" qedi "' > /etc/dracut.conf.d/omit-qedi.conf # echo 'blacklist qedi' > /etc/modprobe.d/blacklist-qedi.conf # KERNEL=$(ls -c /lib/modules | head -n 1) # INITRD=$(find /boot -name 'init*' | grep -m 1 $KERNEL) # dracut -f $INITRD $KERNEL # logout # for i in run sys proc dev/shm dev/pts dev; do umount /fc28/$i; done # > /fc28/root/.bash_history在测试时，qedi 驱动会破坏 iSCSI，因此我们将它禁用。接着，创建一个 fc28.img 稀疏文件。这个稀疏文件代表 iSCSI 目标的后备存储器：# FC28_SIZE=$(du -ms /fc28 | cut -f 1) # dd if=/dev/zero of=/fc28.img bs=1MiB count=0 seek=$(($FC28_SIZE*2))（如果你有一个可使用的独立分区或磁盘驱动器，也可以用它，而不用再去创建这个稀疏文件了。）接着，使用一个文件系统去格式化镜像、挂载它、然后将网络引导镜像复制进去：# mkfs -t xfs -L NETROOT /fc28.img # TEMP_MNT=$(mktemp -d) # mount /fc28.img $TEMP_MNT # cp -a /fc28/* $TEMP_MNT # umount $TEMP_MNT在使用 SquashFS 测试时，客户端偶尔会出现小状况。似乎是因为 SquashFS 在多处理器客户端上没法执行随机 I/O。（更多内容见 squashfs 读取卡顿的奇怪案例）。如果你希望使用文件系统压缩来提升吞吐性能，ZFS 或许是个很好的选择。如果你对 iSCSI 服务器的吞吐性能要求非常高（比如，成百上千的客户端要连接它），可能需要使用带 负载均衡) 的 Ceph 集群了。更多相关内容，请查看 使用 HAProxy 和 Keepalived 负载均衡的 Ceph 对象网关。安装和配置 iSCSI为了给我们的客户端提供网络引导镜像，安装 scsi-target-utils 包：# dnf install -y scsi-target-utils配置 iSCSI 守护程序去提供 fc28.img 文件：# MY_REVERSE_HOSTNAME=$(echo $MY_HOSTNAME | tr '.' "\n" | tac | tr "\n" '.' | cut -b -${#MY_HOSTNAME}) # cat << END > /etc/tgt/conf.d/fc28.conf <target iqn.$MY_REVERSE_HOSTNAME:fc28> backing-store /fc28.img readonly 1 </target> END开头的 iqn. 是 /usr/lib/dracut/modules.d/40network/net-lib.sh 所需要的。添加一个防火墙例外，并启用和启动这个服务：# firewall-cmd --add-service=iscsi-target # firewall-cmd --runtime-to-permanent # systemctl enable tgtd.service # systemctl start tgtd.service你现在应该能够使用 tatadm 命令看到这个镜像共享了：# tgtadm --mode target --op show上述命令的输出应该类似如下的内容：Target 1: iqn.edu.example.server-01:fc28 System information: Driver: iscsi State: ready I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: IET 00010000 SCSI SN: beaf10 Size: 0 MB, Block size: 1 Online: Yes Removable media: No Prevent removal: No Readonly: No SWP: No Thin-provisioning: No Backing store type: null Backing store path: None Backing store flags: LUN: 1 Type: disk SCSI ID: IET 00010001 SCSI SN: beaf11 Size: 10488 MB, Block size: 512 Online: Yes Removable media: No Prevent removal: No Readonly: Yes SWP: No Thin-provisioning: No Backing store type: rdwr Backing store path: /fc28.img Backing store flags: Account information: ACL information: ALL现在，我们可以去删除本系列文章的第一部分中创建的 NFS 共享了：# rm -f /etc/exports.d/fc28.exports # exportfs -rv # umount /export/fc28 # rmdir /export/fc28 # sed -i '/^\/fc28 /d' /etc/fstab你也可以删除 /fc28 文件系统，但为了以后进一步更新，你可能需要保留它。更新 ESP 去使用 iSCSI 内核更新 ESP 去包含启用了 iSCSI 的 initramfs：$ rm -vf $HOME/esp/linux/*.fc28.* $ MY_KRNL=$(ls -c /fc28/lib/modules | head -n 1) $ cp $(find /fc28/lib/modules -maxdepth 2 -name 'vmlinuz' | grep -m 1 $MY_KRNL) $HOME/esp/linux/vmlinuz-$MY_KRNL $ cp $(find /fc28/boot -name 'init*' | grep -m 1 $MY_KRNL) $HOME/esp/linux/initramfs-$MY_KRNL.img更新 boot.cfg 文件去传递新的 root 和 netroot 参数：$ MY_NAME=server-01.example.edu $ MY_EMAN=$(echo $MY_NAME | tr '.' "\n" | tac | tr "\n" '.' | cut -b -${#MY_NAME}) $ MY_ADDR=$(host -t A $MY_NAME | awk '{print $4}') $ sed -i "s! root=[^ ]*! root=/dev/disk/by-path/ip-$MY_ADDR:3260-iscsi-iqn.$MY_EMAN:fc28-lun-1 netroot=iscsi:$MY_ADDR::::iqn.$MY_EMAN:fc28!" $HOME/esp/linux/boot.cfg现在，你只需要从 $HOME/esp/linux 目录中复制更新后的文件到所有客户端系统的 ESP 中。你应该会看到类似下面屏幕截图的结果：更新镜像首先，复制出一个当前镜像的副本：# cp -a /fc28 /fc29chroot 进入到镜像的新副本：# for i in dev dev/pts dev/shm proc sys run; do mount -o bind /$i /fc29/$i; done # chroot /fc29 /usr/bin/bash --login允许更新内核：# sed -i 's/^exclude=kernel-\*$/#exclude=kernel-*/' /etc/dnf/dnf.conf执行升级：# dnf distro-sync -y --releasever=29阻止更新过的内核被再次更新：# sed -i 's/^#exclude=kernel-\*$/exclude=kernel-*/' /etc/dnf/dnf.conf上述命令是可选的，但是在以后，如果在镜像中添加和更新了几个包，在你的客户端之外保存有一个最新内核的副本，会在关键时刻对你非常有帮助。清理 dnf 的包缓存：# dnf clean all退出 chroot 并清理 root 的命令历史：# logout # for i in run sys proc dev/shm dev/pts dev; do umount /fc29/$i; done # > /fc29/root/.bash_history创建 iSCSI 镜像：# FC29_SIZE=$(du -ms /fc29 | cut -f 1) # dd if=/dev/zero of=/fc29.img bs=1MiB count=0 seek=$(($FC29_SIZE*2)) # mkfs -t xfs -L NETROOT /fc29.img # TEMP_MNT=$(mktemp -d) # mount /fc29.img $TEMP_MNT # cp -a /fc29/* $TEMP_MNT # umount $TEMP_MNT定义一个新的 iSCSI 目标，指向到新的镜像并导出它：# MY_HOSTNAME=$(</etc/hostname) # MY_REVERSE_HOSTNAME=$(echo $MY_HOSTNAME | tr '.' "\n" | tac | tr "\n" '.' | cut -b -${#MY_HOSTNAME}) # cat << END > /etc/tgt/conf.d/fc29.conf <target iqn.$MY_REVERSE_HOSTNAME:fc29> backing-store /fc29.img readonly 1 </target> END # tgt-admin --update ALL添加新内核和 initramfs 到 ESP：$ MY_KRNL=$(ls -c /fc29/lib/modules | head -n 1) $ cp $(find /fc29/lib/modules -maxdepth 2 -name 'vmlinuz' | grep -m 1 $MY_KRNL) $HOME/esp/linux/vmlinuz-$MY_KRNL $ cp $(find /fc29/boot -name 'init*' | grep -m 1 $MY_KRNL) $HOME/esp/linux/initramfs-$MY_KRNL.img更新 ESP 的 boot.cfg：$ MY_DNS1=192.0.2.91 $ MY_DNS2=192.0.2.92 $ MY_NAME=server-01.example.edu $ MY_EMAN=$(echo $MY_NAME | tr '.' "\n" | tac | tr "\n" '.' | cut -b -${#MY_NAME}) $ MY_ADDR=$(host -t A $MY_NAME | awk '{print $4}') $ cat << END > $HOME/esp/linux/boot.cfg #!ipxe kernel --name kernel.efi \${prefix}/vmlinuz-$MY_KRNL initrd=initrd.img ro ip=dhcp rd.peerdns=0 nameserver=$MY_DNS1 nameserver=$MY_DNS2 root=/dev/disk/by-path/ip-$MY_ADDR:3260-iscsi-iqn.$MY_EMAN:fc29-lun-1 netroot=iscsi:$MY_ADDR::::iqn.$MY_EMAN:fc29 console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet initrd --name initrd.img \${prefix}/initramfs-$MY_KRNL.img boot || exit END最后，从我的 $HOME/esp/linux 目录中复制文件到所有客户端系统的 ESP 中去使用它吧！via: https://fedoramagazine.org/how-to-build-a-netboot-server-part-2/作者：Gregory Bartholomew 选题：lujun9972 译者：qhwdw 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出