👻

コンテナイメージを利用したAWS LambdaのCDパイプラインの作成をしたときのメモ

2022/06/19に公開

はじめに

コンテナイメージを利用した AWS Lambda のデプロイを自動化したときの話です。
筆者はこれまでは AWS 上の GUI ポチポチ民だったのですが、
毎回それをおこなうのはさすがに面倒くさいので勉強がてら CD パイプラインの作成をしました。
今まで自分で CI/CD の設定や作成をおこなったことがなかった初心者なので間違っている部分や効率が悪いことをしている可能性は大ですので、
そのあたりは目をつぶっていただくか、ご指導のほどよろしくお願いします。

作成したパイプラインの処理の流れ

今回は GitLab CI/CD を利用しました。
理由は Gitlab で lambda 関数を置いたリポジトリを管理していたからです。(理由はないのと一緒)
処理のイメージは以下の流れになります。

  1. main ブランチに作業ブランチのマージ
  2. マージしたタイミングで Gitlab CI/CD のジョブが走る
  3. ジョブ内でコンテナイメージのビルドとデプロイを行う。
  4. コンテナイメージのデプロイ後、Lambda 関数の参照コンテナイメージの更新を行う。

以上が今回のパイプラインの大まかな流れになります。
またディレクトリ構成は以下のようになっています。

repository-root/
 └ app/
    └lambda-function
     ├ .env
     ├ app.py
     ├ Dockerfile
     ├ requirement.txt
     └ trust-policy.json
 ├ .gitlab-ci.yml
 └ deploy.sh

やったこと

まずは今まで GUI でポチポチ勢だったので、遠回りですが手動で CLI でデプロイできるかを試しました。
今回は aws cli を利用しました。(それ以外の方法での aws の操作を知らない。)

1. CLI でのデプロイ

0.準備

まずは aws-cli のインストール。
こちらを参考にインストールしました。

またコンテナを扱うための準備。
普段から Docker を利用しているので今回も Docker を利用。
インストールしていない場合はDocker インストールガイドを参考にするのがよさそうです。

また以下出てくる変数は下記のように読み替えてください

$LAMBDA_DIR_NAME={lambda関数のディレクトリ名(上の図でいうlambda-function)}
$IMAGE_REPO_NAME=lambda-$LAMBDA_DIR_NAME
$IMAGE_REPO_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME
$ROLE_ARN=arn:aws:iam::$AWS_ACCOUNT_ID:role/$IMAGE_REPO_NAME

1.Docker イメージを作成する

lambdal 関数のイメージの作成のために lambda 関数のディレクトリ配下に Dockerfile を作成します。
Dockerfile の中身は下記のような感じ

Dockerfile
FROM public.ecr.aws/lambda/python:3.9

COPY requirements.txt  .
RUN pip install --upgrade pip
RUN pip install -r requirements.txt --target "${LAMBDA_TASK_ROOT}"

COPY app.py ${LAMBDA_TASK_ROOT}

CMD ["app.lambda_handler"]

余談ですが requirement.txt 内には app.py で使用しているパッケージを記述しています。
python で lambda 使うなら chalice 使えばよくない?ってなるんですが多くの場合それでいいと思います。
しかし pdf2image を利用するときに poppler をインストールしないといけないといった場合などはそれらをインストールしたコンテナイメージを利用するほうが都合がよかったです。(chalice ではできなかった)

そうすればあとは Docker イメージをビルドするだけになります。
以下のコマンドで build できます。

docker build -t $IMAGE_REPO_NAME

2. AWS ECR にイメージを登録する

aws の公式ドキュメントを参考にログイン、tag の作成、push の流れで登録できます

  1. ECR にログイン
aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
  1. タグの作成
docker tag $IMAGE_REPO_NAME:latest $IMAGE_REPO_URI:latest
  1. イメージの登録
docker push $IMAGE_REPO_NAME:latest

<p style="text-indent:2em;">更新する際にECRリポジトリがない場合は以下コマンドで作成しておきます</p>

  aws ecr create-repository --repository-name $IMAGE_REPO_NAME

3. AWS Lambda の更新

上で登録した ECR のイメージで lambda 関数の更新を行います。
すでに lambda 関数が存在している場合は次のコマンドで更新できます。

deploy_lambda_by_image $IMAGE_REPO_NAME $IMAGE_REPO_URI $AWS_ACCOUNT_ID

lambda 関数がない場合は lambda 関数の作成をする必要があります。
role の作成したのち lambda 関数の作成ができます。

今回は GUI で作成する際に付与されるっものと同じAWSLambdaBasicExecutionRoleというロールで作成します。

aws iam create-role --role-name $IMAGE_REPO_NAME --assume-role-policy-document file://trust-policy.json
   aws iam attach-role-policy --role-name $IMAGE_REPO_NAME --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

作成したロールを使用して lambda 関数の作成

 aws lambda create-function --function-name $IMAGE_REPO_NAME --package-type Image --code ImageUri=$IMAGE_REPO_URI:latest --role $ROLE_ARN

以上で AWS-CLI と Docker を用いて手動でコンテナイメージを利用した AWS Lambda 関数の更新ができました。

CD パイプラインの記述

上記で手動での更新ができたのでこれを自動化していきます。
今回は Gitlab-CI/CD を利用しました。
Gitlab-CI/CD はリポジトリ直下に.gitlab-ci.ymlというファイルが存在すればそのファイルの記述内容が勝手に実行されると思っています。(多分)

以下.gitlab-ci.ymldeploy.shの中身です。

.gitlab-ci.yml
image:
  name: docker:latest

services:
  - docker:dind

stages:
  - deploy

deploy:
  stage: deploy
  only:
    - main
  variables:
    AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
    AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
    AWS_DEFAULT_REGION: $AWS_DEFAULT_REGION
  script:
    # Configure awscli
    - apk add python3 py3-pip
    - pip3 install --upgrade pip
    - pip3 install awscli --upgrade
    - aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
    - aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
    - aws configure set default.region $AWS_DEFAULT_REGION
    # デプロイ
    - sh deploy.sh
deploy.sh
#!/bin/bash
set -euxo pipefail

create_docker_image() {
  AWS_ACCOUNT_ID=$1
  LAMBDA_DIR_NAME=$2

  IMAGE_REPO_NAME=lambda-$LAMBDA_DIR_NAME
  IMAGE_REPO_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME

  # 2. ECRリポジトリを作成(ディレクトリ名を用いる)
  if [ -z "$(aws ecr describe-repositories --repository-names $IMAGE_REPO_NAME)" ]; then
    aws ecr create-repository --repository-name $IMAGE_REPO_NAME
  fi

  # 3. Dockerfileからイメージをビルドし、「2」で作成したリポジトリにプッシュ
  cd ./app/$LAMBDA_DIR_NAME
  docker build -t $IMAGE_REPO_NAME .
  docker tag $IMAGE_REPO_NAME:latest $IMAGE_REPO_URI:latest
  docker push $IMAGE_REPO_URI:latest

  # ECRにpushしたイメージでlambda関数の更新
  deploy_lambda_by_image $IMAGE_REPO_NAME $IMAGE_REPO_URI $AWS_ACCOUNT_ID
}

deploy_lambda_by_image() {
   IMAGE_REPO_NAME=$1
   IMAGE_REPO_URI=$2

  # ECRのイメージを利用してlambdaを最新のコンテナイメージで更新する
  aws lambda update-function-code --function-name $IMAGE_REPO_NAME --image-uri $IMAGE_REPO_URI:latest
}

AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)

# 1. ECRにログイン
aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com

# ディレクトリ名を取得
LAMBDA_DIR_NAMES=`cd app && ls -l | awk '$1 ~ /d/ {print $9}'`
for LAMBDA_DIR_NAME in $LAMBDA_DIR_NAMES
do
  # 並列に実行する
  create_docker_image $AWS_ACCOUNT_ID $LAMBDA_DIR_NAME &
done

.gitlab-ci.ymlの方から見ていく。

only:
  - main

この部分ではどのブランチに push された時に CI/CD を実行するかを指定できます。
今回では deploy というジョブは main ブランチにプッシュ(マージ)されたときに実行されるということになります。

次にvariablesを見ていきます。

Gitlab-CI/CD では環境変数の設定方法が 2 種類あります。

  1. GUI 上でrepository -> setting -> CI/CD -> variablesで環境変数を設定

  2. .gitlab-ci.yml内の variables 内で指定

1.の方法ではファイルに直接記述しないで済むので SERCRET_KEY など他人から見えない方が都合のいいものはこちらの方法を利用して設定する方がいいという認識です。

2.の方法では逆にファイルに直接見えても問題ないものを記述するのがいいという認識です。

残りは基本的に上記の手動で行っていたことを script 以下に記述してあります。

.gitlab-ci.ymlには deploy をするための準備部分を記述してあります。
今回は pip と aws-cli のインストール、aws-cli に環境変数の設定を行っています。
variables に登録している変数は${環境変数名}で呼び出しが可能です。

deploy.shでは手動で行っていた CLI コマンドを記述しているものをまとめたものになります。

# ディレクトリ名を取得
LAMBDA_DIR_NAMES=`cd app && ls -l | awk '$1 ~ /d/ {print $9}'`
for LAMBDA_DIR_NAME in $LAMBDA_DIR_NAMES
do
  # 並列に実行する
  create_docker_image $AWS_ACCOUNT_ID $LAMBDA_DIR_NAME &
done

この部分は今回の例では出てきていませんが、一つの repository で複数の lambda 関数を管理したいときに一つずつ順番にデプロイをしているとかなりの時間がかかってしまうので並列実行しています。
自動で並列実行を可能にするため lambda の管理に app 配下に lambda 関数を作成するなどある程度のルールが必要にはなってきます。

参考: 私が考える Lambda 周りの IaC 及び CI/CD のベタープラクティス

以上で main ブランチに push されたときに Lambda 関数を更新する CD パイプラインが作成できました。

さいごに

コンテナイメージを利用した Lambda 関数の CI/CD の記事があまりなかったので調査段階ではかなり苦戦を強いられました。

しかし手動でやっていたことを自動化するという考え方を軸に CLI の使い方を調べたら意外とすんなり実装できました。

これが最適解かはわかりませんが一旦 CI/CD が実装できるようになったのでこれからも色々記述していこうと思います。

全部無料でできるので、CI/CD 初心者の方はやってみると考え方などわかったりするので一度やってみるといいかもしれません。

私は今度は今回作成したものを Terraform で作り直したりしてみようかなと思います。

参考にした記事

GitHubで編集を提案

Discussion