BerkshelfとChefのインストールに苦労した話


例えばberkshelfひとつ入れるのに、膨大な知識がいる。
もしあなたが単にPHPサーバを作りたいだけだったとしても、berkshelfをちゃんとしたモダンなやり方でいれるには膨大な知識がいる。

例えばruby, rbenv, ruby-build, gem, bundlerなどだ。

まあ、rbenvとruby-buildで最新のrubyを入れること自体はそんなに難しくない。Ruby言語の知識も必要ない。
運よくgem install berkshelfが一発で成功したら、別に問題はない。おめでとう。
そういう人はここから下は読む必要はないです。

ところがである。
ひとたびgem installが失敗したら、そこからは茨の道である。

インフラの問題

まずgemのレポジトリがAWSに依存している。これはつまりAWS障害がおこるとgem installがこけるということだ。

だけど、下記のようなエラーを見て、それがAWSのインフラ障害が原因だと気付くだろうか。
すぐには気づかないだろう。
ERROR:  While executing gem ... (Gem::Requirement::BadRequirementError)     Illformed requirement ["berkshelf"]
"ERROR:  Could not find a valid gem 'chef-solo' (>= 0) in any repository"
私はこの原因がわからず数時間悩んだ。

様子を詳しく見るためにgem install --verboseしてみた。 そしたら同じレポジトリへのHTTPリクエストを何度も何度も繰り返してるように見えた。
いったいなんだろうと考え込んだ。
rubygemのURLにAmazonS3という文字が含まれていたこと、そのときtwitterのTLがAWSの障害騒ぎで盛り上がっていたこと、などから、これは単に通信障害のせいでないかと思った。

数時間待ってからgem install chefしてみたら成功した。
やはり通信障害のせいだった。

通信障害自体はどのホスティングシステムを使っても起こりうるのだからしょうがないのだけど、問題はエラーメッセージは非常にわかりづらく、ruby初心者は自分の環境構築がどこかで間違っていたのかと考えてしまうということだ。

ひとたびgem installがこけたらgemクライアントとrubygemレポジトリの間のHTTP通信がどうなっているのかに気を配らなければいけない。
例えあなたが単にPHPのサーバを構築したいだけだったとしても。

依存性の問題:C言語コンパイラ

chefやknife-soloは、yajl-rubyという別のgemに依存していて、このyajl-rubyはC言語のライブラリに依存しているのでgem installしたときにC言語コンパイラが走る。

これはつまり、chefはC言語コンパイラに依存しているということだ。
あなたの環境でC言語コンパイルがちゃんと動かない場合、ここでこける。
gem install chef
Building native extensions. This could take a while...
ERROR: Error installing chef:
ERROR: Failed to build gem native extension.

make
gcc -I. -I/usr/include/ruby-1.9.1/i386-cygwin -I/usr/include/ruby-1.9.1/ruby/backward -I/usr/include/ruby-1.9.1 -I. -ggdb -O2 -pipe -fno-strict-aliasing -Wall -funroll-loops -o yajl.o -c yajl.c
Makefile:206: recipe for target `yajl.o' failed
make: *** [yajl.o] Error 1
私の場合はgccが(自分のミスで)壊れていたためにこのようなエラーが出た。
makeとMakefileとgccの関係について多少なりとも経験と知識があれば、このエラーをみてもたじろがないかもしれない。
しかしあなたがmakeとMakefileとgccの関係について全く何もしらなかったら、どうだろうか。

chefを入れるためにはC言語コンパイラがちゃんと動くようにしておかなくてはいけない。
例えあなたが単にPHPのサーバを構築したいだけだったとしても。

依存性の問題:ライブラリ間の整合性

さて通信問題も解決してC言語コンパイラもちゃんと入ったとしよう。
そしたらgem install chefとかgem install knife-soloあたりは成功するだろう。

でもgem install berkshelfはもっと難しい。
berkshelfはffiという別のgemに依存している。そしてffiを入れるときにC言語コンパイラがコンパイルを始める。

そしてこける笑
linking shared-object ffi_c.so
Call.o: 関数 `call_blocking_function' 内:
/home/DK/.rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/ffi-1.9.3/ext/ffi_c/Call.c:294: `ffi_call' に対する定義されていない参照です
collect2: エラー: ld はステータス 1 で終了しました
Makefile:232: recipe for target 'ffi_c.so' failed
make: *** [ffi_c.so] Error 1
ERROR:  Error installing ffi:
        ERROR: Failed to build gem native extension.

    Building has failed. See above output for more information on the failure.
make failed, exit code 2

Gem files will remain installed in /home/DK/.rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/ffi-1.9.3 for inspection.
そしたらあなたはffi_c/Call.cの294行目のffi_call関数を見て首をかしげるわけだ。
例えあなたが単にPHPのサーバを構築したいだけだったとしても。

そしてffiについてググりまくったり、ffiのレポジトリを覗いて同様のissue報告があがってないか調べたりしなきゃいけなくなる。
https://github.com/ffi/ffi
ffiについてのスライドシェアを見つけてスライドを読んで、「もしかしたらffiというのはrubygemの名前であって本体はlibffiという別のC言語製ライブラリなのかもしれない」などと考えているうちに、自分がもともと何をしたかったのか忘れてしまうかもしれない。

そしてlibffi本体のレポジトリを見つけてダウンロードする。
コンパイルする。そしてこける笑
libtool: link: gcc -shared  src/.libs/prep_cif.o src/.libs/types.o src/.libs/raw_api.o src/.libs/java_raw_api.o src/.libs/closures.o src/x86/.libs/ffi.o src/x86/.libs/win64.o    -O3 -march=corei7   -o .libs/cygffi-6.dll -Wl,--enable-auto-image-base -Xlinker --out-implib -Xlinker .libs/libffi.dll.a
src/.libs/prep_cif.o:prep_cif.c:(.text+0x53a): undefined reference to `ffi_prep_closure_loc'
さあ、prep_cif.cファイルのffi_prep_closure_loc関数について調べよう、・・・と思うだろうか?
いや思わないだろう。
なぜならあなたは単にBerkshelfを使って便利に鼻血を出したいだけだからだ。

バージョンの整合性問題

さて深掘りしてきた階段をちょっと逆戻りして上ってみよう。
どこまで戻ればいいのか。
そもそもあなたは何がやりかったのか。そうだ、Berkshelfを使いたかったのだ。
とりあえずBerkshelfの公式サイトでも見てみよう。

http://berkshelf.com/

公式サイトを見ると、「依存性のエラーを解決したいならversion 3を使え」と書いてある。
さあ、まさにこれだ。

あなたは、Githubから https://github.com/berkshelf/berkshelf 最新版のbekrshelfをcloneしてきて、ローカルgemをインストールする方法を調べて、ビルドする。

そしてコケる笑

なぜならberkshelfが要求しているrubyのバージョンとあなたがさっきrbenvで入れたrubyのバージョンが一致しないからだ。

この問題を解決するためにあなたはrbenvの動作原理を勉強する。 そして.ruby-versionというファイルが邪魔しているのだと知り、削除する。
rbenv local --unset
さあ、
gem install berkshelf-3.0.0.beta4.gem
きた!
そして
rbenv rehash
そして!
$ berks --version
3.0.0.beta4
やったー!!!berkshelfがついに入ったーーー!!

・・・でもちょっと待ってほしい。

本当にそれは便利なの?

あたなはもしかしたら、運よく一発でgem install chef, gem install berkshelfが成功したかもしれない。
でもそれはたまたまだ。
たまたま、依存するすべてのライブラリの整合性がとれていたからだ。

これだけ無数のソフトウェア・ライブラリに依存してるのだから、たいていどこかでこける。はまる。
こけたら全部自分で解決しないといけない。
yajl.cがコンパイルできない原因について考えないといけない。
gem ffi とlibffiの関係について考えないといけない。
rbenvと.ruby-versionの関係について考えないといけない。
例えあなたが単にPHPのサーバを構築したいだけだったとしても。

本当にそれは便利なの?

Mac使えば解決するのか?

「Cygwin使ってるお前が悪い。Mac使え」と言われるかもしれない。
そうかもしれない。ただし、今回Cygwinのgcc環境が壊れたのは、私がCygwinの64bit化対応をミスったのが原因なのでCygwinのことはどうか責めないでほしい。(32bit → 64bitみたいなアーキテクチャ大移動は数年に1回しか起こらない)
Macを使えばより問題は起こりにくいだろう。ユーザ数もMacの方が多いだろう。
でも大事なことは、Macを使ったからといって依存数が減るわけじゃないということだ。
Macを使ってもC言語コンパイラが走ることに何ら変わりはないし、依存先のライブラリのインストールがこけたら解決しなければいけないことに何ら変わりはない。

Vagrantがgemを捨てたのは賢明な選択だった

私がここまでVagrantをあえてdisらなかったのには理由がある。(別にChefやBerkshelfをdisってるわけでもないけど)
Vagrantはver1.1からgem依存をやめてruby処理系そのものをVagrantパッケージに内蔵するようにしたからだ。
この決断は上で説明したような依存性問題からユーザを解放するためにやったことで、とても正しい選択だったと思う。
MITCHELL HASHIMOTO氏のAbandoning RubyGemsというブログ記事の中で、Vagrantがgemを捨てる決断にいたる経緯が詳しく書かれている。
Vagrant 1.1+ no longer supports RubyGems as an installation method. Instead, you must install Vagrant 1.1+ using pre-made packages or installers. For folks used to the gem-based installation, this has caused a mixture of confusion and disdain. In this post, I enumerate my reasons for abandoning RubyGems, and why it is better for the Vagrant community long-term.

[訳] Vagrantは1.1からはインストール方法としてRubyGemsをサポートしません。
その代わりにビルド済みパッケージまたはインストーラを使う必要があります。
gemベースのインストールに慣れた人たちにとって、これは混乱と軽蔑をもたらしてしまいました。
この記事で、VagrantがなぜRubyGemsを捨てたのか、なぜその方がVagrantコミュニティにとって長期的メリットがあるのか、を説明します。

Abandoning RubyGems

まとめ

「高級言語のライブラリを組み合わせて作られたサーバ構築ツール」を手放しで礼賛する風潮について問題提起しました。
もしそういうツールを使うなら、トラブルが起きたときにソフトウェアレイヤーの階層をどこまでも深掘りしていくだけの覚悟と知識を持ちましょう。

Happy Yak Shaving!
カテゴリ: