自分用のちょっとした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 メッセージを経由してサブスクライバーがデプロイスクリプトを実行します。