この記事は、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とは
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構成
①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する場合は下記の作業が必要となります。
- docker buildコマンドでコンテナイメージを作る
- docker loginでECRにログインする
- docker tagで1で作ったコンテナイメージに、AWS指定の名前・タグ付けを行う
- 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のテストを実施します。
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/
- 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が動き出す
↓
完了
↓
ECRのマネージメントコンソール確認
↓
Lambdaの関数を確認
9.まとめ
- コンテナイメージをサポートしたことで、ECS、EKSと同じようなデプロイフローに乗せることができるようになる。(ECRに集めてそこからデプロイ)
- 個人開発程度であれば、コスト面でもLambdaのコンテナイメージは非常に有効。
- ECS,EKSは起動している分課金が発生するが、Lambdaはリクエスト100万件あたり0.20USD(東京リージョン)
- CircleCI Orbsを利用することで設定ファイルの記述が簡素化できたので、設定ファイルの属人化が排除できると感じた。
- 基本的にLambdaを利用する場合、Lambda以外のリソースも必要になるのでそれらを含めたデプロイを考える必要がある。
- HTTPリクエストを受けたい場合はAPIGatewayが必要だし、Cronみたいに定期実行させたい場合にはEventBridgeが必要である。純粋にLambdaだけで済むケースはない。
参考