自分用のちょっとしたWebサービスをさくら VPS環境に構築しています。こんなオレオレサービスでも Git リポジトリの master ブランチに push したら自動的にデプロイできるようにしたい。毎回 SSH して手動で更新はしたくない。
一般的な方法だと、どうしてもデプロイ先のサーバーにリクエストの受け先を用意する必要があります。概ね HTTP 経由で受けないといけないので、そこから派生するアクションの内容から鑑みても、セキュリティ的にグローバル空間に置かれたサーバでやりたくないものです。
そこで自前で Pub/Sub サービスを経由してやってみることにしました。Pub/Sub であれば懸念事項のアクセスを受け入れることなく、Subscriber も Pull 型で設置できます。
Google Cloud Console
まず Google Cloud Console - 認証情報 でサービスアカウントを作成しておきます。次に Google Cloud Console - Pub/Sub でトピックを作成します。そしてそのトピック対して用意したサービスアカウントにPub/Sub サブスクライバ―とPub/Sub パブリッシャーの権限を与えます。
自動デプロイの仕組みを作る
スクリプトは Node.js を使います。gcloud モジュールをグローバルで使えるようにしておきます。下記の package.json の定義とおり サブスクライバーとなる agent.js と通知をする publish.js をそれぞれ作ります。
package.json
1 2 3 4 5 6 7 8 9 10 11
| { "name": "myapp", "scripts": { "agent": "NODE_PATH=`npm root -g` node agent.js", "publish": "NODE_PATH=`npm root -g` node publish.js" }, "dependencies": { "gcloud": "^0.36.0" }, "private": true }
|
通知
GitLab CI から呼び出して Pub/Sub にデプロイメッセージを発行するスクリプトを作ります。
publish.js
gcloudオブジェクトからpubsubオブジェクトを取得して、トピック名からtopicオブジェクトを取得して、通知を行うというシンプルなものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 'use strict';
const gcloud = require('gcloud')({ keyFilename: 'サービスアカウントのキーJSONファイルパス', projectId: 'Google Cloud ConsoleのプロジェクトID' });
const pubsub = gcloud.pubsub(); const topic = pubsub.topic('トピックID');
topic.publish({ data: { command: 'deploy' } }, function(err) { if (err) { console.error(err); } else { console.info('Published deploy message!'); } });
|
デプロイエージェント
トピックのサブスクライバーとしてメッセージを受信したら、Gitリポジトリから最新の内容を反映してデーモンを再起動するエージェントを作ります。
agent.js
topicオブジェクトを取得して、subscribe(購読)します。このとき1番目の引数が購読名になるため、ホスト名を使って複数のエージェントが存在する状態でもそれぞれに通知されるようにします。同じ名前にしてしまうと同一のサブスクライバーとみなされて、メッセージがいずれかのエージェントにしか飛びません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| 'use strict';
const os = require('os'); const path = require('path'); const execFile = require('child_process').execFile;
const gcloud = require('gcloud')({ keyFilename: 'サービスアカウントのキーJSONファイルパス', projectId: 'Google Cloud ConsoleのプロジェクトID' });
function startDeploy() { const child = execFile('deploy.sh', (error, stdout, stderr) => { if (error) { throw error; } console.log(stderr); console.log(stdout); }); }
const pubsub = gcloud.pubsub(); const topic = pubsub.topic('トピックID'); const name = 'sub_' + os.hostname();
var opts = { autoAck: true, reuseExisting: true, interval: 60 };
topic.subscribe(name, opts, (err, subscription, apiResponse) => { if (err) { console.error(err); return; }
console.info('Subscribed');
subscription.on('message', (msg) => { const data = msg.data; const command = (data && data.command) || null;
if (command === 'deploy') { console.info('Received deploy message id:%s', msg.id);
try { startDeploy(); } catch (ex) { console.error(ex); } } else { console.warn('Received unexpected message:'); console.warn(msg); } }); });
|
注意事項として Pull 型サブスクライバーは接続の度に課金カウンタが上がるので、 interval はできるだけ長くした方が良いです。
メッセージが来たら下記のような deploy.sh スクリプトを用意してリポジトリを更新してサービスをリフレッシュします。
deploy.sh
1 2 3 4 5 6
| #!/bin/bash git fetch --all git checkout --force origin/master
npm run deploy # サービスリフレッシュ echo "Deployed"
|
GitLab CI からキック
下記の設定ファイルをリポジトリに設置するとデプロイステージで発行します。gcloudモジュールのインストールはそれなりの大きさで時間がかかるので、 [https://hub.docker.com/r/tilfin/gitlab-deployer/] で公開している Docker で Node.js と gcloud を入れてあります。これコンテナを deploy タグ付きで CI Runner 登録しておく良いでしょう。
.gitlab-ci.yml
1 2 3 4 5 6 7 8 9 10 11
| stages: - deploy
deploy_job: type: deploy script: - npm run publish tags: - deploy only: - master
|
動作確認
実行環境でも Node.js + gcloud をセットアップしておき npm run agent
してサブスクライバーを起動しておきます。
GitLab のリポジトリの master に push すると Pub/Sub メッセージを経由してサブスクライバーがデプロイスクリプトを実行します。
tilfin
freelance software engineer