17.5.更新多个 Jail
==================

对多个 jail 的管理可能会成为问题,因为每个 jail 在升级时都必须从头开始重新编译。如果要创建并手动更新许多 jail,这可能会十分耗时且无聊。

本节演示了一种解决此问题的方案,其方法是使用 `mount_nullfs(8) <https://www.freebsd.org/cgi/man.cgi?query=mount_nullfs&sektion=8&format=html>`__ 只读挂载在 jail 之间尽可能多的以安全的方式共享内容,以便让更新更简单。从而使得将单个服务(如 HTTP,DNS 和 SMTP)放入不同的 jail 方案更具吸引力。此外,它还提供了一种增加、删除和升级 jail 的简单方法。

   **注意**

   有更简单的解决方案,例如 ezjail,它提供了一种更简单的 FreeBSD jail 管理方法,但其通用性不如此方法。在\ `用 ezjail 管理 jail <https://docs.freebsd.org/en/books/handbook/Jail/#Jail-ezjail>`__ 中有更详细的介绍。

本节中介绍的配置的目标是:

-  建立简单易懂的 jail 结构,不必在每个 jail 上执行完整的 installworld 操作。
-  轻松增加或删除 jail。
-  轻松更新或升级已有 jail。
-  使运行定制的 FreeBSD 分支成为可能。
-  对安全问题持偏执态度,尽可能地减少妥协的可能性。
-  尽可能节省空间和节点。

这种设计依赖于一份单一的、只读的主模板,它被安装到每个 jail 中,每个 jail 有一个读写设备。这个设备可以是单独的物理磁盘,分区,或者虚拟节点支持的内存盘。在这个例子中使用了可读写的 nullfs 挂载。

文件系统布局如下:

-  每个 jail 都位于 **/home** 分区下。
-  每个 jail 都挂载在 **/home/j** 目录下。
-  **/home/j/mroot** 是每个 jail 共用的模板,对所有 jail 而言都是只读分区。
-  每个 jail 都有一个对应的空目录在 **/home/j** 中。
-  每个 jail 都有一个 **/s** 目录,该目录将链接到系统的可读写部分。
-  每个 jail 都有自己的可读写系统,该系统基于 **/home/j/skel**\ 。
-  每个 jail 的可读写空间创建在 **/home/js** 中。

17.5.1.创建模板
---------------

本节介绍创建主模板所需的步骤。

建议首先使用\ `“从源代码更新 FreeBSD” <https://docs.freebsd.org/en/books/handbook/cutting-edge/index.html#makeworld>`__\ 中的说明将 FreeBSD 主机系统更新到最新的 -RELEASE 分支。此外,此模板还依赖 `sysutils/cpdup <https://cgit.freebsd.org/ports/tree/sysutils/cpdup/pkg-descr>`__\ ,可从二进制包或 ports 安装。将使用 `Git <https://docs.freebsd.org/en/books/handbook/mirrors/#git>`__ 下载 FreeBSD Ports。

   1. 首先,为只读文件系统建立一个目录,它将包含 jail 的 FreeBSD 可执行文件。然后,进入 FreeBSD 源代码所在目录,并将只读文件系统安装到 jail 模板中:

   .. code:: shell-session

      # mkdir /home/j /home/j/mroot
      # cd /usr/src
      # make installworld DESTDIR=/home/j/mroot

   1. 接下来,为 jail 准备 FreeBSD ports 以及 FreeBSD 源代码,这是 mergemaster 所必需的:

   .. code:: shell-session

      # cd /home/j/mroot
      # mkdir usr/ports
      # git clone -o freebsd https://git.FreeBSD.org/ports.git /home/j/mroot/usr/ports
      # cpdup /usr/src /home/j/mroot/usr/src

   1. 为系统的可读写部分创建框架:

   .. code:: shell-session

      # mkdir /home/j/skel /home/j/skel/home /home/j/skel/usr-X11R6 /home/j/skel/distfiles
      # mv etc /home/j/skel
      # mv usr/local /home/j/skel/usr-local
      # mv tmp /home/j/skel
      # mv var /home/j/skel
      # mv root /home/j/skel

   1. 使用 mergemaster 安装缺少的配置文件。然后,删除 mergemaster 创建的多余目录:

   .. code:: shell-session

      # mergemaster -t /home/j/skel/var/tmp/temproot -D /home/j/skel -i
      # cd /home/j/skel
      # rm -R bin boot lib libexec mnt proc rescue sbin sys usr dev

   1. 现在,将可读写文件系统符号链接到只读文件系统。确保在正确的 **s/** 位置创建符号链接,因为在错误的位置创建目录将导致安装失败。

   .. code:: shell-session

      # cd /home/j/mroot
      # mkdir s
      # ln -s s/etc etc
      # ln -s s/home home
      # ln -s s/root root
      # ln -s ../s/usr-local usr/local
      # ln -s ../s/usr-X11R6 usr/X11R6
      # ln -s ../../s/distfiles usr/ports/distfiles
      # ln -s s/tmp tmp
      # ln -s s/var var

   1. 最后一步,创建一个包含以下行的通用配置文件 **/home/j/skel/etc/make.conf**\ :

   .. code:: shell-session

      WRKDIRPREFIX?=  /s/portbuild

   这使得在每个 jail 中分别编译 FreeBSD ports 成为可能。请记住,ports 目录是只读系统的一部分。而 ``WRKDIRPREFIX`` 则使得编译过程得以在 jail 中的可读写部分完成。

17.5.2.创建 Jail
----------------

jail 模板现在可用于在 **/etc/rc.conf** 中设置和配置 jail。此示例了演示如何创建 3 个 jail:\ ``NS``\ 、\ ``MAIL`` 和 ``WWW``\ 。

   1. 将以下行添加到 **/etc/fstab**\ ,以便 jail 的只读模板和可读写空间在各自的 jail 中可用:

   .. code:: shell-session

      /home/j/mroot   /home/j/ns     nullfs  ro  0   0
      /home/j/mroot   /home/j/mail   nullfs  ro  0   0
      /home/j/mroot   /home/j/www    nullfs  ro  0   0
      /home/js/ns     /home/j/ns/s   nullfs  rw  0   0
      /home/js/mail   /home/j/mail/s nullfs  rw  0   0
      /home/js/www    /home/j/www/s  nullfs  rw  0   0

   为了防止 fsck 在引导期间检查 nullfs 挂载,并防止转储备份 jail 中的只读 nullfs 挂载,将最后两列都设置为 ``0``\ 。

   1. 在 **/etc/rc.conf** 中配置 jail:

   .. code:: shell-session

      jail_enable="YES"
      jail_set_hostname_allow="NO"
      jail_list="ns mail www"
      jail_ns_hostname="ns.example.org"
      jail_ns_ip="192.168.3.17"
      jail_ns_rootdir="/usr/home/j/ns"
      jail_ns_devfs_enable="YES"
      jail_mail_hostname="mail.example.org"
      jail_mail_ip="192.168.3.18"
      jail_mail_rootdir="/usr/home/j/mail"
      jail_mail_devfs_enable="YES"
      jail_www_hostname="www.example.org"
      jail_www_ip="62.123.43.14"
      jail_www_rootdir="/usr/home/j/www"
      jail_www_devfs_enable="YES"

   应该把 *jailnamerootdir* 变量设置为 **/usr/home** 而不是 **/home**\ ,因为在默认安装的 FreeBSD 上,\ **/home** 的物理路径是 **/usr/home**\ 。\ *jailnamerootdir* 变量必须是一个不包含符号连接的路径,否则 jail 将拒绝启动。

   1. 为每个 jail 创建只读文件系统所需的挂载点:

   .. code:: shell-session

      # mkdir /home/j/ns /home/j/mail /home/j/www

   1. 使用 `sysutils/cpdup <https://cgit.freebsd.org/ports/tree/sysutils/cpdup/pkg-descr>`__ 将可读写模板安装到每个 jail 中:

   .. code:: shell-session

      # mkdir /home/js
      # cpdup /home/j/skel /home/js/ns
      # cpdup /home/j/skel /home/js/mail
      # cpdup /home/j/skel /home/js/www

   1. 在这个阶段,jail 已经建成并准备运行。首先,为每个 jail 挂载所需的文件系统,然后启动它们:

   .. code:: shell-session

      # mount -a
      # service jail start

现在 jail 应该已经运行了。要检查它们是否正常运行,请使用 ``jls``\ 。其输出应类似于以下内容:

.. raw:: latex

   \diilbookstyleinputcell

.. code:: shell-session

   # jls
      JID  IP Address      Hostname                      Path
        3  192.168.3.17    ns.example.org                /home/j/ns
        2  192.168.3.18    mail.example.org              /home/j/mail
        1  62.123.43.14    www.example.org               /home/j/www

此时,应该可以登录到每一个 jail,以添加新的用户,或者配置守护程序了。\ ``JID`` 列表示每个运行中的 jail 的标识。例如使用下面的命令可执行 JID 为 ``3`` 的 jail 的管理任务:

.. raw:: latex

   \diilbookstyleinputcell

.. code:: shell-session

   # jexec 3 tcsh

17.5.3.升级
-----------

此设置的设计提供了一种简单的方法来升级现有的 jail,同时最大限度地减少其停机时间。此外,它还提供了一种在出现问题时回滚到旧版本的方法。

   1. 第一步是升级主机系统。然后,在 **/home/j/mroot2** 中创建新的临时只读模板。

   .. code:: shell-session

      # mkdir /home/j/mroot2
      # cd /usr/src
      # make installworld DESTDIR=/home/j/mroot2
      # cd /home/j/mroot2
      # cpdup /usr/src usr/src
      # mkdir s

   ``installworld`` 将创建一些多余的目录,应将其删除:

   .. code:: shell-session

      # chflags -R 0 var
      # rm -R etc var root usr/local tmp

   1. 为主文件系统重新创建可读写符号链接:

   .. code:: shell-session

      # ln -s s/etc etc
      # ln -s s/root root
      # ln -s s/home home
      # ln -s ../s/usr-local usr/local
      # ln -s ../s/usr-X11R6 usr/X11R6
      # ln -s s/tmp tmp
      # ln -s s/var var

   1. 接下来,关闭 jail:

   .. code:: shell-session

      # service jail stop

   1. 卸载原先的文件系统,因为可读写系统需要连接到只读系统(\ **/s**\ ):

   .. code:: shell-session

      # umount /home/j/ns/s
      # umount /home/j/ns
      # umount /home/j/mail/s
      # umount /home/j/mail
      # umount /home/j/www/s
      # umount /home/j/www

   1. 移动旧的只读文件系统,并将其替换为新的文件系统。如果出现问题,这将用作旧的只读文件系统的备份和存档。此处使用的文件命名对应于创建新的只读文件系统。此外将原来的 FreeBSD ports 移到新的文件系统上 以节省一些空间和节点:

   .. code:: shell-session

      # cd /home/j
      # mv mroot mroot.20060601
      # mv mroot2 mroot
      # mv mroot.20060601/usr/ports mroot/usr

   1. 此时,新的只读模板已准备就绪,因此唯一剩下的任务是重新挂载文件系统并启动 jail:

   .. code:: shell-session

      # mount -a
      # service jail start

最后使用 ``jls`` 来检查 jail 是否正确启动。在每个 jail 中运行 ``mergemaster`` 来更新配置文件。