くにゅくにゅの雑記帳

実験ノート的なやつ

FreeBSD を read-only で起動

Raspberry Pi がそうであるように,SDメモリカードや,USBメモリなど,FlashデバイスでLinuxなどのOSを運用するケースが多くなってきました。これで,一つ気になるのは,頻繁な書き換えに伴うFlashメモリの劣化です。Flashメモリは,消去と書き込みによって劣化が起きるため,信頼性をもって行える回数には上限があります。ちゃんとしたSSDだと,書き換えが特定のメモリブロックに集中しないようウェアレベリングを行っていたりしますが,SDやUSBメモリがどこまでの対応をしているのかは,よくわかりません(やってない?)。

わたし自身は壊した経験はありませんが,Raspberry Pi に MySQL を入れて INSERT や UPDATE が定期的に起きる運用を続けていたら,半年くらいでSDカードが怪しくなってきたという話を知人から聞きました。たまたまかもしれませんが,好ましい使い方でないことは確かだと思います。

そんなわけで,長期にわたって運用する場合は,書き換え回数をなるべく減らすことが吉です。いちばん手っ取り早くて確実なのは,システムが完成して運用フェーズに入ったら,OSのルートファイルシステムを read-only でマウントしてしまい,/var や /tmp はRAMディスクとして用意することです。副次的に,すべてのファイルシステムが read-only なら,なんの罪悪感もなく電源をぶち切りできるシステムなります。

FreeBSDの場合

FreeBSD だと,picobsd や nanobsd みたいな選択肢もあり得ますが,わたしは普通の FreeBSD を read-only で実際に運用しているので,やり方を紹介します。

/ を read-only に

まず,/ を read-only にする方法。/etc/fstab の / について,Options を rw から ro 書き換えるだけです。

# Device        Mountpoint      FStype  Options Dump    Pass#
/dev/da0p2      /               ufs     ro      1       1
/dev/da0p3      /persistent     ufs     rw      1       1

このシステムでは,ディスクを2パーティションに分割し,/ のほかに /persistent を作って read-write でマウントしてあります。これは必須ではありませんが,後述するように,/var をRAMディスクとすると再起動するごとに内容が消えてしまうので,消えて欲しくないデータを置くところとして使います。あと,swap したら負けなので,swap パーティションはそもそも作りすらしません。

RAMディスクを作る

次に,RAMディスクの設定。/etc/rc.conf に以下の行を追加するだけで,勝手に作ってくれます。サイズは調整可能です。

tmpmfs="YES"
tmpsize="20m"
varmfs="YES"
varsize="32m

これで,起動時に 20BMの /tmp と 32MBの /var がRAM上に作られ,newfs とマウントに加え,中身の作成まで勝手にやってくれます(fstabに追記する必要はありません)。

確認

 上記の設定を行って再起動し,mount コマンドで確認しましょう。以下のように / が read-only でマウントされ,/var と /tmp がRAMディスクになっていたらOKです。

/dev/da0p2 on / (ufs, local, read-only)
devfs on /dev (devfs, local, multilabel)
/dev/da0p3 on /persistent (ufs, local, soft-updates)
/dev/md0 on /var (ufs, local)
/dev/md1 on /tmp (ufs, local)
一時的に read-only と read-write を行き来する

 mount -o ro / と mount -o rw / でできます。read-write から read-only に戻すとき,オープンされているファイルがあるとエラーになるので注意してください(それでもなお強制的に戻す場合は -f を付ける)。

/var の中身はどうやって作られているのか

RAMディスク上に置いた /var は,起動するごとに新たに作られます。この時点では /var は空っぽのはずですが,起動が完了した時点では,なぜかちゃんとした /var のディレクトリツリーができあがっており,/var/log や /var/mail などが存在しています。

これは,/etc/rc.d/var というスクリプトが,うまくやってくれているためです。/var が空っぽであった場合は,/var に新たなディレクトリツリーを作る処理が入っています。何をどう作るかの定義は,/etc/mtree/BSD.var.dist および /etc/mtree/BSD.sendmail.dist (sendmailを使う場合のみ)に書かれています。中身を見ると,少し取っつきにくいファイルですが,/var の構造が定義されていることがわかります。

作られた /var 以下に独自のディレクトリが欲しい場合は,/etc/mtree/BSD.var.dist に定義を追記すると作ってくれます。

/var に起動時からファイルを置きたい

/etc/mtree/BSD.var.dist では,/var に作るディレクトリしか定義できません。したがって,この定義から,ファイルを作らせることはできません。しかし,/var になんらかのファイルを置きたい場合もあります。いちばんあり得るのは,crontab でしょう。crontab は /var/cron/tabs に保存されますが,/var は毎回まっさらになるので,このままでは cron が使えません。

いちばん綺麗な解決策は,なるべく /var に期待しない方法を考えることです。cron の場合であれば,/var/cron/tabs を使わず,すべて /etc/crontab に書いてしまえば解決します。そのほかにも,プログラム側で使用するディレクトリを指定できる場合は,/var 以外のどこかに指定します。こういったときに,前述した /persistent が生きてきます。

もう一つは,消えないところにひな形を用意しておき,/etc/rc が実行されるタイミングで,必要なファイルをコピーしたり,シンボリックリンクを作るスクリプトを用意しておくことです。

消えては困るファイルの扱い

RAMディスク上の /var は,再起動で綺麗さっぱり消えてしまいます。しかし,記録として重要なログや,再起動をまたいだ継続性が必要なステートを記録しているファイルなどは,消えてしまうと困ります。こういうときのために,わたしは /persistent を使うようにしています。

たいていのアプリケーションは,自らが書き出すファイルのパスを設定することができるので,必要に応じて /persistent に向けておきます。それで対処ができない場合は,/var から /persistent  へシンボリックリンクを張ってもかまいません。

大事なのは,劣化防止を目的として read-only にしたのですから, /persistent にどうでもいいようなものをゴチャゴチャと置かないことです。真に必要なものだけに絞りましょう。

お行儀の悪いプログラムとの付き合い方

/ とか /usr が read-only で大丈夫なの?という疑問がわくかもしれませんが,通常運用時には,/ や /usr に書き込む必要はないので,ほぼ大丈夫です。問題は,どうしてもそういうところに書き込みたがる,お行儀の悪いプログラムがある場合です。

そういうものに出くわしたときは,まず,できるだけ設定で /var あたりに持って行けないか検討します。ただし,それではどうしてもダメなものもあります。代表的なものとしては,resolv.conf を /etc に書き出す必要がある dhclient などがあります。

この手のものは,個別に対処するしかありません。特定のファイルだけ /var あたりへのシンボリックリンクとしたりとか。