JavaScriptとShellScriptで、プログラマブルなインフラを実現する


インフラエンジニア歴6年、js歴6年(業務での利用経験なし)の私が「JavaScriptでインフラの構成管理、テスト、運用自動化を実現する」というビジョンをかかげ Submarine.jsというフレームワークの開発に着手して1年が経ちました

まだまだ普及はしていませんが、少しずつ宣伝活動をしつつアップデートも重ね、v2.0.0のリリースに至りました

基本的なデザインはAnsibleとの比較で以前こちらに記載させていただきました

今回は、より高機能になったSubmarine.jsで、どんなことができるようになったのかを紹介していきます

サーバの状態と、そのテスト結果をブラウザに表示

Submarine.jsの特徴として、サーバの状態を取得するShellScript、取得した値をテストする関数( 状態の参照 のみの処理)、とサーバに 変更 を加えるShellScriptを、JavaScript上で明確に分離するというものがあります(詳細はこちらの記事)

参照と変更を分離しているからこそ 「参照」 部分に関してはJSON形式でHTTPレスポンスとして返すこともできるし、HTMLに値を埋め込んでブラウザ表示させることもできます

上のTweetはオープンソースカンファレンスのブースにて展示していた画面のキャプチャです。左からサーバへのコマンド実行が失敗したときの画面、成功したときの画面、オフィシャルページになります。真ん中の画面では、サーバから取得したShellScriptの実行結果と、それらをテストした結果、さらに実行したShellScriptの中身が見られます

  • この機能を使えば、何か障害が起きたときに 「よく分からないけど、ひとまずここを見て、テストが失敗していないか確認する」 ということができます
  • ShellScriptと、その実行結果をHTTPで公開できるので インフラエンジニアのポートフォリオ としても使えるかもしれません

あるサーバの状態と、別のサーバの状態を複合的にテストする

例えば、サーバが3台あり 「それらのディスクサイズの合計が一定値以上であること」 をテストしたいとき、1台のサーバから取得できる状態だけでは、テストはできません

サーバ全台分のディスクサイズを取得して、それらを合計した値に対して、テストをしなければなりません

こういったケースに対応するためSubmarine.js v2.xでは collaborate という機能が追加されています

サンプルコードを見てみましょう

(1) サーバ1台からディスクサイズを取得するクラスです

Collaborate-with-other-servers.js
const Submarine=require('Submarine');


const GetVolSize=class extends Submarine {
  query(){
    return {
      volkb_sizes: String.raw`
        df -P \
          |grep -v "^Filesystem" \
          |awk '{print $3+$4}'
      `,
    };
  }
  format(stats){
    return {
      volkb_largest: stats.volkb_sizes
        .split(/\r\n|\r|\n/)
        .sort(
          (a, b) => b*1 - a*1
        )[0], // largest volume is chosen.
    };
  }

  test(r){
    const { stats }=r;

    return {
      size_enough:
        2 * 1024 * 1024 < stats.volkb_largest,
    };
  }
}

(2) (1)のクラスを複数サーバで実行できるようクラス化

Collaborate-with-other-servers.js
const GetVolSizes=Submarine.collect(
  host => new GetVolSize({
    conn: 'ssh',
    host: host,
  }),

  { type: 'gen',
    coll: 'bash',
    cmd: 'echo server{1..5}', },
  { type: 'fil',
    coll: 'func',
    func: hosts => hosts.filter(
      host => host.match(/[2-4]$/)
    ), },
  { type: 'fil',
    coll: 'ping', }
);

(3) サーバ全台のディスクサイズの合計値をテストするクラス

test 関数内で collabs という値が利用できます

この collabs の中には、次の(4)の collaborate 関数に渡された、サーバ3台分のインスタンスから取得した状態が含まれています

Collaborate-with-other-servers.js
const TestTotalVolSize=class extends Submarine {
  test(r){
    const { collabs }=r;

    return {
      each_size_enough:
        collabs.every(
          collab => collab.exams.ok
        ),
      total_size_enough:
        4 * 1024 *1024 < collabs.reduce((siz, collab)=>{
          return siz + collab.stats.volkb_largest;
        }, 0), // larger than 4MB
    };
  }
}

(4) (3)をインスタンス化し collaborate 関数に(2)のインスタンスを渡す

Collaborate-with-other-servers.js
const enough=new TestTotalVolSize({
  conn: 'sh',
}).collaborate(
  new GetVolSizes()
);



enough.check()
  .then(
    r => JSON.stringify(r)
  ).then(console.log)
  .catch(console.error)
.finally(
  _ => enough.close()
);

こうすることで test 関数の中で、他のサーバ群の test 結果が使えるようになり、より複合的なテストができるようになります

Submarine.jsを使ってみたいという方へ

オフィシャルページはこちらになります
全機能の使い方のTutrialはこちらになります