深夜systemd雑談

深夜に某IRCでsystemdの話してたのをちょっと編集したログ

コマンドオプション順序

ar*******> sudo systemctl hogehoge start にしてほしかった
moriwaka> sudo service hogehoge start とすればいいんじゃないかな
moriwaka> LSBにserviceがある限りたぶん永遠に残るよ
ar*******> なるほど
ar*******> service 使えばいいのはわかるんだけど、systemctl が service と記述順を違えたのはなんでかしら
ca***> command subcommand ... という書式で統一したかったとか?
moriwaka> command subcommand object にしたかったんでしょうなあ
moriwaka> shellの補完と相性よさそうだし
ar*******> stop して start してとかやるときの編集がめんどい
pl*****> コマンド順はあんまり気にならないんだけど、
pl*****> systemctl start hogehoge してから systemctl status hogehogeするのがたまにダルイ
ar*******> それ
moriwaka> わかる
ar*******> history 編集するときカーソル移動だるい
moriwaka> 僕もctrl-w で1 word消すのが癖になってるから今でも編集ミスるわ
pl*****> unit自分で書いてると頻繁にそれやるからつかれる
ar*******> カーソル移動の時間が積もり積もって過重労働に
moriwaka> Alt-Bとか使えばいいんだけど手癖がなあ……

Restartの契機

pl*****> 最近はまったのはRestart=(OnFailure=だったかな?)がType=simpleじゃないと機能しないこと
pl*****> simpleだと機能してoneshotだとダメなんだったっけ
pl*****> 忘れっちった
pl*****> コマンドをサービス化してるのでなるべくoneshot使ってるんだけど、typeによって使えない機能があったりするのは知らなかった・・・
moriwaka> oneshotだとprocessがexitするの想定された動作だから
moriwaka> processがexitしててもserviceとしてはactiveでrestart動かないのはまあそうかなという気持ち
pl*****> うむー
pl*****> 確かにそう言われるとそういうもんか

Conditionほげほげが好き

moriwaka> Conditionalなんとか が割と好きで、どの条件で失敗したかstatusで見えると「おお…… 進歩してる……」って感じになる
pl*****> conditionalって何?
moriwaka> Conditionなんとかか。ConditionPathExists= とか
pl*****> ConditionPathExistsとかか
pl*****> あれ便利なので多様してる〜

バイナリログつらい

ts******> systemdのユニットファイルなどは使いやすいけど、何故バイナリログにしてやがったのか・・・
pl*****> 並列起動だから出力されるログの順序が決定的じゃないってのもたまに騙される
ts******> "SYSTEMD_PAGER="を設定すると使い勝手が良くなる >systemd

WantedBy=multi-user.targetの動き

pl*****> 未だにちょっと理解できてないのが、
pl*****> WantedBy=multi-user.targetしてるユニットは、
pl*****> Reached target Multi-User System.ってログに出てたらキックされてるってことでいいのかな
pl*****> Reached target Multi-User System.って出た後にwants/wantedbyしてるユニットが起動されるのかなmoriwaka> それ[install]セクションにかくので
moriwaka> enableするとmulti-user.targetのWantsに追加される
moriwaka> Wantsなだけなので、前後関係は決まらない
moriwaka> systemdの「起動したいリスト」にはのってるはず
pl*****> あーーーっていうことは
pl*****> Reached target Multi-User System.って出てるから、サービスがキックされてるって保証はないのか・・・
pl*****> systemdは何をもってReached target 〜を出力してるんだろ・・・
moriwaka> そのtargetをactivateしたらじゃないの。
moriwaka> Before=multi-user.target とか書けばいいのでは(targetについた時点で起動がはじまってると保証してほしいときは
pl*****> だとするとbefore=multi-user.target必要になるのか・・・
pl*****> ふむ
pl*****> ぁーそーかーかなり勘違いしてたなぁ
moriwaka> わりとそのへんごちゃっとするよね……
pl*****> wants/requireは順序関係ないから、WantedBy=Xしてても「Reached target Xって出てるからサービスはキックされてるはず」とは鳴らないのか・・・
moriwaka> はい
pl*****> ぐへぇ。勉強になりました・・・
moriwaka> 「必要なんだから先に起動してるじゃろ」と思うのはすごくよくあると思うのでみんなハマるところだとおもう
pl*****> むずかしー
pl*****> ぜひmastering systemdを書いてほしい・・・
moriwaka> 本はしらないんだけどpacktとかにないのかな

Conflicts=shutdown.target

pl*****> あと、Conflicts=shutdown.targetって何の意味があるんだろう
moriwaka> shutdownコマンドとかでshutdown.target を呼びにいくときには俺を終了してくれよなという意味では>conflicts
pl*****> ぁーーーそうか。終了するときに shutdown.targetに遷移するから、その時に殺してくれるのか・・・

pl*****> conflicts=にサービスが設定されてると、このサービスはあのサービスと競合するんだなって分かるけど、
pl*****> shutdown.targetと競合する?どういうことだ?ってなってた・・・

moriwaka>------------------------------------------------------------------------
shutdown.target
          A special target unit that terminates the services on system shutdown.
          Services that shall be terminated on system shutdown shall add Conflicts= and Before= dependencies to
          this unit for their service unit, which is implicitly done when DefaultDependencies=yes is set (the
          default).
------------------------------------------------------------------------
systemd.special(7)に↑の記載があったよ
pl*****> DefaultDependencies=noにしてる場合のみ必ず書く必要があるって感じか

systemdのドキュメント

pl*****> ぁー systemd.specialなんてあるのか・・・読んでませんでした
moriwaka> systemd組込みのunitは Documentation=man:systemd.special(7) みたいな感じでどこ読めばいいかも書いてくれてるからそこから辿っていくとやりやすいです
pl*****> ProtectSystem=とかすごい安心感ある・・・こんなのあるんだ
pl*****> ドキュメント辿ってるだけで時間が無限に過ぎそう
moriwaka> dpkg -L systemd|grep /man|wc -l → 198
多すぎてさすがにびびった。まあだいたい7章から辿れるはず……

pl*****> 片手間で鯖缶してる人なんかは、たしかにsystemd勉強するのは辛いかもね
pl*****> 読むもの大杉ってなりそう

moriwaka> おおざっぱには、systemd-* はあんまりユーザが叩かないコマンドとかなので
systemd.*(5) でunitの設定読んで、必要に応じて*.conf読んで、あと なんとかctl のman pagesを読んでおけばだいたい日常の用は足りるはず
systemd.directives.7.gz というのがunitで書けるディレクティブの辞書的になってて
こっから記載があるman pageがたどれる
pl*****> ふむ
moriwaka> unitファイル見たら知らないディレクティブがでたときむけ(だいたいsectionでわかるけど

コマンド分け……てるよ

ar*******> journalctl とか、わけるのかくっつけるのか、一貫性がないな。
ar*******> それわけるなら、systemctl をもう少し細かく分けたらいいのに。
aa**> systemdってなんでしょうか?サービスを管理するなにか?
moriwaka> linuxユーザーランドの基盤をととのえるプロジェクトの名前兼、initをやるデーモンの名前
pl*****> 個人的にユーザーランドに頭出してる第二のカーネルと思ってる
moriwaka>
------------------------------------------------------------------------
$ dpkg -L systemd | grep bin/
/bin/journalctl
/bin/loginctl
/bin/networkctl
/bin/systemctl
/bin/systemd-ask-password
/bin/systemd-escape
/bin/systemd-inhibit
/bin/systemd-machine-id-setup
/bin/systemd-notify
/bin/systemd-sysusers
/bin/systemd-tmpfiles
/bin/systemd-tty-ask-password-agent
/usr/bin/bootctl
/usr/bin/busctl
/usr/bin/hostnamectl
/usr/bin/kernel-install
/usr/bin/localectl
/usr/bin/systemd-analyze
/usr/bin/systemd-cat
/usr/bin/systemd-cgls
/usr/bin/systemd-cgtop
/usr/bin/systemd-delta
/usr/bin/systemd-detect-virt
/usr/bin/systemd-mount
/usr/bin/systemd-path
/usr/bin/systemd-resolve
/usr/bin/systemd-run
/usr/bin/systemd-socket-activate
/usr/bin/systemd-stdio-bridge
/usr/bin/timedatectl
/bin/systemd
/usr/bin/systemd-umount
------------------------------------------------------------------------
実行ファイルだけでこんだけある
まあsystemctl, journalctl, loginctlくらいしか日常叩かないけど

このへんのソフトウェア群がサービスが起動したりログインセッションはじまったりするための環境をととのえるために色々な仕事をします
aa**> あーそれなのか、systemctl, journalctl,はよくお世話になってます

timedatectlつかう?

pl*****> timedatectl、新し目のrhelだとtimesynd使うようになったり?
pl*****> 7系はchrony固定なのかな
moriwaka> 7はchronyかntpdですね。timesyncdは自分でつかったことがないからよく知らない
pl*****> なるほど 

systemdのめっちゃ嬉しい機能をダラダラと説明する

2018/06/07〜2018/06/08 あたりにtwitterでつぶやいたsystemdのうれしいシーンまとめ

Package管理と相性がよい

  • sysvinitのスクリプトちょっといじってulimit文足したあとにパッケージupdateしたら消えたりしたことがある人はsystemdならその不幸はもう起きない 
  • systemdは設定ファイルが /usr, /etc, /run くらいにバラけて配置されるのでパッケージが提供するのをカスタマイズで変更するのは簡単にできるしパッケージシステムの更新などと相性もいい。
  • バラバラだと作業が煩雑になりがちなので関連コマンドが充実
    • パッケージのデフォルト、/etc/での設定、/run/の自動生成された設定などをまとめてunitを表示してくれる systemctl cat 
    • unitをカスタマイズするときに適切なファイルを編集してくれる systemctl edit などのコマンドも便利
    • 現在の設定を評価してデフォルトからどう変更されたかをまとめて表示してくれる systemd-delta コマンドもあるよ。 

sysvinit等でありがちだった罠にハマりにくい

  • /etc/init.d/hoge を直接実行してserviceコマンド経由での起動時と環境変数やらが違うからハマった人、systemdならその不幸はもう起きない
  • 同じサービスを1システム内で複数インスタンス構築してpidファイル被ったりログがどれがどれやら混乱したり、そういうことはsystemdをちゃんと使えば回避できる(残念ながら対応したパッケージは少ない) 
    • 設定ファイルのunit名を @で終わらせると複数インスタンスに対応。たとえばgetty@tty1.service というunitはtty1用のgettyで、仮想端末にあわせてgettyをいくつも作成する。この場合設定ファイルはgetty@.serviceになる。
  • /etc/init.d以下のスクリプトがしれっとタイムアウトしてるくせにexit 0してて「フザケンナ」ってなったことがある人、残念ながらsystemdでも敢えてやればできちゃうけど普通の使い方ならちゃんと失敗したらnon zeroなexit code返すよ 
    • systemctl status で指定したunit(のどこかで)失敗が発生しているとexit codeがnon zeroになる
  • systemdのunitではサービス実行前のチェック、サービス実行直前の準備コマンド、サービスそのもの、サービス実行直後に実行するコマンドなど細かくフェーズを分けてコマンドを記述できる。設定は面倒かもしれないが実際にどこかで失敗が発生したときにどのコマンドで問題が発生したか、exit codeは何かなどを細かくレポートしてくれて素敵。

Linuxの新しい機能などを上手くサポートしてくれる

  • 一時ファイルを/tmpに作って置き換え可能なタイミングが発生する典型的なセキュリティホール作っちゃった人、systemdならそのサービス専用の/tmpをbind mountして分離する緩和策が1行書くだけで実施できるよ 
    • PrivateTmp=true と指定するだけ
    • mount namespaceとbind mountを使ってunitに特有の/tmp を作ってくれる
    • 他にもいろいろ制限をかける仕組みがあるので systemd.exec(5) のSandboxingを見るとよい
  • linuxはcgroupでリソース制限かけてメモリ+swapの上限とか設定できる」それどうやって使うんだろうと思った人、systemdならサービス毎にcgroup作ってるから設定1行書くだけでできるよ  

サービスの管理や追跡の機能が豊富 (いくつかはcgroup統合のおかげ)

  • なぜか時々死ぬサービスを再起動するためにdaemontools入れたことある人、systemdならそれ最初からあるよ 
    •  Restart=always とか。どういう条件でプロセス終了した場合に再起動するかなどをRestart=の設定で細かく指定できたり、再起動前の待ち時間をRestartSec=で指定できたりするよ。
  • 「ん、このサービスいつ起動したっけ、先週月曜だっけ……?」ってログ漁ったことある人、systemdなら各サービス毎にいつ起動したかちゃんと覚えてるよ
  • 謎のbashが動いてて「え、これ何から呼ばれてるの」と思ったけどpstreeで見たらinitの子になってて「なんだろー」ってなったことある人、systemdならどのサービスか、それともユーザーセッションから出てきたプロセスか即わかるよ 
    • "systemctl status" だけ打つと各サービスやユーザセッションとの関係もでてきます
  • libvirtが起動するdnsmasqがサービス終了しても継続して動作してて、killall -9 dnsmasqしてNetworkManagerが呼んでる関係ないdnsmasqまで殺したことがある人、systemdならlibvirt関連の全プロセスだけを一発でkillできるよ 
    • "systemctl kill <サービス名>" で、 サービスに関連した全プロセスにkillを送れる
  • systemdはサービス毎にcgroupで追跡してるから、「このサービスどのくらいメモリやCPUつかってるのかな」って思ったらsystemctl statusやるだけでわかるよ 

f:id:mrwk:20190501065924j:plain

systemctl statusの例。起動時刻、CPUおよびメモリ消費、関連プロセス、最近のログ10行などがまとめて表示される

bus activationなどが素敵

  • systemdは「ooがされたらxxを起動する」みたいな奴に色々(dbus, mount, デバイス, ファイル, socket等)対応してるから、cockpitみたいな「どこでも使えてほしいけど使わないときはリソース節約のため落ちていてほしい」サービスとかを設定しておくとリソースあまり使わずに待ちうけできるよ 
    • cupsが駆使してる。job queue(/var/spool/cups)にjob fileがあれば起動、dbus経由でアクセスがあれば起動、tcp port631にアクセスがあれば起動
  • udevがsystemdと連携して動作するので検出されたデバイスを依存関係に簡単に書ける。特定のデバイスが存在する場合にだけ有益なbluetoothdとか、lvmで必要なサービスを非同期で起動させるとか、ストレージの暗号化などで大活躍。

systemdの管理と統合されたsystemd-journaldによるログ管理素敵

  • 複数台の/var/log/messagesを見比べて「何で時間が秒単位なん!どっちが先か後かわからん! (>_<)」ってなったことがある人、systemd-journaldならマイクロ秒まで保持してるよ 
    • journalctl -o short-precise で時刻を細かくした出力が得られる
  • トラブルシューティングでsyslogでためてるメッセージを解析する時に"warning"とかで検索してcriticalなメッセージ見逃したことがある人、systemd-journaldならログレベル保持してるからあらかじめ分けておかなくても「warn以上」とかですぐ絞りこめるよ 
    • journalctl -p 3 で error以上、など。
  • 全然ログらしいの吐かないデーモンについて詳しい人に聞いたら「daemonizeさせずにforegroundで動かしてstderr見ればいいんだよ」とか言われて「そんなん知らんがな」と思ったことがある人、systemd + systemd-journaldならサービスのstdout,stderrもきっちりログに収集するよ 
  • systemdはサービス管理とロギングが統合されてるから journalctl -u サービス名 で特定のサービスに関連するプロセスが出したログだけ出してくれて便利。特に子プロセスの名前がメインのプロセスと違う時に見逃しがない。
  • journaldはtimestampをUTCで保持しているのでシステムのTZに依存せず簡単にログを比較できるよ 
  • 各システムにユニークなmachine-idがついて、そのmachine-idのディレクトリにログが保存されるので複数システムのログを同じところにまとめやすいし、まとめたログファイル群をマージして読めるよ
    • /var/log/journal/b1cb7fb826fe490da1e256a4300f4c2f/system.journal みたいなファイルになります
    • machine-idは /etc/machine-id に保存されている。このファイルを削除して再起動するか systemd-machine-id-setup コマンドで作り直しされる。

こまかい便利機能

  • systemdは競合するサービスをConflict=で明示的に定義できるからntpd動いてるのにchrony動かそうとすると実際に競合する前に検出して失敗してくれるよ
  • systemd、電源断や再起動だけでなくサスペンドやハイバネートもやるよ

systemdに興味を持った人むけリンク

 
 

3週間ほどGoogle Homeを使ったまとめ

サードパーティアプリの強制終了は「キャンセル」

サードパーティアプリそのものは玉石混交でまあどんどん変わっていくと思うので詳細には触れない。音声対話をしているときにサードパーティアプリが想定しているコマンドがわからなくなると「すみません、もう一回言ってください」「最後のコマンドを言い替えてください」などのループに簡単に陥って脱出の方法がわからなくなる。そんなときは「キャンセル」と言えば強制終了できる。

https://developers.google.com/actions/assistant/app-exits Actions on Googleのドキュメントを見ていると、英語では以下5コマンドで止められるらしい。僕は日本語で「終了」「終わり」「もういいです」「ストップ」「さようなら」くらいをためしたが全滅だった。困ったら「キャンセル」を覚えておくだけで全然違うとおもう。
  • "exit"
  • "cancel"
  • "stop"
  • "nevermind"
  • "goodbye"

ショートカット作成はアクティビティログを見ながら

Google homeへのコマンド入力は、音声も含めてアクティビティログ  に記録されている。
Google homeアプリの「マイ アクティビティ」のほうが省略されないので見易い。

ショートカットを作成するときは、このマイアクティビティを見ると、どう認識したか、ショートカットをどう展開したか、その反応は何かがわかる。

たとえば「リアを再生」→「Liaを再生」というショートカットを作ってテストをすると以下のようなログが残ってちゃんと期待通り展開されていることがわかる。
f:id:mrwk:20190501073519p:plain

radiko

まんまradikoです。
http://radiko.jp で自分の地域で聞けるラジオ局を確認して、「ねえGoogle、○○を聞きたい」というとかけてくれます。プレミアム会員じゃないけどアカウント関連の何もないのでプレミアムの機能はたぶん使えない。
  • やりたくてできてないこと1: 特定の時間や番組を予約して再生をはじめさせること。「毎週火曜午前1:00から午前3:00までTBSラジオを聞きたい」とか指定したい。
  • やりたくてできてないこと2: 「あと2時間で終了」みたいにタイマーを設定して再生を終了させること。「2時間後に停止」と言うと「2時間後にリビングを消します」というような若干物騒なメッセージとともにスリープタイマーを設定できる。
まあ言うほど困ってないんですが、コンピュータなら余裕でできるやろというところができていないので惜しい感がある。

「ねえGoogle TBSラジオ」だと通じないのでよく使う局はショートカットを作るといいかな。

ニュースのpodcast

これも普通の「ニュース」コマンド。「ねえGoogle、ニュース」と言うとあらかじめGoogle Homeアプリで指定したPodcastを再生してくれます。任意の音声読みあげとかカスタムURLとかは今のところなさげ。あればいいのに。
Google Play MusicPodcast対応してるからか海外のPodcastはびっくりするくらい数がありますね。

Google Homeアプリ」→「その他の設定」→「ニュース」→「ニュース提供元」→「ニュース提供元を追加」で出てくるところで言語をEnglish (United States)にするともう十分やろというくらいどっさりでてきます。

tuneinの再生

あんまり使っていないので謎が多い。そしてあまりうまく使えてもいない。でもISC Stormcast(セキュリティネタで平日に5-6分くらいやってるpodcast)とか聞きたい。
とりあえずわかっていること:
  • 現状、日本語モードで英語タイトルの認識はびっくりするくらいできない。jazz fmは「じゃずえふえむ」ではまったく通じず「じぇーえーぜっとぜっとえふえむ」なら通じる。ショートカット設定して、文字列で「jazzfm」と書けば認識してくれるのでgoogle homeが素直に音声認識してくれる「ねえGoogle、ジャズ」とかのワードで呼びだすようにする。
  • Google homeを英語モードにさえすれば"Play ISC stormcast podcast"であっさり再生できる。なのでリージョンの問題とかではなく日本語音声認識を使っているときのコマンド入力の問題っぽい
  • ショートカットのコマンドで「tuneinでjazzfmを再生」は使えるのに「tuneinでISC stormcastを再生」は失敗する。何が失敗する原因かは不明。
  • ショートカットのコマンドに他の言語は使えない。つまり「セキュリティ」とかで認識させて「play ISC stormcast podcast」を英語で評価してもらうということはできない。その結果、なんとか日本語モードで実行が成功できるコマンドをみつけないといけないという謎のゲームになっている。。
  • 日本のローカルFMのストリーミングを再生させることにまだ一回も成功していない。ひょっとして地域か言語絞られてるのかな??

今のところ「海外のラジオやpodcastをききたかったら英語モードにすればtuneinが普通に使えるよ」というところまでしか知見がない。
 

音楽再生をお金を使わずにやる

各種音楽サービスの課金をめっちゃ勧められるけどお金つかいたくないよね……。

自分でアーカイブをもっていない場合は、Spotifyの無料プランくらいしかないかも。
僕はアーカイブをもっているので以下におちつきました。

Google Play Musicの無料プランを使う
  • Google Homeアプリの表示とかを見ると有料プランの購読が必須のように見えるけど別にそんなことない。(2017年11月時点)単に音楽のプロバイダにGoogle Play Musicを選択するだけ。
  • 無料プランで最大50000曲保存できるロッカー機能があるのでそれを利用して自分のアーカイブを登録(PCでGoogle play musicのページにドラッグ&ドロップするだけ。アップロートにはめっちゃ時間がかかる)

 

音楽再生関連のコマンド

いろいろ受けつけてくれるけど、以下におちつきました
  • ねえGoogle、BGM → ライブラリからランダム(?)に再生。「ねえGoogle、何か音楽を再生」と同様だけど短いのでこれにしてる
  • ねえGoogle、次 → 再生中に次の曲へスキップ。
  • ねえGoogle、この曲好き → google play musicでThumb upプレイリストに登録。
  • ねえGoogle、シャッフル → 次の曲からシャッフル再生
  • ねえGoogle、お気に入りを再生 → google play musicで"Thumb upプレイリスト"を再生
  • ねえGoogle、<プレイリスト名>を再生 → google play musicで指定したプレイリストを再生
  • ねえGoogle、この曲何? → 再生中の曲のアーティストと曲名を答えてくれる
  • ねえGoogle、停止 → 曲の再生を停止
  • ねえGoogle、再開 → 停止していた曲再生を再開。停止したあと他のchromecastに関連するコマンド(?)をつかっているとだめ
これにおちつくまでちょっとしたトラブルあり。「ねえGoogle、音楽を再生」って言うとKalafinaの「音楽」という曲が再生されるという……。

プレイリストや曲、アーティストの名前にアルファベットが使われていると検索に失敗することがよくある。「Liaを再生」のつもりで「りあを再生」と言っても受けつけず。「える、あい、えーを再生」ならコマンドが通る。そのとき「リアを再生します」と回答してくれてるのでgoogleが頑張れば逆変換もそのうちできるようになりそう。

環境音

Alexaのスキルみてて滝の音とかあったので試したらあったという……。 
  • ねえGoogle、環境音 → 「リラックスできる音を再生します」といって波の音や暖炉の音を再生してくれます
  • ねえGoogle、<名前>の音 → 「海の音」「風の音(扇風機の音)」「雷雨の音」「雨の音」川の音」「森の音」「暖炉の音」 で個別指定できる。環境音のほうで再生されるけど名前があたらないものがあるのでもうすこしコマンドありそう。
  • ねえGoogle、ホワイトノイズ → ホワイトノイズを再生。寝るときに良いといわれるピンクノイズはなかった。

ちょっとだけ工夫

Google homeアプリのショートカットを使ってプレイリスト再生 

ラジオ体操をしたいのでこんなことしました
  • ラジオ体操動画をダウンロード
  • mp3に変換してGoogle Play Musicに登録。「ラジオ体操」プレイリストを作って登録
  • Google homeアプリ」→「その他の設定」→「ショートカット」でショートカットを追加。「ラジオ体操」で「ラジオ体操を再生」とする。
  • 「ねえGoogle、ラジオ体操」というとラジオ体操ができる(‥)v
 
 

元号の処理がどこで行われているか探してみる

この記事はLinux Advent Calendar 2016の4日目の記事です。

わりとマイナーな機能ですが、linuxでja_JP.utf8のlocaleで以下のようにコマンドを叩くと、元号をちゃんと処理して表示してくれたりします。

$ LC_TIME=C date +'%EY'
2016
$ LC_TIME=ja_JP.utf8 date +'%EY'
平成28年

ちょっと前に国の象徴のおじいさん(82)が「退位したい」みたいなこと言ってましたし、いいかげん年なのでしんどかろうと思います。法律の要件とかはわかりませんが退位が行われれば元号が変わるでしょうから、庶民の我々も少なくともこのコードを修正しないといけないわけです。  

これはどこからきているのかを探りましょう。

 $ ldd `which date`
    linux-vdso.so.1 (0x00007ffd14593000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fea550ed000)
    /lib64/ld-linux-x86-64.so.2 (0x000056070c37e000)

シンプル。linux kernel, dateコマンドの中身, libcのどれかに絞られました。
kernelはありえないので候補から外します。心配な人はstraceでもして安心してください。
date本体もないと思っているのですが念の為確認します。

$ LC_TIME=ja_JP.utf8 ltrace date +'%EY'
(中略)
strftime(" \345\271\263\346\210\22028\345\271\264", 1024, " %EY", 0x7f8f5c1464a0)                                                   = 12
fwrite("\345\271\263\346\210\22028\345\271\264", 11, 1, 0x7f8f5c142600)                                                             = 1
(以下略)

libcのstrftimeがやっているようです。man 3 strftime を見ます。

(ー略ー)
        %E     Modifier: use alternative format, see below. (SU)
(ー略ー)
(SU) The Single UNIX Specification mentions %Ec, %EC, %Ex, %EX, 
       %Ey,  %EY,  %Od,  %Oe, %OH, %OI, %Om, %OM, %OS, %Ou, 
       %OU, %OV, %Ow, %OW, %Oy, where the effect of the O modifier 
       is to use alternative  numeric  symbols  (say,  roman numerals),  and  
       that  of  the  E modifier is to use a locale-dependent alternative 
       representation.

あ、Single UNIX Specificationで決まってたのか。ってことはきっとAppleOS Xとかでもつかえるんだろうな。。持ってないので知らないけど。

さて、libcでlocale-dependentだということがわかりました。ソースコードとってきましょう。

$ find glibc-2.24 |grep ja 
./localedata/locales/ja_JP
./benchtests/strcoll-inputs/lorem_ipsum#ja_JP.UTF-8
./po/ja.po
./debian/po/ja.po

ということで以下のファイルに含まれてそうです。 

glibc-2.24/localedata/locales/ja_JP

いかにもそれらしい「era」というエントリがあります。きっとこれなんですが…… 

era     "<U002B><U003A><U0032><U003A><U0031><U0039><U0039><U0030><U002F><U0030><U0031><U002F><U0030><U0031><U003A><U002B><U002A><U003A><U5E73><U6210><U003A><U0025><U0045><U0043><U0025><U0045><U0079><U5E74>";/
        "<U002B><U003A><U0031><U003A><U0031><U0039><U0038><U0039><U002F><U0030><U0031><U002F><U0030><U0038><U003A><U0031><U0039><U0038><U0039><U002F><U0031><U0032><U002F><U0033><U0031><U003A><U5E73><U6210><U003A><U0025><U0045><U0043><U5143><U5E74>";/
        "<U002B><U003A><U0032><U003A><U0031><U0039><U0032><U0037><U002F><U0030><U0031><U002F><U0030><U0031><U003A><U0031><U0039><U0038><U0039><U002F><U0030><U0031><U002F><U0030><U0037><U003A><U662D><U548C><U003A><U0025><U0045><U0043><U0025><U0045><U0079><U5E74>";/
        "<U002B><U003A><U0031><U003A><U0031><U0039><U0032><U0036><U002F><U0031><U0032><U002F><U0032><U0035><U003A><U0031><U0039><U0032><U0036><U002F><U0031><U0032><U002F><U0033><U0031><U003A><U662D><U548C><U003A><U0025><U0045><U0043><U5143><U5E74>";/
        "<U002B><U003A><U0032><U003A><U0031><U0039><U0031><U0033><U002F><U0030><U0031><U002F><U0030><U0031><U003A><U0031><U0039><U0032><U0036><U002F><U0031><U0032><U002F><U0032><U0034><U003A><U5927><U6B63><U003A><U0025><U0045><U0043><U0025><U0045><U0079><U5E74>";/
        "<U002B><U003A><U0032><U003A><U0031><U0039><U0031><U0032><U002F><U0030><U0037><U002F><U0033><U0030><U003A><U0031><U0039><U0031><U0032><U002F><U0031><U0032><U002F><U0033><U0031><U003A><U5927><U6B63><U003A><U0025><U0045><U0043><U5143><U5E74>";/
        "<U002B><U003A><U0036><U003A><U0031><U0038><U0037><U0033><U002F><U0030><U0031><U002F><U0030><U0031><U003A><U0031><U0039><U0031><U0032><U002F><U0030><U0037><U002F><U0032><U0039><U003A><U660E><U6CBB><U003A><U0025><U0045><U0043><U0025><U0045><U0079><U5E74>";/
        "<U002B><U003A><U0031><U003A><U0030><U0030><U0030><U0031><U002F><U0030><U0031><U002F><U0030><U0031><U003A><U0031><U0038><U0037><U0032><U002F><U0031><U0032><U002F><U0033><U0031><U003A><U897F><U66A6><U003A><U0025><U0045><U0043><U0025><U0045><U0079><U5E74>";/
        "<U002B><U003A><U0031><U003A><U002D><U0030><U0030><U0030><U0031><U002F><U0031><U0032><U002F><U0033><U0031><U003A><U002D><U002A><U003A><U7D00><U5143><U524D><U003A><U0025><U0045><U0043><U0025><U0045><U0079><U5E74>"


ソースコードのくせに謎エンコードされてて読めないよ! >_<

きっとunicodeの表が頭に全部はいっててデコードできる人は心の目で読めるんですがそんなハンドアセンブルの100倍くらいしんどそうなスキルは持っていません。ちなみにハンドアセンブルも表がないとできません。

しょうがないので<Uxxxx>; みたいなのが来たらxxxx部分にあたるunicode文字に置換する使い捨てスクリプトをシコシコ書きます。

import os, re

def main():
    for line in file('ja_JP'):
        out = ""
        while 1:
            m = re.search('(<U([0-9a-fA-F]+)>;?)', line)
            if m:
                out = out + line[:m.start()] + unichr(int(m.group(2), base=16))
                line = line[m.end():]
            else:
                out = out + line
                break
        print out.encode('utf8'),

main()

 これを実行すると.. でました

era     "+:2:1990/01/01:+*:平成:%EC%Ey年";/
        "+:1:1989/01/08:1989/12/31:平成:%EC元年";/
        "+:2:1927/01/01:1989/01/07:昭和:%EC%Ey年";/
        "+:1:1926/12/25:1926/12/31:昭和:%EC元年";/
        "+:2:1913/01/01:1926/12/24:大正:%EC%Ey年";/
        "+:2:1912/07/30:1912/12/31:大正:%EC元年";/
        "+:6:1873/01/01:1912/07/29:明治:%EC%Ey年";/
        "+:1:0001/01/01:1872/12/31:西暦:%EC%Ey年";/
        "+:1:-0001/12/31:-*:紀元前:%EC%Ey年"

元年だけ別の行になってるんですね…… 明治は6年からなので旧暦のごちゃっとしたあたりは気にしないと。

というわけで元号の場所はわかりました。おじいさんが退位したらここを直してみんなでglibcをアップデートしよう。

ではでは。


追記

「別ファイルになってないの?」と聞かれたので追記 。
たとえば /usr/share/i18n/locales/ja_JP のように別ファイルになっていて、localedefを使うことで独自のlocaleを定義することもできる。
でもlocaleの設定がおこなわれるのはプロセスの最初のほうで setlocale() されるタイミングだから結局元号の処理更新を反映したいプロセスの再起動が必要なのは変わらないのでした……。

OOM Killerにであったら何をするべきか?

OOM killerで大事なプロセスが殺される。困りますね。。 google で OOM Killerと入力すると 「無効」とか補完されます。しかしどうするのが良いのか、あまりよく説明されている記事がみあたらなかったので自分の考えをメモしておきます。

OOM Killer の目的は何か?

まずは何故OOM Killerが発生しているのかについて、ざっくりイメージをつかみましょう。linux kernelはプロセスからの「メモリくれ」という要求に対してたぶん足りそうだという場合に「OK」といって渡します。実際のメモリ割り当てはアクセスが発生するタイミングまで遅延させます。これを遅延アロケーションといい、だいたいにおいてうまく動きます。ただし必ずうまくいくと保証されているわけではないので破綻することがあります。 

OOM Killerはこの遅延アロケーションが破綻しそうなときに、適当にプロセスを殺すことでメモリを開放してやろう、そうやってメモリを開けて動作を継続したほうが全滅するよりはマシだろうという緩和策です。
「半端に動くんじゃなくて死んでくれた方がいいです」という場合には、 sysctl で以下のように設定して、OOM Killerを動作させるのではなくカーネルをpanicさせることができます。

vm.panic_on_oom = 1

OOM Killerは破綻したときの緩和策だったので、OOM Killerを止めて破綻をより直接的にシステム停止に繋げています。きっちり死んでくれるのでOOM Killerよりわかりやすいですね。

何故OOM Killerが発生しているかを調査する

できればOOM Killerを避けたいですが、避けるためには何故OOM Killerが発生しているのかを調査する必要があります。これをおこなうためには、まずさきほどのpanic_on_oom設定をおこないます。

加えてpanic時にメモリダンプを取得する仕組み(いろいろありますがRHELFedoraだとkdump)を仕込みましょう。これでpanicしたときのダンプを調査することで、多くの場合OOM Killerの発生原因を推定できます。

RHELであれば「カーネルクラッシュダンプガイド」にダンプの設定と解析ツールについての解説があります。


https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/7/html/Kernel_Crash_Dump_Guide/index.html

 

対処その1. アプリケーションに対してストレージを速くする

メモリにはざっくり3種類あります。

  • ファイルやブロックデバイスをバックエンドとするfile mapped page
  • バックエンドはなく、必要になるとswap領域にバックエンドを作るanonymous page
  • 特に使われていないfree

linuxでは基本的にはディスクやファイル、swap領域に対するキャッシュとしてメモリは扱われます。そうではなくメモリ上に必ず維持されるようにする仕組み(mlock)もありますがそれが気になる人はこんなblog読まなくて大丈夫なはずなので割愛します。

アプリケーションが何かしらストレージへ書きだすとき、まずは書き出すべき情報をメモリ上に保持します。このようなメモリをDirtyと呼びます。
Dirtyなメモリはストレージへの反映による永続化が必要な情報を持っているので、ストレージに書きだしをおこなうまでは消せません。恒常的にストレージの負荷が高いとDirtyなメモリがどんどん増えていきます。

さきほどのメモリダンプを解析ツールに食わせると、dirtyなページが多くなっているのですぐわかります。このような場合、対処としては以下があります

  • ストレージの障害をチェックする。何かデグレードが発生して遅くなっている可能性を排除しましょう。 高いストレージだとストレージ側で統計情報がとれます。これも確認しましょう。
  • ストレージを増強する。根本対策です。
  • アプリケーションの書きだしを遅くする。dirtyなページを書きだすプロセスの実行を遅くします。
    アプリケーションがdirect I/Oとよばれる手法を利用している場合に限られますが、cgroupで制限することができます。systemdのunit内にBlockIOWriteBandWidth=で利用する帯域幅の上限を設定することができます。指定された上限にを越えて書きこみをしようとするとアプリケーションは一時停止します。
    https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/7/html/Resource_Management_Guide/sec-Modifying_Control_Groups.html#sec-Modifying_Unit_Files
  • 書きだしを遅くする(その2)。共有ストレージを持つクラスタ構成の場合、ストレージに対しての計算ノード数を減らします。
  • 書きだしを遅くする(その3)。メモリがでかいときはvm.dirty_ratioを変更したりすると良いケースもあります。これはdirtyなページが何パーセント溜まったら、新規に書きだしを行おうとしているプロセスを一時停止するかの指標です。
  • メモリを増やす。ストレージへの負荷が適度に増減する場合、次に負荷が減るまでなんとか持てばいいので、その余裕がある時間をかせぎます。場合によりますが、メモリがとても少ない場合をのぞいてあまり有効な対応ではありません。

対処その2. 積極的にswapさせる

利用しているアプリケーションのメモリ管理があまりうまくできていないと、実際の利用はそれほどでもないのにanonymousとして確保するメモリ量がじわじわ増大していきます。このような場合、アプリケーションによりますがswapを積極的に行うことも一つの手です。

 sysctlで、以下のようにするとRHELではfile mappedと同じ程度の基準でanonymousメモリをswapに吐きだします。

vm.swappiness = 100

 ゆっくりとしたメモリ消費増加への対策としてはswapはそれなりに有効です。もちろんswapがあふれると破綻しますから適宜swap使用量を監視する必要があります。 

swapについてはとにかく発生させたくないと、拒否反応を示す人がいます。
「swapで遅くなる」という現象のほとんどは、1) anonymous pageを一旦swapへ書きだしてメモリから除いたあと、2) もう一度アクセスしようとしたときに発生します。
この2の動作をswap inと呼びます。vmstatだとsiと書いてある欄で表示されます。これが継続的に発生する場合、swapによるパフォーマンス劣化が発生していると考えてまず間違いありません。この場合、アプリケーションのメモリ利用パターンがswapの積極的な利用に向いていません。

swap inによるパフォーマンス劣化が継続的ではなくちょこちょこ程度で起きる場合、主要ではないアプリケーションがswap outしている可能性が考えられます。この影響を緩和したい場合「メモリを増設するのは高いのでswap領域をSSDにしてしのぐ」というアイデアには一考の余地があります。

利用しているアプリケーションに、ある程度予想がつくゆっくりとしたメモリ利用の増加がある場合にはswapと適当な頻度でのサービス再起動の組み合わせは有効です。ただしswap inが継続的に発生する場合はswapによる緩和策はあまり向いていません。

対処その3. アプリケーションのメモリ消費を制限する

swapが向いていないアプリや、メモリの大量消費が突発的に発生するアプリケーションもあります。後者は70%くらいバグな気がしますが、残念ながらそういうアプリケーションでも使わないといけないシチュエーションはあります。

そのような場合、cgroupを利用してアプリケーションのメモリ消費を制限することが有効です。systemdを利用しているディストリビューションでは、systemdがcgroupの設定を管理します。 RHELであれば以下に和訳ドキュメントがあるので読みましょう。
https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/7/html/Resource_Management_Guide/index.html 

てっとり早く答えにたどりつきたい人は以下ページを見て、MemoryLimitを設定しましょう。 これで、指定したサービスの消費メモリを制限できます。制限に触れるとOOM Killerが動作しますが、このcgroup内のプロセスだけを殺しますのでサービスだけが死にます。周りにあまり迷惑がかからないのはいいですね。
https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/7/html/Resource_Management_Guide/sec-Modifying_Control_Groups.html#sec-Setting_Parameters_from_the_Command-Line_Interface 


これに加えて、サービス停止時の再起動設定もsystemdのunitに書くことができます。ただし自動再起動の設定をするのは「大量消費→OOM Killer→再起動→大量消費→……」 のタイトなループに陥る可能性が排除できている場合に限定しましょう。このループが数日や数ヶ月など、許容できる場合なら

Restart=yes  

とunitに書くことでsystemdが再起動の面倒もみてくれます。

対処その4. メモリを増やす 

「さっきあまり有効でないって書いてたのに!?」と思うかもしれませんが、アプリケーションが必要とするメモリ量の見積りが単純に低すぎる場合があります。

dirtyなページがあまり多いわけでもなく、swapさせてもswap inが発生しつづける場合は、アプリケーションが必要としている量のメモリを用意できていない場合が考えられます。可能であればアプリケーションの処理を勘案して必要サイズを推定しましょう。
メモリ使用量を調査するためのツールについては以下ドキュメントに紹介されています。 
https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/7/html/Performance_Tuning_Guide/sect-Red_Hat_Enterprise_Linux-Performance_Tuning_Guide-Memory-Monitoring_and_diagnosing_performance_problems.html 

メモリを大量消費するアプリケーションには、自身がどれくらいのメモリを消費するか調整するための設定が用意されていることが多いです。これを増減させて消費メモリ量の増減とパフォーマンスへの影響を観察すると落としどころがみつかるかもしれません。

HPCなどでは「でかいデータを扱うからでかいメモリが要るんだよ!」 みたいなケースもあります。こういうときは金と物理(メモリ)の力で殴るしかないです。 

対処ではない: oom_score_adj 

OOM killerの調整ということで検索すると名前がよくでてくるoom_score_adjですが、特定プロセスをOOM killerの対象外にしたり対象になる確率を上げたり下げたりということができます。
しかしOOM killerが発生した原因であるメモリ逼迫については何も改善しないので、あまり意味はありません。

特定のプロセスが死ななければ他がランダムに殺されても大丈夫でしょうか? 普通はランダムに殺されても問題ないというプロセスはほぼ無いはずです。不要なプロセスが存在するなら、特にscoreとかいじる前に単純に起動しないようにする方がいくらかマシです。

oom_score_adjを活用できるシチュエーションをどうにか考えだしてみると、
1)  大事なプロセスに関連したものについてはOOM killerの対象外にしておく
2)  メモリ逼迫を検出するためのカナリア役として優先的に殺されるプロセス(無意味にメモリを確保する)を仕込んでこれを監視に利用
3) カナリア役が殺され次第できるだけ正常な終了手順で大事なプロセスを終了

というような仕組みを作り込む時くらいではないかと思います。OOM killerが動きだしたあとに大容量メモリを確保しているであろう大事なプロセスの終了処理がうまく動くかは非常に疑問ですが、終了処理が追加メモリをあまり使わず動作する場合には使えそうです。

まれに有効: vm.overcommit_memory設定によるovercommitの禁止

vm.overcommit_memoryによるovercommitの禁止は「ほとんど全てのアプリケーションが必要より多くメモリをリクエストしてくる」ため、非常に効率は悪くなりますがメモリ確保の要求量(割り当て量ではない)が特定の値を越えるとOOM Killerによるプロセスの終了が発生します。早く失敗することで延々処理したあと死ぬよりはマシなのではないか、という考え方ですね。 

特定のアプリケーションがメモリのほとんどを占め、必要分ギリギリしか要求しないことが分かっている、なおかつ他のプロセスはほぼ動作していない。という前提が満たせる場合はいいのですが、なかなか厳しいです。実際のところアプリケーションによっては通常の利用時に消費されるメモリ量の数十倍から100倍くらいのメモリを要求することもごく普通にあります。極めて特殊な、アプリケーションを選ぶ対策と言ってよいかと思います。

おまけ

 

いろいろいじりたい人にはRHELのドキュメント「パフォーマンスチューニングガイド」がまずはおすすめ。 
https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/7/html/Performance_Tuning_Guide/chap-Red_Hat_Enterprise_Linux-Performance_Tuning_Guide-Performance_Features_in_RednbspHat_EnterprisenbspLinuxnbsp7.html

logitechのマウスから謎のイベントが発生してハマった話と暫定的な対策

今にいたるも正確な原因はつかめていないのだが、同じようなハマり方をする人がいるかもしれないのでメモ。

問題


1. 自宅および会社のX環境で、logitech(logicool)の M705, Performance MXを楽しく使っていたのだが、不定期にフォーカスの動き方がおかしくなる現象に見舞われた。

2. 現象が発生するとキーボードのフォーカスはAlt+tabなどで他のwindowに移動できるが、マウスのフォーカスはある1つのwindowに固定されたまま他のwindowに移動できない。

3. マウスの電源オフするかunifiedレシーバを抜くと現象がおさまって普通に動作する。

4. はじめはランダムに発生していたが電源いれるといきなり発生するようになった。

さて、現在のところ原因追及はできていない。だがXよりも下位の、usbhid以下のどこかがおかしいようだ。原因追及は4のほぼ常時発生するようになってから進捗した。

再現&観測手順


1. 他のマウスとM705を2本接続し、M705の電源を一旦切った状態で操作をはじめる

2. xev起動( xev | grep -A3 Button )

3. もうひとつのマウスで xevのwindow上にカーソルを持っていったのちM705の電源を入れると以下のようなイベント表示があらわれる。

ButtonPress event, serial 33, synthetic NO, window 0x3a00001,
    root 0x2da, subw 0x0, time 520218479, (136,90), root:(1337,150),
    state 0x0, button 20, same_screen YES

存在しない20番目のボタンが押されたというイベントだ。そのためこのウィンドウ内でマウスのドラッグがはじまった状態になり、マウスのフォーカスが失われない。(観察した範囲では20番ボタンに限らず2番ボタンなどのイベントも発生する)

なおなんらかの方法(電源offやunified receiverの接続断)で接続を切ると対応するButtonRelease eventが発生してドラッグ状態が解除される。おそらくこれはxinputのどこかの終了処理が行っているのだろう。発生したイベントに対応するボタンがあればそれをクリックすることでもButtonReleaseイベントが発生するので対策になる。

現象が再現している状態でxinputコマンドによりマウスの状態を見ると20番ボタンがdownになっていることでも確認できる。

$ xinput query-state 11
2 classes :
ButtonClass
    button[1]=up
    button[2]=up
    button[3]=up
    button[4]=up
    button[5]=up
    button[6]=up
    button[7]=up
    button[8]=up
    button[9]=up
    button[10]=up
    button[11]=up
    button[12]=up
    button[13]=up
    button[14]=up
    button[15]=up
    button[16]=up
    button[17]=up
    button[18]=up
    button[19]=up
    button[20]=down
    button[21]=up
    button[22]=up
    button[23]=up
    button[24]=up
ValuatorClass Mode=Relative Proximity=In
    valuator[0]=1622
    valuator[1]=1635
    valuator[2]=0
    valuator[3]=-3

暫定的な対策

solaarにより Side Scrolling を有効にすることでこの現象が発生しなくなった。
正直なところ原因をはっきりさせていないので、これで対策になっているのかもわからないのだが、とりあえず動いている。

 暫定的な対策その2(??)

 もろもろ未確認だが、solaarがデバイス検出時行っている初期化処理で何か起きてるような気がする。solaarを全く動作させずに利用していると対策になるかもしれない。

solaarは過去1年くらい平和に動いていたのと、新しく買ったマウスではsolaarで問題が発生しないので、マウスの中の何かしらの状態とsolaarがリバースエンジニアリングして喋ってるlogitech独自プロトコルのあわせ技で問題が発生している可能性が疑われる。。

実験:

  • マウス接続 → solaar起動 → ボタン20down発生 → solaar終了→down継続→マウスoff/on→ボタン20down発生せず
  • solaar起動 → マウス接続 → ボタン20down発生 → マウスoff/on→ボタン20down再度発生

根本的(?)な対策

logicoolのサポートに連絡して押していないボタンについて押したような動作があると相談したところ、故障が疑わしいということで対応していただけました。保証期間内だと相談するといいかもしれません。 

 おそらく関連するbugzilla

https://bugs.freedesktop.org/show_bug.cgi?id=77037 
closeされちゃってるけどたぶんこの問題と似た話。「xinputで状態確認するとよさそう。俺んとこはドラッグ中のような状態になってた」とコメントを書くためにアカウントを取ろうとしたら失敗したのでメールで申請中……。

RHEL7でとったスナップショットにroot fsをロールバックするメモ

snapperで作ったスナップショットに戻したい

なにもなかったことにしたいとき、あります。
というわけでsnapperで取得したsnapshotの差分をとって…… というのではなく、まるごとそのまま前の状態にもどしましょう。

rootfs切り替え時の注意

RHELだと/bootはLVMで管理しない生 パーティションのため、この手法でロールバックされません。「カーネルやinitramfsが/bootに入っているが/lib/modules以下にカー ネルモジュールが入っていない」という状態になる可能性があります。root fsのmountまではinitramfsでできているはずなのでなんとかkernelパッケージを再インストールしてしのぎます。
snapperのメタデータは対象のディレクトリ直下にある .snapshots ディレクトリに作成されます。この情報も古い状態にもどってしまいますので、必要に応じてバックアップ&リストアするのがよいです。
snapperはスナップショットのlv名が衝突すると適当に数を大きくして競合しないように命名してくれるので、既にあるスナップショットを snapperが管理してくれなくなる(=古いスナップショットが消えないので容量の無駄になる)以外には大きな実害はありません。

snapperによって作成されるLVの属性を確認する

まずはsnapperが作成するLVの状態を確認します。

 [root@localhost ~]# lvs
  LV              VG   Attr       LSize  Pool   Origin Data%  Meta%  Move Log Cpy%Sync Convert
  pool00          rhel twi-aotz-- 12.00g               10.85  18.55                           
  root            rhel Vwi-aotz-- 12.00g pool00        10.02                                  
  root-snapshot10 rhel Vri---tz-k 12.00g pool00 root                                          
  root-snapshot11 rhel Vri---tz-k 12.00g pool00 root                                          
  root-snapshot12 rhel Vri---tz-k 12.00g pool00 root                                          
  root-snapshot13 rhel Vri---tz-k 12.00g pool00 root
Vri—-tz-k というのが属性で、元々のroot fsであるVwi-aotz— というのとはちょっと違いますね。
man lvs 内に各フラグの意味が書いてあります。関係するところだけひっぱってきます

              1  Volume  type:  thin (V)olume
              2  Permissions: (w)riteable, (r)ead-only
              3  Allocation policy:  (i)nherited
              4  fixed (m)inor ← なし
              5  State:  (a)ctive
              6  device (o)pen
              7  Target type: (t)hin; whereas snapshots of thin volumes using the new thin provisioning driver appear as (t).
              8  Newly-allocated data blocks are overwritten with blocks of (z)eroes before use.
              9  Volume Health: ← なし
              10 s(k)ip activation: this volume is flagged to be skipped during activation.
rootと、root-snapshot10 を比べてみると「read-onlyでactiveではなく、openされておらず、activationのときにskipされる」というのが違いです。
つまりこれを普通のボリュームにするには、「writableに変更し、activationのときにスキップしないように変更する」必要があります。起動時に自動的にLVをactivationする処理により、activeとopenは勝手に設定されます。
普段はsnapshotのデバイスがずらずらと並んでも同じUUIDを持っていたりしてxfsの誤動作の元になったりするくらいでいいことがないのでskipにしてあります。

dracutによるinitramfsで、root fsをmountする前に停止する

「じゃあ一旦umountしてからlvchangeでフラグを変更して、lvrenameすればいいんだな……」となるわけですが、 root fsが含まれているので「一旦umountして」という処理がちょっと面倒になります。
RHELの6から採用されているdracutでは、 カーネルコマンドラインオプションに rd.break=pre-mount とつけることでroot fsをマウントする直前のところで停止してshellに落ちてくれる便利機能がついています。(RHEL6だとすこしオプション名がちがってrdbreak=…です)
man dracut.cmdline より

       rd.break={cmdline|pre-udev|pre-trigger|initqueue|pre-mount|mount|pre-pivot|cleanup}
           drop to a shell on defined breakpoint
再起動して、grubのメニューから rd.break=pre-mount というオプションを一時的に追加することで、initramfsだけがマウントされている状態のシェルが起動します。
f:id:mrwk:20190501080007p:plain
この環境ではlvchangeなどのコマンドはlvmの対話シェル内から打ちこみます。

f:id:mrwk:20190501080029p:plain

lvmでの作業

デフォルトではinitramfs内でLVMのメタデータを変更しない設定になっているので、lvmでの作業前に /etc/lvm/lvm.conf 内の locking_mode = 4 となっている行を1に変更します。
lvmの中で以下のような変更をおこないます。今回はsnapshot10の状態に戻してみます。
lvm> lvrename rhel/root root-original
lvm> lvrename rhel/root-snapshot10 root

lvm> lvchange -k n rhel/root   ← s(k)ip をはずす
lvm> lvchange -p rw rhel/root    ← readwriteができるようにする

lvm> lvchange -k y rhel/root-original  ← s(k)ipするようにする
lvm> lvchange -p ro rhel/root-original ← read only にする

lvm> exit
しくじると再起動するときにroot fsのmountに失敗してしまうので、随時lvsなどで思った通り操作できているか確認しましょう。
このあと reboot コマンドで再起動します。

再起動後


[root@localhost ~]# snapper create

[root@localhost ~]# lvs
  LV              VG   Attr       LSize  Pool   Origin        Data%  Meta%  Move Log Cpy%Sync Convert
  pool00          rhel twi-aotz-- 12.00g                      11.15  24.41                           
  root            rhel Vwi-aotz-- 12.00g pool00 root-original 10.00                                  
  root-original   rhel Vwi---tz-k 12.00g pool00                                                      
  root-snapshot11 rhel Vri---tz-k 12.00g pool00 root-original                                        
  root-snapshot12 rhel Vri---tz-k 12.00g pool00 root-original                                        
  root-snapshot13 rhel Vri---tz-k 12.00g pool00 root-original                                        
  root-snapshot14 rhel Vri---tz-k 12.00g pool00 root-original                                        
  root-snapshot15 rhel Vri---tz-k 12.00g pool00 root-original                                        
  root-snapshot16 rhel Vri---tz-k 12.00g pool00 root-original                                        
  root-snapshot17 rhel Vri---tz-k 12.00g pool00 root-original                                        
  root-snapshot18 rhel Vri---tz-k 12.00g pool00 root-original                                        
  root-snapshot19 rhel Vri---tz-k 12.00g pool00 root                                                 
  root-snapshot9  rhel Vri---tz-k 12.00g pool00 root-original                                        
  swap            rhel -wi-ao----  2.00g               


[root@localhost ~]# snapper list
Type   | #  | Pre # | Date                     | User | Cleanup | Description | Userdata
-------+----+-------+--------------------------+------+---------+-------------+---------
single | 0  |       |                          | root |         | current     |         
single | 9  |       | Mon May 18 18:15:10 2015 | root |         |             |         
single | 19 |       | Tue May 19 18:48:48 2015 | root |         |             |
snapperはきれいさっぱり10から18までのことは忘れています。記録がないから当然ですね。

元LVの削除

もろもろ試して、うまいこと古い状態に戻せて いることが確認できたら、元のLVをlvremoveで削除します。

[root@localhost ~]# lvremove rhel/root-original
  Logical volume "root-original" successfully removed
[root@localhost ~]# lvs
  LV              VG   Attr       LSize  Pool   Origin Data%  Meta%  Move Log Cpy%Sync Convert
  pool00          rhel twi-aotz-- 12.00g               11.07  22.66                           
  root            rhel Vwi-aotz-- 12.00g pool00        10.00                                  
  root-snapshot11 rhel Vri---tz-k 12.00g pool00                                               
  root-snapshot12 rhel Vri---tz-k 12.00g pool00                                               
  root-snapshot13 rhel Vri---tz-k 12.00g pool00                                               
  root-snapshot14 rhel Vri---tz-k 12.00g pool00                                               
  root-snapshot15 rhel Vri---tz-k 12.00g pool00                                               
  root-snapshot16 rhel Vri---tz-k 12.00g pool00                                               
  root-snapshot17 rhel Vri---tz-k 12.00g pool00                                               
  root-snapshot18 rhel Vri---tz-k 12.00g pool00                                               
  root-snapshot19 rhel Vri---tz-k 12.00g pool00 root                                          
  root-snapshot9  rhel Vri---tz-k 12.00g pool00                                               
  swap            rhel -wi-ao----  2.00g
Originの表記が消えたりしていますがやはりあまり実害はないので気にしない方向で… 。