【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
とする)。
手順その 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 オプション
- e オプション
- リモートシェル(=通常 ssh)を指定する。
- rsync daemon を同期先に常駐させておくことが理想的(?)であるが、秘密鍵暗号方式で行うやり方が分からなかったので over ssh とした。
- ポート番号や鍵の指定方法はコードの通り。
-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
とあるが、現在時点では必須である。通常、新しいホストからSSH接続する場合fingerprintを~/.ssh/known_hosts
等に記録するステップが踏まれるが、GAでは対話的実行ができないためこれを省略する。MITM攻撃に脆弱となってしまうため、解決策があれば積極的に改善したい。
- [SOURCE][dist]
- 最後に「同期元ファイル」「同期先ファイル」を指定して転送開始。
- 同期元ファイルに関して、末尾に
/
を付けるか否かで大きく挙動が変わるので注意されたい。私は明示的に*
ワイルドカードを使うことを推奨する。
以上。
疲れたけど一日で終わらせたので、まぁ良しとする。
あとがき
最近魚がポケモンを操作する時代になったんですよ。
しかも何千もの人がそれを見守っているらしい。
自分はBGMとして聴いていますが、とても複雑な感情になれるのでオススメです。
ちなみに自分の推しは ア!!エッ 。
(追記) 推し解雇されてて草
(追々記) 名前が変わっていたらしい。おもしろ。