GithubActionsでECRのImageをもとにLambdaをデプロイする方法

## 概要
- GithubActionsと ECRのイメージをもとにLambdaをデプロイするCICDを構築したため、記録として残しておく。
- 本記事を通して、下記が実現できるように記載していく
    - ローカルでコード編集
    - Commitして、GithubへPush
    - GithubActionsが実行
    - 編集後のコードをLambdaに自動デプロイする
- 最終的な構成として、キャプチャのよう流れとなる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/416025ea-2643-0084-e630-25d3625f83bd.png)


## 前提
- Githubの操作（CommitやPushなど）経験があること
- AWSのアカウントがあること
  - この記事内ではオレゴンリージョンを使用しているため、リージョンコードが `us-west-2`で進めています。なので、東京リージョンなどを使用する際は適宜書き換えてください
- CICDの「CI」の部分は本記事には記載しません

## ECRでプライベートリポジトリを作る
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/19bbfc81-a671-ae40-3259-310feb642985.png)


## アップロードするためのLambda関数を作る&Build
#### 関数作成
  - [AWS公式リファレンス](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-image.html)を参照する
  - 最終的なディレクトリ構成
      ```sh
        ├── .github
        │   └── workflows
        │       └── cicd.yml
        ├── Dockerfile
        ├── README.md
        ├── app
        │   ├── __init__.py
        │   └── main.py
        └── requirements.txt
      ```
  - app/main.pyを作る
    ```python
    # 公式URL：https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-image.html
    import sys
    
    
    def handler(event, context):
        return "Hello from AWS Lambda using Python" + sys.version + "!"
    ```
  - Dockerfileを作る
    ```dockerfile
    FROM public.ecr.aws/lambda/python:3.12

    # Copy requirements.txt
    COPY requirements.txt ${LAMBDA_TASK_ROOT}
    
    # Install the specified packages
    RUN pip install -r requirements.txt
    
    # Copy function code
    COPY ./app/main.py ${LAMBDA_TASK_ROOT}
    
    # Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
    CMD [ "main.handler" ]
    ```
  - requirements.txtを作る
    ```txt
    boto3
    ```
#### Dockerを起動して、ローカルで動作検証
```sh
# ImageをBuild
docker build --platform linux/amd64 -t docker-image:test .

# コンテナを起動
docker run --platform linux/amd64 -p 9000:8080 docker-image:test

# curlでリクエストする
curl -i "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'
```
- うまくいけば、下記のようなレスポンスが返る
```sh
HTTP/1.1 200 OK
Date: Wed, 29 May 2024 15:27:12 GMT
Content-Length: 114
Content-Type: text/plain; charset=utf-8

"Hello from AWS Lambda using Python3.12.3 (main, Apr 29 2024, 08:31:45) [GCC 11.4.1 20230605 (Red Hat 11.4.1-2)]!"%   
```

#### DockerをECRにPushする
- 作成したECRのリポジトリを開き、プッシュコマンドを表示する ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/d9ffcd6a-42cb-c4b1-e9ed-da9d9f474262.png)
- コマンドを順次叩く
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/c7943585-a4db-4a6d-eb39-823752fe2a3d.png)

:::note info
Terraformの利用経験がある方向け
この後記述されているLambdaとGithubActionsとの連携用のロールをTerraformで作りたい方は、リポジトリの方に手順あるので、参考にしてください。（init、applyのみで作れます）
https://github.com/Kyohei-takiyama/sample-upload-lambda-deploy-gha?tab=readme-ov-file#terraform
:::

## Lambdaを作る
- コンテナイメージは作成したレジストリを指定する
- ロールなどは適宜設定する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/8f7c6b5e-2f2d-c57f-1aad-b633bc4c6b64.png)
- そのまま変更せずテストし、テストが成功することを確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/fdc15582-1519-9d98-4917-fb022b0e7205.png)

## GithubActionsを作る
- 参考
  - クラスメソッドさんの記事がすごくわかりやすく一部引用などさせていただきました。
  - https://dev.classmethod.jp/articles/allowing-assumerole-only-for-specific-repositories-and-branches-with-oidc-collaboration-between-github-actions-and-aws/
  - https://dev.classmethod.jp/articles/github-actions-aws-sts-credentials-iamrole/ 

#### GithubActionsからAWSに対して、アクセストークンを取得するためのIAMロールを作成する
- アクセストークンを取得するために、AWS側にIAMロールの作成を行うのですが、この方の[記事](https://qiita.com/suzuki0430/items/c1fbde1550950ca41f7f)が非常にわかりやすいので、参考に作成していただければと思います。

- 作成したロールに対して許可を追加する
 　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/6528ce11-4dce-082d-dca3-664dc5d2ddea.png)
- インラインポリシーを作成する
　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/e33c4a03-c9f9-a34b-7cb4-c6edd2492954.png)
- サービスから「Lambda」を選択して下記の許可を追加
  - GetFunctionConfiguration
  - UpdateFunctionCode
- 「Elastic Container Registry」を選択して下記キャプチャの許可を追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/1001612d-400e-4cbc-a5e3-e72c33b13768.png)
- 下記のように作れたら、arnをコピーしておく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/6a49aa12-ffb8-7883-2f22-b84d4de17035.png)

### IAMロールのARNをGithubのシークレットに保存する
 - GithubのリポジトリのSettingsを開き、
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/c3ae99eb-0599-7307-bf70-cbd0a6728aa3.png)
- シークレット作成画面を開く
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/2829e1d3-261d-7238-25e4-35662465518a.png)
- arnを登録する
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/85c4c298-e1a2-ecb0-dd30-2583fbe95a18.png)

- GHAをかく

- 流れとしては下記となる
  - トリガータイミングの指定
  - 環境変数を定義
  - ワークフロー内でIdトークンを取得したり、コンテンツに対する読み取り権限を付与
  - jobのstep
    - コンテンツを読み込み
    - IAMロールのarnをGithubのシークレットから取得
    - AWSへのアクセストークン（認証）をリクエスト
    - ECRへのログイン
    - DockerでImageをBuild及びPush（詳しくは[公式参照](https://github.com/docker/build-push-action)）
    - ECRに格納されたImageをもとにLambdaをデプロイ(詳しくはupdate-function-codeの[公式参照](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/update-function-code.html))

```yml .github/workflows/cicd.yml
name: Deploy to AWS

# トリガーのタイミングは適宜変更
on:
  push:
    branches:
      - main
    paths:
      - ".github/workflows/**"
      - "app/**"

env:
  AWS_REGION: us-west-2
  ECR_REPOSITORY: sample-lambda
  FUNCTION_NAME: sample-lambda-function

# ワークフロー内で、Idトークンを使用するために権限を付与
# https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-permissions-settings
permissions:
  id-token: write
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout the code
        uses: actions/checkout@v4

      - name: set aws role to assume
        run: echo "AWS_ROLE_ARN=${{ secrets.AWS_ROLE_TO_ASSUME }}" >> $GITHUB_ENV

        # 「aws-actions/configure-aws-credentials」で、
        # GitHub OIDC プロバイダーから JWT を受け取り、アクセス トークンを AWS に要求
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-region: ${{ env.AWS_REGION }}
          role-to-assume: ${{ env.AWS_ROLE_ARN }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
        with:
          platforms: linux/arm64

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push image
        uses: docker/build-push-action@v5
        id: build-image
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          provenance: false
          platforms: linux/arm64

      - name: Deploy to Lambda
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        run: |
          aws lambda update-function-code --function-name ${{ env.FUNCTION_NAME }} --image-uri $ECR_REGISTRY/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
          # wait処理を入れる
          # https://docs.aws.amazon.com/cli/latest/reference/lambda/wait/#cli-aws-lambda-wait
          aws lambda wait function-updated --function-name ${{ env.FUNCTION_NAME }}
```

- Pushして確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/f450a63d-15fb-65ee-f258-8b08b51dd27b.png)
- ECRのイメージが作成されていることを確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/990ef7fe-b558-59da-f289-b9f121e3dc8f.png)
- Lambdaがデプロイされていることを確認する

#### Lambdaコードを修正してデプロイする
```python app/main.py
# https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-image.html
import sys


def handler(event, context):
    return "MainへのPushを通じでデプロイされるように変更" + sys.version + "!"

```

- pushしてGHAが成功したことを確認してLambdaをテストしてみる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/2fcd2fe9-d7ac-7b44-40b8-6404b97f9b54.png)

- 以上の結果から、「ローカルでコード編集=>Commitして、GithubへPush=>GithubActionsが実行=>編集後のコードがLambdaにデプロイ」までの一連のCDフローを構築することができたと思われる（CIのテストの部分は省略）


## まとめ
- 一見難しそうなことは、最初の一歩を踏み出すのが大変だが、実は踏み込んでみると今までわからなかったことが一気にわかるようになったりするため楽しい。
- 意外とやることが多く、また色々な用語やリソースが出てくるため最初は全く理解できなかったが、自分でいろいろ調べながら簡単な構成を作ることで理解が深まった。（今後はもっと応用できそうという自信も持てた）
- また、色々な人が記事を残してくれているおかげで、用語や認証周りの流れなどは理解がしやすかった（一人では無理でした）
- 私も誰かのためになればと思い、記事を残そうと改めて思った。本記事が誰かの一助になれば幸いです。
- 作成したコード
　https://github.com/Kyohei-takiyama/sample-upload-lambda-deploy-gha/tree/main