前回のあらすじ
- GatsbyをFirebase Hosting にデプロイした
- 独自ドメインでアクセスできるようにした
この記事ですること
- Github Actionsを使ってみる
- Gatsbyのデプロイをデザイン変更時(GithubにPush時)に自動で行う
- Gatsbyのデプロイをブログ記事投稿/編集時にも自動で行う
Github Actions
https://github.co.jp/features/actions
Githubが提供しているCI/CDツール。
特に何もしなくてもリポジトリにタブが付いているので手軽に使える。
Pushやプルリクに合わせてビルドしたりデプロイしたりが比較的簡単にできる。
今回はCircleCIとか外部のツールを使う理由もなかったのと、一回触ってみたかったという理由で使ってみた。
Push時にデプロイ
まずはデザインの変更などでGatsbyのソースに変更を加えてGithubに反映した時に、Firebaseに自動デプロイする仕組みを作っていこう。
まずGithubの自分のGatsbyリポジトリにアクセスする。
そしてタブからActionsを選ぶとアクションの例みたいなのが並んでいる画面が出てくる。
画面上にSkip this and set up a workflow yourself→
とあるところから独自のアクションを作っていく。
# This is a basic workflow to help you get started with Actions
name: Deploy Gatsby to Firebase Hosting
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: '12.x'
- name: Install dependencies
run: |
NODE_ENV=production
yarn --frozen-lockfile
- name: Build Gatsby
run: yarn build
env:
API_URL: ${{ secrets.API_URL }}
- name: Setup Firebase CLI
run: npm install -g firebase-tools
- name: Deploy Firebase
run: yarn deploy --token=${{ secrets.FIREBASE_TOKEN }}
書かれている内容以外を詳しく知りたい人はここを読むこと。
https://docs.github.com/ja/actions/reference/workflow-syntax-for-github-actions
最初に書かれているのがこのCI/CD(=ジョブ)をいつ動かすのかだ。
Pushとプルリクエスト時に動かす設定だが、ここにプルリク送る人はいないのでPushだけでいいとおもう。
ブランチはmaster。
個人開発だし特に切る理由もない。
チームならブランチ毎に設定変えたり出来そう。
次にジョブの実行環境を書いていく。
名前はbuild
でubuntu-latest
で動かす。steps
からが実行する内容になる。
まずはactions/checkout@v2
を使ってリポジトリをチェックアウトする。
https://github.com/actions/checkoutuses
を使うと他の人が作ったアクションを実行できる。
同じようにactions/setup-node@v1
を使ってnodeの12系をインストール。
ステップに名前をつけると、実行時に見やすい。
次はyarn --frozen-lockfile
で依存関係のインストール。
ローカルとCIで挙動を変えたくないので更新がかからないようにする。
そしてGatsbyのビルド。
環境変数として本番のAPI_URL
が必要なので予めリポジトリのSettingから登録しておく。
最後にFirebase Hostingにデプロイ。firebase-tools
をなぜかnpmで入れて…(yarnでいいと思うので後で直す)
CI用のトークンでデプロイ。
これでPush時に自動でデプロイされる仕組みが完成した。
Github Actionsで使うSecrets登録
ここから登録。以上。
CIで使うFirebase Tokenの取得方法
https://firebase.google.com/docs/cli?hl=ja#cli-ci-systems
firebase login:ci
ログインしたらトークンが出てくる。以上。
ブログ投稿時にデプロイ
さて、Push時にデプロイされることにはなったが、記事を更新した際にもサイトを最新版にしたいはずだ。
そこで、Github ActionsにはWebhookをトリガーにアクションを実行できる機能もある。
https://docs.github.com/ja/actions/reference/events-that-trigger-workflows#manual-events
https://docs.github.com/ja/rest/reference/actions
これを使ってStrapiでの更新時にジョブを走らせる仕組みを作る。
とりあえずGithubに対してこんな感じで投げればいいらしい。
$ curl \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H 'Authorization: token <Githubのトークン>'
https://api.github.com/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches \
-d '{"ref":"master"}'
owner: GithubID
repo: リポジトリ
workflow_id: ワークフローのID
Githubのトークンがない状態でしようとすると、"message": "Must have admin rights to Repository."
って怒られるので作ろう。
ユーザーのSettingのメニュー左下、Developer settings
からPersonal access tokens
で作れる。
権限はread:repo_hook, repo
で一応通ってるけどもっと減らせるかもしれない。
ワークフローのIDは以下のAPIから取得できる。
$ curl \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/{owner}/{repo}/actions/workflows
これでコマンドラインからジョブの実行が出来るようになった。
Strapiと連携させていこう。
Strapiには更新時に通知を飛ばせる機能があり、要は特定URLにPOSTしているだけなので使えそうだ。
画面を見てみる。
URLとヘッダーが設定できる。
あれBodyは…?
実はBodyはStrapi側が作ったものが投げられるらしい。
さて、ここでGithub側に投げたいcurlを思い出してみよう。
bodyにブランチを指定している。
これは無理なのでは…?
案の定URLとヘッダーを指定してもエラーを返された。
余計なbodyがあるのも駄目らしい。
ここで諦めると利便性が最悪なブログシステムになってしまうので、やり方を考える。
StrapiはPOSTを投げれるけど形式は選べない。
Githubは特定の形式のPOSTでジョブを動かす。
つまり、StrapiのPOSTを受けてGithubに指示を出す中間管理職を作ってあげればいいということになる。
それがこれ。
https://github.com/Tim0401/dreamer-strapi-webhook
ただのnodeサーバーでアクセスされたらGithubにデプロイ指示を出すだけ。
index.ts
import * as http from "http";
var requestMsg = require('request');
var headers = {
'Authorization': 'token ' + process.env.GITHUB_TOKEN,
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'dreamer-gatsby'
};
var dataString = '{"ref":"master"}';
var options = {
url: process.env.WEBHOOK_URL,
method: 'POST',
headers: headers,
body: dataString
};
function callback(error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body);
} else {
console.log(response);
}
}
class Main {
constructor() {
// httpサーバーを設定する
const server: http.Server = http.createServer(
(request: http.IncomingMessage, response: http.ServerResponse) =>
this.requestHandler(request, response));
// サーバーを起動してリクエストを待ち受け状態にする
server.listen(process.env.PORT);
}
/*
* サーバーにリクエストがあった時に実行される関数
*/
private requestHandler(
request: http.IncomingMessage,response: http.ServerResponse): void {
requestMsg(options, callback);
response.end('Hook Github Actions');
}
}
const main = new Main();
こいつにStrapiから更新通知を投げてあげればいいわけだ。
このサーバーどこに置こう…
今回はStrapiの補助的な役割ということでStrapiコンテナのサイドカーとして走らせることにした。
というわけでStrapi-deployment.yaml
を編集して構成していく。
containers:
- name: strapi-container
...etc
- name: strapi-webhook-container
image: tim0401/dreamer-strapi-webhook:latest
env:
- name: PORT
value: "1338"
- name: WEBHOOK_URL
valueFrom:
secretKeyRef:
name: secret
key: webhook-url
- name: GITHUB_TOKEN
valueFrom:
secretKeyRef:
name: secret
key: github-token
長いので前に紹介した分はカット。
全文見たい場合はリポジトリにどうぞ。
さっきの簡単なnodeサーバーをImageにして使っているのがtim0401/dreamer-strapi-webhook:latest
になる。
ビルド時にPORT
を環境変数としてDockerhubで渡しているので、それと同じPORT
をこちらのENVでも指定する。WEBHOOK_URL
とGITHUB_TOKEN
はsecret.yaml
に設定値を追加しておく。
これでStrapiと同じPodで中間管理職が走るようになった。
Pod内のコンテナ間通信はlocalhostでいけるので、Strapiからこいつにアクセスしたい場合は、localhost:1338
を使うといいわけだ。
Podsを見てみると2/2
になっていて、コンテナが2個入っているのが分かる。
strapi-deployment-754494fd88-74bd5 2/2 Running 0 7d20h
strapi-deployment-754494fd88-dcft2 2/2 Running 0 7d20h
strapi-deployment-754494fd88-smf2f 2/2 Running 0 7d20h
これでStrapi更新時に、通知先URLとしてlocalhost:1338
を指定すれば、アクセスを受けたnodeサーバーがGithubのAPIを叩いてデプロイが始まる。
無事に記事更新時に自動でコンテンツを更新することができるようになった。
これで一旦ブログ構築は区切りとなる。
とはいえこの記事を書いている間もちょっとずつアップデートしていっているので、都度記事は増やして行こうと思う。
次回は開発のまとめと後半詰まったところを紹介していく。