非公開ではありますがここ最近ブログをよく書いていて、GitHub上の変更をすぐに本番環境へ反映できたらなあということを思ったので、CircleCIのWorkflowから自動でデプロイできるようにしてみました。

この記事ではその際に行った手順や用いた設定をまとめて紹介します。

 

実現すること・前提

GitHubのmasterブランチに変更が加えられたらビルドが走り、エラーがなければ本番サーバーへ反映されるようにします。

アプリケーション(ブログ)はリモートサーバー上で稼働しており、PHPで書かれています。パッケージ管理のためにComposerや、フロントエンドではnpm、Sass、webpackを利用しています。本番サーバーはCentOS 7上でApacheが動いています。

CircleCIのWorkflowから、ビルド成功後にSSHでリモートサーバーへ接続し、デプロイ用シェルスクリプトを実行します。シェルスクリプトではソースコードの取得、ビルド、公開ディレクトリへの配置を行います。

 

CircleCI Workflowについて

GitHubと手軽に連携できてプライベートリポジトリにも無料で対応できるので、CircleCIを重宝しています。

CircleCIではビルドやテストといった各工程をJobと呼び、Workflowによってそれらを関連づけます。2.0から登場したこれらを使うことで、各工程について実行順序の依存関係などを指定できるようになります。

Using Workflows to Schedule Jobs - CircleCI

 

CircleCI Workflowの設定

はじめに、CircleCIのWorkflowを設定します。ビルドに成功したらSSHでリモートサーバーへ接続し、シェルスクリプトを実行します。単純な処理なのでほぼ公式のドキュメント通りです。

Configuring Deploys - CircleCI

version: 2
jobs:
  build:
    #...
  deploy:
    machine:
      enabled: true
    steps:
      - run: ssh -p $SSH_PORT $SSH_USER@$SSH_HOST "~/deploy.sh"
workflows:
  version: 2
  build-and-deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: master

$SSH_PORTなどの環境変数は、CircleCIのWebサイトから簡単に追加可能です。

Using Environment Variables - CircleCI

requires:の記述によって、ビルド成功後にデプロイのJobが実行されるようになり、filters:以下の記述によって、GitHubのmasterブランチ以外ではデプロイのJobが実行されないようになります。

 

CircleCIからリモートサーバーへ接続するために、事前にSSH鍵の作成や登録が必要なので、こちらも公式のドキュメントを参考に行っておきます。

Adding an SSH Key to CircleCI - CircleCI

 

デプロイ用シェルスクリプトの作成

続いて、リモートサーバーに設置するデプロイ用シェルスクリプトdeploy.shを作成します。このスクリプトは特定のユーザーdeployerでの実行を想定しており、以下の内容を含んでいます。

  1. GitHubから最新のmasterブランチをクローン
  2. Composerパッケージのインストール
  3. npmパッケージのインストール
  4. アプリケーションのビルド(すでに用意されているnpm-scriptsを利用)
  5. ビルドされたアプリケーションのソースコードを公開ディレクトリへ配置
#! /bin/bash

set -eu

readonly WORKSPACE="/home/deployer/tmp"
readonly NPM_DIR="/home/deployer/.npm"
readonly DEST="/var/www/public"

if [ -d ${WORKSPACE} ]; then
    rm -rf ${WORKSPACE}
fi
mkdir ${WORKSPACE}

if [ -d ${NPM_DIR} ]; then
    rm -rf ${NPM_DIR}
fi
mkdir ${NPM_DIR}

cd ${WORKSPACE}

echo "Cloning git repository..."

GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" git clone --depth 1 git@github.com:g737a6b/example.git .

echo "Building..."

docker run --rm -u `id -u`:`id -g` -v ${WORKSPACE}:/app composer:1.6 install
docker run --rm -u `id -u`:`id -g` -v ${WORKSPACE}:/app -v ${NPM_DIR}:/.npm -w /app node:9 npm install
docker run --rm -u `id -u`:`id -g` -v ${WORKSPACE}:/app -v ${NPM_DIR}:/.npm -w /app node:9 npm run build

echo "Reflecting..."

chown -R deployer:apache ${WORKSPACE}
rsync -rlOtgov ${WORKSPACE}/ ${DEST} --exclude='.git' --exclude='node_modules' --delete

echo "Done!"

ビルド用にComposerやNode.jsの環境を用意するのは管理が大変になりそうなので、Dockerコンテナを利用してビルドを行っています。この時-uオプション無しのdocker runでは生成されるファイルの所有者がrootになってしまうので、これを変更するには上記のように実行ユーザーを指定する必要があります。

 

リモートサーバーの設定

自動デプロイ環境を構築するための主要な部分は、以上の内容で完了です。残りは参考のために関連部分のAnsibleレシピを載せておきます。

変わったことはしておらず、ポイントはユーザー権限でdockerを実行するために、dockerグループを作成してユーザーを所属させている点くらいです。

- file:
    path: /var/www/public
    owner: apache
    group: apache
    state: directory
    mode: 0775
- file:
    src: /var/www/public
    dest: /var/www/html
    state: link
- name: Install docker and git
  yum:
    name: "{{item}}"
    state: present
  with_items:
    - docker
    - git
- name: Add the group "docker"
  group:
    name: docker
- name: Add the user "deployer"
  user:
    name: deployer
    groups: docker,apache
    generate_ssh_key: yes
    ssh_key_bits: 4096
    ssh_key_file: .ssh/id_rsa
- name: Start docker
  service:
    name: docker
    state: restarted
    enabled: yes
- name: Copy deploy.sh
  copy:
    src: ../files/deploy.sh
    dest: /home/deployer/deploy.sh
    owner: deployer
    group: deployer
    mode: 0744

 

おわり

これまでリリースの度にリモートサーバーへ接続して一連のコマンドを叩いていましたが、今回の取り組みによってGitHubを更新したら自動反映されるのを待つだけで良くなりました。

浮いた時間はもっと価値のある仕事に回せます。単純作業はどんどん自動化していきたいです。