LoginSignup
2
4

More than 3 years have passed since last update.

Node.jsでインフラのテスト、構成管理、オペレーション自動化

Posted at

とあるプロジェクトで昨年から約1年半、Ansibleでサーバ構築をしていて書きづらいなーと思いながらDocker/Kubenetesとか使いたいけど、それに合わせたシステムの改修をが間に合うわけもなく……

という鬱々とした気持ちを抱えておりました。が、不平不満を言うだけでははじまらないと思い立って、自分の好きなように書けるツールを作成してみました

Submarine - https://gitlab.com/mjusui/submarine

私がAnsibleを書きづらいと感じたのは、別にAnsibleが悪いわけではなく、単にプログラミングパラダイムの問題です
結論から言うと、タイトルどおりNode.jsで書きたかったんです

紹介の前にインフラエンジニア以外の人向けにAnsibleがどういうものなのか、簡単に説明しておきます

Ansibleとは

サーバ構築、構成管理や自動化のためのツール。特徴としては以下のようなものがあります

  • コードは全部YAMLで表現される
  • オブジェクト指向関数の概念はなく、条件分岐とループのみで処理が進んでいく(ただしコードをinclude/importできるので多少の再利用性はある)
  • 変数は基本すべてグローバルアクセス可能で、再代入も自由(一部スコープを制限する機能はある)
  • サーバ構築でよく使う機能(ファイルコピーなど)はmoduleという単位でAnsibleやサードパーティが用意してくれたものを使う
  • moduleを自前で作成することもできる
  • Ansible自体はPythonで開発されており、Pythonの機能や、パッケージを使ってmoduleも開発されている
  • サーバにはsshさえできればよく、専用のエージェントをインストールする必要はない

プログラミング経験があまりないインフラエンジニアにも分かりやすいよう、シンプルに設計されているという印象です

Submarineとは

Node.jsで開発するインフラ管理のFrameworkです。以下のような特徴があります

  • Ansibleは構成管理がメインのツールですが、Submairneはサーバのテストもできます
    • テストの結果に応じて、コマンドを実行する/しないが決定されます
  • Classと継承を使って、コードの再利用性を高めています
  • Node.jsの機能を使えば、変数の再代入を制限したり、スコープを限定することもできます
  • サーバの状態を取得する関数、取得した状態をテストする関数、サーバに変更を加える関数を分離することで、安全で読みやすい(条件分岐の少ない)実装ができます
  • HTTPサーバを起動して、上記で実装した関数をエンドポイントとして公開する機能があります

今回はSubmarineの基本的な機能をサンプルコードをまじえて、紹介していこうと思います

サーバの状態を取得する

まずはサーバから情報を取得します

Node.js
const Submarine = require('Submarine');

const Tutrial = class extends Submarine {
  query(){
    return {
      hostname: 'hostname -s',

      ipv4_addrs: String.raw`
        ip -o -f inet a \
        |awk '{print $4}'
      `
    };
  }
}


const tut=new Tutrial({
  conn: 'bash'
});


tut.current().then((stats)=>{
  console.log(stats);
});

Node.jsが読める人は感覚的にわかるかもしれませんが Submarine というクラスを継承して Tutrial というオリジナルのクラスを定義しています。そして、そのクラスを new でインスタンス化して tut.current() というところで Tutrial クラスに定義した query 関数が実行されます

クラスをインスタンス化するときに { conn: 'bash' } という引数を与えることで Tutrial クラスに定義した query 関数の中のコマンド(hostname -s など)が localhostbash で実行される、ということを指定しています

クラスをインスタンス化するときに例えば { conn: 'ssh', host: <ip address> } といった具合に指定すれば、指定したIPアドレスにsshして、コマンドが実行されます

サーバの状態をテストする

今度は取得した情報をテストします

Node.js
const Submarine = require('Submarine');

const Tutrial = class extends Submarine {
  query(){
    return {
      nonexecutable: String.raw`
        which none \
          2> /dev/null \
          || exit 0
      `,

      executable: String.raw`
        which node \
          2> /dev/null \
          || exit 0
      `,
    };
  }

  test(stats){
    return {
      none_is_not_executable: stats.nonexecutable === '',
      node_is_executable: stats.executable,
    };
  }

}


const tut=new Tutrial({
  conn: 'ssh',
  host: '127.0.0.1'
});


tut.check().then((done)=>{
  console.log(done);
});

query の部分で none というコマンドと node というコマンドのパスをwhichコマンドで引くことができるか確認しています。none というコマンドは、普通は存在しませんから test の中でパスが空 '' であることを検証しています。一方 node というコマンドは、このNode.jsのコードが実行できている以上、どこかに存在しますからwhichコマンドの結果に何らかのパスが含まれていることでしょう。test 関数の戻り値に含まれている2つの値(none_is_not_executablenode_is_executable)は tut.check() という部分で評価され論理積(AND)でTrue/Falseが決定されます。結果は done 変数に格納されています

サーバに変更を加える

サーバの状態を取得して、それをテストしました。テストの結果が問題なければ、何もする必要はありませんが、テストで異常が発見された場合は、それを修正しなければなりません。テストがFalseになった場合にだけ実行されるコマンドを以下のように定義します

Node.js
const Submarine = require('Submarine');

const Tutrial = class extends Submarine {
  query(){
    return {
      file_content: String.raw`
        [ -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(props){
    return String.raw`
      mkdir -p /tmp/submarine \
      && echo ${props.msg} > /tmp/submarine/hogehoge
    `;
  }

}


const tut=new Tutrial({
  conn: 'ssh',
  host: '127.0.0.1'
});


tut.correct({
  msg: 'fugafuga'
}).then((done)=>{
  console.log(done);
});

/tmp/submarine/hogehoge というファイルの内容が 'hogehoge' であるか確認し、そうでない場合はファイルが作成され、内容は 'hogehoge' で書き換えられます

done にはコマンドが実行された場合は、実行したコマンドのreturn codeやstdoutの情報が格納され、実行されなかった場合は test 関数のときの結果が格納されます

複数サーバーで実行する

上記の例は1台のサーバに対してコマンドを実行していましたが、複数台のサーバに実行することも可能です

Node.js
const Submarine = require('Submarine');

const Tutrial = class extends Submarine {
  query(files){
    return {
      availables: String.raw`
        df -P \
        |awk '{print $4}' \
        |grep "^[0-9]*$"
      `
    };

  }

  format(stats){
    return {
      available_max: Array.isArray(stats.availables)
        ? stats.availables.map(
            available => available * 1
          ).sort((a ,b)=>{
            return a < b
              ? -1
              : a == b
                ? 0
                : 1;
          }).reverse()[0]
        : stats.availables * 1
    };
  }

}

const Tutrials = Submarine.hosts(
  host => new Tutrial({
    conn: 'ssh',
    host: host
  }),

  server1,
  server2,
  server3,
  server4,
  server5
);

const tut = new Tutrials();


tut.current().then(
  hosts => hosts.map(
    host => host.available_max
  ).reduce((a, b)=>{
    return a + b;
  }) / 1024 / 1024
).then((available_sum)=>{
  console.log(available_sum);
});

server1 から server5 がそれぞれ new Tutrial... でインスタンス化され tut.current() で全台に対して query 関数が評価されます。結果はホスト1台のときと同じフォーマットの結果が、配列で返されます(コードの hosts に格納されている)

このサンプルコードでは、5台のサーバのディスク空き容量を足し合わせています

おわりに

他にも、Ansibleの基本的な機能は置き換えられるようか機能が実装されております

各機能のTutrialを鋭意作成中です

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4