GitHub Actionsを使ってServerless Frameworkチーム開発効率化

## はじめに
この記事は2020年のRevCommアドベントカレンダー10日目の記事です。9日目は @sukekd さんの [Google Workspace（旧称G Suite） アカウントのSAML認証を使ってAWS CLIの認証を行う](https://qiita.com/sukekd/items/dce4799014396ad579da) でした。

こんにちは。
RevCommでエンジニアリングマネージャーを担当している@series7です。
エンジニアリングマネージャーということで最近は組織づくりや採用を主に担当していますが、まだまだエンジニアの人数が少ないので日々プログラミングも行っています。

ちょっとしたLambdaを作ってデプロイしたいときにServerless Framework便利ですよね。
ただチームで開発をしていてるとプルリクエストレビュー時の実機確認をローカルで行うか、自分でデプロイするかなど悩みもあると思います。
そんなときに便利なのがGitHub Actions。
数行のコードで、

- プルリクエスト単位にPR単位の独自環境を作成
- チェックしてマージ完了した段階で削除

ということが全自動でできちゃいます。
それでは、そんな素敵な環境作りの第一歩ということで簡単な環境を構築してみましょう。

## 今回作成するもの
* 簡単なServerless Frameworkアプリ
* GitHub Actions

## 前提
PCにnpmがインストールされていること

## Serverless Frameworkアプリの準備
今回はあくまでもPull Request単位に環境の自動作成を試すだけなので簡潔にすませます。

まずはServerlessコマンドのインストール

````sh
npm install -g serverless
```

インストールが完了したら適当なディレクトリーで

```sh
sls create -t aws-nodejs-typescript --path advent2020
```

これでファイル一式が入ったディレクトリーが作成されたと思います。
handlers.tsを覗いてみるとこんな状況になっているはずです。

```typescript:handlers.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
import 'source-map-support/register';

export const hello: APIGatewayProxyHandler = async (event, _context) => {
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Go Serverless Webpack (Typescript) v1.0! Your function executed successfully!',
      input: event,
    }, null, 2),
  };
}
```

このままdeployを行うとデフォルトの米国東部（バージニア北部）リージョン `us-east-1` に環境構築が行われるので、フォルダー内の `serverless.ts` にリージョン指定を設定します。

```typescript:serverless.ts
provider: {
    name: 'aws',
    runtime: 'nodejs12.x',
    region: 'ap-northeast-1', //この行を追加してリージョンを指定。
    apiGateway: {
      minimumCompressionSize: 1024,
    },
    environment: {
      AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
    },
  },
```

### GitHubに保存
保存しましょう。
適当にGitHubのレポジトリを作成して、ひとまずdefaultブランチにドーーン。

```
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin {リモートレポジトリ名}
git push -u origin main
```

## AWSにデプロイするための準備
### Deploy用のAWS IAMユーザーを作成する
AWSに環境構築をする上でユーザーが必要となるのでAWS IAMユーザーを作成します。

まずはAWSマネージメントコンソールにログインしてプログラムアクセスのみのユーザーを作成します。
![advent-aws-console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596458/77650a2d-60eb-7cc0-821e-d0641bc38042.png)

今回はあくまでもサンプルなので全権限移譲したユーザーを作成します。
**実際の開発では権限をちゃんとしぼったユーザーにしましょうね。**
![advent-aws-console2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596458/b62057a8-96ac-3bfd-4ec8-694289d92702.png)

ユーザーを作成したらアクセスキーとシークレットアクセスキーをメモっときます。
![advent-aws-console3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596458/fbb5a524-db9c-f7e3-ccfc-3e82b327bcfa.png)

これで準備は完了です。
ここからは実際にGitHub Actionsの設定をしていきます。


## Github Actionsの準備
### GitHub Secretesの登録
さきほど作成したAWS IAMユーザーのアクセスキーとシークレットアクセスキーをGitHubレポジトリのシークレットに登録します。
[設定場所]
Settings -> Secrets
![Secrets.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596458/8cc5d24c-8a8e-5858-a5c0-537f6118a888.png)

`AWS_ACCESS_KEY_ID` と `AWS_SECRET_ACCESS_KEY`　の２つを設定します。

### デフォルトブランチ用のGitHub Actionsの設定
デフォルトブランチに変更が入った場合にAWSの環境をアップデートするようなActionsを設定していきます。

`.github`というフォルダーをプロジェクトルート直下に作成し、その中に `workflows`というフォルダーを作成します。
このフォルダーに実行したいGitHub Actionsのコードをいれていくことになります。

デフォルトブランチ用ということで、 `main.yml` というファイルを作成します。

```yml:.github/workflows/main.yml
name: Main CI/CD

on:
  push:
    branches:
      - main

jobs:
  primary:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-node@v1
      with:
        node-version: '12'
    - name: install dependencies
      run: yarn install
    - name: serverless config
      run: npx serverless config credentials --provider aws --key ${{ secrets.AWS_ACCESS_KEY_ID }} --secret ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    - name: serverless deploy
      run: npx serverless deploy --stage prod
```

GitHub Actionsの記法自体は[GitHub Docs](https://docs.github.com/ja/free-pro-team@latest/actions)を参照して頂くとして、重要点だけ。

```yml
on:
  push:
    branches:
      - main
```
これでmainブランチにpushされたときに記載されたコードが動くようになります。

```yml
    - name: serverless config
      run: npx serverless config credentials --provider aws --key ${{ secrets.AWS_ACCESS_KEY_ID }} --secret ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    - name: serverless deploy
      run: npx serverless deploy --stage prod
```
この部分が実際のServerless FrameworkのDeployコードとなります。
今回はmainブランチのstageを`prod`という名称でデプロイしています。

実際にこの `.github/workflows/main.yml` をレポジトリーにプッシュしてみましょう。

プッシュが終わったらブラウザでGitHubのレポジトリーサイトを開いてみます。
Actionsページをみると稼働しているActionのJobの状態が確認できるはずです。
![githubaction-result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596458/0c65b391-87dc-95a0-33a0-f82a501cda64.png)

完了したら、AWSマネージメントコンソールのAPI Gatewayページを見てみます。
![api-geteway1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596458/9ae9262c-7211-ff4b-6f8e-0ea3fb4f71d4.png)

無事 `prod-advent2020`というAPIが作成されていますね。
実際に動いている状況をみてみたいので、API名をクリックしてダッシュボードメニューへ遷移します。

![apigateway2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596458/697387a6-fab3-d14e-d705-2ac470da452f.png)

右上にAPIのURLが記載されていると思うので末尾に　`/hello/` を付け足してブラウザで見てます。
実際のURLはこのような形になるはずです。
`https://{生成されたURL}.execute-api.ap-northeast-1.amazonaws.com/prod/hello/`

ブラウザに下のような画面がでれば成功です。
![apigateway-result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596458/31b493b4-4f90-095c-c752-896014576ddb.png)

`handers.ts`内に記載されてた `Go Serverless Webpack (Typescript) v1.0! Your function executed successfully!`という文言が表示されていますね。

### プルリクエスト作成時用のAction
それでは本題であるプルリクエストが作成されたときにPR単位の独自環境を作成するコードを追記していきましょう。
先程と同じく .github -> workflows フォルダー内に pr-open.ymlというファイルを作成します。

```yaml:.github/workflows/pr-open.yml
name: Pull Request CI/CD

on: pull_request

jobs:
  primary:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-node@v1
      with:
        node-version: '12'
    - name: install dependencies
      run: yarn install
    - name: serverless config
      run: npx serverless config credentials --provider aws --key ${{ secrets.AWS_ACCESS_KEY_ID }} --secret ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    - name: serverless deploy
      run: npx serverless deploy --stage pr$(jq --raw-output .number "$GITHUB_EVENT_PATH")
```


ほぼデフォルトブランチ用のActionを同じなのですが、
重要となる変更点は２箇所。

プルリクエストが作成されたときに動くことを宣言している部分と

```yaml
on: pull_request
```

デプロイする環境名（stage)をプルリクエストの番号をもとに自動生成しているところです。

```yaml
- name: serverless deploy
      run: npx serverless deploy --stage pr$(jq --raw-output .number "$GITHUB_EVENT_PATH")
```


### プルリクエストマージ時用のAction
レビュー後、マージ時（またはキャンセルのタイミング）に実行するGitHub Actionsのコードはこちら。

```yaml:.github/workflows/pr-close.yml
name: Pull Request Close CI/CD

on:
  pull_request:
    types: [closed]

jobs:
  primary:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-node@v1
      with:
        node-version: '12'
    - name: install dependencies
      run: yarn install
    - name: serverless config
      run: npx serverless config credentials --provider aws --key ${{ secrets.AWS_ACCESS_KEY_ID }} --secret ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    - name: serverless remove
      run: npx serverless remove --stage pr$(jq --raw-output .number "$GITHUB_EVENT_PATH")
```

プルリクエストオープン時との違いは２箇所。

```yaml
on:
  pull_request:
    types: [closed]
```
この部分でpull_requestがcloseされた時にのみ動くよう指定しています。

```yml
- name: serverless remove
      run: npx serverless remove --stage pr$(jq --raw-output .number "$GITHUB_EVENT_PATH")
```

作成時には `serverlss` のあとが `deploy` となっていたのに対してcloseされた際には環境を削除したいので `remove`　となっています。


## プルリクエスト動作の実行確認
準備が終わったので実行してみましょう。

### GitHub Actionsコードのプッシュ
さきほど追加で作成したプルリクエスト　オープン用とクローズ用のコードもmainブランチにpushしておきます。
GitHub Actionsはデフォルトブランチのコードのみが動作対象となるのが注意点です。

### 新規ブランチの作成

適当にブランチを作成してマスターブランチに対してPRを作成しましょう。

`test`というブランチを作成してチェックアウトします。

```sh
git checkout -b test
```

表示されるメッセージだけ変更します。

```patch:handlers.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
import 'source-map-support/register';

export const hello: APIGatewayProxyHandler = async (event, _context) => {
  return {
    statusCode: 200,
-    body: JSON.stringify({
-      message: 'Go Serverless Webpack (Typescript) v1.0! Your function -executed successfully!',
-      input: event,
-    }, null, 2),
+    body: JSON.stringify({
+      message: 'GO! GitHub Actions!!',
+    }, null, 2),
  };
}
```

変更したファイルをリモートのtestブランチにpushします。
プッシュ後にプルリクエストを作成。

![pr_open.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596458/9b4739e1-dbb8-deea-951e-08d173ca5b03.png)



PRを作成した段階でGitHub のActionsページをみると処理がはしっているはずです。
エラーが出ないことを確認したあとにAWSのコンソールをみてみると

![apigete3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596458/f06ededf-321c-472b-54de-0c8b64769b52.png)

PR番号がついたAPI Gatewayがありますね！
実際に表示されたURLを確認してみます。

![api-result3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596458/4ec2c2a7-3d59-72be-c372-72b7e91da4e4.png)

変更されたメッセージが画面に表示されましたね。

確認できたのでマージします。

再びGitHub Actionsのページをみると処理が行われていることがわかります。
この段階ではPRのクローズ用のActionとmainブランチのdeploy用のActionの２つが走っています。
![prcloseaction.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596458/cd46145b-824b-f834-7f99-3cc0f8f6b400.png)

AWSコンソールの画面をみると、先程作成されたapi gatewayが削除されていることが確認できますね。
![final.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596458/35b30612-7754-20b1-f98b-95f42b4b7423.png)

もとのブランチもアップデートされているので残っているAPI Gatewayのレスポンスも変更されているかと思います。

## おわりに
最後まで読んでいただきありがとうございます。
GitHub Actionsを使うと簡単に確認のための環境を構築できましたね！
これでプルリクエストのレビュー時にローカル環境で構築してみる or 手動でデプロイしてみる、という手間が不要になるのでチーム開発がスムーズになる・・・かもしれません。

実務ではDynamoDBやS3など各種リソースの記載と合わせて使うことが多いかと思いますが、その際にもリソース名にコマンドラインから渡されたstage名を含めると完全に分離された環境が作成可能です。
GitHub ActionsからSeed Dataの登録なども行うとテストデータも用意された環境構築もできます。
需要があれば別の機会にでも書きたいと思います。

RevCommではサーバーサイド、フロントエンド、モバイルアプリ、インフラを問わずエンジニア絶賛募集中です。
興味ある方はぜひまずはおしゃべりしましょう。
エントリーはこちらから。
https://hrmos.co/pages/revcomm/jobs

明日は@hiratake55さんの記事になります。