Help us understand the problem. What is going on with this article?

[Raspberry Piメモ] Node.jsでCIっぽいことをする(GitHubにPushしたらRaspberry PiがPullして実行)

Raspberry Pi上でTypescriptで書いたプログラムを動かしたいが、開発はPCでやりたい場合は、GitHubからのWebHookをトリガとしてJenkinsからビルド・実行する方法がメジャー(?)だと思う。ただ、JenkinsをRaspberry Pi上で動かすのは敷居が高く感じるし、まだ試行錯誤している段階で、WebHookを受け取るためにポートを解放するのも怖く感じる。

Jenkinsを導入していなくても、ちょっとしたことでCIっぽいことができるので、以下に方法をまとめる。複雑なことをやろうとしたらJenkinsの方が良いかもしれないが、取り敢えず動かしたいだけならそこそこ使えそうだった。

ここではNestJSのプログラムで試しているが特にプラットフォームやライブラリは問わない。

概要

Raspberry Piは非力なので、TypescriptのトランスパイルまでをPCで行う。手間を省くため、[1]ソースの編集があるたびに[2]変更を検出して自動的にトランスパイルを行う。(Eclipseであればエラーをすぐ見つけてくれるので、変更の度にビルドする必要はないが、たまにビルドしないと分からない変なエラーがあるので用意しておく)

PC上での編集が終わりRaspberry Pi上で動作確認する段階になったら、[3][4]ソースとトランスパイル結果をGitHubにpushする。[5]PCからのPushが完了したら、TortoiseGitのPost-push hookによりRaspberry Piへpush完了を通知する。

[7]Raspberry Pi上の「通知受付」が[6]push完了の通知を受け取ったらgit pullし、ソースとトランスパイル結果を更新する。[8]Raspberry Pi上のnodemonがトランスパイル結果の変更を検出したら、nodeを再起動し更新されたプログラムを開始する。

なお、Raspberry Pi上の「通知受付」はJenkinsでも実現できるものの、ここでは簡易的にHTTPリクエストを受け付けたらgit pullするだけにする。簡単ではあるが、ローカルでWebHookに相当することができる。

ローカルならDDoS的なことは気にしなくてよいが、Pullの間隔を制限するなど工夫した方がベターだとは思う。

準備: Node.jsのインストール

まずはNode.jsをインストールすることから始める。Raspberry Pi3 B+では「Node.js and Raspberry Pi」のコマンドを実行するだけでインストールできる。

v12系の最新版をインストールする場合
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -

しかし、ずいぶん前に購入したRaspberry Pi B+はCPUがARMv6のため、以下のようなメッセージが出てインストールできなかった。

ARMv6で起きるエラー
You appear to be running on ARMv6 hardware.
Unfortunately this is not currently supported by the NodeSource Linux distributions.
Please use the 'linux-armv6l' binary tarballs available directly from nodejs.org for Node.js 4 and later.

その場合は、対象を直接wgetして解凍、インストールしたい場所にコピーすることになる。ARMv6向けのバイナリは現時点のLTSである10.16.0と、サポートが無い11.xしかない。

ARMv6へのインストール
wget https://nodejs.org/dist/latest-v10.x/node-v10.16.0-linux-armv6l.tar.gz
tar -zxvf node-v10.16.0-linux-armv6l.tar.gz
cd node-v10.16.0-linux-armv6l.tar.gz
sudo cp -R * /usr/local/

設定

人が行う操作[1][3]以外が行えるよう、設定・ちょっとしたプログラムの作成を行う。

package.jsonの設定

[2]PC上の変更検出・自動トランスパイルのために、build:watchコマンドを作る。このコマンドでは-w(watch)オプションを付けてtscを実行することにより、tsconfig.build.jsonで指定したソースディレクトリ内の変更を検出し、自動的にトランスパイルを開始する。

また、[8]Raspberry Pi上の変更検出・node再起動のために、start:raspiコマンドを作る。このコマンドでは、nodemonによりトランスパイル結果distの中身が変更されたらnodeを再起動する。

package.json
{
  "scripts": {
    "build:watch": "tsc -w -p tsconfig.build.json",
    "start:raspi": "nodemon --config nodemon-raspi.json",
  },
}

tsconfig.build.jsonの変更

tsconfig.build.json(NestJSが作成したファイル)でdistを変更の検出対象から除外することで、build:watchでトランスパイル→トランスパイル結果distが変わる→変更を検出→トランスパイル…にならないようにする。(実際は無限ループは起きないが、エラーになる)。これにはtsconfig.build.json(ベースとなるNestJSが生成したファイルの場合)に対して、excludeにトランスパイル結果distを追加すればよい。

tsconfig.build.json変更(distを除外)
{
  "extends": "./tsconfig.json",
  "exclude": ["node_modules", "test", "**/*spec.ts", "dist" ]
}

nodemon-raspi.jsonの作成

トランスパイル結果dist内の.jsファイルが変更されたらnodeを再起動するよう、nodemonの設定ファイルを以下のように作成する。

nodemon-raspi.json
{
  "watch": ["dist"],
  "ext": "js",
  "exec": "node dist/main.js"
}

通知受付サーバの作成

Raspbery Pi上に、[5]PCからPush完了の通知を受け付け、[6]git pullするための小規模なサーバを設ける。ここでは、Listenしているポートで何らかのリクエストを受信したらgit pullするだけの簡易的な機能にとどめている。

trigger/index.js
const http = require( 'http' );
const spawn = require('child_process').spawn;

// git pullする。spawn( 'git pull' )にするとエラーになるので注意。
function executePull() {
    const prc = spawn( 'git', [ 'pull' ] );

    return new Promise( ( resolve, reject ) => {
        prc.once( 'exit', code => { 
            resolve( code );
        } );
        prc.once( 'error', err => { 
            reject( err )
        } );
    } );
}

// 通知受付サーバを作る
const app = http.createServer( ( req, res ) => {
    let message = '';

    executePull().then( ( code ) => {
        // 成功した場合
        res.writeHead( 200, { 'Content-Type': 'text/plain' } );
        message = `Pull succeeded. (code:${code})`;
    } ).catch( ( err ) => {
        // 失敗した場合
        res.writeHead( 405, { 'Content-Type': 'text/plain' } );
        message = `Pull failed. (code:${err})`;
    } ).finally( () => {
        console.log( message );
        // 応答を返す
        res.write( message );
        res.end();
    } );
} );

// 通知受付サーバを起動する。ポートは適当。
app.listen( 4000 );

Post-push Hookの追加

TortoiseGitの設定画面の「Hook Scripts」で、[5]Push完了をRaspberry Piに通知するためのスクリプトを追加する。フックはTortoiseGit全体に適用されてしまう(らしい)ので、「Run when working tree path is under:」で適用対象を絞るとよい。また、通知を行うコマンドはHTTPリクエストさえRaspberry Piに届けばよいので、curl <RaspberyPiのIP:簡易トリガのPort>のように設定する。

image.png

起動

予め上記設定をしたプログラムをGitHubにpush済みの状態から、pushをトリガに変更が自動的に反映できるようにする。

ソースの入手/更新とプログラムの起動

Raspberry Pi上に何もない状態から開始する場合、git cloneでGitHubにpush済みのプログラムを入手する。既にclone済みであれば、git pullにより更新する。

# コードをGitHubから持ってくる。初回はclone、2回目以降はpullで良い。
git clone <repository> <clone先>
cd <clone先>

# パッケージをインストールする(これは実行するところでしかできない)
npm install

# package.json で追加した Raspberry Pi用起動コマンド。
# distの変更を検出したらnodeを再起動する。
npm run start:raspi

# Raspberry Piにトランスパイルからやらせたければ、NestJSで元々存在する以下のコマンドで起動する。
# Rapsberry Pi B+ではNestJSのテンプレ(Hello Worldレベル)でさえ3~4分かかるので、かなり遅い。
# npm run start:dev

通知受付サーバの起動

通知受付サーバは以下のようなコマンドで起動する。(「Linuxコマンド(Bash)でバックグラウンド実行する方法のまとめメモ」参照)

通知サーバのディレクトリでのコマンド例
# フォアグラウンドで実行…画面上にpullできたか表示されるので安心
node index.js

# バックグラウンドで実行
node index.js &

# SSH切断後もバックグラウンドで実行
nohup node index.js &

まとめ

動作しているところの動画は用意していないが、以上のやり方でPCで行った変更をGitHub経由で反映し、動作確認することができるようになった。リソースが潤沢なPCであればJenkinsという汎用的な手段が使えると思うが、上記の設定だけでも似たようなことが軽量に実行できるので、お手軽に使いたい場合に役に立つかと思う。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away