diff --git a/translated/tech/20190114 How to Build a Netboot Server, Part 4.md b/translated/tech/20190114 How to Build a Netboot Server, Part 4.md index 84065b2b40..d3eae1aefe 100644 --- a/translated/tech/20190114 How to Build a Netboot Server, Part 4.md +++ b/translated/tech/20190114 How to Build a Netboot Server, Part 4.md @@ -1,6 +1,6 @@ [#]: collector: "lujun9972" [#]: translator: "qhwdw" -[#]: reviewer: " " +[#]: reviewer: "wxy" [#]: publisher: " " [#]: url: " " [#]: subject: "How to Build a Netboot Server, Part 4" @@ -15,7 +15,7 @@ 可写镜像的另外的好处是,终端用户“私人定制”的操作系统,在下次不同的工作站上使用时能够“跟着”他们。 -### 修改 Bootmenu 应用程序去使用 HTTPS +### 修改 Bootmenu 应用程序以使用 HTTPS 为 bootmenu 应用程序创建一个自签名的证书: @@ -27,7 +27,7 @@ $ sudo -i # 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 名字是相匹配的: +验证你的证书的值。确保 `Subject` 行中 `CN` 的值与你的 iPXE 客户端连接你的网络引导服务器所使用的 DNS 名字是相匹配的: ``` # openssl x509 -text -noout -in $MY_TLSD/$MY_NAME.pem @@ -49,7 +49,7 @@ GnuTLS 要求 “CAP_DAC_READ_SEARCH” 能力,因此将它添加到 bootmenu # systemctl daemon-reload ``` -现在,在防火墙中为 bootmenu 服务添加一个例外规则并重启动服务: +现在,在防火墙中为 bootmenu 服务添加一个例外规则并重启动该服务: ``` # MY_SUBNET=192.0.2.0 @@ -59,7 +59,7 @@ GnuTLS 要求 “CAP_DAC_READ_SEARCH” 能力,因此将它添加到 bootmenu # systemctl restart bootmenu.service ``` -使用 wget 去验证是否工作正常: +使用 `wget` 去验证是否工作正常: ``` $ MY_NAME=server-01.example.edu @@ -69,7 +69,7 @@ $ wget -q --ca-certificate=$MY_TLSD/$MY_NAME.pem -O - https://$MY_NAME/menu ### 添加 HTTPS 到 iPXE -更新 init.ipxe 去使用 HTTPS。接着使用选项重新编译 ipxe 引导加载器,以便它包含和信任你为 bootmenu 应用程序创建的自签名证书: +更新 `init.ipxe` 去使用 HTTPS。接着使用选项重新编译 ipxe 引导加载器,以便它包含和信任你为 bootmenu 应用程序创建的自签名证书: ``` $ echo '#define DOWNLOAD_PROTO_HTTPS' >> $HOME/ipxe/src/config/local/general.h @@ -95,7 +95,7 @@ $ cp $HOME/ipxe/src/bin-x86_64-efi/ipxe.efi $HOME/esp/efi/boot/bootx64.efi # echo 'auth required pam_krb5.so' > /etc/pam.d/bootmenu ``` -添加一个库到 bootmenu 应用程序中,它使用 Authen-PAM 的 perl 模块去执行用户验证: +添加一个库到 bootmenu 应用程序中,它使用 Authen-PAM 的 Perl 模块去执行用户验证: ``` # dnf install -y perl-Authen-PAM; @@ -107,37 +107,37 @@ package PAM; use Authen::PAM; sub auth { - my $success = 0; + my $success = 0; - my $username = shift; - my $password = shift; + my $username = shift; + my $password = shift; - my $callback = sub { - my @res; - while (@_) { - my $code = shift; - my $msg = shift; - my $ans = ""; + 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(); - $ans = $username if ($code == PAM_PROMPT_ECHO_ON()); - $ans = $password if ($code == PAM_PROMPT_ECHO_OFF()); + return @res; + }; - push @res, (PAM_SUCCESS(), $ans); - } - push @res, PAM_SUCCESS(); + my $pamh = new Authen::PAM('bootmenu', $username, $callback); - return @res; - }; + { + last unless ref $pamh; + last unless $pamh->pam_authenticate() == PAM_SUCCESS; + $success = 1; + } - my $pamh = new Authen::PAM('bootmenu', $username, $callback); - - { - last unless ref $pamh; - last unless $pamh->pam_authenticate() == PAM_SUCCESS; - $success = 1; - } - - return $success; + return $success; } return 1; @@ -163,29 +163,29 @@ plugin 'Config'; get '/menu'; get '/boot' => sub { - my $c = shift; + my $c = shift; - my $instance = $c->param('instance'); - my $username = $c->param('username'); - my $password = $c->param('password'); + my $instance = $c->param('instance'); + my $username = $c->param('username'); + my $password = $c->param('password'); - my $template = 'menu'; + my $template = 'menu'; - { - last unless $instance =~ /^fc[[:digit:]]{2}$/; - last unless $username =~ /^[[:alnum:]]+$/; - last unless PAM::auth($username, url_unescape($password)); - $template = $instance; - } + { + 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); + return $c->render(template => $template); }; app->start; END ``` -bootmenu 应用程序现在查找 lib 去找到相应的 WorkingDirectory。但是,默认情况下,对于 systemd 单元它的工作目录设置为服务器的 root 目录。因此,你必须更新 systemd 单元去设置 WorkingDirectory 为 bootmenu 应用程序的根目录: +bootmenu 应用程序现在查找 `lib` 命令去找到相应的 `WorkingDirectory`。但是,默认情况下,对于 systemd 单元它的工作目录设置为服务器的 root 目录。因此,你必须更新 systemd 单元去设置 `WorkingDirectory` 为 bootmenu 应用程序的根目录: ``` # sed -i "/^RuntimeDirectory=/ a WorkingDirectory=$MY_MOJO" /etc/systemd/system/bootmenu.service @@ -203,7 +203,7 @@ bootmenu 应用程序现在查找 lib 去找到相应的 WorkingDirectory。但 上面的最后的命令将生成类似下面的三个文件: -**menu.html.ep** : +`menu.html.ep`: ``` #!ipxe @@ -241,7 +241,7 @@ login chain https://server-01.example.edu/boot?instance=fc28&username=${username}&password=${password:uristring} || goto failed ``` -**fc29.html.ep** : +`fc29.html.ep`: ``` #!ipxe @@ -250,7 +250,7 @@ initrd --name initrd.img ${prefix}/initramfs-4.19.5-300.fc29.x86_64.img boot || chain https://server-01.example.edu/menu ``` -**fc28.html.ep** : +`fc28.html.ep`: ``` #!ipxe @@ -267,17 +267,15 @@ boot || chain https://server-01.example.edu/menu ### 使得 iSCSI Target 可写 -现在,用户验证通过 iPXE 可以正常工作,在用户连接时,你可以按需在只读镜像的上面创建每用户的可写覆盖。使用一个 [写时复制][2] 覆盖与简单地为每个用户复制原始镜像相比有三个好处: +现在,用户验证通过 iPXE 可以正常工作,在用户连接时,你可以按需在只读镜像的上面创建每用户可写的overlay叠加层。使用一个 [写时复制][2] 的叠加层与简单地为每个用户复制原始镜像相比有三个好处: - 1. 复制创建非常快。这样就可以按需创建。 - 2. 复制并不增加服务器上的磁盘使用。除了原始镜像之外,仅存储用户写入个人镜像的内容。 - 3. 由于每个用户复制的扇区大多都是服务器的存储上的相同的扇区,在随后的用户访问这些操作系统的副本时,它们可能已经加载到内存中,这样就提升了服务器的性能,因为对内存的访问速度要比磁盘 I/O 快得多。 + 1. 副本创建非常快。这样就可以按需创建。 + 2. 副本并不增加服务器上的磁盘使用。除了原始镜像之外,仅存储用户写入个人镜像的内容。 + 3. 由于每个副本的扇区大多都是服务器的存储器上的相同扇区,在随后的用户访问这些操作系统的副本时,它们可能已经加载到内存中,这样就提升了服务器的性能,因为对内存的访问速度要比磁盘 I/O 快得多。 +使用写时复制的一个潜在隐患是,一旦叠加层创建后,叠加层之下的镜像就不能再改变。如果它们改变,所有它们之上的叠加层将出错。因此,叠加层必须被删除并用新的、空白的进行替换。即便只是简单地以读写模式加载的镜像,也可能因为某些文件系统更新导致叠加层出错。 - -使用写时复制的一个潜在隐患是,一旦覆盖创建后,覆盖之下的镜像就不能再改变。如果它们改变,所有它们之上的覆盖将出错。因此,覆盖必须被删除并用新的、空白的替换。即便只是简单地以读写模式加载的镜像,也可能因为某些文件系统更新导致覆盖出错。 - -由于这个隐患,如果原始镜像被修改将导致覆盖出错,因此运行下列的命令,将原始镜像标记为不可改变: +由于这个隐患,如果原始镜像被修改将导致叠加层出错,因此运行下列的命令,将原始镜像标记为不可改变: ``` # chattr +i @@ -293,7 +291,7 @@ boot || chain https://server-01.example.edu/menu 当仍有连接打开的时候,运行这个命令一般需要一分钟或更长的时间。 -现在,移除只读的 iSCSI 出口。然后更新模板中的配置文件为 readonly-root,以使镜像不再是只读的: +现在,移除只读的 iSCSI 出口。然后更新模板中的 `readonly-root` 配置文件,以使镜像不再是只读的: ``` # MY_FC=fc29 @@ -305,7 +303,7 @@ boot || chain https://server-01.example.edu/menu # umount $TEMP_MNT ``` -将 Journald 日志从发送到内存修改回缺省值(记录到磁盘,如果 /var/log/journal 存在的话),因为一个用户报告说,他的客户端由于应用程序生成了大量的系统日志而产生内存溢出错误,导致它的客户端被卡住。而将日志记录到磁盘的负面影响是客户端产生了额外的写入流量,这将在你的网络引导服务器上可能增加一些没有必要的 I/O。你应该去决定到底使用哪个选择 — 记录到内存还是记录到硬盘 — 哪个更合适取决于你的环境。 +将 journald 日志从发送到内存修改回缺省值(如果 `/var/log/journal` 存在的话记录到磁盘),因为一个用户报告说,他的客户端由于应用程序生成了大量的系统日志而产生内存溢出错误,导致它的客户端被卡住。而将日志记录到磁盘的负面影响是客户端产生了额外的写入流量,这将在你的网络引导服务器上可能增加一些没有必要的 I/O。你应该去决定到底使用哪个选择 —— 记录到内存还是记录到硬盘 —— 哪个更合适取决于你的环境。 因为你的模板镜像在以后不能做任何的更改,因此在它上面设置不可更改标志,然后重启动 tgtd.service: @@ -331,37 +329,37 @@ plugin 'Config'; get '/menu'; get '/boot' => sub { - my $c = shift; + my $c = shift; - my $instance = $c->param('instance'); - my $username = $c->param('username'); - my $password = $c->param('password'); + my $instance = $c->param('instance'); + my $username = $c->param('username'); + my $password = $c->param('password'); - my $chapscrt; - my $template = 'menu'; + 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; - } + { + 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); + return $c->render(template => $template, username => $username, chapscrt => $chapscrt); }; app->start; END ``` -新版本的 bootmenu 应用程序调用一个定制的 mktgt 脚本,如果成功,它将为每个它自己创建的新的 iSCSI 目标返回一个随机的 [CHAP][3] 密码。这个 CHAP 密码可以防止其它用户的 iSCSI 目标以间接方式挂载这个用户的目标。这个应用程序只有在用户密码认证成功之后才返回一个正确的 iSCSI 目标密码。 +新版本的 bootmenu 应用程序调用一个定制的 `mktgt` 脚本,如果成功,它将为每个它自己创建的新的 iSCSI 目标返回一个随机的 [CHAP][3] 密码。这个 CHAP 密码可以防止其它用户的 iSCSI 目标以间接方式挂载这个用户的目标。这个应用程序只有在用户密码认证成功之后才返回一个正确的 iSCSI 目标密码。 -mktgt 脚本要加 sudo 前缀来运行,因为它需要 root 权限去创建目标。 +`mktgt` 脚本要加 `sudo` 前缀来运行,因为它需要 root 权限去创建目标。 -\$username 和 \$chapscrt 变量也传递 render 命令,因此在需要的时候,它们也能够被纳入到模板中返回给用户。 +`$username` 和 `$chapscrt` 变量也传递给 `render` 命令,因此在需要的时候,它们也能够被纳入到模板中返回给用户。 -接下来,更新我们的引导模板,以便于它们能够读取用户名和 chapscrt 变量,并传递它们到所属的终端用户。也要更新模板以 rw(读写)模式加载根文件系统: +接下来,更新我们的引导模板,以便于它们能够读取用户名和 `chapscrt` 变量,并传递它们到所属的终端用户。也要更新模板以 rw(读写)模式加载根文件系统: ``` # cd $MY_MOJO/templates @@ -379,17 +377,17 @@ initrd --name initrd.img ${prefix}/initramfs-4.19.5-300.fc29.x86_64.img boot || chain https://server-01.example.edu/menu ``` -注意:如果在 [插入][4] 变量后需要查看引导模板,你可以在 “boot” 命令之前,在它自己的行中插入 “shell” 命令。然后在你网络引导你的客户端时,iPXE 将在那里给你提供一个用于交互的 shell,你可以在 shell 中输入 “imgstat” 去查看传递到内核的参数。如果一切正确,你可以输入 “exit” 去退出 shell 并继续引导过程。 +注意:如果在 [插入][4] 变量后需要查看引导模板,你可以在 `boot` 命令之前,在它自己的行中插入 `shell` 命令。然后在你网络引导你的客户端时,iPXE 将在那里给你提供一个用于交互的 shell,你可以在 shell 中输入 `imgstat` 去查看传递到内核的参数。如果一切正确,你可以输入 `exit` 去退出 shell 并继续引导过程。 -现在,通过 sudo 允许 bootmenu 用户以 root 权限去运行 mktgt 脚本(仅那个脚本): +现在,通过 `sudo` 允许 bootmenu 用户以 root 权限去运行 `mktgt` 脚本(仅这个脚本): ``` # echo "bootmenu ALL = NOPASSWD: $MY_MOJO/scripts/mktgt *" > /etc/sudoers.d/bootmenu ``` -bootmenu 用户不应该写访问 mktgt 脚本或在它的 home 目录下的任何其它文件。在 /opt/bootmenu 目录下的所有文件的属主应该是 root,并且不应该被其它任何 root 以外的用户可写。 +bootmenu 用户不应该写访问 `mktgt` 脚本或在它的家目录下的任何其它文件。在 `/opt/bootmenu` 目录下的所有文件的属主应该是 root,并且不应该被其它任何 root 以外的用户可写。 -Sudo 在使用 systemd 的 DynamicUser 选项下不能正常工作,因此创建一个普通用户帐户,并设置 systemd 服务以那个用户运行: +`sudo` 在使用 systemd 的 `DynamicUser` 选项下不能正常工作,因此创建一个普通用户帐户,并设置 systemd 服务以那个用户运行: ``` # useradd -r -c 'iPXE Boot Menu Service' -d /opt/bootmenu -s /sbin/nologin bootmenu @@ -397,7 +395,7 @@ Sudo 在使用 systemd 的 DynamicUser 选项下不能正常工作,因此创 # systemctl daemon-reload ``` -最后,为写时复制覆盖创建一个目录,并创建管理 iSCSI 目标的 mktgt 脚本和它们的覆盖支持存储: +最后,为写时复制覆盖创建一个目录,并创建管理 iSCSI 目标的 `mktgt` 脚本和它们的覆盖支持存储: ``` # mkdir /$MY_FC.cow @@ -510,25 +508,23 @@ END 上面的脚本将做以下五件事情: - 1. 创建 /.cow/ 稀疏文件(如果不存在的话)。 - 2. 创建 /dev/mapper/- 设备节点作为 iSCSI 目标的写时复制支持存储(如果不存在的话)。 - 3. 创建 iqn.:- iSCSI 目标(如果不存在的话)。或者,如果已存在了,它将关闭任何已存在的连接,因为在任何时刻,镜像只能以只读模式从一个地方打开。 - 4. 它在 iqn.:- iSCSI 目标上(重新)设置 chap 密码为一个新的随机值。 + 1. 创建 `/.cow/` 稀疏文件(如果不存在的话)。 + 2. 创建 `/dev/mapper/-` 设备节点作为 iSCSI 目标的写时复制支持存储(如果不存在的话)。 + 3. 创建 `iqn.:-` iSCSI 目标(如果不存在的话)。或者,如果已存在了,它将关闭任何已存在的连接,因为在任何时刻,镜像只能以只读模式从一个地方打开。 + 4. 它在 `iqn.:-` iSCSI 目标上(重新)设置 chap 密码为一个新的随机值。 5. (如果前面的所有任务都成功的话)它在 [标准输出][5] 上显示新的 chap 密码。 - - -你应该可以在命令行上通过使用有效的测试参数来运行它,以测试 mktgt 脚本能否正常工作。例如: +你应该可以在命令行上通过使用有效的测试参数来运行它,以测试 `mktgt` 脚本能否正常工作。例如: ``` # echo `$MY_MOJO/scripts/mktgt fc29 jsmith` ``` -当你从命令行上运行时,mktgt 脚本应该会输出 iSCSI 目标的一个随意的八字符随机密码(如果成功的话)或者是出错位置的行号(如果失败的话)。 +当你从命令行上运行时,`mktgt` 脚本应该会输出 iSCSI 目标的一个随意的八字符随机密码(如果成功的话)或者是出错位置的行号(如果失败的话)。 -有时候,你可能需要在不停止整个服务的情况下删除一个 iSCSI 目标。例如,一个用户可能无意中损坏了他的个人镜像,在那种情况下,你可能需要按步骤撤销上面的 mktgt 脚本所做的事情,以便于他下次登入时他将得到一个原始镜像。 +有时候,你可能需要在不停止整个服务的情况下删除一个 iSCSI 目标。例如,一个用户可能无意中损坏了他的个人镜像,在那种情况下,你可能需要按步骤撤销上面的 `mktgt` 脚本所做的事情,以便于他下次登入时他将得到一个原始镜像。 -下面是用于撤销的 rmtgt 脚本,它以相反的顺序做了上面 mktgt 脚本所做的事情: +下面是用于撤销的 `rmtgt` 脚本,它以相反的顺序做了上面 `mktgt` 脚本所做的事情: ``` # mkdir $HOME/bin @@ -556,39 +552,39 @@ my @targets = $text =~ /(?:^T.*\n)(?:^ .*\n)*/mg; my $targets = {}; foreach (@targets) { - my $tgt; - my $sid; + my $tgt; + my $sid; - foreach (split /\n/) { - /^Target (\d+)(?{ $tgt = $targets->{$^N} = [] })/; - /I_T nexus: (\d+)(?{ $sid = $^N })/; - /Connection: (\d+)(?{ push @{$tgt}, [ $sid, $^N ] })/; - } + 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; + 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"; + 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"; + 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 @@ -600,7 +596,7 @@ END # rmtgt fc29 jsmith +f ``` -一旦你验证 mktgt 脚本工作正常,你可以重启动 bootmenu 服务。下次有人从网络引导时,他们应该能够接收到一个他们可以写入的、可”私人定制“的网络引导镜像的副本: +一旦你验证 `mktgt` 脚本工作正常,你可以重启动 bootmenu 服务。下次有人从网络引导时,他们应该能够接收到一个他们可以写入的、可”私人定制“的网络引导镜像的副本: ``` # systemctl restart bootmenu.service @@ -617,7 +613,7 @@ via: https://fedoramagazine.org/how-to-build-a-netboot-server-part-4/ 作者:[Gregory Bartholomew][a] 选题:[lujun9972][b] 译者:[qhwdw](https://github.com/qhwdw) -校对:[校对者ID](https://github.com/校对者ID) +校对:[wxy](https://github.com/wxy) 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出