ラズパイでログディレクトリをRAM DISKに移行する手順の簡略化

**`我、ラズパイにおけるログディレクトリRAM DISK化の新興勢力とならむ`**
～無駄なディレクトリを`rc.local`で量産するのはもうやめよう。～

----

**`Raspberry Pi`**で**SDカードの延命**が必要なのは周知の事実です。
Web上で見当たる主な手法は以下四つ。

- `SWAP`の無効化
- ログ削減
- **一時ディレクトリ**の`RAM DISK化`
- **ログディレクトリ**の`RAM DISK化`

上三つは簡単に行えますが、ログのRAM DISK化だけ難しそうなイメージがあり避けていました。
`SDカードなんて大容量で安いし別にいいかぁ`と日和っていたのですが、ラズパイなんて趣味の世界、妥協したらそこで終わりと思い直してトライしてみました。
ところが、イメージ通り**どのサイトを見ても面倒くさそうなことが書いてあります**。
やめて！怠惰星からやって来た僕のライフはもうゼロよ！
僕はクーデターを起こそうと思います。

# 前提条件

まず、言うまでもないですが自己責任でお願いいたします。
ラズパイが動かなくなろうが壊れようが爆発しようが一切の責任を負いません。
~~爆発はちょっと気になりますが。~~

今回のスタンスは`エラーが出なければOK`です。
相容れない方にはお勧めいたしません。

## 環境

```
Device => Raspberry Pi 4 2GB
OS => Raspberry Pi OS Lite

$ uname -a
Linux raspberrypi 5.4.51-v7+ #1333 SMP Mon Aug 10 16:45:19 BST 2020 armv7l GNU/Linux
```

## 主流の手法（読み飛ばしていただいても結構）

だいたい以下のような感じかと思います。

1. `/etc/fstab`を編集して`/var/log`をマウントするように設定
2. `/etc/rc.local`を編集して起動時にディレクトリを作成するように設定
3. 再起動
4. マウントされていることをチェック
5. 古い`/var/log`を削除

とりあえず一つツッコんでおきたいのが、マウント後に`rm -rf /var/log/*`を実行してもSDカード上のログディレクトリは消えないということ。

そしてもうひとつ`/etc/rc.local`でディレクトリを作成するのは誤りであるということ。

<details><summary>先生！rc.local君がまだ来てません！</summary>
`/etc/rc.local`の実行タイミングはとても遅いです。
試しに`cron@reboot`と`rc.local`の実行タイミングを計ってみました。<br>
`デーモン名:秒_ミリ秒`
`crontab :38s_954ms`
`rc.local:41s_869ms #実にcron@rebootの3秒後！`<br>
今日の`rc.local`がどのように実行されているかというと、**`systemd`の`rc-local.service`というデーモンとしてキック**されています。
このデーモンのユニットファイルを見ると起動順序は`After=network.target`となっていて、これは`nginx`や`Apache`と同じです。
つまり、**`nginx`等より後に実行される**（nginx起動時に必要なファイルを用意できない）可能性が十分にありますから、`rc.local`に記述するのは誤りということになります。~~（（多分。））~~
</details>

# (1) RAM上にディレクトリをマウント

まず、[素晴らしいコマンド](https://qiita.com/yone098@github/items/bba8a42de6b06e40983b)を用いてログディレクトリを把握しておきます。
ホームディレクトリで以下のコマンドを実行しましょう。

```
$ sudo find /var/log | sort | sed '1d;s/^\.//;s/\/\([^/]*\)$/|--\1/;s/\/[^/|]*/|  /g' > logtree.txt
$ cat logtree.txt
|  |  |--alternatives.log
|  |  |--apt
|  |  |  |--eipp.log.xz
|  |  |  |--history.log
|  |  |  |--term.log
|  |  |--auth.log
|  |  |--auth.log.1
|  |  |--btmp
|  |  |--daemon.log
|  |  |--daemon.log.1
|  |  |--debug
(以下略)
```

それでは`sudo nano /etc/fstab`で以下を追記しましょう。
ちなみに`/var/tmp`は`RAMDISK`化するなと[Arch wiki](https://wiki.archlinux.jp/index.php/Tmpfs)に書かれています。
各容量（`size=XXm`）はデバイスに応じて調整してください。
`/tmp`は少なすぎるとエラーが発生し、`/var/log`は毎日再起動すれば`16m`でも十分である点を考慮してください。

```bash:/etc/fstab
tmpfs  /tmp      tmpfs defaults,size=128m,noatime,mode=1777 0 0 #一時フォルダのRAM DISK化
tmpfs  /var/log  tmpfs defaults,size=32m,noatime,mode=0755 0 0
```

<details><summary>fstabのオプションについて</summary>
**`defaults`:** [基本オプションを一括指定](https://wiki.archlinux.jp/index.php/Fstab#.E3.83.95.E3.82.A3.E3.83.BC.E3.83.AB.E3.83.89.E3.81.AE.E5.AE.9A.E7.BE.A9)
**`size`:** 最大サイズを指定
**`noatime`:** [パフォーマンス向上](https://wiki.archlinux.jp/index.php/Fstab#atime_.E3.82.AA.E3.83.97.E3.82.B7.E3.83.A7.E3.83.B3)
**`mode`:** 権限 **`1　 　7 　 6　　5`**
. 　 　　 　　 **0**:無 　**rwx**　**rw**　**rx**
. 　 　　 　　 **1**:配下でファイル所有者のみが操作可（一時フォルダ向け設定）
</details>

~~再起動すればこれらがマウントされているはずです。~~
**追記(2021/09/03)**
**以下に記載の`★追記（エラー対策）`をしないと起動できない可能性があります。必ず対策を行ってから再起動してください。強く推奨します。**

コマンド`df -h`で、`Filesystem`が`tmpfs`となっているディレクトリ`/var/log`を確認できれば無事完了です。

ここで、`ls -l /var/log`と打ちログディレクトリを確認すると、いくつかのログファイルが自動的に作成されているのが確認できます。
試しに`sudo apt update`を実行してみると、`/var/log/apt`が自動で作成されるはずです。（アップデートがあれば）
このようにディレクトリを用意してあげなくても**システムの起動自体に支障はありません**し、**多くの場合は自動で`ディレクトリ/ファイル`が作成されます**。
ただ、`nginx`など一部のデーモンはエラーとなって起動できないので`cat /var/log/syslog | grep /var/log`などでログディレクトリに関するエラーを確認してみましょう。
`No such file`などの文言が含まれるエラーがあれば、次のステップに進んで該当するファイルを作成しなければなりません。
特にそのような**エラーが見当たらなければログディレクトリの`RAMDISK`化は以上です**。

ちなみに、次のエラー`Directory /var/log to mount over is not empty, mounting anyway`は`SDカード側の/var/logは空じゃないよ！`という意味ですので(3)でSD上の`/var/log`を空にすれば消えます。

### ★追記（エラー対策）
デバイスによって上記手法でエラーが出なくても極稀にですが正常に動作しない事がありましたので、~~念の為~~対策です。
実行しておくことをおすすめ致します。

**追記(2021/09/03)**
実行しない場合、OSが起動しない可能性があります。
必ず行うようにしてください。

`sudo nano /lib/systemd/system/rsyslog.service`で以下のように`After=local-fs.target`を追記。

```bash:/lib/systemd/system/rsyslog.service
[Unit]
Description=System Logging Service
Requires=syslog.socket
After=local-fs.target # ここを追記！
Documentation=man:rsyslogd(8)
Documentation=https://www.rsyslog.com/doc/

[Service]
Type=notify
ExecStart=/usr/sbin/rsyslogd -n -iNONE
StandardOutput=null
Restart=on-failure

# Increase the default a bit in order to allow many simultaneous
# files to be monitored, we might need a lot of fds.
LimitNOFILE=16384

[Install]
WantedBy=multi-user.target
Alias=syslog.service
```

要するに、マウントが完了してからログサービスを起動しましょうということです。

# (2) エラー対策

不幸にも自分でログディレクトリを作成してくれないデーモンに行き会った方々へ。
例として`nginx`を用いて、エラーへの対処法を記します。
~~怠惰な人向けと、まめな人向けの二つ手法を用意しました。~~
**全人類向けに統一しました。(2021/3/31)**

## 全人類向け

`systemd`に`ExecStartPre`なんて便利な機能があります。

`sudo nano /opt/arrange.sh`で以下のシェルスクリプトを作成します。
`syslog`上のエラーを確認して、保存しておいた`logtree.txt`を参考に不足しているディレクトリとファイルに適宜書き換えてください。
`Nginx`では以下のようにします。

```bash:/opt/arrange.sh
#!/bin/bash

mkdir /var/log/nginx # ディレクトリを作成
touch /var/log/nginx/error.log # 空ファイルを作成
touch /var/log/nginx/access.log # 空ファイルを作成
```

##### 実行権を付与します。

```
sudo chmod a+x /opt/arrange.sh
```

##### ユニットファイルを編集します。

```
sudo nano /lib/systemd/system/rsyslog.service
```

以下のように`ExecStartPre`を追記してください。

```bash:/lib/systemd/system/rsyslog.service
[Unit]
Description=System Logging Service
Requires=syslog.socket
After=local-fs.target  # エラー対策
Documentation=man:rsyslogd(8)
Documentation=https://www.rsyslog.com/doc/

[Service]
Type=notify
ExecStartPre=/opt/arrange.sh  # ここを追記！
ExecStart=/usr/sbin/rsyslogd -n -iNONE
StandardOutput=null
Restart=on-failure

# Increase the default a bit in order to allow many simultaneous
# files to be monitored, we might need a lot of fds.
LimitNOFILE=16384

[Install]
WantedBy=multi-user.target
Alias=syslog.service
```

`ExecStartPre`は、その名の通り`ExecStart`の前に実行されます。
一行で完結するので全人類にぴったりだと思います。

再起動してエラーが出なければ`RAMDISK化`完了です。
お疲れ様でした。
**(3)**の清掃に進んでください。

エラーが出てしまった人は再度不足しているファイルを確認し、正しいディレクトリを作成できるように試行錯誤してください。

#### 追記（2021/07/19）

@farmertaki 様より`mysql`の対策についてコメントいただきました。
ありがとうございます。
導入されている方はコメント欄をご参照ください。

### ~~怠惰な人向け~~

**本項目は非推奨です。(2021/3/30)**

cronでお手軽にディレクトリを作成します。

まず、`sudo nano /opt/arrange.sh`で以下のシェルスクリプトを作成します。
`syslog`上のエラーを確認し、不足しているディレクトリとファイルを設定してください。
階層構造は保存しておいた`logtree.txt`を参考にすると良いでしょう。

```bash:/opt/arrange.sh
#!/bin/bash

mkdir /var/log/nginx # ディレクトリを作成
touch /var/log/nginx/error.log # 空ファイルを作成
touch /var/log/nginx/access.log # 空ファイルを作成
```


1. **`sudo chmod a+x /opt/arrange.sh`** (実行権付与)
1. **`sudo crontab -e`** (crontabを編集)
1. **`@reboot /opt/arrange.sh`左を追記** (再起動時に`/opt/arrange.sh`を実行)

以上で再起動時に必要なディレクトリが作成されます。
再起動してエラーが出ないことを確認できれば**RAMDISK化**完了です。

### ~~まめな人向け~~

**本項目は非推奨です。(2021/3/30)**

`crontab`は起動順序と手間のバランスから最適解ですが、最善の策とは言い難いです。
ここでは`systemd`を用いて**正しい**実装を行いたいと思います。

まず、実行するファイルを作成するところまでは上の`怠惰な人向け`と変わりませんので、同様に`/opt/arrange.sh`を作成して**実行権付与**まで行います。

作成した`/opt/arrange.sh`の実行タイミングは、**`tmpfsマウント`後、かつ`rsyslog.service`起動前**が正しいと思われますので、以下のようにユニットファイルを記述します。

```bash:/etc/systemd/system/arrange.service
[Unit]
Description=Arrange log directory
After=local-fs.target
Before=rsyslog.service

[Service]
ExecStart=/opt/arrange.sh
Type=oneshot
RemainAfterExit=True

[Install]
WantedBy=multi-user.target
```

`local-fs.target`は**`/etc/fstab`の記述に従ってファイルシステムをマウント**してくれるサービスです。
多くの場合`crontab @reboot`も同様のタイミングで実行され、結果的に上の条件を満たしますが、`systemd`経由の実行で明示するのが最善でしょう。

1. `sudo systemctl daemon-reload` (ユニットファイル更新)
1. `sudo systemctl enable arrange.service` (自動起動するように設定)

再起動してエラーが出ないことを確認できれば、**ログディレクトリの`RAMDISK`化完了**です。

# (3) 旧ディレクトリの清掃

正しく移動できることが確認できたのでSDカード上のログディレクトリは削除します。

1. `sudo nano /etc/fstab` => **`/var/log`の行をコメントアウト**
1. 再起動
1. `sudo systemctl stop syslog.socket rsyslog`でログを一時停止
1. `sudo nano /etc/fstab` => **`/var/log`のコメントアウトを外す**
1. `sudo rm -rf /var/log/*`でログディレクトリをクリア
1. 再起動

`/var/log`に直で書き込みに来るデーモン（`rbfeeder`など）があればそれも止めてからクリアしてください。
`Directory /var/log to mount over is not empty, mounting anyway`がエラーとして出ている間はSDのログディレクトリがクリアな状態でシステム終了できていません。
どのデーモンが書き込みに来てるか分からない場合は、自分でインストールしたデーモンをとにかく止めてみましょう。

# 備考

**RAM DISK**化後は、定期的に再起動してあげないとRAM上に用意した領域を使い切ってしまいますので、僕は`cron`で毎晩深夜に再起動させてます。
`sudo crontab -e`を実行して以下のように追記します。

```bash
16 3 * * * /sbin/reboot # 毎日3時16分に再起動
```

### ログの保存

やはり再起動で消えてしまうのはログとしてよろしくありません。
以下のようにして、シャットダウン時にSDへ移動するようにしました。

まずスクリプトを用意して、実行権を付与します。

```bash:/opt/log.sh
#!/bin/bash

tar Jcf /var/log_history/`date "+%y%m%d_%H%M%S"`.tar.xz /var/log
find /var/log_history -name '*' -mtime +7 -delete
```

これを毎シャットダウン時に実行すると、`/var/log`配下のディレクトリとファイルが全て`/var/log_history/YYMMDD_HHMMSS.tar.xz`という形式で一週間保存されます。

1. `sudo mkdir /var/log_history`でディレクトリを用意します。
2. `sudo nano /lib/systemd/system/rsyslog.service`で**`ExecStop`を追記**します。

```bash:/lib/systemd/system/rsyslog.service
[Unit]
Description=System Logging Service
Requires=syslog.socket
After=local-fs.target  # エラー対策
Documentation=man:rsyslogd(8)
Documentation=https://www.rsyslog.com/doc/

[Service]
Type=notify
ExecStartPre=/opt/arrange.sh  # エラー対策
ExecStart=/usr/sbin/rsyslogd -n -iNONE
ExecStop=/opt/log.sh   # ！！ここを追記！！他を書き換えるとログが止まってしまうかもしれないので注意！！
StandardOutput=null
Restart=on-failure

# Increase the default a bit in order to allow many simultaneous
# files to be monitored, we might need a lot of fds.
LimitNOFILE=16384

[Install]
WantedBy=multi-user.target
Alias=syslog.service
```

僕は`rsyslog.service`終了時のコマンドとして実行するように設定しました。

最後に`sudo systemctl daemon-reload`でユニットファイルをリロードしたら再起動してみてください。
`/var/log_history`配下に`YYMMDD_HHMMSS.tar.xz`というファイルが存在していれば成功です。
ちなみに`.xz`は高能率な圧縮形式で解凍は`sudo tar xvf hogehoge.tar.xz`です。

# Congratulations!!

クーデターといった割には変わらずに煩雑な手法になってしまいました。
`rc-local.service`のユニットファイルに**`/etc/rc.local Compatibility`**とあるように、`/etc/rc.local`は、`Linux`が**systemd**に移行してから振る舞いが変わっています。
古い記事を参照するときに`rc.local`があれば実行タイミングに気を使う必要がありそうですね。

なにはともあれ、このRAM DISK化でそれなりに延命が図られるはずです。
末永く素敵な木苺ライフをお楽しみ下さい。