[AWS] CloudFormationテンプレートをGitHub Actionsで自動デプロイする

# 概要
本記事ではLambdaをデプロイするためのCloudFormationに必要な、

* zipファイル
* ymlファイル

を作成し、S3にアップロードするということをGitHub Actionsを用いてコマンド1つで可能とする設定をまとめました。

## 背景
以前LambdaをデプロイするCloudFormationテンプレートを作成しました。しかし、当時の構成ではLambda用のプログラムの構成が変わるたびに、以下のステップを踏んでいました。

1. Lambda用のプログラムを編集
2. 編集したプログラムをディレクトリごとzipファイルに固める
3. AWSにログイン
4. S3バケットにアップロード

上記のステップを踏むにはLambda用のプログラムの編集時間を差し引いても最低5分は要し、非常に手間のかかる作業でした。

そこで、上記ステップの2, 3, 4をGitHub Actionsに実行させるという構成にすることでCloudFormationテンプレート作成にかかる手間を減らすことが可能となりました。本記事はその際の備忘録として残します。

## 前提条件
GitHub Actionsを利用するため、GitHubアカウントを事前に作成してください。



# 手順
## GitHub Actionsの設定

### S3バケットの作成
まず、S3バケットを作成します。本記事ではS3バケット名は`qiita-github-actions-bucket`とします。

### IAMユーザーの作成
次に、GitHub ActionsからS3バケットにアップロードするためのIAMユーザーを作成し、作成したユーザーに必要なIAMポリシーを割り当てます。IAMユーザーは`プログラムによるアクセス`にチェックをいれて作成してください。そして以下のポリシーを新規作成しユーザーに割り当ててください。
（本記事で作成するIAMユーザーは先程作成したS3バケット`qiita-github-actions-bucket`のみにアクセス可能なIAMポリシーを割り当てています。）

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::qiita-github-actions-bucket",
                "arn:aws:s3:::qiita-github-actions-bucket/*"
            ]
        }
    ]
}
```
IAMユーザーの作成が完了すると、`アクセスキーID`と`シークレットアクセスキー`が画面に表示されます。これらの値は後ほど使用するため、CSVをダウンロードもしくは値を控えておいてください。

### GitHubリポジトリの設定
次に、gitリポジトリを作成してください。本記事では例として`qiita-github-actions-repo`という名前のレポジトリを作成します。
（gitリポジトリをローカルPCで作成した場合は、`git remote add`を実行し、GitHubで作成したリポジトリが見れる状態にしてください。）
そしてリポジトリの`Settings`タブから`Secrets`を開き、`New repository secret`を押し、GitHub Actions用の環境変数を登録します。以下3点の環境変数を登録してください。

|Secretsに登録する変数名  |説明  
|---|---
|`AWS_ACCESS_KEY_ID`  | 先程作成したIAMユーザーの`アクセスキーID`
|`AWS_SECRET_ACCESS_KEY`  | 先程作成したIAMユーザーの`シークレットアクセスキー`
|`AWS_S3_BUCKET_REGION` | S3バケットを作成したリージョン
|`S3_UPLOAD_BUCKET` | 先程作成したS3バケット名

### GitHub Actionsのワークフローの作成
次に、GitHub Actionsのワークフローを作成します。リポジトリ直下に`/.github/workflows/s3upload.yml`ファイルを作成し、以下を記述します。
GitHub Actionsで実行しているステップは以下の通りです。

| ステップ名 | ステップ詳細
|---|---
| Configure AWS Credentials | GitHub Secretsに登録したAWS Credentialsを環境変数として登録
| Python to Zip | S3に送信するためのディレクトリ`s3upload`を作成、Lambda用のPythonである`main.py`を`/s3upload/main_py.zip`としてzip圧縮
| Upload Zip to S3 | `/s3upload/main_py.zip`をS3にアップロード

また、`main`ブランチへの`push`がトリガーとなっています。

```/.github/workflows/s3upload.yml
name: Amazon S3 Upload

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 3

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_S3_BUCKET_REGION }}

      - name: Python to Zip
        run: |
          mkdir ./s3upload
          zip ./s3upload/main_py ./main.py

      - name: Upload Zip to S3
        env: 
          S3_UPLOAD_BUCKET: ${{ secrets.S3_UPLOAD_BUCKET }}
        run: |
          aws s3 sync ./s3upload s3://$S3_UPLOAD_BUCKET/ --quiet
```

`git push`し、GitHubのActionsタブを開くと先程の`git push`によってGitHub Actionsが正常に動作したかどうかがわかります。

### Lambdaにデプロイするためのプログラムの作成
次に、Lambda用のプログラムを作成します。本記事ではPythonプログラムを例にとり解説します。

```main.py
def main():
    print('Hello World')
```


## CloudFormationの設定
### CloudFormationテンプレートの作成
S3にアップロードしたzipファイルをLambdaにデプロイするCloudFormationを作成しました。リポジトリ直下（`main.py`と同じディレクトリ）に作成してください。

```template.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: This is a CloudFormation that deploys a Lambda function that outputs Hello World.

Resources:
  HelloPyLambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      Handler: main.main
      Runtime: python3.8
      Code:
        S3Bucket: qiita-github-actions-bucket
        S3Key: main_py.zip
      Role: !GetAtt 
        - LambdaExecutionRole
        - Arn
      Description: ''
      MemorySize: 128
      Timeout: 10
      
  LambdaExecutionRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
```

### GitHub Actions実行
CloudFormationテンプレートを作成したあと、もう一度mainブランチに`push`してください。S3にこちらのテンプレートがアップロードされます。

### CloudFormationでLambdaをデプロイ
こちらが最後の手順です。AWSコンソールでCloudFormationを開き、Amazon S3 URLに作成したCloudFormationテンプレートファイルのURLを入力します。
URLは以下のフォーマットなっているため、ご自分で作成されたS3バケット名等を適宜代入してください。

```
https://<s3_bucket_name>.s3-<s3_bucket_region>.amazonaws.com/<cloudformation_template>.yml
# 例：https://qiita-github-actions-bucket.s3-ap-northeast-1.amazonaws.com/template.yml
```
URLの入力後、スタックを作成するとLambdaがデプロイされます。