自分用のちょっとした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
{
"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オブジェクトを取得して、通知を行うというシンプルなものです。
'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番目の引数が購読名になるため、ホスト名を使って複数のエージェントが存在する状態でもそれぞれに通知されるようにします。同じ名前にしてしまうと同一のサブスクライバーとみなされて、メッセージがいずれかのエージェントにしか飛びません。
'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
#!/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
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's note よりクロスポスト