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




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

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

基本的なデザインはAnsibleとの比較で以前[こちら](https://qiita.com/mjusui/items/bce4566ffdb54ba7039a)に記載させていただきました

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

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

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

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

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/osc19tk?src=hash&amp;ref_src=twsrc%5Etfw">#osc19tk</a> 本日も展示中<br><br>ERROR画面のCSSにこだわったのであえて左端に表示 <a href="https://t.co/i9SeNf5KTa">pic.twitter.com/i9SeNf5KTa</a></p>&mdash; 入水岬@インフラエンジニア (@Mjusui) <a href="https://twitter.com/Mjusui/status/1198446707647766530?ref_src=twsrc%5Etfw">November 24, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

上のTweetは[オープンソースカンファレンス](https://www.ospn.jp/osc2019-fall/)のブースにて展示していた画面のキャプチャです。左からサーバへのコマンド実行が失敗したときの画面、成功したときの画面、オフィシャルページになります。真ん中の画面では、サーバから取得した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を使ってみたいという方へ

オフィシャルページは[こちら](https://www.submarinejs.blue)になります
全機能の使い方のTutrialは[こちら](https://gitlab.com/mjusui/submarine/tree/latest/doc/en/Tutrial)になります