Systemd かつ UEFI boot な Linux で Swap をOffにする方法 ～  fstab が全てとは限らない

# まとめ

Ubuntu Linux 16.04 LTS でSwapをOffにしようとしたのですが、/etc/fstab をコメントアウトするだけでは恒久化されず、再起動するたびに復活してしまうので原因を探ったところ、Systemd が /etc/fstab 以外にもマウントポイントを漁る仕組みを持っているのが原因でした、という件について仕組みを調べてみました。

ざっくり言うと、

*  Systemd で起動するLinuxで、起動ディスクのパーティーションテーブルが GPT で UEFI ブートする場合、/etc/fstab に書いてないものがマウントされることがあります。
* 具体的には、Swapと、/, /home, /srv について、UEFIブートした起動ディスクに存在し、特定パーティーションタイプ(GUID)を持つ形で設定されているパーティーションは、/etc/fstab に書いてなかったりコメントアウトされていても、自動的にマウント・Swapon されます
* この動作を止めたい場合、fdisk でパーティーションタイプ(パーティーションGUID)を変更するか、GPTから消すしかありません
   * fdisk でGUID Partition type をGeneric な Linux filiesystem (fdisk の Hexcode 20, GUID Parition Type が0FC63DAF- から始まるもの) あたりに変更しておけばよい

以下はその仕組みのまとめです。ファイルパスはUbuntu16.04 を想定しており、RedHat/CentOS や他ディストリビューション・バージョンでは微妙に違うことがあります。多くの場合 /usr をつけたりつけなかったりすれば対応できます。

# 恒久的に swapoff したい方へのインストラクション

* 起動ディスクのデバイス名を確認しましょう。
* `df` で見えるroot partition (下記の例では /dev/nvme0n1p2) と同じデバイス(下記の例では、/dev/nvme0n1) 上の別パーティーションで /boot/efi (下記の例では /dev/nvme0n1p1) がマウントされており、このデバイス上に swap が存在する場合、この記事に書いてある仕組みが理由で swap 無効化ができない可能性があります。

```
$ df                                                 
Filesystem      1K-blocks     Used  Available Use% Mounted on           
udev            197411372        0  197411372   0% /dev                 
tmpfs            39487264    10660   39476604   1% /run                 
/dev/nvme0n1p2 xxxxxxxxxx xxxxxxxx xxxxxxxxxx   2% /                  
tmpfs           197436320      140  197436180   1% /dev/shm             
tmpfs                5120        4       5116   1% /run/lock            
tmpfs           197436320        0  197436320   0% /sys/fs/cgroup       
/dev/nvme0n1p1     307016     9696     297320   4% /boot/efi          
tmpfs            39487264       32   39487232   1% /run/user/1000       
```
* `$ cat /proc/swap` して、現在有効になっている swap パーティション(例では /dev/nvme0n1p3) が起動ディスク上(例では /dev/nvme0n1) にあるか確認します、もし違うディスク上であればまた別の話なので頑張って理由を探ってください

```
$ cat /proc/swaps
Filename                                Type            Size    Used    Priority
/dev/nvme0n1p3                          partition       xxxxxxxx        0       -1
```
* `$ sudo swapoff -a` で全 Swap を外すか、`$ sudo swapoff [当該デバイス]` して、まずは swap を切りましょう
* `$ sudo fdisk /dev/nvme0n1` で当該起動ディスクのパーティーションテーブルを開き、p で Swap Parition の番号を確認し (例では 3番パーティーション) 、t コマンドでParition Type を 20 などに変更し、w で書き込みましょう

```
$ sudo fdisk /dev/nvme0n1

Welcome to fdisk (util-linux 2.27.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Command (m for help): p
Disk /dev/nvme0n1: サイズ
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: [略]

Device              Start        End    Sectors  Size Type
/dev/nvme0n1p1       2048     616447     614400  300M EFI System
/dev/nvme0n1p2     xxxxxx xxxxxxxxxx xxxxxxxxxx  x.xx Linux filesystem
/dev/nvme0n1p3 xxxxxxxxxx xxxxxxxxxx   xxxxxxxx   16G Linux swap

Command (m for help): t
Partition number (1-3, default 3):
Hex code (type L to list all codes): L

Type of partition 3 is unchanged: Linux swap.

Command (m for help): t
Partition number (1-3, default 3):
Hex code (type L to list all codes): L
  1 EFI System                     C12A7328-F81F-11D2-BA4B-00A0C93EC93B
<中略>
 19 Linux swap                     0657FD6D-A4AB-43C4-84E5-0933C84B4F4F
 20 Linux filesystem               0FC63DAF-8483-4772-8E79-3D69D8477DE4
 21 Linux server data              3B8F8425-20E0-4F3B-907F-1A25A76F98E8
 22 Linux root (x86)               44479540-F297-41B2-9AF7-D131D5F0458A
 23 Linux root (ARM)               69DAD710-2CE4-4E3C-B16C-21A1D49ABED3
 24 Linux root (x86-64)            4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709
 25 Linux root (ARM-64)            B921B045-1DF0-41C3-AF44-4C6F280D3FAE
 26 Linux reserved                 8DA63339-0007-60C0-C436-083AC8230908
 27 Linux home                     933AC7E1-2EB4-4F13-B844-0E14E2AEF915
 28 Linux RAID                     A19D880F-05FC-4D3B-A006-743F0F84911E
 29 Linux extended boot            BC13C2FF-59E6-4262-A352-B275FD6F7172
 30 Linux LVM                      E6D6D379-F507-44C2-A23C-238F2A3DF928
<中略>

Hex code (type L to list all codes): 20

Changed type of partition 'Linux swap' to 'Linux filesystem'.

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Re-reading the partition table failed.: Device or resource busy

The kernel still uses the old table. The new table will be used at the next reboot or after you run partprobe(8) or kpartx(8).
```
* 上記例のHexcode List で言えば、19 のパーティーションが mkswap されていたり、21～25,27 になっているパーティーションがフォーマットされている場合、/etc/fstab に書いてなくてもマウントされることになりますので、上記例では 20 (一般的な Linux filesystem) に変更して、書き込んでいます
* 再起動して、`$ df` や `$ cat /proc/swaps` で予期に反したことが起きていないか確認しましょう

# 経緯

Kubernetes クラスタを構築しようと Ubuntu16.04 lts のホストを設定しておりました。
Kubernetes はswap を無効化しておかないといけないのですが、`$ swapoff -a` して、/etc/fstab の swap 行をコメントアウトしても再起動すると swap が復活してしまいます。

試行錯誤しつつ理由を探っていたところ、Systemd の systemd-gpt-auto-generator の仕組みによるところであることがわかり、興味深かったので動きを追ってみました。

# 以下、仕組みの説明

## 前提知識

* ubuntu も 15.04 あたりから　sysv-init や upstart でなくて、Systemd を起動管理に使います　
* Systemd は、いろんな物事を unit という単位で考え、unit には旧来の /etc/init.d/以下のスクリプト的なものもあれば、動的に生成されるものもあります
* Systemd はマウントやSwap処理をネイティブコードで /sbin/mount や /sbin/swapon を呼び出して行い、シェルスクリプトを経由しません
* Systemdの仕組みについては他のドキュメント (https://qiita.com/a_yasui/items/f2d8b57aa616e523ede4) をご参照ください

## Systemd におけるマウント・Swapon 処理の概要

Systemd は、ファイルシステムマウントや swapon に関しては、`man systemd.special` に書かれているように special unit として、ネイティブに直接実行されます。このため、老害にはおなじみの /etc/init.d/mount.sh 的なmount や swapon などのコマンドを呼び出すシェルスクリプトが存在しません。「どこをマウントすべきか、Swapon すべきか」が書かれた特殊な unit file のみが存在し、これに基づいて実際のマウントが行われます。Swapなら `*.swap`([systemd.swap](https://www.freedesktop.org/software/systemd/man/systemd.swap.html#))、ファイルシステムマウントなら `*.mount`([systemd.mount](https://www.freedesktop.org/software/systemd/man/systemd.mount.html#)) という末尾で終わる特殊 unit file が該当します。

これらの特殊 unit file は、`man system-generator` に書かれている仕組みで、起動直後に動的に生成されます。system-generator は Systemd から呼び出される小さなバイナリで、fstab 以外にも他の system-generator も存在しています。Systemd 起動直後のUnit群を起動する手前のタイミングでSystemd が system-generator 群を呼び出すことによって動的な unit file を自分で生成し、この生成しておいた 動的 unit file に定義された必要な依存関係のステージにおいて、Systemd によって改めて実行されます。

皆様が慣れ親しんでいる /etc/fstab の解釈は、このような動的unitを生成するSystemdの構成要素である system-generator の一つである、 `man systemd-fstab-generator` が行います。これと並んで、表題の件に関しては `man systemd-gpt-auto-generator` がマウントやswap処理を指示するunit設定ファイルを生成し、これらが systemd-fstab-generator とほぼ同じタイミングで処理される動的 unit file として解釈処理されるため、「/etc/fstab に書いてなくてもSwapがOnになる」ということが発生します。

## ざっくりと処理の流れ

1. Systemd が動きはじめる
2. 起動直後に、system-generator が呼び出され、おおむねデバイス単位で動的 unit file が生成される
    * 一つは systemd-fstab-generator で、 fstab に基づいて /run/systemd/generator(.late)/*.mount などに unit file を生成する
    * 一つは systemd-gpt-auto-generator で、GPT の Parition table に基づいて /run/systemd/generator(.late)/*.mount などに unit file を生成する
    * このフレームワークの仕組みとして、ここで他の generator が *.mount や *.swap な unit file を生成してもおかしくない
3. Systemd による Unit 群の起動が始まる
4. Systemd target と依存関係に基づいた必要なタイミングで 2. で生成された動的 unit file に基づいて、Systemd 自身が /sbin/mount や /sbin/swapon を呼び出す
    * タイミング1: default.target -> multi-user.target -> basic.target -> -.mount -> root の /sbin/mount
    * タイミング2: default.target -> multi-user.target -> basic.target -> sysvinit.target -> local-fs.target -> *.mount -> 各パーティーションの /sbin/mount
    * タイミング3: default.target -> multi-user.target -> basic.target -> sysvinit.target -> swap.target -> *.swap -> 各パーティーションの /sbin/swap

## 関係する unit configuration file

* Ubuntuの場合、`/lib/systemd/system-generators` に各generatorが存在し、この generator が `/run/systemd/generator(.late)` に動的なunit file を生成します
* 動的な unit configuration file の末尾が "*.swap" なものは、`man systemd.swap` に書かれているような special unit 処理として、Systemd 自身が /sbin/swapon を呼び出して有効化します
* unit configufation file の末尾が "*.mount" なものは、同様に `man systemd.mount` な special unit 処理としてSystemdが直接 /sbin/mount 他を呼び出して有効化します
* `/lib/systemd/system` の下に、他の一般的な service の unit 定義ファイルと並んで、`local-fs.target` や `swap.target` というそれっぽい Target Unit定義ファイルが存在しますが、これを見ても実際にどこをどうマウントするのかは書いてないので、上記動的ファイルを探る必要があります
* *.swap は、fstab を見て生成する systemd-fstab-generator 以外に、systemd-gpt-auto-generator というのが存在して、/run/systemd/generator に unit file を作る、というのが表題の件のミソです

## systemd-gpt-auto-generator による GPT に基づいたマウント

* 詳細は `man systemd-gpt-auto-generator`
* UEFIで起動し、起動ディスクがGPTテーブルを持ち、EFI system partition と同一ディスク上に存在する下記のパーティーション（正確には下記の Parition Type GUID を持つもの）は、fstab に書いてなくてもマウントするよう、*.swap や *.mount を作るのです
	* / - Parition Type が次のうちどれか
	    * 22 Linux root (x86)               44479540-F297-41B2-9AF7-D131D5F0458A
	    * 23 Linux root (ARM)               69DAD710-2CE4-4E3C-B16C-21A1D49ABED3
	    * 24 Linux root (x86-64)            4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709
	    * 25 Linux root (ARM-64)            B921B045-1DF0-41C3-AF44-4C6F280D3FAE
	* /home - Partition Type GUID: A19D880F-05FC-4D3B-A006-743F0F84911E
	* /srv  - Partition Type GUID: 3B8F8425-20E0-4F3B-907F-1A25A76F98E8
	* Swap  - Partition Type GUID: 0657FD6D-A4AB-43C4-84E5-0933C84B4F4F
* 逆に言うと fstab でいくらコメントアウトしてもマウントします
* とはいえ、実際のところ Ubuntu のインストーラーはSwap以外のファイルシステムについては上記 Partition Type GUID を使わず、Hexcode 20 の Linux filesystem (Partition Type GUID: 0FC63DAF-8483-4772-8E79-3D69D8477DE4) を使うのが標準動作なようなので、明示的に上記Typeを受かってファイルシステムを作らなければ、ファイルシステムに関してはめったに起きない動作だと思われます
* 一方、Swap の場合は上記Type を使ってインストールされるので、表題の件が一番起きやすい困惑する動作だと思われます
* 動的生成された mount/swap　の unitは、以下に存在します
    * ファイルシステムの場合 `/run/systemd/generator/[マウントポイント名].mount` 
    * swap の場合 `/run/systemd/generator.late/dev-[デバイス名].swap`
* ソースコードはこんな感じで、割とダイレクトに unit config 書いてる
https://github.com/systemd/systemd/blob/master/src/gpt-auto-generator/gpt-auto-generator.c

## systemd-fstab-generator による fstab に戻づいたマウント

* `/etc/fstab` は sytemd-fstab-generator が /etc/fstab を読んで、systemd native unit に変換するという手順で処理されています
* systemd-fstab-generator は、起動直後に `/etc/fstab` を読み込んでその内容に基づいて `/run/systemd/generator/*.mount` を生成します
  * / なら、`/run/systemd/generator/-.mount`
  * /xxx なら、`/run/systemd/generator/xxx.mount`
  * さらに、
* `/run/systemd/generator/*.mount` は起動プロセスの後段、local-fs.target などのタイミングで systemd が native で理解して /sbin/mount を実行します
* systemd-fstab-generator は、systemd-gpt-auto-generator と、systemd-*-generator のフレームワークにおいて同列なので、どちらか片方によって拾われればマウントされることになります
* ソースコードはこんな感じで割とシンプルに読んで書いてる
https://github.com/systemd/systemd/blob/master/src/fstab-generator/fstab-generator.c

## 動的 unit file に基づく実際の swapon 処理

* ソースコードのここらへん https://github.com/systemd/systemd/blob/master/src/core/swap.c
    * 末尾の UnitVTable swap_vtable で unit として呼ばれる関数テーブルとして定義されると思われる
    * start 時なら、 `swap_start() -> swap_enter_activating() -> swap_spawn() -> exec_spawn()` で実際の `/sbin/swapon` を呼ぶ
    * 呼ばれるコマンドのパスは、`swap_enter_activating()` 中で `exec_command_set(s->control_command, "/sbin/swapon", NULL);` で設定されているようである

## 動的 unit file に基づく実際の mount 処理

* 同様にソースコードのここらへん https://github.com/systemd/systemd/blob/master/src/core/mount.c
     * `mount_start()` あたりから

## この仕組みの副作用と感想

「必ずしもfstabにおける設定が全てとは限らない」のは、Traditional な UNIX Mannerの直感にはだいぶ反するので、いろいろ起きてもおかしくない気はする。

が、前述のように Linux ファイルシステムを使う場合の最も標準的なGUID Partition Type である Linux filesystem は少なくとも gpt-auto-generator は扱わないので、「ファイルシステムマウント」の Issue としては発生してもおかしくないが発生にくいと思われます。

一方、Swap は気付きにくい分（Swapなんて今時ほとんどのシステムはあってもなくても動くし気付かないし嫌がるのは k8s ぐらいだろうし)、問題起こしやすいような気はする。例えばこの([systemd-gpt-auto-generator overrides swap options in fstab #6192](https://github.com/systemd/systemd/issues/6192) は、Swap パーティーションに fstab で TRIM option つけても有効化されないという問題（もう修正されてる）。なおこのfix だと、「Swap が fstab に書いてない」状況はスルーされるので、表題の件は関係ない。

フレームワークとしては *-generator で動的に生成した *.mount と *.swap が後段でマウントする、という仕組みは特徴的であり、今後他の generator が何かしでかすようになる可能性はあると思うので、「ああ Systemd というのはこうやってるんだなあ」という理解はしておくべきかと思ったので、こんなエントリにまとめてみた次第。

ていうか /etc の下をキーワードでGrepすればなんとかなると思っている老害に Systemd つらい。

# fdisk, fstab と unit file の具体例
## /dev/nvme0n1p1 がUEFI起動パーティーションで、/dev/nvme0n1p3 が Linux swap のGUID Partition Type を持つ場合

fstab に swap が書いてなくても、swapが有効化されている

```
$ sudo fdisk -l /dev/nvme0n1
Disk /dev/nvme0n1: x.y TiB, x bytes, x sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 略

Device              Start        End    Sectors  Size Type
/dev/nvme0n1p1       2048     616447     614400  300M EFI System
/dev/nvme0n1p2     616448 xxxxxxxxxx xxxxxxxxxx  x.yT Linux filesystem
/dev/nvme0n1p3 xxxxxxxxxx xxxxxxxxxx   xxxxxxxx   16G Linux swap

$ cat /proc/swaps
Filename                                Type            Size    Used    Priority
/dev/nvme0n1p3                          partition       16777280        0       -1

$ cat /etc/fstab 
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
# / was on /dev/sda2 during installation
UUID=[実際のUUID]                   /               ext4    errors=remount-ro 0       1
# /boot/efi was on /dev/sda1 during installation
UUID=2B5E-0A5B /boot/efi       vfat    umask=0077      0       1
# swap was on /dev/sda3 during installation
# UUID=[実際のUUID] swap swap    sw              0       0

$ cat /run/systemd/generator.late/dev-nvme0n1p3.swap
# Automatically generated by systemd-gpt-auto-generator

[Unit]
Description=Swap Partition
Documentation=man:systemd-gpt-auto-generator(8)

[Swap]
What=/dev/nvme0n1p3
```

## /dev/nvme0n1p1 がUEFI起動パーティーションで、/dev/nvme0n1p3 が Linux server data のGUID Partition Type を持つ場合

fstab に書かれていなくても、/srv にマウントされている

```
$ sudo fdisk -l /dev/nvme0n1
Disk /dev/nvme0n1: x.x TiB, x bytes, x sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 略

Device              Start        End    Sectors  Size Type
/dev/nvme0n1p1       2048     616447     614400  300M EFI System
/dev/nvme0n1p2     616448 xxxxxxxxxx xxxxxxxxxx  x.xT Linux filesystem
/dev/nvme0n1p3 xxxxxxxxxx xxxxxxxxxx   33554575   16G Linux server data

$ cat /run/systemd/generator.late/srv.mount
# Automatically generated by systemd-gpt-auto-generator

[Unit]
Description=Server Data Partition
Documentation=man:systemd-gpt-auto-generator(8)
Before=local-fs.target
Requires=systemd-fsck@dev-nvme0n1p3.service
After=systemd-fsck@dev-nvme0n1p3.service

[Mount]
What=/dev/nvme0n1p3
Where=/srv
Type=ext4
Options=rw

$ cat /etc/fstab
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
# / was on /dev/sda2 during installation
UUID=[実際のUUID]          /               ext4    errors=remount-ro 0       1
# /boot/efi was on /dev/sda1 during installation
UUID=2B5E-0A5B /boot/efi       vfat    umask=0077      0       1

$ df -k
Filesystem      1K-blocks     Used  Available Use% Mounted on
udev            197411372        0  197411372   0% /dev
tmpfs            39487264    10660   39476604   1% /run
/dev/nvme0n1p2 xxxxxxxxxx xxxxxxxx xxxxxxxxxx   2% /
tmpfs           197436320      140  197436180   1% /dev/shm
tmpfs                5120        4       5116   1% /run/lock
tmpfs           197436320        0  197436320   0% /sys/fs/cgroup
/dev/nvme0n1p1     307016     9696     297320   4% /boot/efi
/dev/nvme0n1p3   16382376    45036   15482096   1% /srv
tmpfs            39487264       28   39487236   1% /run/user/1000
```

# 参考資料
* Swap の Systemdによる 有効化: https://wiki.archlinux.jp/index.php/%E3%82%B9%E3%83%AF%E3%83%83%E3%83%97#systemd_.E3.81.AB.E3.82.88.E3.82.8B.E6.9C.89.E5.8A.B9.E5.8C.96
* Systemdメモ書き: https://qiita.com/a_yasui/items/f2d8b57aa616e523ede4
* 各コマンドの man page
    * systemd.special(7): https://www.freedesktop.org/software/systemd/man/systemd.special.html
    * systemd.mount(5): https://www.freedesktop.org/software/systemd/man/systemd.mount.html
    * systemd.swap(5): https://www.freedesktop.org/software/systemd/man/systemd.swap.html
    * systemd-generator(7): https://www.freedesktop.org/software/systemd/man/systemd.generator.html
    * systemd-gpt-auto-generator(8): https://www.freedesktop.org/software/systemd/man/systemd-gpt-auto-generator.html
    * systemd-fstab-generator(8): https://www.freedesktop.org/software/systemd/man/systemd-fstab-generator.html