AWS新機能「AWS Lambda Container Image Support」利用時のCI/CDパイプライン構築

AWS新機能「AWS Lambda Container Image Support」利用時のCI/CDパイプライン構築

はじめに

2020年11月30日から開催されているAWSのグローバルカンファレンスAWS re:Invent 2020 にて、「AWS Lambda Container Image Support」 という新サービスが発表されました。

AWS Lambda Container Image Supportは、AWSのサーバーレスコンピューティング「AWS Lambda」にて、仮想コンテナ技術を利用できるようになる画期的なサービスです。

本検証では、AWS Lambda Container Image Supportを使い、任意のDockerイメージをデプロイするまでのContinuous Integration(CI) / Continuous Delivery(CD)の実装をご紹介いたします。

検証の背景と目的

MMMでは運用しているLambdaワークロードで wget および awscli を利用しているケースがあります。

しかし、Lambda実行環境には wget がインストールされておらす、これまではビルドした wget バイナリを追加して利用するという手法を取っていました。ビルドの実行環境によって生成されるバイナリは異なるため、今後のLambda環境の変更により、バイナリが動作しなくなるというリスクが内包していました。

また、AWSコマンドスクリプトを作成してプロジェクトに追加することによって、Lambda内で awscli を利用し aws s3 sync コマンドを実行しているケースもあります。

今回のAWS Lambda Container Image Supportの活用により、wget そして awscli をDockerビルドプロセスの中でインストールし、プロジェクト構成の簡略化及びワークロードの安定性を高めることを目的としています。

検証の流れ

  1. python:3.9-alpine3.12をベースイメージとしawsclipip wgetapk addでそれぞれインストール
  2. Amazon ECRにリポジトリを作成
  3. ビルドしたコンテナイメージをAmazon ECRにプッシュ
  4. プッシュしたコンテナイメージからLambda関数を作成
  5. 上記1-4を自動化するCI/CDパイプラインを実装

対象ワークロードの概念は以下のとおりです。

ワークロードイメージ図

1. python:3.9-alpine3.12ベースのDockerイメージビルド

今回はpython:3.9-alpine3.12をベースイメージとして使用し、このイメージに対して wgetawscliをインストールしていきます。

プロジェクトにDockerを利用する前後で、下記ディレクトリ構成に変更しています。

Docker利用前

.
├── aws
├── bin
│   └── wget
├── main.py
└── rename.sh

Docker利用後

.
├── .circleci
│   └── config.yml
├── Dockerfile
├── app
│   ├── main.py
│   └── rename.sh
└── scripts
    ├── circleci_lambda.sh
    ├── create_envfile.sh
    └── docker-build.sh

本検証では下記Dockerfileを作成します。

# Define global args
ARG FUNCTION_DIR="/home/app/"

# Stage 1 - bundle base image + runtime
# Grab a fresh copy of the image and install GCC
FROM python:3.9-alpine3.12 AS python-alpine
# Install GCC (Alpine uses musl but we compile and link dependencies with GCC)
RUN apk add --no-cache \
    libstdc++

# Stage 2 - build function and dependencies

# Install aws-lambda-cpp build dependencies
RUN apk add --no-cache \
    build-base \
    libtool \
    autoconf \
    automake \
    libexecinfo-dev \
    make \
    cmake \
    libcurl \
    wget \
    bash \
    which \
    groff

# Install AWS CLI
RUN pip install awscli

FROM python-alpine AS build-image

# Authenticating with AWS CLI
ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY
ENV AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
ENV AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}

# Include global args in this stage of the build
# Create function directory
RUN mkdir -p /home/app/
# Copy handler function
COPY app/* /home/app/

RUN ls -la /home/app/

RUN chmod 755 /home/app/rename.sh

RUN ls -la /home/app/

# Install the function's dependencies
RUN aws s3 cp s3://aws-lambda-runtime-clients/python/awslambdaruntimeclient-0.0.1.tar.gz awslambdaruntimeclient.tar.gz && \
    python3.9 -m pip install \
    awslambdaruntimeclient.tar.gz \
    --target /home/app/

# Stage 3 - final runtime image
# Grab a fresh copy of the Python image
FROM python-alpine

# Set working directory to function root directory
WORKDIR /home/app/
# Copy in the built dependencies
COPY --from=build-image /home/app/ /home/app/

RUN ls -la /home/app/

RUN ls -la /usr/local/bin

RUN ls -la /usr/bin

ENTRYPOINT [ "/usr/local/bin/python", "-m", "awslambdaruntimeclient" ]
CMD [ "main.lambda_handler" ]

Dockerfile作成後、イメージビルドを実施します。

$ docker build . \
-f Dockerfile \
-t lambda/python:3.9-alpine3.12 \
--build-arg AWS_ACCESS_KEY_ID="$(aws configure get mmm-test-env.aws_access_key_id)" \
--build-arg AWS_SECRET_ACCESS_KEY="$(aws configure get mmm-test-env.aws_secret_access_key)"

ビルドが成功し、wgetawscliがインストールされていることが確認できます。

2. Amazon ECRにリポジトリを作成

コンテナイメージが用意できたら、次はイメージをプッシュするAmazon Elastic Container Registry(ECR)を用意します。

今回はdocker-lambdaという名前でリポジトリを用意しました。

ECR

3. ビルドしたコンテナイメージをAmazon ECRにプッシュする

ビルドしたコンテナイメージにタグ付けを行い、先ほど作成したECRリポジトリにプッシュします。

$ docker tag lambda/python:3.9-alpine3.12 000000000000.dkr.ecr.sa-east-1.amazonaws.com/docker-lambda:v1.0.0

$ export AWS_ACCESS_KEY_ID=<YOUR_AWS_ACCESS_KEY_ID>
$ export AWS_SECRET_ACCESS_KEY=<YOUR_AWS_SECRET_ACCESS_KEY>
$(aws ecr get-login --region sa-east-1 --no-include-email)

$ docker push 000000000000.dkr.ecr.sa-east-1.amazonaws.com/docker-lambda:v1.0.0

AWSコンソールでECRを開くと、先程プッシュしたイメージがリポジトリに追加されていることが確認できます。

Image

4. プッシュしたコンテナイメージからLambda関数を作成する

イメージプッシュが完了できたら、早速Lambda関数を作成していきましょう。

下記コマンドのように--code ImageUri=<YOUR_IMAGE_URI>で先程プッシュしたイメージのURIを指定してあげます。

$ aws lambda --region sa-east-1 create-function \
--function-name docker-lambda-function --package-type Image \
--code ImageUri=<YOUR_IMAGE_URI> \
--role <YOUR_IAM_ROLE_ARN>

AWSコンソールからLambda関数が作成できていることが確認できます。(作成完了までに多少時間がかかることがあります)

Lambda

関数を作成できたら環境変数をセットします。今回はAmazon S3に静的ファイルをアップロードするというワークロードのため、S3BUCKETというキーに対して任意のバケット名を設定します。

Lambda

また、Lambdaではデフォルトでタイムアウト3秒に設定されているため、60秒程度に延長しておきます。

次に関数をテストしていきます。

今回は下記のようなテストイベントを作成しました。

{
  "url": "https://mmmcorp.co.jp/"
}

Test

S3バケットを確認すると、静的ファイルがアップロードされており、DockerイメージがLambda上で正常に動作していることが確認できます。

S3Bucket

5. CI/CDパイプラインの実装

次に、イメージビルド、ECRへのイメージプッシュ、Lambda関数のアップデートの一連の流れを、masterブランチへのPushをトリガーに、CI/CDパイプラインとして実装してきます。

CI/CDにはCircleCIを利用し、実装イメージは下記となります。

masterブランチへのPush・マージを起点として、Dockerコンテナ内でイメージビルドを実行。ビルドしたコンテナイメージをECRにプッシュし、awscliでLambda関数をアップデートするという流れです。

Pipeline

早速、.circleci/config.ymlと、ymlファイル内で実行するスクリプトファイルを準備します。

defaults: &defaults
    working_directory: ~/app
    machine:
      image: ubuntu-1604:201903-01
      docker_layer_caching: true
version: 2.1
jobs:
  build:
    <<: *defaults
    steps:
      - checkout
      - run:
          name: ls
          command: ls
      - run:
          name: create .env file
          command: ./scripts/create_envfile.sh
      - persist_to_workspace:
          root: ~/app
          paths:
              - ./*
  deploy:
    working_directory: ~/app
    docker:
      - image: docker:19-git
    steps:
      - attach_workspace:
          at: ~/app
      - setup_remote_docker
      - run:
          name: install python, pip, awscli
          command: |
            apk add --update python2 \
            jq \
            python2-dev \
            py-pip \
            build-base \
            gcc \
            abuild \
            binutils \
            binutils-doc \
            gcc-doc \
            bash \
            && pip install --no-cache-dir awscli
      - run:
          name: ls
          command: ls
      - run:
          name: docker build
          command: ./scripts/docker-build.sh
      - run:
          name: ECR login
          command: $(aws ecr get-login --region sa-east-1 --no-include-email)
      - run:
          name: ECR deploy
          no_output_timeout: 2400
          command: ./scripts/circleci_lambda.sh
workflows:
  version: 2
  build_and_deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build
          filters:
            branches:
              only:
                - master

今回、CircleCIのジョブの中で3種類のスクリプトファイルを呼び出して実行しています。

#!/bin/bash

env | sort | grep AWS_ > .env
cat .env

exit 0
#!/bin/bash

docker build . -f Dockerfile --tag <YOUR_ECR_REPOSITORY_NAME>:<YOUR_TAG> --build-arg AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" --build-arg AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}"
#!/bin/bash

echo "Push Image to ECR..."
docker push <YOUR_ECR_REPOSITORY_NAME>:<YOUR_TAG>

echo "Download the model update from Amazon S3 to enable the AWS Lambda private beta feature..."
aws s3 cp s3://aws-lambda-image-beta-tooling/cli/lambda-2015-03-31.normal.json .

echo "Install the model..."
aws configure add-model --service-model file://lambda-2015-03-31.normal.json --service-name lambda

echo "build done and running lambda update..."
aws lambda update-function-code --region sa-east-1 --function-name <YOUR_LAMBDA_FUNCTION_NAME> --image-uri <YOUR_IMAGE_URI>

RETURNCD=$?
if [ ${RETURNCD} -ne 0 ]; then
  echo
  echo "Lambda Update FAILED"
  echo
  exit ${RETURNCD}
fi

exit 0

検証の振り返り

今回の検証ではAWS Lambda Container Image Supportを活用して、既存ワークロードの改善を図っています。プロジェクトをコンテナでパッケージ化することで、Lambda基盤に依存しない形でのワークロード実行が可能になり、ワークロードの安定性を向上させることができました。

AWS Lambdaのビジネス活用をサポート

株式会社MMMは 、AWS Lambdaのビジネス活用に豊富な実績を持っており、AWS LambdaのAWSサービスデリバリーパートナーにも認定されています。

MMMは、多種多様なAWSマネージドサービスをクラウドのベストプラクティスに準拠した形で活用することで、クラウドの真の旨味である柔軟性や俊敏性をお客様のビジネスに組み込むことを得意としています。

今回リリースされたAWS Lambda Container Image Supportも含め、 サーバーレスアーキテクチャ(AWS Lambda) の導入や無料相談に関しては次のページよりお気軽にご相談ください。