先日、個人開発のOSS - Submarine.jsのv1.0をリリースしました 記事はこちら
Ansibleの冪等性に関する Tweet が、結構な反応をいただけたこともあり、今回は、私がSubmarine.jsで克服したかった「冪等性の限界」と、それを、どのような哲学で解決しようとしているかについて、説明しようと思います
冪等性の理想と現実
そもそも冪等性とは? という話は、各所で説明されていることなので簡単に
- 冪等性とは
- 同じ操作を何回実行しても、同じ結果が得られること
- インフラの構成管理の世界では、何回同じコードを実行しても、同じサーバの状態が得られるという意味で使われる
- Webの世界ではHTTPのGETやPUTメソッドは冪等だけど、POSTは冪等ではないなどと言われる
Ansibleでは、この冪等性を簡単に実現するために、目的に応じて膨大な数のモジュールが提供されています
Ansibleを使って構成管理をしているプロジェクトでは、これら膨大なモジュール群の中から、自分のやりたいことに合ったモジュールを探して、組み合わせて利用します
ところが、意外と自分のほしい機能がない(あるいは見つからない)ことがあり、そういうときにはshellモジュールなどを使って、冪等なコードを自前で書く必要があります
この「冪等なコード」というのが、結構複雑になりがちで、レビューやメンテナンスがしづらかったり、バグの温床になったりします
冪等なコードを書くために必要なこと
前述のように冪等性というのは、構成管理をする上で理想的な考えではあるけれども、実装上の困難が多いのも事実です
そもそも冪等なコードを実現するためには、以下のような3つの要件を、1度に実現する複雑な処理が必要です
- 冪等なコードの要件 (引用 : https://twitter.com/tmiki74306808/status/1175631407516807175)
- 在るべき状態の定義
- 現在の状態の確認手段
- 状態を移行するための手段
多くのことを1度にやろうとすると、大体うまくいかないものです
Submarine.jsのアプローチ
Submarine.jsでは、まず上記の冪等なコードに必要な処理を、1度に実行するのではなく明確に分割しようと考え、現在の状態の確認手段を query
、あるべき状態の定義(と確認)を test
、状態を移行するための手段を command
、として1つのクラスのメソッドとして定義できるようにしました
const Submarine=require('Submarine');
const CorrectServerState=class extends Submarine {
query(){
return {
file_content: String.raw`
test -r /tmp/submarine/hogehoge \
&& cat /tmp/submarine/hogehoge \
|| echo 'File not readable' \
>&2
`,
};
}
test(stats){
return {
file_content_is_hogehoge: stats.file_content === 'hogehoge',
};
}
command(){
return String.raw`
mkdir -p /tmp/submarine \
&& echo 'hogehoge' \
> /tmp/submarine/hogehoge
`;
}
}
const state=new CorrectServerState({
conn: 'ssh',
host: 'server1',
});
state.correct()
.then(console.log);
上記のコードで query
は、キーと値のセットを返します。値はサーバの状態を確認するShellScriptの文字列で、キーは単なる名前です
test
は query
で定義したShellScript群の実行結果が引数として与えられ、それらが、あるべき状態か否かの複数の判定項目を判定して、判定結果をキーと値で返します
そして command
では、判定結果に応じて実行したいShellScriptを文字列として返します
こうして定義されたclassをnewするときに接続先のホストを指定して correct
関数を実行すると query
を実行し、その結果を test
し、あるべき状態でないときだけ command
で定義したShellScriptが実行されるようになっています
また command
を実行せず query
だけ、あるいは query
と test
だけを実行する関数も用意されています ( current
と check
という関数です)
当然、これらの処理を複数のサーバで実行する方法や、複数classの処理を順番に実行するような機構もあります(ここでの説明は割愛しますが)
こうすることで、冪等なコードに一定の秩序が生まれます。Shellの中に書くif文もかなり減らせます。しかもサーバの状態を確認する処理と、サーバに変更を加える処理が分離されたことで、変更の処理が実行されるかどうかを安全に確認でき、メンテナンス性も向上しています。
また command
を実装せず、テストのためだけに利用するなど、応用範囲も広がります
その他、開発の経緯など
このSubmarine.jsのアイディアは、オブジェクト指向の世界で、CQS(コマンドとクエリの分離)や、その発展形であるCQRS(コマンド・クエリ責務分離)と呼ばれている考え方に触発されたものです
またJavaScriptを使って状態を管理するのはReact.jsやVue.jsに影響を受けています
HTTPの世界でGETやPUTとPOSTが明確に分けられていたり、データベースの世界ではCRUDのような概念があるように、インフラの世界でも「状態」を扱うのに適したフレームワークがあっても良いかなと思って開発してみました