GithubActionsでVueアプリをS3にデプロイしてみた

# はじめに
前回まででGithubActiosでBeanstalkやEC2の自動デプロイを行えるようにしてきました。
今回は、VueCLIで作ったフロントエンドアプリのデプロイをGithubActionsで自動化してみます。

具体的には

- S3にデプロイ
- CloudFrontのキャッシュクリア（invalidation）

の作業を自動化します。
CloudFrontはACMを紐付け、S3のリソースをHTTPSで外部にホストするために利用しています。

なお、VueCLIやS3の初期セットアップ方法についてはここでは触れません。
既にVueアプリをS3で運用していて、GithubActionsでデプロイを自動化したい人向けの内容になります。（Vueに限らず`npm run build` でビルドできるReactなども同様です）

# 前提
- S3でVueアプリをホストしている
- Githubアカウントがある

# 開発環境
macOS Catalina
node v12.10.0
npm 6.10.3

# 大まかな作業の流れ
1. GithubActionsのworkflowを作成する
2. Secretsに必要なキー、値を登録する
3. pushして自動デプロイを確認

# GithubActionsのworkflowを作成する
今回作成する workflow のStepsの概要は以下の通りです。

1. Vueアプリケーションをビルドする
2. ビルドされたファイル群をS3にデプロイ（Sync）する
3. CloudFrontのキャッシュをクリア（invalidationを作成）する

実際に作成したファイルは以下になります。
（作成手順は後に説明します）

```yaml:deploy-s3.yml
name: deploy to S3

on:
  push:
    branches: [ master ]

jobs:
  build:
    name: build and deploy to s3
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [12.x]
    steps:
    - uses: actions/checkout@v2

    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm run build --if-present
    - run: npm test

    - name: s3 sync
      uses: jakejarvis/s3-sync-action@master
      with:
        args: --acl public-read --follow-symlinks --delete
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        AWS_REGION: 'ap-northeast-1'
        AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
        SOURCE_DIR: 'dist'

    - name: invalidate cloudfront
      uses: chetan/invalidate-cloudfront-action@master
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        AWS_REGION: 'ap-northeast-1'
        DISTRIBUTION: ${{ secrets.AWS_CF_DISTRIBUTION }}
        PATHS: '/*'
```

作成手順は以下になります。

### 雛形を使ってworkflowファイルを作成
workflow は

- YAML形式で記述する(ファイル名は任意)
- `.github/workflows` ディレクトリ内に置く(複数配置可)

ことで実行できるようになります。
手で作成してもよいですが、ブラウザ上からテンプレートを使って作ると間違いもなく簡単です。

今回は最初にVueアプリのビルドを行いたいので、 `Node.js` の雛形を利用してみます。
（VueプロジェクトがGithubにPushされていない場合はPushしてRepositoryに登録しておきます）

1. GithubのRepositoryの `Actions` タブから `New workfow` をクリックします。
![スクリーンショット 2020-06-30 22.51.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430093/7fc1b38a-a687-bb10-8f02-4277780fb80c.png)


2. `Node.js` の雛形を探して `Set up this workflw` をクリックします。
![スクリーンショット 2020-06-30 22.48.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430093/dcec8b58-ddbf-35c6-cea3-bde2edb27d2f.png)


3. `npm run build` を含むテンプレートが表示されるので `Start commit` をクリックしてコミットコメントを記入して `Commit new file` をクリックします。
![スクリーンショット 2020-07-01 0.02.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430093/e4ddd1b1-12e0-d644-5435-fb6f526e6d8f.png)


4. localの開発環境で先ほどコミットした workflow ファイルを `master` から　`pull` して取得します。

これで雛形となる workflow ファイルを作成できました。
以降は local で workflow ファイルを修正していきます。

### Node.jsの雛形をカスタマイズする
`Node.js` の雛形は、複数のNodeバージョンでのビルド＆テストを実施する workflow のため

- `Pull request` もトリガとなっている
- npmのビルドが `v12.x` だけでなく、 `10.x` `14.x` も実行されてしまう

という点が余計のため、これらを削除して以下のようにしました。

```yaml:deploy-s3.yml
name: deploy to S3

on:
  push:
    branches: [ master ]

jobs:
  build:
    name: build and deploy to s3
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [12.x]
    steps:
    - uses: actions/checkout@v2

    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm run build --if-present
    - run: npm test
```

これで `master` にPushされた時に `node v12.x` でのクリーンインストール＆ビルド＆テストのみが動きます。
このビルドでプロジェクト直下の `dist` というディレクトリ内に、ビルドされたファイル群が出力されます。

### S3にデプロイするStepを追記する
S3へのデプロイは marketplace にサードパーティ製の Action があったのでそれを利用します。
https://github.com/marketplace/actions/s3-sync
![スクリーンショット 2020-07-01 0.41.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430093/73b2fe99-f733-8e49-a9e7-232e353b965a.png)

ここの例に従って引数やパラメータを指定したものが以下になります。

```yaml
    - name: s3 sync
      uses: jakejarvis/s3-sync-action@master
      with:
        args: --acl public-read --follow-symlinks --delete
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        AWS_REGION: 'ap-northeast-1'
        AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
        SOURCE_DIR: 'dist'
```

`env` でよく使うものを以下に抜粋したので参考にしてください。

| キー | 設定する値 | 必須 | デフォルト値 |
|---|---|---|---|
|AWS_ACCESS_KEY_ID|IAMのアクセスキーID（取得方法は後述の補足２参照）|Y|-|
|AWS_SECRET_ACCESS_KEY|IAMのシークレットアクセスキー（　〃　）|Y|-|
|AWS_S3_BUCKET|S3のバケット名|Y|-|
|AWS_REGION|S3のリージョン名|N|us-east-1|
|SOURCE_DIR|S3にアップロードするディレクトリ|N|./ (リポジトリのルート)|
|DEST_DIR|アップロード先のS3のディレクトリ|N|/ (S3のバケットのルート)|

##### 補足１:セキュアな情報は `Secrets` に登録して参照する
値の指定で `${{ secrets.〜 }}` とある記述は、Repositoryの `Secrets` に登録した値を参照しています。IAMのアクセスキーなどのセキュアな情報は workflow ファイル内にベタ書きしないようにします。
（Secretsの登録手順は後述します）


##### 補足2：IAMのアクセスキーIDとシークレットアクセスキーの取得方法
IAMのアクセスキーIDとシークレットアクセスキーは、AWSのコンソールにIAMでログイン後、ツールバーのアカウント名のメニューから `マイセキュリティ資格情報` > `アクセスキー` > `新しいアクセスキーの作成` から生成することができます。
<img width="1130" alt="スクリーンショット 2020-07-01 1.33.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430093/c62bbdd3-662a-5c42-f28f-81c0d41736c5.png">
＊これはAWS CLIなどを使ってAWSのリソースを操作する際に使うセキュアな情報です。
＊シークレットアクセスキーは、初回に作成時しか表示されないので注意してメモります。

### CloudFrontのキャッシュをクリアするStepを追記する
＊CloudFrontを使ってない場合はこのStepは省略してよいです。
S3の前にEdgeサーバーとしてCloudFrontを使っている場合は、キャッシュをクリアしないと更新したS3のコンテンツがしばらく配信されません。
いつもは手動で行なっていたキャッシュクリア（invalidation）も自動化します。

例によってmarcketplaceにサードパーティ製の Action があったのでそれを利用します。
https://github.com/marketplace/actions/invalidate-cloudfront
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430093/bfc9037c-927b-5f29-316b-736b52d55012.png)
ここの例に従って引数やパラメータを指定したものが以下になります。

```yaml

    - name: invalidate cloudfront
      uses: chetan/invalidate-cloudfront-action@master
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        AWS_REGION: 'ap-northeast-1'
        DISTRIBUTION: ${{ secrets.AWS_CF_DISTRIBUTION }}
        PATHS: '/*'
```
`env` で指定しているキーは以下を参考にしてください。

| キー | 設定する値 | 必須 |
|---|---|---|---|
|AWS_ACCESS_KEY_ID|IAMのアクセスキーID（先述と同じ）|Y|
|AWS_SECRET_ACCESS_KEY|IAMのシークレットアクセスキー（　〃　）|Y|
|AWS_REGION|リージョン名|Y|
|DISTRIBUTION|対象のCloudFrontディストリビューションID|Y|
|PATHS|キャッシュクリアする１つ以上のパスのリスト（スペース区切りで複数指定可）|Y|

以上で、冒頭の workflow ファイルの作成が完了しました。

# Secretsに必要なキー、値を登録する
workflow 内のパラメータの指定で `${{ secrets.〜 }}` とある記述は、Repositoryの `Settings` > `Secrets` に登録した値を参照しています。
IAMのアクセスキーなどのセキュアな情報は workflow ファイル内にベタ書きせず、ここに登録して参照するようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430093/2d75087d-bb2e-ffba-fb07-66ca94e194b3.png)
`Secrets` の値は、仮に `echo` などで出力してもマスキングされるので安全です。

# pushして自動デプロイを確認
Vueアプリのリソースの一部に変更を加えて `master` にCommit&Pushします。
その後、Githubの `Actions` タブで自動デプロイの状況を確認してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430093/23c4b6c1-3266-477f-2374-4a78fa1bdb6e.png)
このように、オールグリーンなら workflow は成功です！
実際にブラウザからも正常に変更が反映されていることが確認できるはずです。

# あとがき
S3へのリソースのアップロード（Sync）、その後のCloudFrontのキャッシュクリアという地味に面倒な作業から開放されました！！
AWSを対象としたCI/CDの工程で必要なStepは、大抵公式かmarketplaceでActionが公開されているので、簡単に workflow が作れるのも手軽でよいですね。