2016/12/21

qcow2 の cluster leak を直す

qcow2 の cluster leak を直してみたログ。
オチがついてて原因が完全特定できていないのでふわっとしたお話。


環境

Host OS: CentOS 7.1.1503
Host Kernel: 3.10.0-327.4.5.el7.x86_64
qemu-img: 1.5.3


状況

VMイメージ を GFS2 上に置いているVMで ls が返ってこない。
pcs から見る限り clvmd も dlm も全ノード上で動いているし全ノード online になっている。
過去にクラスタの1ノードが物理的な問題で急に落ちたこともあって VM イメージの破損を疑ってみる。
調べてみると qcow2 の整合性の確認には qemu-img の check コマンドを使うことができるらしい。

qemu-img check を qcow2 に対して打つとこんな結果が返ってくる。
[root@hoge ~]# qemu-img check /mnt/hoge/fuga.qcow2
Leaked cluster 42362 refcount=1 reference=0
Leaked cluster 42363 refcount=1 reference=0
Leaked cluster 42364 refcount=1 reference=0
Leaked cluster 42365 refcount=1 reference=0
4 leaked clusters were found on the image.
This means waste of disk space, but no harm to data.
65043/524288 = 12.41% allocated, 0.15% fragmented, 0.00% compressed clusters
Image end offset: 4264099840
何やらエラーが返ってきている。
同じ GFS2 の上にある VM に対しても qemu-img check すると
[root@hoge ~]# qemu-img check /mnt/hoge/piyo.qcow2
No errors were found on the image.
468877/524288 = 89.43% allocated, 0.13% fragmented, 0.00% compressed clusters
Image end offset: 30733303808
と返すので問題の VM のみがエラーを吐いているらしい。


解決方法

qemu-img check -h を読んでいると -r オプションがあって、リカバリを試すことができる様子。
Parameters to check subcommand:
  '-r' tries to repair any inconsistencies that are found during the check.
       '-r leaks' repairs only cluster leaks, whereas '-r all' fixes all
       kinds of errors, with a higher risk of choosing the wrong fix or
       hiding corruption that has already occurred.
今回は cluster leak のようなので qemu-img -check r leaks を実行してみた。
どうやら上手くいったようで check してもエラーを返さなくなった。
直したイメージを起動すると ls が返ってきたのでめでたしめでたし。


オチ

これで直ったと思っていたらlsを返さない他のVMを発見。
そしてこいつは qemu-img check でエラーを返さない。
修復した VM の /var/log/messages に、 nfs への書き込みが time out している kernel panic っぽいものを発見。
両方のVMは同じ nfs をマウントしてたので nfs が原因説が濃厚に。
ちなみにある時間から nfs head server がレスポンスを返さない状態になっていたらしい。
おそらく前にもあった案件と同じ原因では無いかと思ってます。
しかし若干の謎があって、nfsをマウントしていたVMは最低限3つある上2つは死んでいたのに1つだけは生きていた。
しかも生きていたVMの mount option に intr は無い。
生きてたVMは書き込していなかった、とすれば辻褄はあうのですがそれはログからは分からなかった。

nfs server 側は私が直接触っていないので余計曖昧な情報ですが、レスポンスを返さない原因は特定のパーティションの容量が一杯だったせいとか。
容量一杯で nfs への書き込みができないのはともかく、それでハングするものなのだろうか。
最終的な対応としては nfs server 側の容量を空けることでおしまい。


ということで qcow2 の cluster leak を直したものの、本来の原因では無かった可能性がある。

2016/12/06

何故 Rails では View から Controller のインスタンス変数にアクセスできるのか

この記事は Okinawa.rb Advent Calendar 2016 6日目の記事です。


はじまり

むかしむかし、あるところでひとりのあっとんさんが Okinawa.rb Advent Calendar のネタをどうるか考えていました。
そこへ @CodeHex さんがあらわれ、ちょっと Rails について質問をしても良いですかと尋ねてきました。

CodeHex さん「Controller で @ を付けた変数は View からアクセスできるんですか?」
あっとんさん「Rails ではそうですね。ちなみに Ruby では @ が付いた変数はインスタンス変数なのです。」
CodeHex さん「なるほど。 Perl では @ があると配列なので違和感がありますね。」

さて、質問された時に Rails ではそういうものだという解答をしたのですがふと思いました。
「別にインスタンス変数だからと言って View から見えるのは関係が無いよね」
そこで調査班は実装を見付けるために Rails へと踏み込むことにしました。


View はどのようにインスタンス変数にアクセスしているのか

まずは View のクラスを確認してみます。
単に Controller を継承しているだけなのかもしれません。
それならこの記事は終わってしまいますがまず確認しないことには始まりません。

ということで Rails のプロジェクトを作ります
$ rails -v # 5.0.01
$ rails new advent2016
$ cd advent2016
$ rails generate scaffold user uid:string
ここで User#index に binding.pry を仕込んで self.class を確認してみます。
[2] pry(#<#<Class:0x007ffdce310d48>>)> self.class
=> #<Class:0x007ffdce310d48>
どうやら特異クラス。さて ancestors を確認してみると
となっていました。
継承しているものに Controller らしきものはパッと見は無いようですね。
調査は続行しそうです。

そこで self を確認していると
とそのまま @_assigns というインスタンス変数があることを発見。あやしいですね。
もちろんこの段階では @users は存在しているので
$ self.instance_eval{@_assigns= nil}
としても @users は変更されません。


@_assign を追いかける

@_assign はどこで変更されるのか。
調査隊はさらに Rails の奥へと踏み込むことを決意します。
$ git grep @_assigns
するとactionview/lib/action_view/base.rb の一つしかありません。
しかもやっていることは instance_variable_set なので怪しさしかありません。
assigns は引数なのでどこかで new されているはず。
次はそれを探します。


ActionView::Base.new はどこだ

まずは素直に git grep します。test もあったので grep -v で除外。
$ git grep ActionView::Base.new | grep -v test
actionpack/lib/action_controller/metal/helpers.rb が引っかかりました。
しかしこいつは module でこれが呼ばれるのを探すのは面倒。
また pry に戻って探してみることにします。

backtrace を覗く

また User#index の binding.pry に戻ります。
User#index の段階では view は用意されてるのでどこかで既に new されているはず。
なのでまず backtrace を眺めてみます。
pry-byebug を入れて pry-backtrace と打つと backtrace が見られます。
さて覗いてみると

多い。111もあります。
折角 byebug で repl も動くことなので up していきます。
途中で String を freeze しているころを見付けます。
噂の物体を副産物で見付けた気分ですね。
どんどん up していきます。
actionview/lib/action_view/renderer/template_renderer.rb の render_with_layout くらいから view という変数がちらほら出てきますね。あやしい。
view は context という名前になったりしながら連れ回されているようです。
actionview/lib/action_view/rendering.rb の _render_template までそれっぽいのがありますが、actionpack/lib/action_controller/metal/streaming.rb の _render_template あたりから出てこなくなります。
この辺りに真相が潜んでいそうです。
というか metal は見覚えがありますね。 ActionView::Base.new しているのも metal の helper でした。


結局どこでインスタンス変数を渡しているのか

最後に view っぽいのを扱っているのは actionview/lib/action_view/rendering.rb の _render_template でした。
context に view_context を代入しています。
view_context の定義に view_assigns という怪しさ満点のものを発見。
しかし rendering.rb に view_assigns は無い。
名前が分かればとりあえず git grep します。
actionpack/lib/abstract_controller/rendering.rb に定義を発見しました。
instance_variables を取ってきてhash にしています。
slice しているのは @ を取りのぞくためですね。
ここでおもしろいのは protected_vars を reject していること。
@_formats などは controller から渡さないようにしているようです。
view_assigns は AbstractController::Rendering に定義されています。
ということは ApplicationController が AbstractController::Rendering を継承していれば、ユーザ定義のコントローラは view_assigns を経由でインスタンス変数を取得して view を生成していそうですね。
となれば気になるのは UsersController の ancestors です。
と AbstractController::Rendering がばっちりありますね。56行目です。
という訳で「何故 View は Controller のインスタンス変数へとアクセスできるのか」を発見できました。
それは「Controller が View を Rendering する時に自分のインスタンス変数を渡してやっているから」です。


view_assigns を上書きするとどうなるか

実装が分かれば遊びたくなるのが常。
自分はきちんとコードが読めたのかも含めてちょっと遊んでみます。
内容は @users という変数を無理矢理作ってやるというもの。
この状態で user#index にアクセスすると DB の中身に関係無く、定義された内容が表示されました。
やはり view に渡すためのインスタンス変数を取得するメソッドは AbstractController::Rendering#view_assign で間違いない様子。
ということで無事 Rails の中からお目当てのものを見付けられました。
めでたしめでたし。


おわり

というおはなしなのでした。
読むのに pry-byebug が便利でした。
むしろ無かったら見付けられなかったかもしれない。
結論としては Controller で view_assigns を上書きすると大変なことになりそう、って感じです。


みなさま良い年末をー。