以前から業務で React を使用していましたが、デプロイは手動でやっていたため、自動化できたらいいなという話をチームでしていました。
そこで、正式リリースもされた GitHub Actions をせっかくなので使ってみたいなと思い、自動化に挑戦してみました。
※この記事は元々 Qiita からの転載です。
現在は Qiita でなく Zenn の方で更新しています。
目次
GitHub Actions とは? #
簡単に言うと GitHub が公式で公開している機能で、トリガーをきっかけにあらかじめ定義しておいた処理を実行するというものです。はじめはベータ版で公開されていましたが、2019年11月に正式公開されました。
いわゆる CI/CD を実現できます。
これまでは Circle CI などの外部サービスと連携させて行うことが多かったですが、GitHub Actions を使用することで、GitHub だけで実現させることができます。
こちらの記事で概要から機能まで幅広く解説されています。
※以下、AWS アカウント、Slack ワークスペースがある前提で進めていきます。
S3 の準備 #
GitHub Actions でデプロイする先を用意します。
マネジメントコンソールで S3 の画面へ。
バケットの作成 #
- 「バケットを作成する」を選択
- 名前とリージョン
- バケット名を入力(全世界で一意の名前の必要があります)
- リージョンを選択
- 「次へ」
- オプションの設定
- 任意で設定(そのまま「次へ」でもOK)
- アクセス許可の設定
4つともオフにする(本来は適度にオンがいいと思いますが、とりあえず動作させたいのでオフにしました)4つともオンのままでOK(※2020/3/14修正)- 「次へ」
- 確認
- 確認して問題なければ「バケットを作成」
静的サイトとして公開 #
※2020/3/14追記
今回の Cloud Front の OAI 認証を用いるやり方では、この手順は不要なので静的サイトとして非公開のままで大丈夫です。
ちなみに Cloud Front と組み合わせるやり方は以下の記事にある通り、2パターンあるそうです。今回は構成1の方になります。
- 作成したバケットを選択
- プロパティタブ → Static website hosting を選択
- 「このバケットを使用してウェブサイトをホストする」を選択し、インデックスドキュメントに「index.html」を入力
- 「保存」を選択
その後、バケットホスティングと表示されていれば公開されています。
なお、バケット名はポリシー作成で使用するので控えておきます。
バケットを静的サイトとして公開した場合は、通常、サイトへのアクセスを受け入れるために以下のようなバケットポリシーを設定します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::{※バケット名}/*"
}
]
}
ですが、今回は Cloud Front を経由してのみアクセスできるようにするために、あとで設定を上書きします。この場では何も設定しません。
ちなみにバケット作成時にブロックパブリックアクセスを4つすべてオフにした場合、オフにしたからといって、誰でも自由にアクセスできるようになるということではありません。
バケットポリシーで許可されたアクセスであったり、付属ポリシーで許可された IAM ユーザのアクセスであれば可能です。
Cloud Front の準備 #
ホスティング自体は S3 だけで可能ですが、CDN を前に置きたいので設定します。
マネジメントコンソールで Cloud Front の画面へ。
ディストリビューションの作成へ #
- 「Create Distribution」を選択
- Webの「Get Started」を選択
Create Distribution #
- Origin Settings
- Origin Domain Name:先ほど作成したバケットを選択(選択するとRestrict Bucket Access が出現します)
- Origin ID:Origin Domain Nameを選択すると自動的に入力される
- Restrict Bucket Access:Yes(Yes にすると以下の項目が出現します)
- Origin Access Identity:Create a New Identity
- Comment:任意の名称(デフォルトのままでも OK)
- Grant Read Permissions on Bucket:Yes, Update Bucket Policy
- ※これ以外は任意(そのままでも OK)
- Default Cache Behavior Settings
- ※任意(そのままでも OK)
- Distribution Settings
- Default Root Object:「index.html」と入力
- ※これ以外は任意(そのままでも OK)
- 「Create Distribution」を選択
これでディストリビューションが作成されますが、動作するまで15分ほどかかります。
status が In Progress から Deployed に変わればOKです。
ディストリビューションの ARN をポリシー作成で使用するので控えておきます。
arn:aws:cloudfront::XXXXXXXXXXXX:distribution/XXXXXXXXXXXXXX
のような形式です。
(ディストリビューションを選択して、General タブで確認できます)
補足 #
Create Distribution での設定について。
Restrict Bucket Access と Origin Access Identity #
Restrict Bucket Access
については、S3 バケットへのアクセスを Clout Front 経由からのみにする設定です。
この設定をするにあたって、Cloud Front のどのディストリビューションからのみアクセスを受け入れればいいのか、S3 が判断するために必要なのがOrigin Access Identity
(通称:OAI)です。
OAI 自体はただの文字列にすぎませんが、ディストリビューションと紐づけることで、アクセス制限をかけられます。
ちなみにRestrict Bucket Access
はOrigin Domain Name
でバケットを選択することで出現しますが、うまく動作しなかったのか出現しなかったことがありました。(何度かやりなおしたら出現しました)
Grant Read Permissions on Bucket #
ディストリビューションに紐づいている S3 バケットのバケットポリシーを、OAI を使用したアクセス制限をかけるための設定へ上書きするかです。
上書きすることで以下のようなバケットポリシーに変わります。
{
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [
{
"Sid": "1",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity {※OAI ID}"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::{※バケット名}/*"
}
]
}
Default Root Object #
ルート階層のオブジェクトとしたいものを指定します。
今回の場合は「index.html」なので、index.html でアクセスを受け取る感じです。
ちなみに S3 バケットを静的サイトとして公開する設定にしている場合、その設定の中にインデックスドキュメントを指定するところがあります。
これと同様の設定のように思えますが、Cloud Front 側でも設定しておく必要がありました(設定しないとブラウザからアクセスしたとき AccessDenied になりました)
IAM の準備 #
GitHub Actions でデプロイする際に使用する、デプロイ用の IAM ユーザを作成します。
マネジメントコンソールで IAM の画面へ。
ポリシーの作成 #
- ポリシー一覧から「ポリシーを作成」を選択
- JSON に以下を記述して「ポリシーの確認」を選択
{}
の部分はこれまで控えたものに適宜置き換えてください。 - ポリシーの確認
- 任意の名前と説明を入力して「ポリシーの作成」を選択
2 で記述するポリシーの JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::{※S3バケット名}",
"arn:aws:s3:::{※S3バケット名}/*"
]
},
{
"Effect": "Allow",
"Action": [
"cloudfront:GetDistribution",
"cloudfront:GetDistributionConfig",
"cloudfront:CreateInvalidation",
"cloudfront:ListInvalidations",
"cloudfront:GetInvalidation"
],
"Resource": "{※Cloud FrontディストリビューションのARN}"
},
{
"Effect": "Allow",
"Action": [
"cloudfront:ListDistributions",
"cloudfront:ListStreamingDistributions"
],
"Resource": "*"
}
]
}
ユーザの作成 #
- ユーザ一覧から「ユーザを追加」を選択
- ユーザ名は任意、アクセスの種類は「プログラムによるアクセス」のみ選択して「次のステップ」
- アクセス権限
- 「既存のポリシーを直接アタッチ」を選択
- 先ほど作成したポリシーを選択して「次のステップ」
- タグ
- 任意(そのまま次のステップでも OK)
- 確認して「ユーザの作成」を選択
- アクセスキーとシークレットキーを控えておく
Slack の準備 #
GitHub Actions の結果を通知するための Webhook URL を用意します。
Slack App の作成 #
- Slack ワークスペースのワークスペース名のところからメニューを開く
- ビルド画面へ
- ※ワークスペースのオーナーの場合
その他管理項目 → App を管理する → 右上の「ビルド」 - ※オーナーでない場合
Slack をカスタマイズ → 左上のメニューを開く → App 管理 → 右上の「ビルド」
- ※ワークスペースのオーナーの場合
- Start Building を選択
- App Name:任意の名前
- Development Slack Workspace:任意のワークスペース
- 「Create App」を選択
以下、作成した App の画面で進めます。
Bot の作成 #
Incomming Webhook を使用するために必要なので用意します。
- Building Apps for Slack の Add features and functionality を開いて、Bot sを選択
- How Your App Displaysで「Edit」を選択
- Display Name (Bot Name):任意の名前を入力
- Default username:任意の名前を入力
- 「Add」を選択
Incomming Webhook URL の作成 #
- Building Apps for Slack の Add features and functionality を開いて、Incomming Webhooks を選択
- Activate Incoming Webhooks を ON にして、「Add New Webhook to Workspace」
- 投稿するチャンネルを選択して、「許可する」を選択
これで Webhook URL が生成されるので控えておきます。
GitHub の準備 #
リポジトリ #
適当なリポジトリを用意します。
React アプリ #
React アプリをリポジトリに push して用意しておきます。
ちなみに自分はcreate-react-app
にて作れる雛形でやりました。
npx や yarn コマンドでサクッと作れますが、自分は yarn を使っているので、以降は yarn コマンドで書いていきます。
(create-react-app
はグローバルインストールサポートしなくなったらしいです)
$ yarn create react-app (作成する場所のパス)
それと GitHub Actions の中で ESLint を使うので、package.json の scripts に以下を追加しておきます。
"scripts": {
.
.
.
"lint": "yarn -s run eslint \"src/**/*.js\""
}
GitHub Secrets #
GitHub Actions で使用する環境変数を設定します。
公開したくない秘匿情報などを定義するのに向いており、GitHub Secrets に登録した変数の値は後から確認できないようになっています。
リポジトリの Settings → Secrets で以下の情報を登録しておきます。値はこれまで控えてきたものです。
(AWS_S3_BUCKET_PATH はs3://{s3のバケット名}
形式です)
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_S3_BUCKET_PATH
- SLACK_WEBHOOK_URL
なお、GITHUB_TOKEN については自動で値がセットされるので、設定しなくて大丈夫です。
GitHub Actions のワークフロー定義ファイル作成 #
ワークフロー定義ファイルは yml で記述して、リポジトリルート直下に.github/workflows/ワークフロー定義ファイル名.yml
で配置します。
配置する場所さえ合っていれば、ファイル名は何でも OK です。
自分の場合は以下のようになりました(Docker 使ってやってました)
(プロジェクトフォルダ)
├─ .github
| ├─ workflows
| | ├─ ※ワークフロー定義ファイル
├─ node_modules
| ├─ (各種パッケージ)
├─ public
| ├─ favicon.ico
| ├─ index.html
| ├─ logo192.png
| ├─ logo512.png
| ├─ manifest.json
| ├─ rebots.txt
├─ src
| ├─ App.css
| ├─ App.js
| ├─ App.test.js
| ├─ index.css
| ├─ index.js
| ├─ logo.svg
| ├─ serviceWorker.js
| ├─ setupTest.js
├─ docker-compose.yml
├─ Dockerfile
├─ .gitignore
├─ package.json
├─ README.md
├─ yarn.lock
以下の内容で yml ファイルを作成します。
自分の場合、ステージング環境にデプロイするイメージで作りました。
name: Staging Deploy
on:
push:
branches:
- release/**
env:
project-name: ga-test-project
jobs:
check:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Get Yarn Cache Directory Path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache Node Modules
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-${{ env.project-name }}-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
restore-keys: |
${{ runner.os }}-${{ env.project-name }}-
- name: Package Install
run: yarn install
- name: Lint
run: yarn lint
- name: Slack Notification by NonSuccess
uses: 8398a7/action-slack@v2
if: success() != true
with:
status: ${{ job.status }}
author_name: 'check'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
deploy:
name: Build & Deploy
needs: check
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Get Yarn Cache Directory Path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache Node Modules
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-${{ env.project-name }}-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
restore-keys: |
${{ runner.os }}-${{ env.project-name }}-
- name: Package Install
run: yarn install
- name: Build
run: yarn build
- name: Publish to AWS S3 & CloudFront Cache Clear
uses: opspresso/action-s3-sync@v0.2.3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'ap-northeast-1'
FROM_PATH: './build'
DEST_PATH: ${{ secrets.AWS_S3_BUCKET_PATH }}
- name: Slack Notification
uses: 8398a7/action-slack@v2
if: always()
with:
status: ${{ job.status }}
author_name: 'deploy'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
いざデプロイ #
今回はrelease/
が接頭辞のブランチを push することがトリガーになっているため、これに該当する名称のブランチを作成して push してみましょう。
ちゃんと動作していれば、リポジトリの Actions からワークフローの状況を確認できるようになっているはずです。
それぞれのステップのログも確認できるので、もしどこかでエラーになって失敗しても原因調査に役立ちます。
無事最後まで成功していれば、Cloud Front からデプロイしたサイトにアクセスしてみましょう。
(ディストリビューションの General の Domain Name で URL が確認できます)
また、S3 に直接アクセスできないことも確認してみましょう。403 Forbidden になるでしょうか。
(プロパティの Static website hosting から URL が確認できます)
静的サイトとして非公開なので確認は不要です(※2020/3/14修正)
そして、Slack にもちゃんと通知できているでしょうか?
通知の内容は以下のような感じになります。
成功
キャンセル
失敗
ワークフロー定義ファイルの簡単な解説 #
ワークフロー名 #
GitHub の Actions 上では、この名前が表示されます。
name: Staging Deploy
トリガー #
on
でトリガーになる GitHub イベントを指定。
指定できる GitHub イベントについては以下を参照ください。
イベントをpush
かpull_request
にした場合は、さらにブランチ(branches
)やタグ(tags
)の指定ができます。
今回の場合は、release/
が接頭辞のブランチを push することをトリガーにしています。
on:
push:
branches:
- release/**
逆にこのブランチやタグはトリガーから除外したいという場合は、それぞれbranches-ignore
、tags-ignore
で指定ができます。
また、on
には GitHub イベントだけでなく、schedule
が指定でき、クーロン構文を指定してスケジュール実行させることもできます。
ワークフローの環境変数 #
ワークフロー全体で使用できる環境変数の指定。
ここで設定したものはenv.〇〇
で使用することができます。
env:
project-name: ga-test-project
ジョブ #
ワークフローで実行するジョブ定義を記述。複数指定可能です。
GitHub の Actions 上ではジョブ名が表示されます。
実行環境の種類については以下を参照。
- GitHub Actions - GitHubホストランナーの仮想環境
- GitHub Actions - GitHub Actionsのワークフロー構文 - jobs.{job_id}.runs-on
※2020/10/18追記
なお、自分でホストしている実行環境を使うこともできるようです。
jobs:
deploy: # ジョブID
name: Build & Deploy # ジョブ名
needs: check # このジョブ実行前に完了する必要があるジョブのID
runs-on: ubuntu-latest # ジョブを実行する実行環境の種類
steps:
# 各種ステップの定義
jobs.<job_id>.steps
で、そのジョブで実行するステップ処理を記述。
uses
で指定している actions は、あらかじめ処理を定義しているもので、これを使用することでステップを組み立てやすくなります。
ステップ・リポジトリのチェックアウト #
リポジトリのコードを落としてきます。
- name: Checkout
uses: actions/checkout@v2
ステップ・Node.js のセットアップ #
ちなみに Node.js だけでなく、いろんな言語のセットアップ action が提供されています。
- name: Setup Node
uses: actions/setup-node@v1
with:
node-version: 12.x
ステップ・yarn のキャッシュを格納するディレクトリのパスを取得 #
- name: Get Yarn Cache Directory Path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
ステップ・キャッシュの復元もしくは作成 #
key
の完全一致で既存のキャッシュを探し、マッチすればキャッシュをpath
に復元。
なければrestore-key
の完全一致で既存のキャッシュを探し、マッチすればキャッシュをpath
に復元。
さらになければrestore-key
の部分一致で既存のキャッシュを探し、見つければ最新のキャッシュをpath
に復元。
key
の完全一致がなかった場合に、このワークフローが成功して完了した際にキャッシュを作成します。
詳細は以下を参照。
ちなみに今回の場合だと、yarn.lock
の内容に変更があった時に key が変わります。
- name: Cache Node Modules
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-${{ env.project-name }}-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
restore-keys: |
${{ runner.os }}-${{ env.project-name }}-
キャッシュのあるなしで実行時間を比較してみたところ、こんな感じになりました。
キャッシュなし・46s
キャッシュあり・26s
ステップ・yarn でパッケージのインストール #
- name: Package Install
run: yarn install
ステップ・ESLint で静的解析チェック #
あらかじめpackage.json
に定義しておいた、ESLint 実行コマンドを実行。
テストコードを書いている場合は、これに加えてテスト実行もあると一緒に自動化できます。
- name: Lint
run: yarn lint
ステップ・yarn でビルド #
ビルドの成果物は./build
に格納されます。
- name: Build
run: yarn build
ステップ・S3 にデプロイ & Cloud Front のキャッシュクリア #
必要な情報を指定することで、S3 にデプロイを実行します。また、S3 から辿って、紐づいている Cloud Front のディストリビューションのキャッシュクリアも一緒に行ってくれます。
- name: Publish to AWS S3 & CloudFront Cache Clear
uses: opspresso/action-s3-sync@v0.2.3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} # デプロイ用IAMユーザのアクセスキー
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # デプロイ用IAMユーザのシークレットキー
AWS_REGION: 'ap-northeast-1' # デプロイ先のリージョン
FROM_PATH: './build' # デプロイするものがあるパス
DEST_PATH: ${{ secrets.AWS_S3_BUCKET_PATH }} # デプロイ先のS3バケットパス
ステップ・Slackに通知 #
if
で通知する条件を指定。
success()
:ジョブの前のステップが成功した場合always()
:ジョブのこのステップが実行された場合cancelled()
:ワークフローがキャンセルされた場合failure()
:ジョブの前のステップが失敗した場合、もしくはジョブが失敗した場合
job.status
にはそのジョブの成功、失敗、キャンセルに応じた値が入ります。
- name: Slack Notification
uses: 8398a7/action-slack@v2
if: always()
with:
status: ${{ job.status }}
author_name: 'deploy'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # 自動で設定される
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # Slack投稿用のWebhook URL
開発用ブランチでも静的解析等を自動化したい #
上記のワークフローのうち、check ジョブは開発用ブランチでも使えると思います。
自分の場合はトリガーだけ変えて別ファイルにして、開発ブランチ用のワークフローを作りました。
on:
push:
branches-ignore:
- master
- release/**
ちなみに開発用ブランチでワークフローを実行して、そのブランチでプルリクを出した場合、プルリクの画面で以下のようにワークフローが表示されるようになります。
とりあえずデプロイまで無事到達できてよかったのですが、色々検証しながらやっていたら、かなり時間を使ってました(苦笑)
まぁ、はじめてやることに時間かかるのはつきものでしょうか。
GitHub Actions を使おうとしている方の何かの参考になれば幸いです。
参考リンクまとめ #
- GitHub Actions
- GitHubの新機能「GitHub Actions」で試すCI/CD
- GitHub Actionsでキャッシュが使えるようになって嬉しい!
- GitHub - actions/cache
- GitHub Actions(beta)向けにslack通知プラグインを作った
- GitHub Actions を使って AWS S3 に自動デプロイする
- GitHub ActionsでS3にデプロイする!
- CloudFrontエッジキャッシュクリアのみを行うユーザーの作成
- AWS S3で静的Webページをホスティングする
- CloudfrontにアクセスしたらAccessDeniedでアクセス出来ない
- CloudFront オリジンアクセスアイデンティティでクロスアカウントなS3オリジンへのアクセスを制限する
- 【S3】Web アクセス制限いろいろ (CloudFront もあるよ)
- CloudFront + S3 で静的サイトを運用する際の注意点