LoginSignup
9
3

More than 1 year has passed since last update.

【AWS】CircleCIを使ってコンテナイメージをAWS LambdaへデプロイするCI/CD環境を構築してみた

Last updated at Posted at 2021-11-18

この記事は、AWS Lambda と Serverless Advent Calendar 2021 16日目の投稿です。

1.はじめに

2020年12月にAWS Lambda はコンテナイメージのサポート開始を発表しました。
ECSやEKSといったコンテナ系のサービスだけでなく、Lambdaでもコンテナネイティブな開発環境を作ることができます。

これまでのLambda関数のデプロイ方法は下記の通りです。

  • マネージメントコンソールからソースコードを編集する
  • ZIPファイルをアップロードする
  • Amazon S3に配置したZIPファイルをデプロイする

上記の3つに加えてECRに配置したコンテナイメージをデプロイする方式が利用可能になりましたので、
こちらを利用した開発ワークフローをCI/CD環境をCircleCIで構築してみました。


2.本記事の対象者

  • CircleCIを利用したい方
  • コンテナイメージをLambdaにデプロイしたい方

3.CircleCIとは

image.png


4.AWS Lambdaにデプロイするコンテナイメージ

GO言語で作成したAPIとなります。本記事はこちらのコンテナイメージを利用しています。

  • 開発したもの
    • DynamoDBへ接続をサーバレスで実装
  • APIエンドポイント
    • https://*************.execute-api.ap-northeast-1.amazonaws.com/dev/V1/<コマンド>
  • GETリクエスト
コマンド パラメータ 説明
actuator-health なし DynamoDBへ接続し値が返却されるかどうかを確認し、
接続有無をJSON形式で通知

5.CI/CD構成

circleci_gaiyou.png

①GitHubへpush
②指定のリポジトリにpushされたことをトリガーにして、CircleCIを起動します。
③リポジトリ内のconfig.ymlの構成に従い、コンテナイメージを作成後にECRにPush
④Lambdaの更新・作成
⑤③でPushしたイメージを取得する


6.IAMユーザの作成

CircleCIからAWSのリソースを扱うためには、CircleCI用のIAMユーザーを作成・権限付与をし、アクセスキーを払い出す必要があります。ロールは以下の2つを付与します。

  • AmazonEC2ContainerRegistryFullAccess
  • AWSLambda_FullAccess

作成したIAMユーザーのアクセスキーとシークレットアクセスキーはCircleCIの環境変数へ後ほど使うので、忘れないようにどこかに控えておきます。


7.CI/CDのセットアップ

CI/CDに必要なファイルを作成していきます。

 (プロジェクトルートディレクトリ)/
     └── src
     │    └── ・・・・        # サンプルプログラム
     └── Dockerfile      # コンテナイメージ作成用ファイル
     └── .circleci
          └── config.yml   # CircleCIの設定ファイル

7-1.Dockerfileの作成

Dockerファイルの設定は下記の通りです。

# FROM でベースのコンテナイメージを指定
FROM golang:1.17.2-alpine
MAINTAINER kemper0530

ENV GOPATH /go
ENV PATH=$PATH:$GOPATH/src

# COPYでコンテナイメージにバンドルするファイルを指定
ENV PATH=$PATH:$GOPATH/src/github.com/kemper0530/go-lambda-api-demo/src
WORKDIR $GOPATH/src/github.com/kemper0530/go-lambda-api-demo
COPY  /src $GOPATH/src/github.com/kemper0530/go-lambda-api-demo/src
# モジュールの依存関係を作成
RUN go mod init go-lambda-api-demo
RUN go mod tidy
# ビルド
RUN GOOS=linux go build -o go-lambda-api-demo ./src
# ENTRYPOINTでLambda Functionのエントリポイント(handler)を指定
ENTRYPOINT ["/go/src/github.com/kemper0530/go-lambda-api-demo/go-lambda-api-demo"]

7-2.config.ymlの作成

config.ymlファイルの設定は下記の通りです。
CircleCIのOrbsを利用するためバージョン2.1を指定しております。

version: 2.1
# Orbsの利用
orbs:
  aws-ecr: circleci/aws-ecr@6.15.3
  aws-cli: circleci/aws-cli@2.0
  go: circleci/go@1.7.0
# ワークフローの定義
workflows:
  version: 2
  test_and_build_and_deploy:
    jobs:
      - test
      - aws-ecr/build-and-push-image:
          region: AWS_REGION
          account-url: AWS_ECR_ACCOUNT_URL
          repo: "${AWS_RESOURCE_NAME_PREFIX}"
          create-repo: true
          requires:
            - test
          filters:
            branches:
              only: main
      - aws-lambda-deploy:
          requires:
            - aws-ecr/build-and-push-image
          filters:
            branches:
              only: main
# ジョブの定義
jobs:
  test:
    executor:
      name: go/default
      tag: '1.17'
    steps:
      - checkout
      - go/load-cache
      - go/mod-download
      - go/save-cache
      - go/test:
          packages: ./tests
          covermode: atomic
          failfast: true
          race: true
  aws-lambda-deploy:
    executor: aws-cli/default
    steps:
      - checkout
      - aws-cli/setup
      - run:
          name: Deploy Lambda
          command: |
            DIGEST=$(aws ecr list-images --repository-name ${AWS_RESOURCE_NAME_PREFIX} --out text --query "imageIds[?imageTag=='${AWS_ECR_IMAGE_TAG}'] | [0].imageDigest")
            if ! aws lambda get-function --region ${AWS_REGION} --function-name ${LAMBDA_FUNCTION} > /dev/null 2>&1; then
              aws lambda create-function \
               --function-name ${LAMBDA_FUNCTION} \
               --package-type Image \
               --code ImageUri=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${AWS_RESOURCE_NAME_PREFIX}@${DIGEST} \
               --region ${AWS_REGION} \
               --environment "Variables={GO_ENV="production",PORT=${PORT}}" \
               --role ${AWS_LAMBDA_ROLE}
            else
              aws lambda update-function-code \
               --function-name ${LAMBDA_FUNCTION} \
               --image-uri ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${AWS_RESOURCE_NAME_PREFIX}:${AWS_ECR_IMAGE_TAG} \
               --region ${AWS_REGION}
            fi



7-2-1.Orbsの宣言

そもそも、CircleCIに限らずCI/CDで設定ファイルの中身は結局の所、「コマンドベース」で指定していく形となっていました。
そんな煩わしい定義を「パッケージ」としてまとめたのがorbsです。

例えば、ECRへコンテナイメージをPushする場合は下記の作業が必要となります。

  1. docker buildコマンドでコンテナイメージを作る
  2. docker loginでECRにログインする
  3. docker tagで1で作ったコンテナイメージに、AWS指定の名前・タグ付けを行う
  4. 3のイメージをECRにdocker pushする

上記の作業に対して何のコマンドを実行するのかを定義しなくてはいけませんでした。
Orbsを利用することでconfig.ymlへの記載内容を大幅に簡素化できます。

今回は「aws-ecr」と「aws-cli」と「circleci/go」のOrbsを利用します。

orbs:
  aws-ecr: circleci/aws-ecr@6.15.3
  aws-cli: circleci/aws-cli@2.0
  go: circleci/go@1.7.0

7-2-2.テストの実施

Orbsを利用してgoのテストを実施します。

config.yml
    executor:
      name: go/default
      tag: '1.17'
    steps:
      - checkout
      - go/mod-download
      - go/test:
          packages: ./tests

7-2-3.ECRへのPUSH

Orbsを利用してコンテナイメージをECRへPUSHします。
『aws-ecr/build-and-push-image』と記載してパラメータを設定するだけで、
パラメータ内容に基づいてコンテナイメージ作成してECRへPUSHまでを実施してくれます。

...
      - aws-ecr/build-and-push-image:
          region: AWS_REGION
          account-url: AWS_ECR_ACCOUNT_URL
          repo: "${AWS_RESOURCE_NAME_PREFIX}"
          create-repo: true
...
  • reigion リージョン
  • account-url AmazonECRアカウントのURL(例:{アカウントID} .dkr.ecr.{リージョン}.amazonaws.com)
  • repo リポジトリ名
  • create-repo 対象リポジトリ名がなかったら新規作成するか

7-2-4.Lambdaへのデプロイ

Lambdaへのデプロイについてはaws-cliのOrbsを利用し、AWS-CLIを利用し、
肝心なデプロイ部分はコマンドを書いていきます。(ECR→LambdaへデプロイするOrbsがないため)

・・・
  aws-lambda-deploy:
    executor: aws-cli/default
    steps:
      - checkout
      - aws-cli/setup
      - run:
          name: Deploy Lambda
          command: |
            DIGEST=$(aws ecr list-images --repository-name ${AWS_RESOURCE_NAME_PREFIX} --out text --query "imageIds[?imageTag=='${AWS_ECR_IMAGE_TAG}'] | [0].imageDigest")
            if ! aws lambda get-function --region ${AWS_REGION} --function-name ${LAMBDA_FUNCTION} > /dev/null 2>&1; then
              aws lambda create-function \
               --function-name ${LAMBDA_FUNCTION} \
               --package-type Image \
               --code ImageUri=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${AWS_RESOURCE_NAME_PREFIX}@${DIGEST} \
               --region ${AWS_REGION} \
               --environment "Variables={DBMS=${DBMS},DB_NAME=${DB_NAME},DB_PASS=${DB_PASS},DB_PROTOCOL=${DB_PROTOCOL},DB_USER=${DB_USER},PORT=${PORT}}" \
               --role ${AWS_LAMBDA_ROLE}
            else
              aws lambda update-function-code \
               --function-name ${LAMBDA_FUNCTION} \
               --image-uri ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${AWS_RESOURCE_NAME_PREFIX}:${AWS_ECR_IMAGE_TAG} \
               --region ${AWS_REGION}
            fi
・・・

7-3.CircleCIの利用準備

下記へアクセスしログインを実施する
https://circleci.com/ja/vcs-authorize/

スクリーンショット 2021-11-14 22.52.53.png


対象のリポジトリを選択し「SetupProject」を押下
スクリーンショット 2021-11-08 19.58.26.png


「Let's Go」を押下
スクリーンショット 2021-11-08 20.20.54.png


CircleCIの環境変数に設定します
スクリーンショット 2021-11-16 21.38.53.png

  • AWS_REGION: リージョン(例:ap-northeast-1)
  • AWS_ECR_ACCOUNT_URL: {アカウントID}.dkr.ecr.{リージョン}.amazonaws.com
  • AWS_RESOURCE_NAME_PREFIX: ECRのリポジトリ名
  • AWS_ACCESS_KEY_ID: アクセスキー
  • AWS_SECRET_ACCESS_KEY: シークレットキー
  • LAMBDA_FUNCTION: Lambdaの関数名
  • AWS_LAMBDA_ROLE: Lambdaで利用するIAMのロール
  • AWS_ACCOUNT_ID: AWSアカウントID

8.デモ

mainブランチにpushするとCircleCIが動き出す
スクリーンショット 2021-11-14 17.08.10.png

完了
スクリーンショット 2021-11-14 17.11.59.png

ECRのマネージメントコンソール確認
スクリーンショット 2021-11-14 17.14.01.png

Lambdaの関数を確認
スクリーンショット 2021-11-14 17.12.58.png
スクリーンショット 2021-11-14 17.13.15.png


9.まとめ

  • コンテナイメージをサポートしたことで、ECS、EKSと同じようなデプロイフローに乗せることができるようになる。(ECRに集めてそこからデプロイ)
  • 個人開発程度であれば、コスト面でもLambdaのコンテナイメージは非常に有効。
    • ECS,EKSは起動している分課金が発生するが、Lambdaはリクエスト100万件あたり0.20USD(東京リージョン)
  • CircleCI Orbsを利用することで設定ファイルの記述が簡素化できたので、設定ファイルの属人化が排除できると感じた。
  • 基本的にLambdaを利用する場合、Lambda以外のリソースも必要になるのでそれらを含めたデプロイを考える必要がある。
    • HTTPリクエストを受けたい場合はAPIGatewayが必要だし、Cronみたいに定期実行させたい場合にはEventBridgeが必要である。純粋にLambdaだけで済むケースはない。

参考

9
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3