『Serverspec』を読みました
『Serverspec』を読んだ(ご恵贈ありがとうございました!)ので感想とかを書いてみたいと思います。
ちなみに、本書は1/17発売なので既に書店に並んでいると思いますし、
- 作者: 宮下剛輔
- 出版社/メーカー: オライリージャパン
- 発売日: 2015/01/17
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (6件) を見る
電子書籍版もあるのでお急ぎの向きはそちらを購入するのもいいかと思います。
Serverspec, Specinfra を拡張したい、内部を知りたい、使い倒したい人
Serverespec, Specinfraの生みの親であり本書の著者でもある mizzy ([twitter:@gosukenator]) さんが自ら、
その代わり、開発に至る経緯や開発する上での哲学、動作仕様や内部のアーキテクチャ、ソースコードレベルでの拡張方法など、表から見ただけではわからないようなことをふんだんに盛り込んでいます。
Serverspec について詳しく解説するということは、必然的に Specinfra のことも詳しく解説することになるわけですが、ドキュメントがまったくない Specinfra についても、ソースコードレベルで詳しく解説していますので、Specinfra をベースに Serverspec や Itamae とは違う何かをつくってみたい、という方にもお勧めです。
http://mizzy.org/blog/2014/12/25/1/
と言ってるのでまちがいないです。さっさと購入して熟読しましょう。
自分も以前、ある機能を追加しようと思いざっとコード読んだのですが、ちょっと把握するのに時間がかかりそうだったんで mizzy さんに相談したら実装してくれた経験があるのですが、本書を読んだ今なら自分でできそうな気がしています!!
Serverspecを使えればいい、またはServerspecには特に関心がないインフラエンジニアの人
使うだけならネットに情報が豊富にあるのでググれば事足ります。本書でもそういった「使い方」の情報は最小限にとどめているので、そういった目的では本書はおすすめできません。
「そういった目的では」です。
「Infrastructure as Code」とは何か?改めて説明するまでもないと思いますが、インフラエンジニアならもうこの潮流は避けて通れないでしょう。
ServerspecはこのInfrastructure as Codeの重要なピースの一つである「テスト」を担う実装であり、本書の第一章にはServerspecの生まれた経緯や必要性、利用目的が書かれています。こういった内容は、コードを読んだだけでは伝わらないのでとても貴重だと思います。
つまり、本書を読むことでInfrastructure as Codeにおけるテストの重要性や戦略をServerspecというプロダクトを媒介として知ることができます。たとえServerspecに興味がなかったとしても、Infrastructure as Codeを知る上でこれはとても有益であるでしょう。
まとめると
すべてのインフラエンジニアにおすすめです!!!
MySQL 5.5以降のutf8mb4とPerlのDBD::mysqlのmysql_enable_utf8のワナ
結論から言うと、ひろせが望ましいと思う順にこうすればいいんじゃないの?ってのを列挙します。
- my.cnfに[libmysqlclient]グループを追加しそこにdefault-character-set = utf8mb4と書き、DBD::mysqlでは mysql_read_default_file=/etc/my.cnf;mysql_read_default_group=libmysqlclient と指定する
- mysql_enable_utf8を使いたい場合はSET NAMES指定も必要
- my.cnfの[client]グループにdefault-character-set = utf8mb4と書き、DBD::mysqlでは mysql_read_default_file=/etc/my.cnf と指定する。mysqlbinlogなど--default-character-setオプションを解せずエラーになるものは--no-defaultsをつけて実行する
- mysql_enable_utf8を使いたい場合はSET NAMES指定も必要
- DBD::mysqlで接続後に SET NAMES utf8mb4 を実行する
オハマリポイントは
- DBD::mysql (libmysqlclientなクライアント全般?) は loose-default-character-set の設定値を認識しない
- select結果をPerlの内部表現文字列(UTF8フラグ)にしようと DBD::mysql の mysql_enable_utf8 を使うと、ついでにクライアント側も文字コード設定を(utf8mb4ではなく)utf8にしてしまう
です。
DBD::mysql (libmysqlclientなクライアント全般?) は loose-default-character-set の設定値を認識しない
my.cnfの[client]グループに書いた設定はクライアント全般に適用されるので、ここにdefault-character-set = utf8mb4 と書ければいいのですが、[client]グループに書くとmysqlbinlogなどこのオプションに対応していないクライアントがエラー終了してしまいます。
my.cnfの設定項目には loose- というprefixをつけることができて、対応していないクライアントはそれを無視することができます。
なので、[client]グループに loose-default-character-set = utf8mb4 と書いておけば、シアワセになれると思っていたのですが、loose- だとDBD::mysql (libmysqlcientを使うクライアント全般かもしれません)が認識しくれず、libmysqlclientのデフォルト文字コード(特に変えてなければ latin1)になってしまいます(ガーン)。
仕方ないので、[libmysqlclient]といったグループを新規で追加してそこにdefault-character-setを書き、DBD::mysqlにはそのグループを読むように指示するか、mysqlbinlogがエラーになるのは --no-defaults オプションをつけて回避するか、の2択になります。
SET NAMESよりdefault-character-setを推す理由は、エスケープ処理に関連するからです。詳しくはこのへんを参照してください。
select結果をPerlの内部表現文字列(UTF8フラグ)にしようと DBD::mysql の mysql_enable_utf8 を使うと、ついでにクライアント側も文字コード設定を(utf8mb4ではなく)utf8にしてしまう
の通りですが、DBD::mysqlの mysql_enable_utf8 はPerlの文字列の内部表現化(sv_utf8_decode)するだけでなく、mysql_options(MYSQL_SET_CHARSET_NAME) も実行します。
なにが問題になるかというと、mysql_enable_utf8 を 1 にしてネコ (Unicode: U+1F639, UTF-8: \xF0\x9F\x98\xB9) をutf8mb4なテーブルにinsertしても、utf8mb4でなく MYSQL_SET_CHARSET_NAME=utf8 になっているので DBD::mysql::st execute failed: Incorrect string value というエラーでinsertできません。
また、mysql_enable_utf8 を 0 にした場合(MYSQL_SET_CHARSET_NAME=latin1 を実行している)やmysql_enable_utf8を指定しなかった場合(libmysqlclientのデフォルトであるlatin1が使われる)、my.cnfの適切なグループにdefault-character-set = utf8mb4と書いてあれば、character_set_clientや_connectionはutf8mb4になりますが、libmysqlclientのエスケープ処理は(多分)latin1で行われるのでもしかしたら問題があるかもしれません。
(エスケープ処理が本当にlatin1で行われるのかと、その場合具体的にどういう問題があるのか、はちょっと調べきれてないので情報あったら教えてください><)
エスケープの問題を別にすれば、mysql_enable_utf8を有効にしてもしなくてもいずれにせよ、MySQLに接続した直後にSET NAMES utf8mb4を実行すれば、ネコもちゃんとハンドリングできます。
生のDBD::mysqlなら、Callbacksのconnectedでやるといいと思います。
my $dbh = DBI->connect($dsn, $user, $password, { ... Callbacks => { connected => sub { $_[0]->do('SET NAMES utf8mb4'); return; }, }, });
bashでsplitを書いてみた
空白絡むとどうにも配列で返せなかったんで、裏変数(_split)経由で結果渡すようにしてるのがイマイチ。。。
#!/bin/bash set -u set -e export LANG="C" split() { sep=$1 str=$2 _split=() if [[ $str =~ $sep ]]; then while IFS= read -r e; do _split+=("$e") done < <(echo "${str//$sep/$'\n'}") fi # declare -p _split >&2 } split ':' 'foo:bar:baz' declare -p _split echo ">${_split[0]}<" split ':' 'f o o:b a r:baz' declare -p _split echo ">${_split[0]}<" split ':' ':' declare -p _split echo ">${_split[0]}<" exit
結果:
declare -a _split='([0]="foo" [1]="bar" [2]="baz")' >foo< declare -a _split='([0]="f o o" [1]="b a r" [2]="baz")' >f o o< declare -a _split='([0]="" [1]="")' ><
PerlのDBD::mysqlをlibmysqlclient.aとstatic linkしたい話
static linkするにあたっての動機、諸注意(ハメがあるので必読)は [twitter:@sonots] さんの
を参照してください。
ここではDBD::mysqlをビルドする際のオプションのみ記します。
http://dev.mysql.com/downloads/mysql/ からダウンロードできるrpm
- MySQL-client-5.6.21-1.el6.x86_64.rpm
- MySQL-devel-5.6.21-1.el6.x86_64.rpm
- MySQL-shared-5.6.21-1.el6.x86_64.rpm
の場合、
$ ldconfig -p | grep libmysqlclient libmysqlclient.so.18 (libc6,x86-64) => /usr/lib64/libmysqlclient.so.18 libmysqlclient.so (libc6,x86-64) => /usr/lib64/libmysqlclient.so $ rpm -ql MySQL-devel | grep libmysqlclient.a /usr/lib64/mysql/libmysqlclient.a $ mysql_config --libs -L/usr/lib64 -lmysqlclient -lpthread -lm -lrt -ldl
こういう感じの構成になっているので、
- /usr/lib64の代わりにlibmysqlclient.aがある/usr/lib64/mysqlにライブラリパスを通す
- libstdc++もリンクする
するように、このように
$ mysql_config --libs | sed -e 's@-L/usr/lib64@-L/usr/lib64/mysql@' -e 's@$@ -lstdc++@' -L/usr/lib64/mysql -lmysqlclient -lpthread -lm -lrt -ldl -lstdc++
します。
ソースを展開してMakefile.PLを使ってビルドする場合は、--libsにこれを指定すればよいです。
$ perl Makefile.PL --libs="$(mysql_config --libs | sed -e 's@-L/usr/lib64@-L/usr/lib64/mysql@' -e 's@$@ -lstdc++@')" $ make $ ldd blib/arch/auto/DBD/mysql/mysql.so linux-vdso.so.1 => (0x00007fff9a52a000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f3434572000) libm.so.6 => /lib64/libm.so.6 (0x00007f34342ee000) librt.so.1 => /lib64/librt.so.1 (0x00007f34340e5000) libdl.so.2 => /lib64/libdl.so.2 (0x00007f3433ee1000) libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f3433ccb000) libc.so.6 => /lib64/libc.so.6 (0x00007f3433936000) /lib64/ld-linux-x86-64.so.2 (0x00007f3434d84000) # ↑libmysqlclient.so を dynamic linkしていない $ perl -Iblib/lib -Iblib/arch -MDBD::mysql -e 1 # ↑ちゃんとモジュールをロードできる $ sudo make install
ちなみに -lstdc++ していない場合はこのようにモジュールのロードで失敗します。
$ perl -Iblib/lib -Iblib/arch -MDBD::mysql -e 1 Can't load 'blib/arch/auto/DBD/mysql/mysql.so' for module DBD::mysql: blib/arch/auto/DBD/mysql/mysql.so: undefined symbol: __cxa_pure_virtual at /usr/lib64/perl5/DynaLoader.pm line 200. at - line 0 Compilation failed in require. BEGIN failed--compilation aborted.
cpanmでインストールする場合は、--configure-args="--libs=..." に指定すればよいです。
$ mylib=$(mysql_config --libs | sed -e 's@-L/usr/lib64@-L/usr/lib64/mysql@' -e 's@$@ -lstdc++@') $ cpanm -n DBD::mysql --configure-args="--libs='${mylib}'"
Oracle謹製のrpmには先程書いた http://dev.mysql.com/downloads/mysql/ で配布しているものの他に、yumレポジトリで配布しているものもあります。
- http://dev.mysql.com/downloads/repo/yum/
- http://repo.mysql.com/yum/
- http://repo.mysql.com/yum/mysql-5.6-community/el/6/x86_64/
- mysql-community-client-5.6.21-2.el6.x86_64.rpm
- mysql-community-common-5.6.21-2.el6.x86_64.rpm
- mysql-community-devel-5.6.21-2.el6.x86_64.rpm
- mysql-community-libs-5.6.21-2.el6.x86_64.rpm
なぜか両者ではライブラリファイルの配置が異なっているので気をつけてください。
このように、
$ ldconfig -p | grep libmysqlclient libmysqlclient.so.18 (libc6,x86-64) => /usr/lib64/mysql/libmysqlclient.so.18 libmysqlclient.so (libc6,x86-64) => /usr/lib64/mysql/libmysqlclient.so $ rpm -ql mysql-community-devel | grep libmysqlclient.a /usr/lib64/mysql/libmysqlclient.a $ mysql_config --libs -L/usr/lib64/mysql -lmysqlclient -lpthread -lm -lrt -ldl
yumで配布しているrpmのはlibmysqlclient.soも.aも同じディレクトリ/usr/lib64/mysqlにあります。
このままではstatic linkできないので、*.aを別のディレクトリにコピーして、そこにライブラリパスを通してビルドしないといけません。
$ mkdir /tmp/oreno-lib $ cp /usr/lib64/mysql/*.a /tmp/oreno-lib/ $ mysql_config --libs -L/usr/lib64/mysql -lmysqlclient -lpthread -lm -lrt -ldl $ mylib="-L/tmp/oreno-lib -lmysqlclient -lpthread -lm -lrt -ldl -lstdc++" $ perl Makefile.PL --libs="${mylib}" OR $ cpanm -n DBD::mysql --configure-args="--libs='${mylib}'"
VirtualBoxのスナップショットを簡単に管理できるツールを書きました。GO言語で。
VagrantではSahara pluginを使うことで、VMの状態を以前の状態に巻き戻すことができます(sandobx mode)。
VMの中でいろいろいじっている際に変更前の状態に戻せるのはとても便利なのですが、Saharaでは戻せるチェックポイントをひとつしか作れません。
自分の場合、深遠なChefのレシピを書いている過程で、戻せるポイントを何個か置きたくなることがよくあります。
さて、VagrantのバックエンドとしてVirtualBoxを使っている人は多いかと思います。
バックエンドがVritualBoxの場合、SaharaのsandboxはVirtualBoxのsnapshotを使って実現されています。
VirtualBoxのsnapshotはひとつだけでなくいくつでも作ることができます。
CUIでVirtualBoxの操作(snapshotを作ったり)をするには、vboxmanage コマンドを使えばできるのですが、VMの名前が長かったりしてちょっと使いづらいです。
そこでsnapshotに関連する操作を簡単に実行できるラッパーツールを作りました。
- https://github.com/hirose31/vboxss
- https://github.com/hirose31/vboxss/releases
- ここから各プラットフォームのバイナリがダウンロードできます!
使い方
動作中のVMの一覧を得る
$ vboxss list vm1 vm1_default_1404895653615_55181 vm2 vm2_default_1404967162355_44921
VMの正規名は右側のながーいやつですが、vboxssは左の短縮名でも受け付けます。多くの場合、短縮名はvagrant initしたディレクトリ名になると思います。
あるVMのsnapshotの一覧を得る
$ vboxss list vm1 List of the snapshots of vm1_default_1404895653615_55181 initial e718f597-22b4-4bef-adf6-239fff78215f install-apps 5337e220-0075-46a5-8aaa-901361f352df before-apply-chef 703071da-26c1-4504-8c81-4535de33c2f2
listの後にVM名を指定すると、そのVMのsnapshotの一覧が表示されます。VM名は短縮名でも長い正規名でもOKです。
snapshotを使ってVMをレストアする
$ vboxss restore vm1 before-apply-chef
vm1という名のVMの状態を、before-apply-chefという名のsnapshotの状態に戻します。
vboxss restoreを実行すると、一旦VMが停止される点に留意してください。(レストア後、自動でまた立ち上がります)
snapshotを消す
$ vboxss delete vm1 provisioned
シンボリックリンク絡みでtail -Fが追従しないケース
tail -Fしているfluent-agent-liteでハマったのでメモ。
存在しないファイルをtail -Fした後、その名前のシンボリックリンクを作った場合
$ rm -fr ~/oreno-tmp && cd ~/oreno-tmp $ tail -F tailme & tail: cannot open `tailme' for reading: No such file or directory $ ln -s real-file tailme tail: cannot watch `tailme': No such file or directory $ date > real-file $ date >> real-file $ date >> real-file # tail -Fから流れてこない
ちなみに、存在するファイルを指すシンボリックをtail -Fしてる場合、途中で指す実ファイルが存在しなくなっても、実ファイルができ次第、追従してくれます。
$ rm -fr ~/oreno-tmp && cd ~/oreno-tmp $ touch real-file.yesterday $ ln -s real-file.yesterday tailme $ tail -F tailme & tail: inotify cannot be used, reverting to polling $ date >> real-file.yesterday Mon Jul 28 21:20:59 JST 2014 $ ln -sf real-file.today tailme tail: `tailme' has become inaccessible: No such file or directory $ date >> real-file.today tail: `tailme' has appeared; following end of new file Mon Jul 28 21:21:39 JST 2014 $ date >> real-file.today Mon Jul 28 21:21:48 JST 2014
つまり、既に存在する日付入りファイル名の実ファイルを指すシンボリックリンクがある場合は、00:00に今日の日付の名前を持つ実ファイルにシンボリックリンクを切り替えたときに、今日の日付の名前の実ファイルはあってもなくてもよい。
が、ド新規で追加したログファイルとかで、実ファイルもシンボリックリンクもない場合はハマる。
実ファイルがシンボリックリンクに変わった場合
$ rm -fr ~/oreno-tmp && cd ~/oreno-tmp $ date > tailme $ tail -F tailme & Mon Jul 28 21:07:06 JST 2014 $ date >> tailme Mon Jul 28 21:07:54 JST 2014 $ ln -sf real-file tailme tail: `tailme' has become inaccessible: No such file or directory tail: cannot watch `tailme': No such file or directory $ date >> real-file $ date >> real-file $ date >> real-file # tail -Fから流れてこない
daemontoolsなserviceをハンドリングするための Chef::Provider::Service::Daemontools を書いてみました
Chefでdaemontools配下のサービスをハンドリングするときは、コミュニティクックブックの daemontools を使ってる人が多いと思います。
- https://supermarket.getchef.com/cookbooks/daemontools
- https://github.com/opscode-cookbooks/daemontools
こんな感じで。
daemontools_service "tinydns-internal" do directory "/etc/djbdns/tinydns-internal" template false action [:enable,:start] end
notification も送れます。
template '...' do ... notifies :restart, 'daemontools_service[tinydns-internal]' end
自分もこれを使おうと思ったのですが、いくつか不満点がありました
- serviceとdaemontools_serviceの両対応のレシピを書く場合、
- service と daemontools_service とでほぼ同じ記述をしないといけない
- notifies を送る側でも service か daemontools_service か意識しないといけない
- action :stop で svc -p (SIGSTOP) している
- なんで svc -d (SIGTERM) じゃないんでしょうか。。。
- サービスのハンドリングだけしたいので、daemontools のインストールとかは別に要らない
- 依存で ucspi-tcp もインストールされるが使ってないので要らない
- RedHat系だと "daemontools" という名前のパッケージを入れようとするが、(内部のyum reposにある)"daemontools-toaster" を入れたい
- attribute によるパッケージ名の変更はできない
- run ファイルの生成機能は別に要らない(あっても使わなければいいだけだけど)
ので、service リソース (http://docs.opscode.com/resource_service.html) の provider として指定可能な Chef::Provider::Service::Daemontools を書いてみました。
- https://rubygems.org/gems/chef-provider-service-daemontools
- https://github.com/hirose31/chef-provider-service-daemontools
gem install chef-provider-service-daemontools でインストールして、こんな感じで使えます。
require 'chef/provider/service/daemontools' template '...' do ... notifies :restart, 'service[oreno-daemon]' end service 'oreno-daemon' do provider Chef::Provider::Service::Daemontools service_dir '/service' directory '/usr/oreno/daemon/oreno-daemon' supports :restart => true, :reload => true action [:enable, :start] end
- provider: 「provider Chef::Provider::Service::Daemontools」は必須です
- service_dir: svscanが監視しているディレクトリです。この下にsymlinkが作られます。デフォルトは /service です
- directory: symlinkが指すrunファイル等があるディレクトリです。デフォルトは /usr/oreno/daemon/#{service_name} です
notifies でも既存の service と同じように 「service[oreno-daemon]」 と指定できるのがミソです。
Special thanks!
daemontools.rb を書くにあたり、ルビーカの低い自分を [twitter:@niku4i] さんと [twitter:@sonots] さんに助けていただきました! あざっっっっっっっっっっっっっす!!!