【CI/CD的な】GitHub Actionsを使ってビルドからrsyncでデプロイまでを自動化する

はじめに

冷静な自分が「本当にこんなことやっている場合か...?」と自身に語りかけるが、費やした時間は返ってこない。本記事を執筆することで今日一日の代償を払うことにした(勉強しろ:angry:)。

TL;DR

GitHub Actions を使って、ビルド&デプロイ を自動化したよ

GitHub Actions とは

GitHub 上のさまざまな操作 (push だけでなく PR とか issue とかを対象としたモノもある) をトリガーに、YML で記述しておいた処理を実行できる機能のこと。昨年 2019 年末に一般ユーザにも解禁されたため、手軽に CI/CD っぽい操作ができるようになった。

先日 HTML と JS だけのシンプルな Web ページを一発で公開できる Webpack & ディレクトリテンプレートを作ってみた1 趣味プに時間費やしすぎ 。この時は主に Netlify と連携することにより自動デプロイすることを目論んでいたが、折角研究室から貸与されているサーバで Apache も構築してあるので、ここにデプロイしよう!と思い立った。

本記事では、この GitHub Actions(以下 GA)を利用して、develop ブランチが更新される度に自動ビルド & デプロイが行われるよう設定した。

用意するもの

  • GA が使えるアカウント
  • 適当なレポジトリ
    • 自環境では、先述のテンプレートから派生させたレポジトリを用いた。このプロジェクトは index.html や 諸 CSS ファイルは全て public/ 下にあり、yarn build を実行すると src/ 下のビルドファイルである public/dist/app.js が生成されることを念頭に置いて頂きたい。
  • 自前サーバ。

手順その 1. Web サーバの準備

以下、{{}} で表現される内容はメモ書きであり、各自環境に合わせて記述して頂きたい。無論 {{}} を書く必要はない(YML 内の ${{}} は例外である)。

1.1. Apache, sshd, rsync の設定

がんばる。

1.2. 鍵の生成

GA から自前サーバへ SSH 接続を行うために鍵を生成する。暗号強度は個人差があります。

$ ssh-keygen -t rsa -b 4096 -f {{ YOUR_KEY_NAME }}

公開鍵は、自前サーバの ~/.ssh/authorized_keys に追記しておけばよい(多分)。秘密鍵丁寧に GitHub の SECRETS へ格納する(画像参照)。名前は何でも良い(ここでは SSH_SECRET_KEY とする)。

f:id:puyobyee18:20200718004606p:plain
シークレットキーの場所

手順その 2. GitHub Actions の準備

ここでは、以下を目標とする。

  • develop (=リポジトリのデフォルトブランチ) への push によって発火する。
  • package.json に記述した yarn build コマンドを実行し、ビルド。
  • rsync によって public/ 下のファイルをデプロイ先指定のディレクトリに同期する。

YML の記述

リポジトリActions タブへ移動すると、"Get started with GitHub Actions" というページが登場する。AWS へのデプロイ等、巨人の肩の上に立つことができる場合はワークフローを Fork すればよいが、ここでは "Skip this and set up a workflow yourself" を選択し、ワークフローを自作する。

GA のワークフローは YML ファイルで記述する。長々しい説明を書くのは疲れたため、最終的なコードは以下を参照されたい。

# deploy.yml

# ワークフローの名前
name: { { YOUR_WORKFOW } }

# トリガーの指定。言うまでもないが、以下は「 develop branch に push した時」となる。
on:
  push:
    branches:
      - develop

# 実行する処理を記述する。
jobs:
  deploy:
    # 走らせる仮想環境を決定する。
    runs-on: ubuntu-latest
    steps:
      # develop ブランチを参照する。後ほど詳述。
      - uses: actions/checkout@v2
        with:
          ref: develop
      # `uses` コマンドに対しては名前を付けることも可能。
      - name: Node setup
        uses: actions/setup-node@v1
      # ビルドコマンドを走らせる。installを忘れないこと。
      - run: yarn install && yarn build
      # 秘密鍵に対する設定。やや回りくどいのは permission 変更のためだと考えている
      - name: ssh key generate
        run: echo "$SSH_SECRET_KEY" > key && chmod 600 key
        env:
          # ここの `secrets` が GitHub 内での API である模様。直後のプロパティ名は手順1.2. で保存したものと同名であるように注意。
          SSH_SECRET_KEY: ${{ secrets.SSH_SECRET_KEY }}
      # 同期のための rsync コマンド。後ほど詳述
      - name: rsync deploy
        run: rsync --checksum            \
          -av                            \
          --delete                       \
          --exclude '{{ YOUR_PATTERN }}' \
          -e "ssh -p {{ PORT }} -i key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" \
          ./public/*                     \
          YOUR_USERNAME@YOUR_SERVER_NAME:/path/to/apache_root/

2.1.1. actions/checkout@v2 について

YML ファイル内、uses コマンドは他者が公開しているコードを利用する機能である。

ここでは GitHub 公式が公開している checkout ライブラリを用いる。このライブラリは、名の通り git のチェックアウトを行う。with: ref: BRANCH_NAME を引数に与えることで指定されたブランチへと移動し、そのクローンファイルに対して期待の処理を実行できる。他にも当該リポジトリだけでなく、他リポジトリを交えた処理も可能なので、README.md を参照されたい。

なお、ref 引数を与えない場合、デフォルトブランチで操作が行われるため、develop 等に変更している場合は注意が必要である。

2.1.2. rsync について

rsync とは、UNIX システムにおいて差分符号化を行って(比較的)高速にデータ転送を行うソフトウェアである。個人的に scp が一般的であるイメージを持っているが、OpenSSH がそのリリースノートで「scp は非推奨」との声明を出している(ソースは各自調べて)。

rsync は差分転送が可能であり、Git のバージョン管理とも相性がよいことから今回のデプロイで採用した。

先述の rsync コマンドの内容を再掲する。

$ rsync --checksum                     \
        -av                            \
        --delete                       \
        --exclude '{{ YOUR_PATTERN }}' \
        -e "ssh -p {{ PORT }} -i key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"   \
        ./public/*                     \
        YOUR_USERNAME@YOUR_SERVER_NAME:/path/to/apache_root/

以下、注意すべきポイントを説明する。

  • dry-run オプション
    • 同期であるが故の悲劇も起きうる2。まずはこれを付けて確認すべし。
  • checksum オプション
    • デフォルトでは「タイムスタンプ+ファイルサイズ」をハッシュ化し、差分の有無を確認している。しかし GA 上では、ファイルのクローンが作成される(要検証)ため、タイムスタンプが都度更新され、中身に更新がない場合でも転送されてしまう。checksum 判定に変えることでこれを回避し、ファイルの中身のみで判定するようにする。
    • ファイル数が多い場合、checksum の計算に CPU リソースを消費してしまうが、GA なのでヨシ!
  • a オプション
    • バックアップ用途の場合、とりあえずこれを付けておけば ok
  • v オプション
    • verbose は義務
  • delete オプション
    • 「削除」を反映させる。
    • 取り扱いには要注意。
  • exclude オプション
    • パターンに一致するディレクトリ・ファイルを rsync の対象から外す。
    • 故に上記の delete からも対象が外れるため、同期先で残しておきたいファイルがある場合はこれを指定しておく。
    • なお delete の対象とさせる --delete-exclude オプションも存在するらしい。
  • e オプション
    • リモートシェル(=通常 ssh)を指定する。
    • rsync daemon を同期先に常駐させておくことが理想的(?)であるが、秘密鍵暗号方式で行うやり方が分からなかったので over ssh とした。
    • ポート番号や鍵の指定方法はコードの通り。
    • -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no とあるが、現在時点では必須である。通常、新しいホストからSSH接続する場合fingerprintを ~/.ssh/known_hosts 等に記録するステップが踏まれるが、GAでは対話的実行ができないためこれを省略する。MITM攻撃に脆弱となってしまうため、解決策があれば積極的に改善したい。
  • [SOURCE][dist]
    • 最後に「同期元ファイル」「同期先ファイル」を指定して転送開始。
    • 同期元ファイルに関して、末尾に / を付けるか否かで大きく挙動が変わるので注意されたい。私は明示的に * ワイルドカードを使うことを推奨する。

以上。

f:id:puyobyee18:20200718005004p:plain
GitHub Actions 実行

疲れたけど一日で終わらせたので、まぁ良しとする。

あとがき

最近魚がポケモンを操作する時代になったんですよ。

しかも何千もの人がそれを見守っているらしい。

自分はBGMとして聴いていますが、とても複雑な感情になれるのでオススメです。

ちなみに自分の推しは ア!!エッ 。

(追記) 推し解雇されてて草

(追々記) 名前が変わっていたらしい。おもしろ。