weblog of key_amb

主にIT関連の技術メモ

ssh と rsync だけで Tree Deploy を実現する "grifork" を作った

はじめに〜fireap to grifork

約半年前に fireap というデプロイツール(タスクランナー)を作りました。

オンプレミスでも使えて、ノード数 N に対して O(log N) で動作する、というものです。
が、前提条件として、システム内の全ホストに fireap をデプロイし、また、全ホストで Consul の agent を動かす必要があります。
その辺りが導入障壁になる環境もあるかもしれないな、と思いました。

…で、少し工夫すれば、「デプロイサーバにだけプログラムがあればツリー状にデプロイできる」ものも作れるだろう、と思って作ってみたのが、今回の grifork になります。

Ruby で書いてます。

Tree Deploy とは

なんとなく言葉の雰囲気で意味が伝わる気もしますが、grifork のメカニズムの説明がてらに Cacoo で絵を描いてみました。

f:id:key_amb:20161002145457p:plain

対象のホストリストをツリー状のグラフにマッピングして、デプロイをキックするサーバ(以下、デプロイサーバ)から樹状にデプロイを伝播していくと、そういうデプロイのやり方を意図しています。*1

fireap 同様、実行時間は O(log N) になります。

grifork: standalone モード

grifork には2つ動作モードがありますが、"standaloneモード" が、デプロイサーバでだけ grifork が動けばいいというモードです。

上図のようなツリーに対して、このモードでデプロイを行うと、下の3段階でデプロイが進行します。

(1) デプロイサーバから子ノードに rsync

f:id:key_amb:20161002151648p:plain

(2) デプロイサーバから第1世代の子ノードに ssh して、第2世代の子ノードへの rsync をキック

f:id:key_amb:20161002151700p:plain

(3) 以下、グラフの葉に達するまで繰り返し

f:id:key_amb:20161002151712p:plain

各世代のジョブは全て Parallel *2 で並行に走らせることにしました。
ので、最大並列数が「最大のノードを持つ世代のノード数」になります。

※まだ最大14ノードでしか試してないのですが、ひょっとしたら並列数がすごく大きくなったら何か問題出てくるかも。

grifork: grifork モード

もう1つの動作モードは "griforkモード" と呼んでいます。
こちらは、全ノードで grifork プログラムが動作する前提になっています。

このモードでは、子ノードで grifork を実行することで、再帰的に葉に達するまでタスクを実行します。

こちらの仕組みについても、先ほどのツリーを使って図解します。

(1) デプロイサーバから子ノードに rsync

※ standalone モードと同じなので、図は割愛

(2) 子ノードに ssh して各サブツリーへの grifork をキック

f:id:key_amb:20161002152223p:plain

(3) 以下、再帰的にグラフの葉に達するまで繰り返し

f:id:key_amb:20161002152251p:plain

standalone モードで grifork や ruby を配布して、grifork モードでアプリケーションをデプロイする、というやり方もできます。
grifork モードの実験をやるときは、実際に standalone モードで全ホストに grifork を配布していました。

使い方

まだ gem にしてないので、今のところ github から clone して使う形になります。

git clone https://github.com/key-amb/grifork.git
cd grifork
bundle install
./bin/grifork [--[f]ile path/to/Griforkfile]

デプロイツールとして紹介してきましたが、デプロイに限らずリモートでタスクを実行させる用途でも使えます。

Griforkfile という DSL ファイルにタスクの設定を書きます。
デフォルトでカレントディレクトリの Griforkfile を参照します。

リポジトリexample ディレクトリに、各モードの Griforkfile のサンプルを置いておきました。

動作例と実行ログ

ちょっとしたサンプルとそのログを Gist に貼っておきました。もし、参考にしたい方はどうぞ。

https://gist.github.com/key-amb/94b8113e315ed1ea0e0d98dd6071a9a9

今後の展望

とりあえず動くものはできた、というレベルなので、まだまだ色々使いづらいところや、エラーケースの対処など漏れているところもありそうです。

gem 化した方が使いやすそうだなと思いつつ、まだやっていません。

また、Go で書き直したら、grifork モードのために grifork 自身を配布するのも楽だろうな、と思っています。やる気次第だけど、できれば、やりたい。

以上、 sshrsync だけでクラスタに対してツリー状にタスクを伝播するタスクランナー "grifork" を紹介しました。

ご興味ありましたら、お試し下さい。

もし何か要望やバグなどありましたら、GitHub Issue か Pull Request か、Twitter などでお知らせ下さい。

余談〜デプロイの未来について

そもそもの話になりますが、PaaS やコンテナの普及によって、サーバアプリケーションをデプロイする仕組みは変わりつつあるように思います。
単純にファイルツリーを配布するようなデプロイツールのニーズは、相対的に下がっていきそうな気もしています。

おまけ〜grifork の語源

graph + fork (+ griffin)

"clenv" がそこそこ xxenv っぽく使えるようになってきた

3日前に上の記事を書いたばかりですが、また少し工事をしたので、自分の中での整理も兼ねて、お知らせ。

"clenv" は私が趣味で作っているもので、シェルスクリプトのパッケージ管理ツールのようなものです。

ソースコードhttps://github.com/key-amb/clenv で公開しています。

追加・変更したコマンドについて

clenv help の実行結果から抜粋します:

clenv init [-]             # Bootstrap
clenv create [ENVIRONMENT] # Initialize ENVIRONMENT
clenv environ              # Show current environment
clenv environs             # List environments
clenv global [ENVIRONMENT] # Get/Set global environment
clenv local  [ENVIRONMENT] # Get/Set local environment
clenv exec   EXECUTABLE    # Execute EXECUTABLE in clenv
clenv which  EXECUTABLE    # Show path of EXECUTABLE in clenv

exec, which は v0.3.0 で、後は v0.5.0 以降で追加しました。
clenv create は v0.4 以前の clenv init-env 相当、 clenv environsclenv-list-env 相当です。

clenv environ[s] は rbenv 等の version[s] サブコマンド相当で、現在の clenv 的な意味の environment を表示します。

コマンド動作イメージ

上のコマンド群を動かした場合の挙動の例を、軽く以下に示します:

% pwd
/home/key-amb/tmp
% clenv environ
default (set by /home/key-amb/.clenv/environment)
% clenv environs
* default
% clenv create foo          
Initialized env 'foo'
% clenv create bar
Initialized env 'bar'
% clenv environs
  bar
* default
  foo
% clenv local foo
% clenv environ
foo (set by /home/key-amb/tmp/.clenv-environment)
% CLENV_ENVIRONMENT=bar clenv environ
bar (set by CLENV_ENVIRONMENT)
% clenv global
default (set by /home/key-amb/.clenv/environment)

create 〜 install, environ[s] 〜 version[s] と考えれば、 rbenv 等に慣れた人であれば、そこそこ違和感なく使えるのではないでしょうか。

初期セットアップの改善

clenv のセットアップ方法も、より xxenv に近づき、以前よりシンプルになりました。
README から抜粋します。

# .bash_profile 等に追記する
export CLENV_ROOT=$HOME/.clenv
export PATH="$CLENV_ROOT/bin:$PATH"
eval "$(clenv init -)"

rbenv や plenv の system に相当するものがないので、 clenv init - の中で、初回は default という環境を create して global にセットすることにしました。

終わりに

clenv の主に v0.4 〜 v0.6 の変更について、ダイジェストでお伝えしました。

よろしければ、手元に入れて遊んでみて下さい。

Enjoy!

過去の記事

シェルスクリプト用テストツール "shove" v0.8 までの更新のお知らせ

シェルスクリプト用のテストツール "shove" を作って、初めて上の記事で紹介したのは約5ヶ月前になります。

今回は、上記事の時点からこれまでの主な差分をお知らせします。
言うなれば CHANGELOG + αな記事となります。

shove は GitHub で公開しています。URL は下になります:

https://github.com/key-amb/shove

CONTENTS:

グルーピングの新しい記法を追加 (v0.8.1)

今まで T_SUB "..." (( ... )) という記法でテストコードをグループ化できるようにしていました。
が、なんか微妙だなと思って t::group "..." ({ ... }) という新しい記法を追加しました。

なんか微妙だな、と思った以上に深い理由はあまりなかったりもします^^;
が、2つの記法をサポートするのはコード上、厳しいことになっていたりもするので、古い記法はその内消そうと思ってます。
ので、もし使っている人がいたら新記法に移行をお願いします。(すいませんすいません)

サンプルコードは README を更新していますので、そちらをご覧くださいませ。

テスト用の関数を12個追加 (v0.8.0)

コードをリファクタしていじりやすくなったので、調子に乗って色んな test 関数のオプションでテストできるように t_xxx な関数を追加しました。

README そのままですが、追加した関数とその使い方は下のようになります:

t_present   $str  "str is present"    # [ -n "$str" ]
t_blank     $str  "str is blank"      # [ -z "$str" ]
t_exist     $path "path exists"       # [ -e "$path" ]
t_file      $path "path is file"      # [ -f "$path" ]
t_directory $path "path is directory" # [ -d "$path" ]
t_symlink   $path "path is symlink"   # [ -L "$path" ]
t_eq        $x $y "x == y"            # [ $x -eq $y ]
t_ne        $x $y "x != y"            # [ $x -ne $y ]
t_gt        $x $y "x >  y"            # [ $x -gt $y ]
t_ge        $x $y "x >= y"            # [ $x -ge $y ]
t_lt        $x $y "x <  y"            # [ $x -lt $y ]
t_le        $x $y "x <= y"            # [ $x -le $y ]

右部のコメントの通りに test 関数が実行され、結果が成功ならテストが通ります。

shpec のテストを追加 (v0.7.2)

テストツールのテストを自分自身でやるのは微妙だな、という気はします。

shpec *1 は上の記事でもちょこっと紹介していますが、RSpec ライクな記法でシェルスクリプトのテストを可能にしてくれるツールです。

CI には入れてませんが、テストコードを少し書いて、ときどき手元で実行しています。

shpec では describe ... endit ... end でブロックを作ることができます。が、 shove のグルーピングのようにサブシェルは使っていないようでした。
単に RSpec っぽく書けて、結果もきれいに出せるもの、という理解でいいのかな、と思っています。

コードがミニマルな感じですごくきれいなので、 shove のリファクタで参考にさせてもらいました。

その他にも、少し内部構造をいじったり、ちょっとした改善がいくつか入っていたりします。

シェルスクリプトのテストをしたいシーンがありましたら、shove のことを思い出していただければ幸いです。

Enjoy!

"clenv" というシェルスクリプトのモジュール管理ツールを引き続き作っている

keyamb.hatenablog.com

上の記事を書いたのが3ヶ月前ですね。
趣味で作っているのでだいぶ波があるのですが、初コミットからは5ヶ月ほど経ちました。

"clenv" って何?

https://github.com/key-amb/clenv です。

説明は上の記事に書きましたが、一応こちらでもかんたんに。

私が趣味で作っているツールで、シェルスクリプトの実行ファイルやライブラリをモジュール化して管理できるようにしてくれるものです。
名前はお察しの通り、rbenv など 〜env が由来です。

"version" という語ではなく "environment" という語を使っています。
"version" だと異なるバージョンのシェルをビルドしてインストールして…のようなことを連想されそうだな、と思いまして。
そういうのをできるツールがあってもいいとは思いますが、それをやる気はない。

"clam" モジュール

これも冒頭の記事に書いたのですが、clenv で使うモジュールを "clam" モジュールと呼ぶことにしました。
git の URL か、ローカルのファイルシステムからインストールできます。

モジュール側では clam.spec というファイルを用意しておく必要があります。
これのファイル形式が v0.3 で少し変わりました*1

まあ、詳しくは README や脚注の PR をご覧くださいということで。

NEW - "cload" コマンドと "cllib" 関数

v0.2 で導入しました。
. or source 相当ですが、 CLOAD_PATH という環境変数の path を見て、そこからの相対パスで読み込めるものです。
これで rubyperl の require 相当のことができる、と考えました。

面倒なのは、これをシェルの関数で提供しなければならないことです。
スクリプティングにおいては、シェル関数を読み込むコードを一行足すか、 eval 実行する必要があります。

使い方としては、下のようになります。

# 例1
eval $(cload "mylib")
eval $(cload "mylib/foo")

# 例2
eval $(cload -)
cllib "mylib"
cllib "mylib/foo"

ここで、 cllib が "cload" のシェル関数版です。

eval $(cload -) をやってくれるラッパーコマンドを作ればいいじゃないか、と思う人もいるかもしれませんが、そうすると実行スクリプト. または source して実行せざるを得なくなる気がします。
ので、厳しそう。

NEW - shims/ に shim を置くことにした

v0.2 までは、 shims => environments/$CLENV_ENVIRONMENT/bin と symlink していただけですが、 rbenv を参考にランチャーとなるシェルスクリプトの実体を置くことにしました。
clam install するときに、下のようなファイルを実行ファイルと同じ名前で shims/ 以下にコピーしています。

#!/usr/bin/env bash

set -euo pipefail
[[ ${CLENV_DEBUG:-} ]] && set -x

program="${0##*/}"

exec "${CLENV_ROOT}/bin/clenv" exec "$program" "$@"

だいたい rbenv や plenv と同様で、clenv exec で対象のコマンドを探して exec します。

Travis CI で継続的にテストできるようにした

シェルスクリプトでどうやるのかな、と思っていたところ、 b4b4r07/enhancdTravis が設定されていたので、参考になりました。

手前味噌ですが、 shove でテストを書いています。*2

実行ファイル系は Bash 前提で書いていますが、シェル関数で機能提供するものは POSIX シェルで動くように書いています。
ので、 make test タスクもそんな感じで書いています。

いま CI 環境 だと、Ubuntu v12.04 のコンテナで sh, bash, dash で CI が走っています。

あらゆる環境で動作保証するものではないけど、まあ、だいたい動くんじゃないかな、というところ。

※テスト足りてないところは色々あります。

clenv 環境で使える Bash 用の Logger モジュールを書いてみた

https://github.com/key-amb/bash-logger

これです。これ自体は bash スクリプト1枚なので、これだけダウンロードして単独で使うこともできます。

clenv 環境にインストールするには clam https://github.com/key-amb/bash-logger.git で。

clenv 環境でこれを使うサンプルコードは下のようになります。

# myapp.bash
eval $(cload logger.bashrc)

log.info "foo"
log.warn "warn"

実行すると下のようになります。

% ./myapp.bash
2016-09-19 08:47:35 [INFO] foo
2016-09-19 08:47:35 [WARN] warn

(本当はログレベルに応じて色が変わります。)

まあ、こういうことがやりたいわけです。
つまり、再利用性のあるライブラリを書いて、再利用して楽にスクリプティングしたい。

今後

shims/ 以下は実体を置くことができたのですが、 environments/$env/{bin,lib} 以下がまだインストールした module への symlink になっているので、それも実体にしないと、ライブラリ同士のディレクトリ構成に依存関係があるときに上手く行きません。

解決する方法は2通りあります。

  1. bin/, lib/ 以下にも中身をコピーする
    • uninstall できるように、何をコピーしたか記録しておかなければなりません
  2. bin/, lib/ 以下に実体は置かない。 cload 時に任意のモジュールについて environments/$env/$module/ 以下の path を CLOAD_PATH に追加して解決する

後者は RubyGems が行っている戦略ですね。*3

前者は clam install/uninstall で頑張る。後者は cload で頑張る感じ。
後者にしても、 bin/ 以下には RubyGems で言うところの binstub 相当を置かないといけないですが、それでも uninstall はだいぶ楽になりそうです。
また、後者にすると同一モジュールでも複数バージョンインストールできるようになったりと、(ニーズはさておき)夢が広がりそうです。

趣味でやっているので、いつになるかわかりませんが、ご興味ありましたら、完成までは気長にお待ちください。

現時点でも上に書いたようなことや、冒頭の記事で書いたような実行ファイルの管理に使えると思います。というか、使っています。

…が、今後、互換性のない変更が入る可能性はありますので、ご注意ください。

脚注

enhancd を改修して引数なし cd コマンドの挙動を変えずに使えるようにした

先日、上の記事を書きました。

記事末尾に「余談」として、次のように書きました。

もう1つ enhancd による cd の挙動変更でときどき戸惑うのは、 cd 単体で実行したときです。 enhancd ではこのときもディレクトリ履歴検索による選択画面になります。

これはとりあえず諦めて受け入れることにしたのですが、今回紹介した変更のように、デフォルトの挙動を変更しないオプションがあってもいいかもしれません。

すると、意外と反響が有って、ニーズがあることがわかりました。

というわけで、オプションを追加して PR してみました。

で、それが本日、めでたくマージされましたので、お知らせします。

~/.bashrc などで以下のオプションを指定して、enhancd をご利用くださいませ。

ENHANCD_DISABLE_HOME=1

これで、 cd コマンドを引数なしで実行したとき、peco などのインタラクティブフィルタを起動せずに、ふつうに $HOME に cd できます。

Enjoy!

余談

前回の cd -, cd .. の拡張の時、ドキュメントの修正までやってなかったので、別 PR でやっておきました。

これも既に master に取り込んでもらっています。

もう一つ余談ですが、enhancd のテストで拙作の shove を使っていただいています。ありがたや。