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

この記事は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の表記が消えたりしていますがやはりあまり実害はないので気にしない方向で… 。

 

RHEL7でスナップショットとり放題設定をためしたメモ

インストール

RHEL7のインストーラではインストール対象としてLVM thinprovisioningが選択できるようになっている。ここを選択して、適当に空きが残るように容量を設定しておく。

状態を確認

操作する前に現在の状態を確認

[root@localhost log]# lsblk
NAME                    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda                       8:0    0   20G  0 disk 
├─sda1                    8:1    0  500M  0 part /boot
└─sda2                    8:2    0 16.4G  0 part 
  ├─rhel-swap           253:0    0    2G  0 lvm  [SWAP]
  ├─rhel-pool00_tmeta   253:1    0   12M  0 lvm  
  │ └─rhel-pool00-tpool 253:3    0   12G  0 lvm  
  │   ├─rhel-root       253:4    0   12G  0 lvm  /
  │   └─rhel-pool00     253:5    0   12G  0 lvm  
  └─rhel-pool00_tdata   253:2    0   12G  0 lvm  
    └─rhel-pool00-tpool 253:3    0   12G  0 lvm  
      ├─rhel-root       253:4    0   12G  0 lvm  /
      └─rhel-pool00     253:5    0   12G  0 lvm  
sr0                      11:0    1  3.6G  0 rom  

[root@localhost log]# lvs
  LV     VG   Attr       LSize  Pool   Origin Data%  Meta%  Move Log Cpy%Sync Convert
  pool00 rhel twi-aotz-- 12.00g               9.38   5.18                            
  root   rhel Vwi-aotz-- 12.00g pool00        9.38                                   
  swap   rhel -wi-ao----  2.00g
lsblkに同じエントリが2回でているのが不安ですが、tmetaの方はメタデータ、tdataの方は実際のデータがはいっています。
12GBのpool00の下に12GBということになっているrootが含まれている状態。
プールの詳細
[root@localhost log]# lvdisplay /dev/rhel/pool00 
  --- Logical volume ---
  LV Name                pool00
  VG Name                rhel
  LV UUID                vuooGK-VHow-JfAo-cPQb-Egf1-fCk4-qRpLqK
  LV Write Access        read/write
  LV Creation host, time localhost, 2015-05-15 19:15:03 +0900
  LV Pool metadata       pool00_tmeta
  LV Pool data           pool00_tdata
  LV Status              available
  # open                 2
  LV Size                12.00 GiB
  Allocated pool data    9.38%
  Allocated metadata     5.18%
  Current LE             3072
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     8192
  Block device           253:3
これで、lvmにより任意のタイミングでsnapshotをとり放題です。以下のようなコマンドでスナップショットを作成できます。

lvcreate --permission r --snapshot --name snapshot  rhel/root

snapper設定の作成

さて、snapshot作り放題、なのはいいですがlvcreateで都度作成するのはしんどいです。というわけでRHEL7には、SUSE出身のsnapperが含まれています。SUSEだとYaSTと連携してGUIまでありますが、RHELだとコマンドラインのみ。しかしとても便利です。
まずは設定を作成します。デフォルトだと “root” という名前の設定で、それ以外だと-c “設定名” みたいなオプションをつけて実行することでどこの操作をしているか指定します。
[root@localhost ~]# snapper create-config -f 'lvm(xfs)' /
このコマンドで /etc/snapper/configs/rootに設定本体が作成され、
さらに設定一覧が /etc/sysconfig/snapper にあるのでここに設定名が追加されます。
今の設定一覧を確認する。”root”設定があるのがわかります。
[root@localhost log]# snapper list-configs
Config | Subvolume
-------+----------
root   | /

snapshot作成

ここまで準備をするとスナップショットの作成は簡単。snapper createコマンドだけです。

[root@localhost log]# snapper create
様子を確認します。

[root@localhost log]# snapper list
Type   | # | Pre # | Date                     | User | Cleanup | Description | Userdata
-------+---+-------+--------------------------+------+---------+-------------+---------
single | 0 |       |                          | root |         | current     |         
single | 9 |       | Mon May 18 18:15:10 2015 | root |         |             |         

[root@localhost log]# lvs
  LV             VG   Attr       LSize  Pool   Origin Data%  Meta%  Move Log Cpy%Sync Convert
  pool00         rhel twi-aotz-- 12.00g               10.00  5.73                            
  root           rhel Vwi-aotz-- 12.00g pool00        10.00                                  
  root-snapshot9 rhel Vri---tz-k 12.00g pool00 root                                          
  swap           rhel -wi-ao----  2.00g
root-snapshot9 という名前のスナップショットが作成されています。

スナップショットのマウント

以下のようにスナップショット番号を指定して、readonlyでmountすることができます。
.snapshotディレクトリ以下に適当にmountされます。

[root@localhost ~]# snapper mount 10 

[root@localhost ~]# df

Filesystem                        1K-blocks    Used Available Use% Mounted on
/dev/mapper/rhel-root              12571648 1226516  11345132  10% /
devtmpfs                            1931348       0   1931348   0% /dev
tmpfs                               1941204       0   1941204   0% /dev/shm
tmpfs                               1941204    8580   1932624   1% /run
tmpfs                               1941204       0   1941204   0% /sys/fs/cgroup
/dev/sda1                            508588  168516    340072  34% /boot
/dev/mapper/rhel-root--snapshot10  12571648 1193528  11378120  10% /.snapshots/10/snapshot

snapshot間の差分

snapperではsnapshotを一時的なディレクトリにmountして、差分を取る仕組みがあります。
さらに通常のスナップショットの他に、管理に便利なようにpreとpostというタイプが導入されています。スナップショット自体は同じなのですが、なんらかの作業前にpreスナップショットを取得、作業後にpostスナップショットを取得してペアにしています。
今回は触れませんが、pre,postのスナップショットを各種の作業前後に自動的に作成して、特に差分がなければ自動的に消す仕組みも含まれています。
preスナップショット作成

[root@localhost ~]# snapper create -t pre
適当にファイル変更

[root@localhost ~]# ls
anaconda-ks.cfg  hoge  lvmdump-localhost.localdomain-20150515103428.tgz
[root@localhost ~]# rm hoge 
rm: remove regular empty file ‘hoge’? y
postスナップショット作成

[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 |         |             |         
pre    | 10 |       | Mon May 18 18:18:32 2015 | root |         |             |         
[root@localhost ~]# snapper create -t post --pre-number=10
[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 |         |             |         
pre    | 10 |       | Mon May 18 18:18:32 2015 | root |         |             |         
post   | 11 | 10    | Mon May 18 18:19:42 2015 | root |         |             |
差分の確認

[root@localhost ~]# snapper status 10..11
-..... /root/hoge
c..... /var/log/messages
c..... /var/log/snapper.log

差分対象の制限(フィルタ)

hogeファイルが消えているのと、/var/log以下のログが増えていることがわかります。
ログの差分をとってもあまり嬉しくないので、/etc/snapper/filters/ に、log.txtという名前で以下のようなファイルを作成します。このパターンにマッチするものは差分チェックの対象になりません。
差分対象の制限

/var/log/*
差分の確認ふたたび

[root@localhost ~]# snapper status 10..11
-..... /root/hoge

undo

差分がとれるので、patchコマンドの要領でundoができます。
ただこのundoをした時に一貫性があるのか、もろもろ大丈夫なのかなどは特に保証されていませんので注意が必要です。

[root@localhost ~]# snapper undochange 10..11 
create:1 modify:0 delete:0

TODO

snapperにはこの他にも定期的にsnapshotをとっておいて、過去数回分を残しておくなどかなり気のきいた機能があります。 裏で古いスナップショットを自動で消したり差分をしらべたりするので勝手に動くのが嫌なひとはオプションしらべてね(丸投げ)!

freeの出力が大幅改善された話

最近freeコマンドを叩くとこんな感じで出力されます。

$ free

total used free shared buff/cache available Mem: 20209620 3859396 6594188 502492 9756036 15323144 Swap: 32767996 0 32767996


availableってなんでしょう? そして -/+ buffers/cache の行がなくなっています。

その背景をちらっと紹介します。

最近linuxの3.14で/proc/meminfo に MemAvailable というフィールドが追加されました。RHEL7.0や6.6にもバックポートされています。(RHEL6.6では互換性に配慮してデフォルトではdisableされています)
http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773

    /proc/meminfo: provide estimated available memory

    Many load balancing and workload placing programs check /proc/meminfo to
    estimate how much free memory is available.  They generally do this by
    adding up "free" and "cached", which was fine ten years ago, but is
    pretty much guaranteed to be wrong today.

    It is wrong because Cached includes memory that is not freeable as page
    cache, for example shared memory segments, tmpfs, and ramfs, and it does
    not include reclaimable slab memory, which can take up a large fraction
    of system memory on mostly idle systems with lots of files.

    Currently, the amount of memory that is available for a new workload,
    without pushing the system into swap, can be estimated from MemFree,
    Active(file), Inactive(file), and SReclaimable, as well as the "low"
    watermarks from /proc/zoneinfo.

    However, this may change in the future, and user space really should not
    be expected to know kernel internals to come up with an estimate for the
    amount of free memory.

    It is more convenient to provide such an estimate in /proc/meminfo.  If
    things change in the future, we only have to change it in one place.

    Signed-off-by: Rik van Riel 
    Reported-by: Erik Mouw 
    Acked-by: Johannes Weiner 
    Signed-off-by: Andrew Morton 
    Signed-off-by: Linus Torvalds 

このフィールドは「だいたいこのくらいはswap発生させずにアプリケーションがallcoateできそう」というメモリ量の推定です。
procps-ngに含まれるfreeコマンドがbuffer や cacheとして使われているメモリをfreeに足して表示する機能があった(過去形)ことも手伝って、これらを単純に全て「カーネルがパフォーマンス改善のために未使用メモリをうまいこと使っている」と見做してシステム管理している人が多くいます。しかしこの見方は正しくありません。buffer や cacheは非常に重要で、もしこれらが0になってしまえばシステムは全く稼動できないでしょう。どれくらい存在すれば十分なパフォーマンスがでるかも、アプリケーションの特性により非常に大きくかわります。

そこでもうちょっとましな推定値をつくろうということで導入されたのがMemAvailableです。現在の実装ではかならずしもカーネルの中でこの推定をする必要はないのですが、将来的にカーネルのメモリ管理のしくみが変わっても同じように利用できるよう追加されました。


さて、これにともなってprocps-ngも3.3.10で更新されています。このprocps-ng 3.3.10はRHEL7.1に含まれています。

availableフィールドを追加  https://gitorious.org/procps/procps/commit/ba6396f886f1a9911221e1c7c4b66dc75acb6948

MemAvailableがなくても同等の計算を他のフィールドからするfallback https://gitorious.org/procps/procps/commit/b779855cf15d68f9038ff1809db18c0788e9ae70

MemAvailableはあくまでざっくりした推定値なので、既存の統計情報を足したり引いたり適当な割り算をしたりして出しています。なので、その元になっているフィールドが提供されているlinux 2.6.27以降であれば同じような計算をしてavailableを出す機能です。 

-----------------------------------------------------------
  /* zero? might need fallback for 2.6.27 <= kernel 
  if (!kb_main_available) {
    if (linux_version_code < LINUX_VERSION(2, 6, 27))
      kb_main_available = kb_main_free;
    else {
      FILE_TO_BUF(VM_MIN_FREE_FILE, vm_min_free_fd);
      kb_min_free = (unsigned long) strtoull(buf,&tail,10);

      watermark_low = kb_min_free * 5 / 4; /* should be equal to sum of all 'low' fields in /proc/zoneinfo */

      mem_available = (signed long)kb_main_free - watermark_low
      + kb_inactive_file + kb_active_file - MIN((kb_inactive_file + kb_active_file) / 2, watermark_low)
      + kb_slab_reclaimable - MIN(kb_slab_reclaimable / 2, watermark_low);

      if (mem_available < 0) mem_available = 0;
      kb_main_available = (unsigned long)mem_available;
    }
  }
-----------------------------------------------------------

もはや不要となった free: remove -/+ buffers/cacheの削除  https://gitorious.org/procps/vnwildman-procps/commit/f47001c9e91a1e9b12db4497051a212cf49a87b1

そういうわけで、freeコマンドの出力が劇的に改善されたのでした。

IPアドレスのvalid lifetime

ip addr とかで 

3: em1: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether d4:3d:7e:dc:c8:ad brd ff:ff:ff:ff:ff:ff
    inet 10.64.193.115/23 brd 10.64.193.255 scope global dynamic em1
       valid_lft 82874sec preferred_lft 82874sec
    inet6 fe80::d63d:7eff:fedc:c8ad/64 scope link 
       valid_lft forever preferred_lft forever

みたいになっているとき、em1のアドレスに有効期限がついている。
valid_lft 82874secが有効期限の設定。
この期限が切れた時には、kworkerがアドレスを切り離す。

これを設定しているのは /usr/sbin/dhclient-script で、以下みたいなコードで設定している。

ip -4 addr add ${new_ip_address}/${new_prefix} broadcast ${new_broadcast_address} dev ${interface} \valid_lft ${new_dhcp_lease_time} preferred_lft ${new_dhcp_lease_time} >/dev/null 2>&1