CircleCI + Serverless でコンテナ環境を AWS Lambda に自動デプロイ




Lambda で外部ライブラリを使いたいとき
実行環境を Docker イメージにして ECR から実行することで実現できますが、自分でやるのはちょっと面倒です
というわけで、CircleCI に任せてしまいましょう

また、この技術を使って**アイコンを手書きで探せるアプリ**を開発しました！
ぜひ使ってみてください！

https://qiita.com/GleamingCake/items/19e865486a1b9f1fedb9


# やりたいこと

ローカルで作った Lambda 用のコンテナを、GitHub の main ブランチにプッシュしたら Lambda へ自動デプロイされるようにしたい

# フロー

- GitHub の main ブランチの更新を CircleCI が感知
- CircleCI のコンテナ内で Docker イメージをビルド
- Docker イメージを ECR へプッシュ
- Serverless が ECR のイメージを使って Lambda へデプロイ

# 実装

### ECR にリポジトリ作成

ECR のプライベートリポジトリを作成しておきます
リポジトリ名はCircleCI の環境変数に入れるので控えておいてください

### IAM ユーザー作成

serverless 用の IAM ユーザーを作成します
プログラムによるアクセスを許可し、アクセスキーとシークレットアクセスキーを控えておきましょう

### CircleCI の環境変数設定

- AWS_ACCOUNTID：AWS のアカウントID
- AWS_ACCESS_KEY_ID：IAM のアクセスキー
- AWS_SECRET_ACCESS_KEY：IAM のシークレットアクセスキー
- AWS_DEFAULT_REGION：リージョン
- FUNC_NAME：先ほど作成した ECR のリポジトリ名と同じにする

### ファイル構成

```
project
├── .circleci
│       └── config.yml
├── app.py
├── Dockerfile
├── requirements.txt
└── serverless.yml
```

### app.py
TensorFlow を入れて動作確認

```python
import tensorflow as tf


def handler(event, context):
    return {
        'statusCode': 200,
        'body':
            {
                'predict': tf.__version__,
            }
    }
```

### Dockerfile
AWS のパブリックイメージからビルドしていきます

```docker
FROM public.ecr.aws/lambda/python:3.8

COPY . ${LAMBDA_TASK_ROOT}
RUN pip install -r requirements.txt
CMD ["app.handler"]
```

### requirements.txt

必要なライブラリを書いておきましょう

```
tensorflow==2.5.0
```

### serverless.yml

Lambda にデプロイする用の設定を書きます
`ECR_DIGEST` の環境変数は、ECR にプッシュしたとき返ってくるダイジェストを入れておきます
プッシュ時に決まるので、その時に環境変数に入れるようにします

```serverless.yml
service: ecr-lambda-test

provider:
  name: aws
  stage: dev
  region: ${env:AWS_DEFAULT_REGION}

functions:
  hoge:
    # ECR のプライベートリポジトリからビルドする
    image: ${env:AWS_ACCOUNTID}.dkr.ecr.${env:AWS_DEFAULT_REGION}.amazonaws.com/${env:FUNC_NAME}@${env:ECR_DIGEST}
    memorySize: 2048
    timeout: 180

```

### .circleci/config.yml

イメージは [docker](https://hub.docker.com/_/docker) を使用しました
これは コンテナ内で Docker ビルドが出来るように整えられている Alpine ベースのイメージです
本当は [amazon/aws-cli](https://hub.docker.com/r/amazon/aws-cli) を使いたかったのですが、Docker ビルドがうまくいかなかったのでこちらを採用しています

```.circleci/config.yml
version: 2.0

jobs:
  deploy_lambda_test:
    docker:
      - image: docker:19
    steps:
      # GitHub のリポジトリを持ってくる
      - checkout
      # Docker でビルドなどを行うためのセットアップ
      - setup_remote_docker
      - run:
          # serverlessをインストール。node が必要なのでそれも込み。あと IAM 情報も登録しておく。
          name: serverless install
          command: |
            apk add --no-cache nodejs npm
            npm install -g serverless
            serverless config credentials --provider aws --key ${AWS_ACCESS_KEY_ID} --secret ${AWS_SECRET_ACCESS_KEY}
      - run:
          # aws cli をインストール。alpine には curl が無いのでまずそれをインストールする。
          name: aws install
          command: |
            apk --no-cache add binutils curl
            curl -sL https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub -o /etc/apk/keys/sgerrand.rsa.pub
            curl -sLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.33-r0/glibc-2.33-r0.apk
            curl -sLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.33-r0/glibc-bin-2.33-r0.apk
            apk add --no-cache glibc-2.33-r0.apk glibc-bin-2.33-r0.apk
            curl -sL https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o awscliv2.zip
            unzip -q awscliv2.zip
            aws/install
            aws --version
      - run:
          # aws の認証を得る。パブリックイメージとプライベートリポジトリの両方。
          name: aws auth
          command: |
            aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws
            aws ecr get-login-password | docker login --username AWS --password-stdin ${AWS_ACCOUNTID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com
      - run:
          # ビルドしてデプロイ。ECR にプッシュするとダイジェストが返ってくるので、それを環境変数に追加する。（serverless が使う）
          name: build image & deploy
          command: |
            docker build -t ${FUNC_NAME} .
            docker tag ${FUNC_NAME} ${AWS_ACCOUNTID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${FUNC_NAME}
            export ECR_DIGEST=`docker push ${AWS_ACCOUNTID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${FUNC_NAME} | grep digest | cut -d ' ' -f3`
            echo ${ECR_DIGEST}
            serverless deploy

workflows:
  version: 2
  build_and_deploy:
    jobs:
      - deploy_lambda_test:
          filters:
            branches:
              # main ブランチのみ
              only: main

```

# 完成
お疲れ様でした
あとは GitHub の main ブランチにプッシュすれば自動的にデプロイされます！

# GitHub
コード全体を上げているので良かったらどうぞ

https://github.com/kobayashiharuto/lambda-circleci-test


# 参考
[Alpine ベースのコンテナイメージで AWS CLI v2 を使う](https://kiririmode.hatenablog.jp/entry/20200725/1595621558)