LoginSignup
6
6

More than 5 years have passed since last update.

GitLab CI から Google Cloud Pub/Sub 経由で自動デプロイ

Posted at

自分用のちょっとした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 よりクロスポスト

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6