可持续托管、托管 F/OSS 对象存储、Redis、普罗米修斯 在 Hetzner、OVH 和 LeaseWeb 上 **tl;dr - 逐行解释我的 ansible-powered ZFS 安装脚本,用于 Hetzner 的专用硬件(Ubuntu 20.04 - Focal )  它不是完美的/最小的,但它对我有用 不久前,我开始在 Hetzner 托管的所有裸机专用硬件上使用 ZFS 来处理连接的 HDD 和 SSD。空间中有很多选择(标准 LVM, 麦迪 btrfs 等),但我选择 ZFS 是因为它的功能集和人体工程学。 我不想让任何人厌烦其中的原因,但我需要了解的其中一件事是如何在 Ubuntu 20.04(Hetzner 支持的操作系统之一)上安装更新版本的 ZFS。我在我的系统上设置 ZFS 时遇到了一些问题(特别是安装后),所以我想介绍一下我是如何做到的。 这篇文章是对我安装 ZFS 的 Ansible 脚本的逐节解释。 在您知道 ZFS(或任何文件系统)是否值得切换到之前,您可能会想要使用 RTFM。 ZFS 有很多 ins,我当然不是这方面的专家,但了解其方式和原因,以及项目的术语甚至历史非常重要。 一些帮助您入门的链接: 除了 ZFS(这可能是这里最不为人知的数量),您可能还想熟悉 Ubuntu 和一般系统管理。那是一个很大的领域要涵盖,但这里有一些链接: 这几乎是不言而喻的,但如果您此时对 Linux 系统管理不是很熟悉,您可能不应该尝试这样做。 我个人选择让我的磁盘采用某种自定义配置 - 我通过以下方式设置了 RAID1(镜像) mdraid,以及我可以交给 ZFS 管理的每个磁盘上的分区。理想情况下,我会将整个磁盘提供给 ZFS,但分区也可以。 操作系统设置( installimage) 包括磁盘设置,从 Hetzner 的救援模式运行,可以使用如下文件进行引导: # installimage 使用的驱动器声明 DRIVE0 /dev/nvme0n1 DRIVE1 /dev/nvme1n1 # 启用软件 RAID(用于操作系统磁盘) SWRAID 1 SWRAIDLEVEL 1 # Bootloader(通常是 grub) BOOTLOADER grub # Machine hostame HOSTNAME machine01 # 分区配置 PART swap swap 32G PART /boot ext4 1G PART / ext4 128G # 最后一个分区将被创建(擦除& 稍后重新创建为 ZFS) PART /root-disk-remaining ext4 all # 您可以通过访问 Hetzner 的网络共享 IMAGE /root/.oldroot/nfs/images/Ubuntu-2004-focal-64-minimal.tar 来指定 Hetzner 使用的图像。广州 这显然是 Hetzner 特有的,但是您可以在自己的系统上做任何您需要做的事情,以使分区/驱动器可供 ZFS 使用。 zfsutils-linuxand zfs-zed 在 Ansible YAML 中,该步骤如下所示: - 名称:保存所有与 zfs 相关的上游包 ansible.builtin.shell:| apt-mark 保持 zfsutils-linux apt-mark 保持 zfs-zed 在撰写本文时,版本为 Ubuntu Focal 中的 zfs-linux 是 0.8.3。由于我们的目标是安装并继续使用较新的版本,因此我们希望确保任何 apt updates 不会用旧版本替换我们安装的版本。 当我们在这里时,让我们也清除包。 - 名称:清除 ZFS 上游 (ubuntu) 软件包(如果已安装) ignore_errors:是 ansible.builtin.command:|易于清除 --allow-change-held-packages zfsutils-linux zfs-zed 您可以阅读文档 容易看到什么 purge 确实“它”与删除类似,但如果存在配置文件也会删除。 您可以像这样安装 OpenZFS 的依赖项: - 名称:构建 ZFS ansible.builtin.apt 的安装要求:名称:packagesupdate_cache:是状态:存在变量:包:-build-essential-autoconf-automake-libtool-gawk-alien-fakeroot-dkms-libblkid-dev-uuid -dev - libudev-dev - libssl-dev - zlib1g-dev - libaio-dev - libattr1-dev - libelf-dev # 下面一行调用 `uname -r` 的输出,例如“5.16.5-arch1- 1"对于我的 arch 系统 # 所以该行将解析为类似 "linux-headers-5.16.5-arch1-1"的内容(当然在 Ubuntu 上无效,但作为示例)- linux-headers uname_r.stdout }} - python3 - python3-dev - python3-setuptools - python3-cffi - libffi-dev - python3-packaging - git - libcurl4-openssl-dev 可能有一些依赖项不是绝对必要的,但几乎所有依赖项都应该是必需的。 /选择/zfs 这是 ZFS: - 名称:创建/opt/zfs ansible.builtin.file:路径:/opt/zfs 状态:目录 如果你想下载 ZFS: - 名称:下载 zfs ansible.builtin.get_url: url: "httpsgithub.com/openzfs/zfs/releases/download/zfs _zfs_version zfs _zfs_version tar.gz"checksum:_zfs_tarball_sha256_checksummode: 0755 dest: "/opt/zfs/zfs _zfs_version tar .gz"这里的替代 {{ _zfs_version }} ( 2.1.1.您还想自己下载并收集/生成校验和以供使用。切勿从互联网上下载未经校验和不应该更改的内容! {{是 Ansible 模板语法)是 如果你想从运行 Ansible 的计算机复制它(这就是我所做的): - 名称:复制 zfs 包(避免速率限制)ansible.builtin.copy: src: files/zfs/zfs _zfs_version tar.gz"mode: 0755 dest: "/opt/zfs/zfs _zfs_version tar.gz"无论您选择如何获取源代码,您当然都需要解压缩它: - 名称:解压 zfs 代码 ansible.builtin.unarchive: src: "/opt/zfs/zfs _zfs_version tar.gz"dest: "/opt/zfs"remote_src: yes 开始构建过程: - 名称:设置和构建 ZFS ansible.builtin.shell:| ./autogen../configure make clean make -j args: chdir: "/opt/zfs/zfs _zfs_version 如果您熟悉从源代码构建东西,各种项目都使用这个通用工具集(一些变体 制作、自动生成和配置脚本)。正如您所料,这可能需要一段时间,所以给它一些时间。 构建 ZFS 之后 - 名称:安装 ZFS ansible.builtin.shell:|使安装参数:chdir:“/opt/zfs/zfs _zfs_version 通常你会认为我们会*就在这里*完成,但这篇文章之所以存在,是因为这样做通常是不够的!让我们继续前进。 如果 ZFS 模块正在运行,首先强制卸载它们: - 名称:强制卸载 ZFS 模块 (ss) ansible.builtin.shell:| ./scripts/zfs.-u args: chdir: "/opt/zfs/zfs _zfs_version 如您所料,*不*在发生这种情况时运行任何工作负载是理想的。 在卸载模块时,我们可以安装一些助手: - 名称:安装后 ZFS 助手 ansible.builtin.shell:| ./scripts/zfs-helpers.-i args: chdir: "/opt/zfs/zfs _zfs_version 安装助手后,让我们强制重新加载 ZFS 模块: - 名称:强制重新加载 ZFS 模块 ansible.builtin.shell:| ./scripts/zfs.args: chdir: "/opt/zfs/zfs _zfs_version 我发现构建和使用 deb 模块(Debian 用于软件包安装的格式)也有助于让安装坚持下去,而不是被 Ubuntu 软件包默认值取代。 首先从源代码构建 ZFS deb 包: - 名称:构建 ZFS deb 包 ansible.builtin.shell:|制作 deb args: chdir: "/opt/zfs/zfs _zfs_version 然后安装它 - 名称:安装 ZFS deb 包 ansible.builtin.shell:|是 | dpkg -i --force-overwrite deb apt install -f -y deb args: chdir: "/opt/zfs/zfs _zfs_version 从理论上讲,这一步*不应该*是必需的(或者应该单独使用),因为我们已经运行了一个安装过程,但我发现在执行其中一个或另一个时我在某些情况下,重新启动会提示使用旧版本的 ZFS(尽管 apt purge) [特别是在内核级别。 调制探头 如果要启用立即安装的 ZFS 模块,请使用 调制探头: - 名称:Modprobe zfs 模块块: - 名称:安装 zfs 内核模块 community.general.modprobe:名称:zfs 状态:存在 安装 ZFS 意味着安装内核模块,但由于我们还没有通过动态内核模块支持将其完全融入其中,因此我们需要启用本地安装的内核模块以供使用: - 名称:确保 extra 位于前面 ansible.builtin.lineinfile:路径:/etc/modules-load.d/modules.conf regexp:'^search'line:“search extra updates ubuntu built-in” state:present 这样做是确保管理内核模块搜索路径的文件 /etc/modules-load.d/modules.conf 中有一行 搜索指定的额外更新。通常至少有一行以 搜索,我们要做的是确保 在此过程的早期搜索额外的模块位置。 我们要通过 DKMS 子系统安装: - 名称:dkms 安装 zfs ansible.builtin.shell:| dkms 安装 zfs _zfs_version }} args: chdir: "/opt/zfs/zfs _zfs_version 此时,您应该能够检查当前活动的 ZFS 版本,它应该会显示如下内容: root@machine01 ~ # zfs 版本 zfs-2.1.1-1 zfs-kmod-2.1.1-1 创建了一些 SystemD 单元(通常 Digital Ocean 有一些很棒的文档)但需要启用 ZFS 才能使用: - 名称:确保启用与 zfs 相关的 systemd 单元块:- ansible.builtin.systemd:名称:itemstate:已启用:是循环:-zfs-import-cache.service - zfs-import.target - zfs-mount.service - zfs-share.service - zfs-zed.service - zfs-volume-wait.service - zfs.target 被循环的变量类似于运行 systemd start and systemd enable . The security-minded reader at home is no doubt cringing into the atmosphere by now, but making sure my kernel upgrades are manual is the only way I’ve found to ensure that the installed version of ZFS was not disabled/unexpectedly altered/broken by kernel upgrades: - name: Hold all kernel upgrades to prevent custom built ZFS from doing fallback ansible.builtin.shell: | apt-mark hold linux-headers-generic apt-mark hold linux-image-generic apt-mark hold {{ uname_r.stdout }} # you'll need that `uname -r` output again here I personally prefer to hold back any kernel upgrades and instead perform them at machine setup rather than during operation. DKMS *should* ensure that with the building of any new kernel the ZFS code is rebuilt but it’s been flaky in the past so I find it hard to trust. Along with all the kernel changes we’ve made so far (good and questionable), one thing we’ll want to do is add the configuration necessary to ensure the kernel module is loaded: - name: Ensure zfs kernel module comes up with next restart tags: [ "zfs:post-install:config" ] block: - name: Add zfs module load file ansible.builtin.template: src: templates/kernel-modules/zfs.conf.j2 dest: /etc/modules-load.d/zfs.conf owner: root group: root mode: 0644 Now that we have ZFS installed (restart once to check I’m going to leave the rest of the cluster setup to you. There are a *lot* of ways to use ZFS and setup zpools (RAID0/1/5/Z Actually using ZFS properly is out of scope (I can only hope I’ve covered *installing* it properly, at least), but please refer to the usual manuals there to set up your ZFS pool and storage requirements. Trying to cover even the basics of how to setup ZFS for your drives once it’s been installed is certainly a lot of reading so we’ll leave it there for today. This is the part where you “draw the rest of the owl” (sorry). txg_timeout While experimenting with some write modes for Postgres on ZFS, I looked into an optimization modes that involved reducing the txg_timeout from it’s default of 5 seconds to 1 second. While I won’t get into the tradeoffs implied by that move (please read my post on Postgres + ZFS), here is the setup that I’ve used for modifying txg_timeout per-machine (some machines might use the optimization some might not): - name: Add systemd unit to support txg timeout customization tags: [ "zfs:post-install:config" ] block: - name: Set current ZFS txg_timeout (to 1) ansible.builtin.shell: | echo 1 > /sys/module/zfs/parameters/zfs_txg_timeout - name: install zfs-config-txg-timeout service ansible.builtin.template: src: templates/zfs-config-txg-timeout.service.j2 dest: /etc/systemd/system/zfs-config-txg-timeout.service owner: root group: root mode: 0644 - name: start & enable zfs-config-txg-timeout service ansible.builtin.systemd: name: zfs-config-txg-timeout.service state: started enabled: yes daemon_reload: yes The template that goes with that looks like this: [Unit] Description=Set ZFS txg_timeout After=zfs.target ConditionPathIsDirectory=/sys/module/zfs [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/bin/bash -c 'echo {{ _zfs_txg_timeout_seconds | default(zfs_txg_timeout_seconds) }} > /sys/module/zfs/parameters/zfs_txg_timeout' # Not needed since we want it to always be whatever the setting is # ExecStop=/usr/bin/bash -c 'echo {{ _zfs_txg_timeout_seconds | default(zfs_txg_timeout_seconds) }} > /sys/module/zfs/parameters/zfs_txg_timeout' [Install] WantedBy=multi-user.target Obviously you’ll want to define {{ _zfs_txg_timeout_seconds }} and the zfs_txg_timeout_seconds. In general, this will make sure that the txg_timeout is set to what you want it to be upon startup after ZFS has started. Wasn’t that easy? If you’re answering no, it wasn’t easy for me to figure out either! I spent a lot of time wondering why DKMS wasn’t working properly (which is why the hack of preventing kernel upgrades is still included) and dealing with other issues. The OpenZFS documentation is pretty great but seems to be missing a guide somewhat like this, so hopefully this post will save people some time going forward when trying to experiment with new ZFS setups on Hetzner. Another thing I’m hoping for with this post is to be corrected – if you see something glaringly wrong in my setup, please reach out. The more people can use low cost infrastructure providers like Hetzner, the more cool software we get and the more interesting/innovative products can be built. To that end I’m working on NimbusWS (I’m behind on this but should launch by end of Q1 2022) – if you’re interested please sign up for the service and kick the tires (free tier usage will be available).