ほんじゃらねっと

ダイエット中プログラマのブログ

node.jsでリモートのLinux環境やデータベースの操作を自動化する

ぼくが仕事でLinuxサーバ環境に接続して行う操作というのはだいたい決まっていて、

  • コマンドでサーバの状態を確認
  • ログをファイル出力してダウンロード
  • サービスの設定変更と再起動
  • SSHトンネル経由でDBアクセス

のうちどれかを行うことが多い。

基本は手作業だったり、シェルスクリプトを作って

実行したりする形で対応できるものなのだけど、

結構めんどくさいので、

今回はnode.jsでどこまで自動化できるかを試してみたい。

事前準備

ローカルのWindowsマシンでnode.jsプログラムを実行して、

プログラム経由でLinux環境に接続して色々操作することを

想定しているので、ローカルにnode.js環境をインストールしておく。

この記事に載せてるサンプルは

Typescriptで書いているけど、だいたいJavascriptとしても動くと思う。

SSHでLinux環境に接続する

まずはSSHで接続してコマンドを実行できる状態にしてみよう。

SSHライブラリは「node-ssh」を使う。

npm install --save node-ssh

github.com

Promise形式で実行できるのでasync/awaitですっきり書ける。

下記はSSH接続して、lsコマンド実行して、切断するサンプル。

const Ssh  = require('node-ssh');

async function main() {
    const ssh = new Ssh();

    const sshPassword = 'パスワード';
    
    // 接続
    await ssh.connect({
        host: 'SSHサーバアドレス',
        port: ポート番号,
        username: 'ユーザー名',
        password: sshPassword
    });

    // コマンド実行
    res = await ssh.execCommand('ls -al', {options: {pty: true}});

    // 切断
    ssh.dispose();
}

main();

optionsで指定してるオプションはなくても良いのだけど、

サービスの再起動なんかを行った時に切断後もサービスが起動したままに

したい時に指定が必要になるので、おまじない的に全部のコマンドにつけてる。

コマンドの実行結果は戻り値として受け取ることができるので、

ローカルでファイルに出力したりできる。リモートスクリプトの良いところ。

sudoで管理用コマンドを実行したい時は、

下記のようにexecCommandメソッドを呼び出す。

        const res2 = await ssh.execCommand('sudo ls /var/log/httpd', {stdin: sshPassword + '\n', options: {pty: true}});

sudoコマンドがパスワードを求めてきたらstdinで指定したパスワードが入力される。

node-sshについてはここまでのことを押さえておけば

大抵のコマンドを実行してその結果を受け取ることができる。

鍵認証でも接続もできるし、node-sshの機能では足りない場合は、

node-sshが参照しているssh2ライブラリを使えばもっと色々できる。

github.com

試してないけどSFTPでのファイル転送もできるみたい。

SSHトンネル経由でLinux上のDBにアクセス

続いて、リモートサーバ上で動作しているデータベースに接続してみよう。

SSHトンネルを使用する。

WindowならPuttyとかでSSHトンネル設定をしておいてから

pgadminとかでトンネルのローカルポートに接続して操作する、

みたいなことはよくやると思うが、これをnode.jsで実現するイメージ。

Puttyで作成したSSHトンネルを使ってnode.jsからDB接続しても良いし、

node.jsのSSHトンネリング用ライブラリを使って

node.jsだけでSSHトンネルを作ってDBに接続する、ということもできる。

せっかくなので今回はnode.jsで完結させてみよう。

SSHトンネル作成には、「tunnel-ssh」という、

上で紹介したssh2ライブラリをトンネリング用に拡張したライブラリを使う。

npm install --save tunnel-ssh

DB接続はknex + pgを使ってPostgreSQLデータベースに接続する。

npm install --save knex @types/knex pg @types/pg

SSHトンネルを作るテンプレは下記のような感じ

const tunnel = require('tunnel-ssh');

async function main() {
        const sshUserName = 'SSHユーザー名';
        const sshPassword = 'SSHパスワード';
    
        let sshConfig = {
            host: 'SSHサーバアドレス',
            port: SSHサーバポート番号,
            username: sshUserName,
            password: sshPassword,
            keepaliveInterval: 60000,
            keepAlive: true,
            dstHost: 'トンネリング先サーバアドレス',
            dstPort: トンネリング先サーバポート番号,
            localHost: 'localhost',
            localPort: ローカルポート番号
        };
        
        const tnl = tunnel(sshConfig, async (err: any, server: any) => {
            if (err) {
                throw err;
            }

            // ここにトンネリング中に実行したい処理を書く
    
            tnl.close();
        });
}

main();

上記のスクリプトは最後にトンネルを終了(tnl.close())してるので

処理完了後にプログラム終了する。

トンネルを終了しなければずっとトンネルを開いたまま待機状態になるので、

node.jsスクリプトでトンネルだけ開いておいて、pgadmin等他のアプリで

トンネルを利用する、ということもできるかも。

node.jsでトンネル経由でDBにアクセスする場合は下記のような

形で処理を追加する。

const tunnel = require('tunnel-ssh');
import knexLib = require('knex');

async function main() {
        const sshUserName = 'SSHユーザー名';
        const sshPassword = 'SSHパスワード';
    
        let sshConfig = {
            host: 'SSHサーバアドレス',
            port: SSHサーバポート番号,
            username: sshUserName,
            password: sshPassword,
            keepaliveInterval: 60000,
            keepAlive: true,
            dstHost: 'トンネリング先サーバアドレス',
            dstPort: トンネリング先ポート番号,
            localHost: 'localhost',
            localPort: 45432
        };
        
        const tnl = tunnel(sshConfig, async (err: any, server: any) => {
            if (err) {
                throw err;
            }

            let knex = knexLib({
                client: 'postgresql',
                connection: {
                    database: dbSetting.database,
                    port:     45432,
                    user:     dbSetting.user,
                    password: dbSetting.password
                }
            });
        
            const rows = await knex.select().from(tableName);
            console.log(rows);
       
            await knex.destroy();
    
            tnl.close();
        });
}

main();

トンネルのローカルポートとDB接続時のポート番号を合わせるだけ。

この方法を使えば、DBの実行結果をローカルに保存したり、

ローカルのExcelから読み込んだ内容をDBに登録したりできる。

おわり

今回はSSH経由でLinuxサーバを操作する方法を2つ紹介した。

応用すれば複数のLinux環境やDBに対して

同じ処理を実行したりすることもできるので、

自動化の幅を広げる役に立つと思う。

JavaScriptの絵本 第2版 Webプログラミングを始める新しい9つの扉

JavaScriptの絵本 第2版 Webプログラミングを始める新しい9つの扉