Arch LinuxでNASを始めた(Arch LinuxのインストールからZFS+Samba+iSCSIターゲットの導入まで)

NAS用にどのディストリビューション入れようかなと思っていた。RHEL, Ubuntuでもいいけど、ちょっと飽きてきた。NAS用ならTrueNAS COREから始めれば良いのにと思いつつも、Arch Linuxに興味を持った。

NASに求めるべき内容ではないとは思うけど、自分が面倒みるからええねん!という感じで始めてみたが、一番最初のセットアップも今までのディストリビューションになかったやり方だったので大変だった。でもわかれば裏でこんなことやってるのかなって思いを馳せながらできるので良かった。

以降はその作業メモ。rootfsの暗号化をするなどちょっと変わった点はある。

Arch Linuxのインストール

インストールメディアを作る

イメージのダウンロード https://ftp.jaist.ac.jp/pub/Linux/ArchLinux/iso/latest/

ダウンロードしたらWindowsならrufusか何かでUSBメモリにISOイメージを焼いておく。

Secure Bootを無効にする

Secure Boot状態だとうまくBootできず手順もよくわからなかったので、Secure Bootは無効にしている。設定の仕方はマザーボードによるので省略。

昨今はブートローダから侵略してくるという事例もあり有効にしたかったが、私にはうまくできなかったので、今まで通り悪いことが起きないよう祈る運用とする。

インストールを始める

ネットワークにつないだ状態で起動する。

USBでブートできると、必要なパッケージをダウンロードし始め、プロンプトが出てきて作業できるようになる。(USBメモリを使ってローカルインストールもできそうな気がするけど、今回は試していない)

作業することが多いので、このままコンソールは辛い。コピペしたい。 USBでブートした直後はSSHサーバが立ち上がった状態になっているので、SSH経由で操作する。

  • passwd でrootのパスワードを変更(一時的)
  • ip addrDHCPで振られたIPを確認
  • リモートクライアントから SSHssh root@IPアドレスでアクセス

以降はSSH経由でやっていく

# インストールするドライブを選ぶ
lsblk

# インストールするドライブを変数にいれておく
SYSTEM_DEVICE=/dev/sda

# パーティション作成
# fdisk, parted, cfdisk, sfdisk, gdisk, sgdisk あたりが使える。
# - 1: 1GiB (/boot用)
# - 2: 16GiB (swap ※暗号化対象)
# - 3: 16GiB (ZFSのZIL用に予約。ZILはZFSが暗号化するはずなので普通に作る)
# - 4: 残り (システム用 ※暗号化対象)
sfdisk $SYSTEM_DEVICE << "__EOF__"
label: gpt
,1G,U
,16G,S
,16G,L
,,L
__EOF__

# rootパーティションを暗号化(パスフレーズ入力)
cryptsetup luksFormat "${SYSTEM_DEVICE}3"
#  暗号化されたrootパーティションを開く(パスフレーズ入力)
cryptsetup open --type luks "${SYSTEM_DEVICE}3" cryptroot
# /dev/mapper/cryptroot が存在すること
ls -l /dev/mapper/cryptroot


# bootのパーティションにFAT32ファイルシステムを作成
mkfs.fat -F 32 "${SYSTEM_DEVICE}1" 
# システム領域のbtrfsファイルシステムを作成
mkfs.btrfs  /dev/mapper/cryptroot

# マウント
mount /dev/mapper/cryptroot -o noatime,compress=zstd:1 /mnt
mount --mkdir "${SYSTEM_DEVICE}1" -o noatime /mnt/boot


# マウントの確認
df
# ...
# /dev/mapper/cryptroot 451007488   5920 448892928   1% /mnt
# /dev/sda1               1046508      4   1046504   1% /mnt/boot



# 一旦Linuxのインストールを進める(/etcディレクトリの生成などが必要なため)
# linux-ltsを使っているがこの辺はお好みで。
pacstrap -K /mnt base linux-lts linux-firmware

# 自動マウント用にfstabの生成の確認
# ※UUID=で始まる指定になっていること(/dev/sda1 等はデバイスの認識の具合で変動することがあったのでお勧めしない)
genfstab -U /mnt
# fstab作成
genfstab -U /mnt >> /mnt/etc/fstab

# SWAP領域をマウントするよう設定(/dev/mapper/swapは次のcrypttabの設定により用意される)
echo "/dev/mapper/swap        none            swap            defaults        0 0" >> /mnt/etc/fstab

# SWAP領域をcrypttabで定義する(起動毎に使い捨てのパスフレーズを使い作成する)
echo "swap     PARTUUID=$(blkid "${SYSTEM_DEVICE}2" -o value -s PARTUUID)    /dev/urandom    swap,cipher=aes-xts-plain64,size=256" >> /mnt/etc/crypttab

# crypttabの生成
# cryptsetup open したブロックデバイスを復号化する作業を **マウント前に** 行うために必要
output_file="/tmp/crypttab"
# 現在マップされている /dev/mapper 配下のデバイスについて処理
for mapper in /dev/mapper/*; do
  name=$(basename "$mapper")
  # cryptsetup status で元デバイスが取得できるか確認(control等関係ないものがいるのでチェックする)
  device=$(cryptsetup status "$name" 2>/dev/null | grep 'device:' | awk '{print $2}')
  if [ -n "$device" ] && [ -b "$device" ]; then
    uuid=$(blkid -s UUID -o value "$device")
    if [ -n "$uuid" ]; then
      echo "$name UUID=$uuid none luks"
    else
      echo "警告: $device のUUIDが取得できませんでした。" >&2
    fi
  fi
done | tee "$output_file"


# teeの結果で問題なければ、 /etc/crypttab に追記
cat "$output_file" >> /mnt/etc/crypttab

chrootして作業していく。

# chroot
arch-chroot /mnt

# ホスト名設定
echo testserver > /etc/hostname

# rootユーザのパスワード変更
passwd

# 管理ユーザの追加。ユーザ名は自由
USERNAME=ryozi
useradd -m $USERNAME -s /bin/bash
usermod -aG wheel $USERNAME

# パスワード設定
passwd $USERNAME

# 日本時間に設定
ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

# /etc/adjtimeの生成
hwclock --systohc

# ロケール設定(英語しかつかわないけど日本語も使えるようにしておく)
cat << '__EOF__' >> /etc/locale.gen
en_US.UTF-8 UTF-8
ja_JP.UTF-8 UTF-8
__EOF__
locale-gen
echo "LANG=en_US.UTF-8" > /etc/locale.conf
echo "KEYMAP=jp106" > /etc/vconsole.conf

# 使うパッケージを入れる
pacman -S \
  grub efibootmgr \
  btrfs-progs \
  openssh sudo less bash-completion

# マイクロコード
# https://wiki.archlinux.org/title/Microcode
# Intel系は intel-ucode
# AMD系は amd-ucode
pacman -S amd-ucode

# 好きなエディタを導入
pacman -S  vim
# viのシンボリックリンク作成
ln -s /usr/bin/vim /usr/bin/vi

# vimのマウス動作を設定なしにする
sudo mkdir /usr/share/vim/vimfiles/plugin
sudo tee /usr/share/vim/vimfiles/plugin/no-mouse.vim << "__EOF__" 
:set mouse=
__EOF__

# 自動起動設定
# (chroot中なのでsystemctl statusなどは使えない)
systemctl enable sshd

# 固定IPを設定するインタフェース名 or MACアドレスを控える
ip addr

# 固定プライベートIPの設定
# インタフェース名やIPなどは変える。MACアドレスも可能
cat << "__EOF__" > /etc/systemd/network/20-wired.network
[Match]
# デバイス名で一致させる
# Name=enp5s0
# MACアドレスで一致させる
MACAddress=XX:XX:XX:XX:XX:XX

[Network]
# 固定IP
Address=192.168.XX.XXX/XX
Gateway=192.168.XX.XXX
DNS=192.168.XX.XXX

# DHCPを使う
# DHCP=yes
__EOF__

# chroot中なので反映の確認はここではできない
# systemctl restart systemd-networkd
# ネットワークの自動起動設定
systemctl enable systemd-networkd

# 名前解決のために systemd-resolved.service を自動起動設定
# https://wiki.archlinux.org/title/Systemd-resolved
systemctl enable systemd-resolved.service


# wheelグループのユーザにsudoが行えるようにする
echo "%wheel    ALL=(ALL)    ALL" > /etc/sudoers.d/10-wheel

# sudoのパスワード再入力させるタイムアウトを延ばす
echo "Defaults timestamp_timeout=30" > /etc/sudoers.d/90-config

# /etc/resolv.conf があることを期待するアプリのためにスタブを配置
# 参考: https://ryozi.hatenadiary.jp/entry/2025/03/26/080118
ln -nfs /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf


# rootパーティションをinitramfsの時点で復号化する必要があるので移動(後述のsd-encryptで機能)
mv /etc/crypttab{,.initramfs}

# ----------------------

# ブートローダのインストール
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=grub --recheck
grub-mkconfig -o /boot/grub/grub.cfg

vim /etc/mkinitcpio.conf

# HOOKS を書き換える
# HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole block sd-encrypt filesystems fsck)

# /boot におくkernelのイメージを作り直す
mkinitcpio -P

# 作り直すと etc/crypttab が見えるはず
lsinitcpio /boot/initramfs-linux-lts.img | grep crypttab

ここまでで一旦完了。

# chrootから抜ける
exit
# 再起動
reboot

再起動すると、GRUBでブートが始まる。 USBブート時とは状況が違うのでパスフレーズを求められる。

その後、ちゃんとbootし、IPでSSH接続できればOK

TPMを使ってrootパーティションの暗号化を自動的に解除する

# 対象デバイス確認
lsblk

# 登録しなおす(/dev/sda3は適宜書き換え。)
systemd-cryptenroll --wipe-slot tpm2 --tpm2-device=auto --tpm2-pcrs=0+4+7 "/dev/sda3"

PCRの値は同じデバイス&ファームウェアなら復号化してよい、というぐらいの内容。物理的な盗難には耐性がないが、ストレージ処分時の機密保護が達成できればいいので、ここは許容する。(Securebootしていない時点で意味ないとは思うし。じゃあなぜPCR7を入れてるんだって話ではあるが。)

自動時刻同期の有効化

何もしないと時刻同期は無効になっている

$ timedatectl
               Local time: Wed 2025-04-09 01:57:30 JST
           Universal time: Tue 2025-04-08 16:57:30 UTC
                 RTC time: Tue 2025-04-08 16:57:33
                Time zone: Asia/Tokyo (JST, +0900)
System clock synchronized: no
              NTP service: inactive
          RTC in local TZ: no

chronyとかを使っていたら、これもsystemdが面倒見てくれるっぽいので試す。

# systemd-timesyncd を使って自動時刻同期を有効化
sudo systemctl enable --now systemd-timesyncd.service

起動するとしばらくして同期される(3秒ほど進んでいたが補正された) また、timedatectlNTP service: active となっていれば定期的に時刻同期が行われる状態になっている。

$ timedatectl
               Local time: Wed 2025-04-09 01:59:40 JST
           Universal time: Tue 2025-04-08 16:59:40 UTC
                 RTC time: Tue 2025-04-08 16:59:40
                Time zone: Asia/Tokyo (JST, +0900)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

時刻同期元は timedatectl show-timesync を見るとわかる。

ちなみに、NTPサーバは以下の方法で選ばれる

  • systemd-networkd による
  • systemd-timesyncd の設定による
    • /etc/systemd/timesyncd.confなど(主にfallback)

IPv6は全く詳しくないのでRA(ルーターアドバタイズメント)でこんなことができるなんて知らなかった。

Firewallの導入

インストール

sudo pacman -Syu nftables

# システムを再起動(インストールしただけでは使えないっぽいので。多分kernel moduleを読み込めば動く気はする)
sudo reboot

再起動後、設定ファイルを書く。nft コマンドを使って定義もできるが、試行錯誤するわけでもない場合は設定ファイルに書いて読み直すのが良いと思われる。

# 既存のルールのバックアップ
sudo nft list ruleset > /tmp/nftables.bak
# バックアップから復元する場合
# sudo nft -f /tmp/nftables.bak

sudo tee /etc/nftables.conf << "__EOF__"
#!/usr/sbin/nft -f

# 古いルールを破棄(これがないと読み直すたびに定義されてしまい二重になってしまったので)
destroy table inet filter

# 新しいルールを追加
table inet filter {
    chain input {
        type filter hook input priority 0
        policy drop

        # 関連および確立済みの接続は許可
        ct state invalid drop comment "early drop of invalid connections"
        ct state {established, related} accept comment "allow tracked connections"

        # ループバックインターフェースの通信は許可
        iif lo accept comment "allow from loopback"

        # SSH(TCPポート22)へのアクセスを許可
        tcp dport 22 accept

### このあたりはお好み
        # ICMPパケットの一部を受け入れる
        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept

        # mDNS(UDPポート5353)へのアクセスを許可
        udp dport 5353 accept

        # Samba(TCPポート139,445)へのアクセスを許可
        tcp dport 139 accept
        tcp dport 445 accept

        # iSCSIへのアクセス(TCPポート3260)を許可
        tcp dport 3260 accept

        # 秒間5回を超えるICMPは不応答
        pkttype host limit rate 5/second counter reject with icmpx type admin-prohibited
        counter
### ここまではお好み
    }

    chain forward {
        type filter hook forward priority 0
        policy drop
    }

    chain output {
        type filter hook output priority 0
        policy accept
    }
}
__EOF__

設定ファイルが構文的に正しいか確認

sudo nft -c -f /etc/nftables.conf

反映

sudo nft -f /etc/nftables.conf

自動起動設定 & 起動

# nftablesサービスを有効化(システム起動時に自動実行)
sudo systemctl enable --now nftables.service

現在のルールを確認するには sudo nft list rulesetで確認できる。また、これはそのまま /etc/nftables.conf へ書き出してもよいが、destroy table inet filterの行がないので、再読み込みするたびにルールが連なってしまうし、コメント行も消える。お好みで。

nftコマンド実行時にnetlink: Error: cache initialization failed: Invalid argumentなどと出る場合は、nftables関連のモジュール(nf_tables)が読み込まれていないとかの理由だと思うので、素直に再起動するとよい。

TODO: dockerを動かす際にiptablesをいじるはずなので、コンテナで動かすアプリを別ネットワークからアクセスする場合は何かする必要があるはず

ZFS (OpenZFS) のインストール

ここで初めてAURのお世話になる。

AURを使うために必要なパッケージを入れる。ビルドが必要なのでビルドツールなど。kernelモジュールならheadersも必要でlinuxlinux-ltsのどちらでインストールしたかどうかで合わせること。あとはパッケージごとに必要な物を入れておく必要がある。

sudo pacman -S base-devel git linux-lts-headers

yayを入れる。これはAURのパッケージの導入をちょっと簡単にしてくれるツール。

https://github.com/Jguer/yay?tab=readme-ov-file#binary

mkdir ~/src
cd ~/src
git clone https://aur.archlinux.org/yay-bin.git
cd yay-bin
makepkg -si

yayを使ってZFSをインストールする。今回はKernelのアップデートを気軽に行えるようにするためDKMS版を使う。

yay zfs-dkms

どのパッケージを入れるか聞かれる。今回は1がお目当てのパッケージ。

$ yay zfs-dkms
4 aur/zfs-dkms-staging-compat-git 2.2.6.r0.gbaa5031456-2 (+4 0.20) (Out-of-date: 2025-01-14)
    Kernel modules for the Zettabyte File System (release staging branch) with compatibility patches for latest stable kernel.
3 aur/zfs-dkms-staging-git 2.3.1.r0.gf3e4043a36-1 (+10 0.29)
    Kernel modules for the Zettabyte File System (release staging branch) with compatibility patches for latest stable kernel.
2 aur/zfs-dkms-git 2:2.3.99.r162.g788e69ca5d-1 (+27 0.43)
    Kernel modules for the Zettabyte File System.
1 aur/zfs-dkms 2.3.1-2 (+186 1.97)
    Kernel modules for the Zettabyte File System.
==> Packages to install (eg: 1 2 3, 1-3 or ^4)
==> 1

依存があると、それも聞かれるので答える。

:: There are 2 providers available for zfs-utils=2.3.1:
:: Repository AUR
    1) zfs-utils 2) zfs-utils-staging-git

Enter a number (default=1):
==> 1

クリーンビルドするか聞かれている?よくわからないが All で答える。

AUR Explicit (1): zfs-dkms-2.3.1-2
AUR Dependency (1): zfs-utils-2.3.1-1
Sync Dependency (1): dkms-3.1.6-1
:: (1/2) Downloaded PKGBUILD: zfs-utils
:: (2/2) Downloaded PKGBUILD: zfs-dkms
  2 zfs-dkms                                 (Build Files Exist)
  1 zfs-utils                                (Build Files Exist)
==> Packages to cleanBuild?
==> [N]one [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)
==> A

差分を見るか聞かれている?何の差分かよくわからんので、NoneのつもりでEnterを押したら次に進んだ。

:: Deleting (1/2): /home/ryozi/.cache/yay/zfs-dkms
HEAD is now at 560cadc ZFS 2.3.1 upstream release
:: Deleting (2/2): /home/ryozi/.cache/yay/zfs-utils
HEAD is now at 48a1037 ZFS 2.3.1 upstream release
  2 zfs-dkms                                 (Build Files Exist)
  1 zfs-utils                                (Build Files Exist)
==> Diffs to show?
==> [N]one [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)
==>

色々ログが出た後、PGP keyをインポートするか確認された。Yesを応答。

 :: PGP keys need importing:
 -> C33DF142657ED1F7C328A2960AB9E991C6AF658B, required by: zfs-dkms  zfs-utils
:: Import? [Y/n] Y

Keyがインポートされると、dkmsのインストールの確認が出てきたのでインストールする。

:: Importing keys with gpg...
gpg: key 0AB9E991C6AF658B: public key "Brian Behlendorf <behlendorf1@llnl.gov>" imported
gpg: Total number processed: 1
gpg:               imported: 1
resolving dependencies...
looking for conflicting packages...

Packages (1) dkms-3.1.6-1

Total Download Size:   0.04 MiB
Total Installed Size:  0.14 MiB

:: Proceed with installation? [Y/n] Y

以降、zfs-dkmsに関するパッケージの取得やビルドが始まる。しばらく待つ。

しばらく待つと、それぞれのパッケージをインストールするか聞かれるのでインストールする。

loading packages...
resolving dependencies...
looking for conflicting packages...
``
Packages (2) zfs-utils-2.3.1-1  zfs-utils-debug-2.3.1-1

Total Installed Size:  42.81 MiB

:: Proceed with installation? [Y/n] Y

...

loading packages...
resolving dependencies...
looking for conflicting packages...

Packages (1) zfs-dkms-2.3.1-2

Total Installed Size:  18.24 MiB

:: Proceed with installation? [Y/n] Y

インストールが進むとkernel モジュールの再構成が行われる。

...
(3/3) Install DKMS modules
==> dkms install --no-depmod zfs/2.3.1 -k 6.12.20-1-lts
==> depmod 6.12.20-1-lts

ここまででうまくいけばインストール完了。

dkmsのステータスはよさそう

$ dkms status
zfs/2.3.1, 6.12.20-1-lts, x86_64: installed

コマンドはまだ使えなさそう。モジュールがロードされていないのが理由。

$ zfs list
The ZFS modules cannot be auto-loaded.
Try running 'modprobe zfs' as root to manually load them.

従って modprobe zfs やってみる

sudo modprobe zfs
lsmod | grep zfs
zfs list

こんな感じになればzfsが使える状態。

$ lsmod | grep zfs
zfs                  6635520  0
spl                   159744  1 zfs
$ zfs list
no datasets available

ZFSのZpoolを作る

ここは環境依存なのでお好みで。

# 対象のデバイスを確認する
lsblk

# zpoolを作る
# zpoolはマウントさせない。名前はzpool0とする。
# 今回は8つのデバイスを使い、mirror vdevを4つ作りストライピングさせる。(mirrorの行)
# ZIL 用 (log)にSSDのZIL用のパーティションを指定する
sudo zpool create -f \
  -O mountpoint=none zpool0 \
  mirror /dev/disk/by-id/ata-WDC_*{4Y2,1C0} \
  mirror /dev/disk/by-id/ata-WDC_*{4DD,HVK} \
  mirror /dev/disk/by-id/ata-WDC_*{5JA,UXL} \
  mirror /dev/disk/by-id/ata-WDC_*{A38,33T} \
  log /dev/disk/by-id/ata-KIOXIA-EXCERIA_SATA_SSD_*0L5-part4

ZFSファイルシステムを作る

# 暗号化用のキーファイルを作る
sudo dd if=/dev/random of=/etc/zfs/pool.key bs=32 count=1
sudo chmod 400 /etc/zfs/pool.key

# チェックサムファイルを作っておく(コピー時の破損確認など)
sudo cat /etc/zfs/pool.key | sha256sum | sudo tee /etc/zfs/pool.key.sha256sum
# チェック
sudo cat /etc/zfs/pool.key | sha256sum -c /etc/zfs/pool.key.sha256sum
# => -: OK
# ※これをなくすと復号化できなくなるのでUSBメモリなどにバックアップとるなどしておく


# ファイルシステム作成。 samba用の領域を作る。
# mountpointは /zpool0/sambashare とする。ファイルシステム名も `zpool0/sambashare`と合わせる
# ZFSによる透過的な暗号化を有効化する。(-o encryptionの行)
# ZFSによる透過的な圧縮を有効化する。アルゴリズムはlz4(圧縮率はイマイチだが、圧縮も解凍もとにかく早い)(-o compressionの行)
# そのほかのオプションはSamba共有するのに指定するとよさそうらしい値を指定している。(本当かどうかは知らない)
sudo zfs create \
  -o mountpoint=/zpool0/sambashare \
  -o encryption=aes-256-gcm -o keylocation=file:///etc/zfs/pool.key -o keyformat=raw \
  -o compression=lz4 \
  -o atime=off -o acltype=posixacl -o xattr=sa -o dnodesize=auto -o normalization=formD -o casesensitivity=insensitive \
  zpool0/sambashare

自動起動設定

https://wiki.archlinux.jp/index.php/ZFS

インストール直後は自動起動する設定になっていない。

今回はzfs-mount.serviceの手順にする。zfs-mount-generator の手順は複雑そうなので避ける。(/varなどをマウントしたい場合には必要らしい)

# zpoolを作ったら、cachefileプロパティを設定する(/etc/zfs/zpool.cacheに書き出される)
# zpool0 というzpoolを作った場合
sudo zpool set cachefile=/etc/zfs/zpool.cache zpool0

# サービスの有効化
# zfs-import-cache: /etc/zfs/zpool.cache に記載があるデバイスからzpoolを読み込む
# zfs-load-key: 暗号化に使っているキーをロードする
# zfs-mount: zpoolからdatasetマウントする
# zfs-volume-wait: /dev/zvolが出てくるまで待つ(iscsiのtarget等で後続の処理に使う場合)
sudo systemctl enable zfs.target
sudo systemctl enable zfs-import.target
sudo systemctl enable zfs-volumes.target
sudo systemctl --now enable zfs-import-cache.service
sudo systemctl --now enable zfs-mount.service 


# datasetを暗号化している場合はこれも必要。 zfs load-key に相当
# しかし Arch Linuxでは /usr/lib/systemd/system/zfs-load-key.service はなぜか /dev/null にリンクされている。
# 調べると「作ればいいじゃん」という感じの回答が多かったのでとりあえず作る
# 参考: https://forum.proxmox.com/threads/zfs-load-key-service-is-masked.155274/

sudo rm /usr/lib/systemd/system/zfs-load-key.service 
sudo tee /usr/lib/systemd/system/zfs-load-key.service << '__EOF__'
[Unit]
Description=Load ZFS encryption keys
DefaultDependencies=no
Before=zfs-mount.service
After=zfs-import.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/zfs load-key -a

[Install]
WantedBy=zfs-mount.service
__EOF__

# 自動起動設定
sudo systemctl daemon-reload
sudo systemctl --now enable zfs-load-key.service

再起動し、マウントされていることを確認する。

# 再起動
sudo reboot 

# 認識していること
zfs list

# マウントできていること
df

自動スクラブ設定

1か月に1度のzfs-scrub-monthly@.timer と1週間に1度のzfs-scrub-weekly@.timerがあるのでこれを利用してサービスを有効化すると楽。

@の後ろはプール名を指定する。

# zpool0 というzpoolを週1でスクラブする場合
sudo systemctl enable zfs-scrub-weekly@zpool0.timer

1か月にすべきか、1週間にすべきかについては、基本1週間がよい。不備は早く気が付けたほうが良いため。

一方でスクラブは全ファイルをチェックするのでHDDにその分の負担がかかる。古(いにしえ)より伝わる祈りの力をもって運用する場合は1か月でもいいだろう。要はバランス。

SambaとiSCSIターゲットの導入

Sambaのインストール

インストール

sudo pacman -Sy samba

下準備と設定ファイルの作成

# 作ったZFSのファイルシステムに
# root以外も読み書きできるよう権限を与える
sudo chown -R nobody:nobody /zpool0/sambashare
sudo chmod 0777 /zpool0/sambashare

# samba共有の設定ファイルの作成
# - guestはNG。(必ずログインさせる)
# - 作成したファイルはログインした誰でも読み取り可能。書き込みはownerのみ
# - グループは考えてない。otherと同じで読み取りのみにする
sudo tee /etc/samba/smb.conf <<__EOF__
[global]
    workgroup = WORKGROUP
    server string = Ore no Samba Server
    security = user
    map to guest = Bad User
    load printers = no
    disable spoolss = yes
    obey pam restrictions = yes

# /home/$USER\\address\homes でアクセスできるようにする(なくてもいい)
[homes]
    comment = Home
    browseable = yes
    writable = yes
    guest ok = no
    valid users = %S

# その他
[sambashare]
    comment = Share
    path = /zpool0/sambashare
    browseable = yes
    writable = yes
    guest ok = no
    create mask = 0755
    directory mask = 0755
__EOF__

自動起動設定。samba共有ができればよく、NetBIOSを使った名前解決はしなくてよいためnmbは不要。

sudo systemctl --now enable smb.service 

ログインユーザを作り、パスワードを設定する。(OS上に存在しないユーザならuseraddも必要なはず)

sudo smbpasswd -a "ryozi"

後は同一ネットワーク内のWindowsなどから接続できるか試す。ファイアウォールの設定も忘れずに。

iSCSiターゲットの導入

iSCSIはもう古い技術ぽいけど、代替もないので。targetcli経由で設定を仕込んでいく。

# AURよりtargetcli-fb をインストール
yay -S targetcli-fb

# ※途中で python-rtslib-fb をインストール後に削除するか確認されるが、`N`を応答すること

# ==> WARNING: Skipping verification of source file PGP signatures.
# ==> Validating source files with sha512sums...
#    rtslib-fb ... Passed
# :: Remove make dependencies after install? [y/N]


# ディレクトリがないと targetcli が次のエラーが出て失敗したので作る
# "Cannot set dbroot to /etc/target. Please check if this directory exists."
sudo mkdir /etc/target

# バージョン確認。
sudo targetcli -v
# => /usr/bin/targetcli version 2.1.58

# もしバージョン番号が出ず、以下のようなエラーが出る場合は以下の2行のコマンドを実行してみる(Pythonのパッケージの参照の仕方が変なのでそれを動きそうな形にいじっているだけ)
# $ sudo targetcli -v
# Traceback (most recent call last):
#   File "/usr/bin/targetcli", line 24, in <module>
#     from targetcli import UIRoot
#   File "/usr/lib/python3.13/site-packages/targetcli/__init__.py", line 18, in <module>
#     from .ui_root import UIRoot
#   File "/usr/lib/python3.13/site-packages/targetcli/ui_root.py", line 31, in <module>
#     from rtslib_fb.utils import ignored
# ModuleNotFoundError: No module named 'rtslib_fb.utils'; 'rtslib_fb' is not a package

sudo cp /usr/lib/python3.13/site-packages/rtslib_fb.py{,.bak}
sudo ln -nfs /usr/lib/python3.13/site-packages/rtslib /usr/lib/python3.13/site-packages/rtslib_fb

ZFSにボリュームを作成する。

# ZFS volumeを作成
# sambaの時に使ったキーを使って透過暗号化(使いまわしていいのか?という話もあるが、鍵ファイルさえ漏れなければ復号化できないという前提ならホームユースならまぁいいでしょうということで。)
# 
sudo zfs create -V 1TB \
  -o encryption=aes-256-gcm -o keylocation=file:///etc/zfs/pool.key -o keyformat=raw \
  zpool0/windows-iscsi

# ZFSボリュームを作ると、/dev/zvol 配下からブロックデバイスとして参照できるようになる
ls -l /dev/zvol/zpool0/windows-iscsi 

iSCSIの設定をしていく

# バックストアを作成
# /dev/zvol/zpool0/windows-iscsi はあらかじめ zfs create -V でWindows機用にボリュームを作っている
sudo targetcli /backstores/block create name=windows-iscsi dev=/dev/zvol/zpool0/windows-iscsi 

#  ターゲットの生成。ターゲット名が自動生成されるので控えておく(末尾のドットは不要)
# ターゲット名は自分で指定することもできるはず
sudo targetcli /iscsi create
# => Created target iqn.2003-01.org.linux-iscsi.testserver.x8664:sn.ad96bd5d6fc9.

# LUNの設定。ターゲットとバックストアを紐づける
sudo targetcli /iscsi/iqn.2003-01.org.linux-iscsi.testserver.x8664:sn.ad96bd5d6fc9/tpg1/luns create /backstores/block/windows-iscsi

# ACLの設定。特定のイニシエーター名のみ接続できるようにする(イニシエータ名を詐称されたらダメなので脆弱ではある)
# イニシエーター名はイニシエータ各自で持っている。Windowsならイニシエーターのプロパティ→構成から確認できる
sudo targetcli /iscsi/iqn.2003-01.org.linux-iscsi.testserver.x8664:sn.ad96bd5d6fc9/tpg1/acls create iqn.1991-05.com.microsoft:desktop-XXXXXXXX

# CHAP認証をつける
sudo targetcli /iscsi/ set discovery_auth enable=1

# ユーザIDはおこのみで。イニシエータ名でもいいでしょう。
sudo targetcli /iscsi/ set discovery_auth userid=ryozi
sudo targetcli /iscsi/ set discovery_auth password=PA$$w0rd!

# 設定を保存
sudo targetcli saveconfig

自動起動設定。なぜかsystemdのユニットファイルがインストールされないので書く。

参考: https://github.com/open-iscsi/targetcli-fb/issues/152

以下のユニットファイルは参考程度に。ZFS volumeをiSCSIで提供したいのでAfterに zfs-volume-wait.service を入れたりしている。

sudo tee /etc/systemd/system/target.service << '__EOF__'
[Unit]
Description="Load/Clear targetcli config"
DefaultDependencies=No
After=zfs-volume-wait.service

[Service]
Type=oneshot
Environment=CONFIG_FILE=/etc/target/saveconfig.json
EnvironmentFile=-/etc/sysconfig/targetcli
ExecStart=-/usr/bin/targetcli restoreconfig $CONFIG_FILE
RemainAfterExit=true
ExecStop=/usr/bin/targetcli clearconfig confirm=True
ExecReload=/usr/bin/targetcli restoreconfig $CONFIG_FILE clearexisting

[Install]
WantedBy=remote-fs.target
__EOF__

# 配置したユニットファイル反映
sudo systemctl daemon-reload

# 自動起動有効化
sudo systemctl --now enable target.service

後は再起動して自動起動するか試す。

targetclidというプログラムも用意されているが、これはtargetcliの動作を高速化するためのもので、iSCSI ターゲットのためのものではないので特に起動する必要はない。(targetcliをたくさんいじるなら必要かもしれないが。)

データの引っ越し

既存のNASから新しいNASへのデータの引っ越し。

時間はかかるが、rsyncチェックサム確認を有効化にしながらおこなった。Sambaで管理していたファイル自体は4TB弱あった。rsyncのCPU使用率が常に100%でここがネックになってたと思われる。

# 両方の環境にrsyncを入れて実行
# - rootログインできない都合で-p,-g,-oは使わない(パーミッションはumask, group, ownerは実行ユーザに依存)
# - 進捗はみたいので -P を使う
rsync -crltvP /oldnas/share/ ryozi@remote-ip:/zpool0/sambashare/

iSCSIで管理していたファイルはメイン機のバックアップと古い仮想HDDファイルだったが、ほとんど使ってなかったのでこれを機に捨てた。(コピー作業が面倒くさかった)

ちなみに、tarとsshでもできる。rsyncチェックサム無しより少し早いぐらい。gzip圧縮はしないほうがよい。チェックサムの仕組みはないし、レジュームもできない(rsyncも更新不要なら送らないだけでレジュームというわけではないが)

# 進捗が見たければ pv を入れて挟むなどする
tar -C /oldnas/share/ -c ./ | ssh ryozi@remote-ip tar xv -C /zpool0/sambashare

関係ないが、ZFSを採用した理由は今後こういうコピーがzfs send/recvで速くできるようになるのではないか、という期待があったりする。

おまけ

mDNS を導入 (systemd)

DNSサーバはもちたくないけど、名前解決を楽にしたかったので。

https://wiki.archlinux.jp/index.php/Systemd-resolved#mDNS

sudo vi /etc/systemd/resolved.conf

# [Resolve] セクションで設定(コメントアウトされているので解除)
MulticastDNS=yes

その後、 反映させるためサービス再起動

sudo systemctl restart systemd-resolved.service

反映確認。Global: yesになっていること。

$ resolvectl mdns 
Global: yes
Link 2 (eth0): no

Link 2 (eth0) の方も対応する必要がある。

# ネットワーク用の設定を書いているはずなので、編集する
sudo vi "/etc/systemd/network/20-wired.conf"

# [Network] セクションに追記
MulticastDNS=yes

その後、 反映させるために サービス再起動

sudo systemctl restart systemd-networkd.service

Windowsからベンチマーク(Samba, iSCSI

10Gbps環境下でのベンチマーク

どっちも結構早い。iSCSIなんていらないのでは、という気もしてくる。

iSCSIとSambaのベンチマーク結果

ランダム読み書きが速すぎると思ったら、Zpoolのキャッシュ設定(primarycache, secondarycache)の存在を知ったので、無効にしてみたところ結構落ちた。でもやっぱりiSCSIいらないのでは

iSCSIとSambaのベンチマーク結果(キャッシュ無効時)

シーケンシャルリードでもネットワーク帯域をきっちり叩けなかったのは残念。ただ200MB/s弱の時と比べたら十分かな。これ以上の速度を求めたければSSDを積みなさいということで。

しばらくはメインマシンのバックアップディスクとLLMのモデル置き場として活躍してもらうこととなるでしょう。

メモ

Arch Linuxのパッケージ管理

dnfaptとは別のpacmanを使う。

リポジトリは公式のcore,extraがあるが、これとは別にArch User Repository (AUR)とよばれるリポジトリがあり、こちらは自分でビルドを行う必要がある。AURで実績があがるとextraに移管される仕組みらしい。

Arch Linuxはサービスも自分で起動設定しないといけなさそう

パッケージをインストールしても自動的にサービス起動することはない模様。全部自分でやれ、という感じでよい。

お仕事で「そのパッケージはインストールすると勝手に有効化されるから有効化の手順はいらないよ」という指摘をもらったが「2重に実行しても影響なくて冪等ならやっておけばええじゃろがい」って口答えをして論争になったので、そういう無駄がなく機械的に「最後に有効化の設定しないと」となるのがよい(?)

mkinitcpio -P 時の警告

# mkinitcpio -P
==> Building image from preset: /etc/mkinitcpio.d/linux-lts.preset: 'default'
==> Using default configuration file: '/etc/mkinitcpio.conf'
  -> -k /boot/vmlinuz-linux-lts -g /boot/initramfs-linux-lts.img
==> Starting build: '6.12.19-1-lts'
  -> Running build hook: [base]
  -> Running build hook: [systemd]
  -> Running build hook: [autodetect]
  -> Running build hook: [microcode]
  -> Running build hook: [modconf]
  -> Running build hook: [kms]
  -> Running build hook: [keyboard]
  -> Running build hook: [sd-vconsole]
  -> Running build hook: [block]
  -> Running build hook: [sd-encrypt]
  -> Running build hook: [filesystems]
  -> Running build hook: [fsck]
==> WARNING: No fsck helpers found. fsck will not be run on boot.
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/boot/initramfs-linux-lts.img'
==> WARNING: errors were encountered during the build. The image may not be complete.
==> Running post hooks
  -> Running post hook: [sbctl]
Signing /boot/vmlinuz-linux-lts
File has already been signed /boot/vmlinuz-linux-lts
==> Post processing done
==> Building image from preset: /etc/mkinitcpio.d/linux-lts.preset: 'fallback'
==> Using default configuration file: '/etc/mkinitcpio.conf'
  -> -k /boot/vmlinuz-linux-lts -g /boot/initramfs-linux-lts-fallback.img -S autodetect
==> Starting build: '6.12.19-1-lts'
  -> Running build hook: [base]
  -> Running build hook: [systemd]
  -> Running build hook: [microcode]
  -> Running build hook: [modconf]
  -> Running build hook: [kms]
==> WARNING: Possibly missing firmware for module: 'ast'
  -> Running build hook: [keyboard]
==> WARNING: Possibly missing firmware for module: 'xhci_pci_renesas'
  -> Running build hook: [sd-vconsole]
  -> Running build hook: [block]
==> WARNING: Possibly missing firmware for module: 'aic94xx'
==> WARNING: Possibly missing firmware for module: 'bfa'
==> WARNING: Possibly missing firmware for module: 'qed'
==> WARNING: Possibly missing firmware for module: 'qla1280'
==> WARNING: Possibly missing firmware for module: 'qla2xxx'
==> WARNING: Possibly missing firmware for module: 'wd719x'
  -> Running build hook: [sd-encrypt]
  -> Running build hook: [filesystems]
  -> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/boot/initramfs-linux-lts-fallback.img'
  -> Early uncompressed CPIO image generation successful
==> Initcpio image generation successful
==> Running post hooks
  -> Running post hook: [sbctl]
Signing /boot/vmlinuz-linux-lts
File has already been signed /boot/vmlinuz-linux-lts
==> Post processing done

WARNING: No fsck helpers found. fsck will not be run on boot.はHOOKSにfsckがあるのに、ヘルパが見つからないので、ブート時にfsckを行わない(だからHOOKSに指定しても機能してないぞ)、という警告。

Linuxfsckといえば、ファイルシステムが正しい状態かチェックしたり必要なら修復したりするツール。実装はファイルシステムごとにあり、btrfsならfsck.btrfsが相当する。packageはbtrfs-progsとして配布されている。

# pacman -F fsck.btrfs
core/btrfs-progs 6.13-1
    usr/bin/fsck.btrfs

なので、ブート時にfsck実施したいならこのbtrfs-progsパッケージを入れておけばやってくれるようになるし、警告に出なくなる。

WARNING: errors were encountered during the build. The image may not be complete. は他の警告があると出るらしい。上記のbtrfs-progsをインストールしたら出なくなった。

WARNING: Possibly missing firmware for module: はそのハードウェアを使っていないなら無視して大丈夫らしい。

https://wiki.archlinux.jp/index.php/Mkinitcpio#Possibly_missing_firmware_for_module_XXXX

よくあるのは上記リンクにある。対応するファームウェアのパッケージのdescriptionを見るとわかる。 また、見ての通り、プリセットがdefaultfallbackのうちのfallbackでの警告であり、fallbackは基本的に使わないはずなので今回は無視する。