Amazon Web Services ブログ

AWS Lambda の新機能 – コンテナイメージのサポート

AWS Lambda では、サーバーについて気にすることなくコードをアップロードして実行できます。多くのお客様に Lambda のこの仕組みをご活用いただいていますが、開発ワークフローのためにコンテナツールに投資した場合は、Lambda でのアプリケーションの構築に同じアプローチを使用することが難しくなります。

この問題に対応するため、Lambda 関数を最大 10 GBコンテナイメージとしてパッケージ化し、デプロイできるようになりました。これにより、機械学習やデータ集約型のワークロードなど、大きな依存関係に頼る大規模なワークロードを簡単に構築してデプロイできます。ZIP アーカイブとしてパッケージ化された関数と同様に、コンテナイメージとしてデプロイされた関数は、同様の操作のシンプルさ、自動スケーリング、高可用性、多数のサービスとのネイティブ統合による恩恵を受けます。

当社では。サポートされているすべての Lambda ランタイム (Python、Node.js、Java、.NET、Go、Ruby) のベースイメージを提供しているため、コードと依存関係を簡単に追加することができます。Amazon Linux ベースのカスタムランタイム用のベースイメージも用意しており、これを拡張して Lambda ランタイム API を実装する独自のランタイムを含めることができます。

AlpineDebian Linux をベースにしたイメージなど、独自のベースイメージを任意で Lambda にデプロイできます。Lambda を操作するには、これらのイメージに Lambda ランタイム API を実装する必要があります。独自のベースイメージの構築を容易にするため、当社ではサポートされているすべてのランタイムにランタイム API を実装する Lambda Runtime Interface Clients をリリースしています。これらの実装は、ネイティブのパッケージマネージャーを介して利用できるため、イメージ内で簡単に取得でき、オープンソースライセンスを使用してコミュニティと共有されます。

また、Lambda Runtime Interface Emulator をオープンソースとしてリリースします。これにより、コンテナイメージのローカルテストを実行して、Lambda にデプロイした際に実行されることを確認することができます。Lambda Runtime Interface Emulator は、AWS が提供するすべてのベースイメージに含まれており、任意のイメージでも使用できます。

コンテナイメージは、Lambda Extensions API を使用して、モニタリング、セキュリティ、その他のツールを Lambda 実行環境に統合することもできます。

コンテナイメージをデプロイするには、Amazon Elastic Amazon Elastic Container Registryリポジトリから 1 つを選択します。これが実際にどのように機能するか、いくつか例を見てみましょう。最初に、AWS が提供する Node.js のイメージを使用し、次に Python のカスタムイメージを構築します。

AWS が提供するベースイメージを Node.js で使用する
以下に、PDFKit モジュールを使用して PDF ファイルを生成するシンプルな Node.js Lambda 関数のコード (app.js) を示します。呼び出されるたびに、faker.js モジュールによって生成されたランダムデータを含む新しいメールが作成されます。関数の出力は、Amazon API Gateway の構文を使用して PDF ファイルを返します。

const PDFDocument = require('pdfkit');
const faker = require('faker');
const getStream = require('get-stream');

exports.lambdaHandler = async (event) => {

    const doc = new PDFDocument();

    const randomName = faker.name.findName();

    doc.text(randomName, { align: 'right' });
    doc.text(faker.address.streetAddress(), { align: 'right' });
    doc.text(faker.address.secondaryAddress(), { align: 'right' });
    doc.text(faker.address.zipCode() + ' ' + faker.address.city(), { align: 'right' });
    doc.moveDown();
    doc.text('Dear ' + randomName + ',');
    doc.moveDown();
    for(let i = 0; i < 3; i++) {
        doc.text(faker.lorem.paragraph());
        doc.moveDown();
    }
    doc.text(faker.name.findName(), { align: 'right' });
    doc.end();

    pdfBuffer = await getStream.buffer(doc);
    pdfBase64 = pdfBuffer.toString('base64');

    const response = {
        statusCode: 200,
        headers: {
            'Content-Length': Buffer.byteLength(pdfBase64),
            'Content-Type': 'application/pdf',
            'Content-disposition': 'attachment;filename=test.pdf'
        },
        isBase64Encoded: true,
        body: pdfBase64
    };
    return response;
};

npm を使用してパッケージを初期化し、必要な 3 つの依存関係を package.json ファイルに追加します。このようにして、package-lock.json ファイルも作成します。より予測可能な結果を得るために、それをコンテナイメージに追加します。

$ npm init
$ npm install pdfkit
$ npm install faker
$ npm install get-stream

ここで、nodejs12.x ランタイム用に AWS が提供するベースイメージから開始し、Lambda 関数のコンテナイメージを作成する Dockerfile を作成します。

FROM amazon/aws-lambda-nodejs:12
COPY app.js package*.json ./
RUN npm install
CMD [ "app.lambdaHandler" ]

Dockerfile は、ソースコード (app.js) と、パッケージと依存関係を記述するファイル (package.json および package-lock.json) をベースイメージに追加します。次に、npm を実行して依存関係をインストールします。CMD を関数ハンドラーに設定しましたが、これは後で Lambda 関数を設定する際にパラメータオーバーライドとして実行することもできます。

Docker CLI を使用して、ランダムな文字のコンテナイメージをローカルに構築します。

$ docker build -t random-letter .

これが機能しているかどうかを確認するには、Lambda Runtime Interface Emulator を使用して、コンテナイメージをローカルで起動します。

$ docker run -p 9000:8080 random-letter:latest

ここで、cURL を使用して関数の呼び出しをテストします。ここでは、空の JSON ペイロードを渡します。

$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'

エラーがある場合は、ローカルで修正できます。うまくいったら、次のステップに進みます。

コンテナイメージをアップロードするには、アカウントに新しい ECR リポジトリを作成し、ローカルイメージにタグを付けて ECR にプッシュします。コンテナイメージのソフトウェアの脆弱性を特定しやすくするために、ECR イメージスキャンを有効にします。

$ aws ecr create-repository --repository-name random-letter --image-scanning-configuration scanOnPush=true
$ docker tag random-letter:latest 123412341234.dkr.ecr.sa-east-1.amazonaws.com/random-letter:latest
$ aws ecr get-login-password | docker login --username AWS --password-stdin 123412341234.dkr.ecr.sa-east-1.amazonaws.com
$ docker push 123412341234.dkr.ecr.sa-east-1.amazonaws.com/random-letter:latest

ここで、AWS マネジメントコンソールを使用して関数の作成を完了します。更新によりコンテナイメージのサポートが追加された、AWS サーバーレスアプリケーションモデルを使用することもできます。

Lambda コンソールで、[Create function (関数の作成)] をクリックします。[Container image (コンテナイメージ)] を選択して関数に名前を付け、[Browse images (イメージの参照)] を選択して ECR リポジトリ内の正しいイメージを探します。

コンソールのスクリーンショットです。

リポジトリを選択したら、アップロードした latest イメージを使用します。イメージを選択すると、Lambda はそれを基盤となるイメージのダイジェスト (下の画像のタグの右側) に変換しています。docker images --digests コマンドを使用して、イメージのダイジェストをローカルで確認することができます。このように、latest タグが新しいタグに渡された場合でも、関数は同じイメージを使用するため、意図しないデプロイから保護されます。関数コードで使用するイメージを更新できます。その間にタグが別のイメージに再割り当てされた場合でも、関数設定の更新が使用されるイメージに影響することはありません。

コンソールのスクリーンショットです。

オプションで、コンテナイメージの値の一部をオーバーライドすることができます。今回はこれを行っていませんが、このようにして、たとえば CMD 値の関数ハンドラーをオーバーライドすることで、さまざまな関数に使用できるイメージを作成できます。

コンソールのスクリーンショットです。

他のすべてのオプションをデフォルトにしたまま、[Create function (関数の作成)] を選択します。

関数のコードを作成または更新するとき、Lambda プラットフォームは新しいコンテナイメージと更新されたコンテナイメージを最適化し、呼び出しを受信できるように準備します。この最適化には、イメージのサイズに応じて数秒から数分かかります。その後、関数を呼び出す準備が整います。コンソールで機能をテストします。

コンソールのスクリーンショットです。

機能しています! それでは、トリガーとして API Gateway を追加しましょう。[Add Trigger (トリガーを追加)] を選択し、HTTP API を使用して API Gateway を追加します。わかりやすくするために、API の認証は開いたままにしておきます。

コンソールのスクリーンショットです。

ここで、API エンドポイントを数回クリックして、ランダムなメールをいくつかダウンロードします。

コンソールのスクリーンショットです。

思った通りに機能しています! faker.js モジュールからのランダムデータを使用して生成される PDF ファイルを、いくつか次に示します。

サンプルアプリケーションの出力です。

 

Python のカスタムイメージを構築する
会社のガイドラインに従ったり、サポートされていないランタイムのバージョンを使用したりするために、カスタムコンテナイメージを使用する必要があることがあります。

ここでは、Python 3.9 を使用するイメージを作成します。関数のコード (app.py) は非常にシンプルです。ただ挨拶して、使用している Python のバージョンを伝えるだけです。

import sys
def handler(event, context): 
    return 'Hello from AWS Lambda using Python' + sys.version + '!'

前に述べたとおり、サポートされるすべてのランタイムで (Runtime API を実装する) Lambda Runtime Interface Clients のオープンソース実装を共有しています。この場合、Alpine Linux に基づく Python イメージから始めます。次に、Python 向けの Lambda Runtime Interface Client (リンクを近日公開) をイメージに追加します。Dockerfile は次のとおりです。

# Define global args
ARG FUNCTION_DIR="/home/app/"
ARG RUNTIME_VERSION="3.9"
ARG DISTRO_VERSION="3.12"

# Stage 1 - bundle base image + runtime
# Grab a fresh copy of the image and install GCC
FROM python:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} 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
FROM python-alpine AS build-image
# Install aws-lambda-cpp build dependencies
RUN apk add --no-cache \
    build-base \
    libtool \
    autoconf \
    automake \
    libexecinfo-dev \
    make \
    cmake \
    libcurl
# Include global args in this stage of the build
ARG FUNCTION_DIR
ARG RUNTIME_VERSION
# Create function directory
RUN mkdir -p ${FUNCTION_DIR}
# Copy handler function
COPY app/* ${FUNCTION_DIR}
# Optional – Install the function's dependencies
# RUN python${RUNTIME_VERSION} -m pip install -r requirements.txt --target ${FUNCTION_DIR}
# Install Lambda Runtime Interface Client for Python
RUN python${RUNTIME_VERSION} -m pip install awslambdaric --target ${FUNCTION_DIR}

# Stage 3 - final runtime image
# Grab a fresh copy of the Python image
FROM python-alpine
# Include global arg in this stage of the build
ARG FUNCTION_DIR
# Set working directory to function root directory
WORKDIR ${FUNCTION_DIR}
# Copy in the built dependencies
COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR}
# (Optional) Add Lambda Runtime Interface Emulator and use a script in the ENTRYPOINT for simpler local runs
ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /usr/bin/aws-lambda-rie
RUN chmod 755 /usr/bin/aws-lambda-rie
COPY entry.sh /
ENTRYPOINT [ "/entry.sh" ]
CMD [ "app.handler" ]

今回の Dockerfile はより明瞭で、複数ステージでの構築に関する Docker のベストプラクティスに従って、3 つのステージで最終イメージを構築します。この 3 段階のアプローチを使用して、独自のカスタムイメージを作成できます。

  • ステージ 1 では、ランタイム (この場合はPython 3.9) と、ステージ 2 で依存関係をコンパイルしてリンクさせるために使用する GCC によってベースイメージを構築しています。
  • ステージ 2 では、Lambda Runtime Interface Client をインストールし、関数と依存関係を構築します。
  • ステージ 3 では、ステージ 1 で作成したベースイメージにステージ 2 からの出力を追加するための最終イメージを作成します。ここでは Lambda Runtime Interface Emulator も追加していますが、これはオプションです。以下を参照してください。

以下の entry.sh スクリプトを作成して、ENTRYPOINT として使用します。Python 向けの Lambda Runtime Interface Client を実行します。実行がローカルの場合、Runtime Interface Client は Lambda Runtime Interface Emulator によってラップされます。

#!/bin/sh
if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
    exec /usr/bin/aws-lambda-rie /usr/local/bin/python -m awslambdaric $1
else
    exec /usr/local/bin/python -m awslambdaric $1
fi

これで、Lambda Runtime Interface Emulator を使用して、関数とコンテナイメージが正しく機能しているかどうかをローカルで確認できるようになりました。

$ docker run -p 9000:8080 lambda/python:3.9-alpine3.12

コンテナイメージに Lambda Runtime Interface Emulator を含まない
カスタムコンテナイメージへの Lambda Runtime Interface Emulator の追加はオプションです。これを含めない場合は、次の手順に従ってローカルマシンに Lambda Runtime Interface Emulator をインストールし、ローカルでテストできます。

  • Dockerfile のステージ 3 で、Lambda Runtime Interface Emulator (aws-lambda-rie) と entry.sh スクリプトをコピーするコマンドを削除します。この場合、entry.sh スクリプトは必要ありません。
  • この ENTRYPOINT を使用して、Lambda Runtime Interface Client をデフォルトで起動させます。
    ENTRYPOINT [ "/usr/local/bin/python", “-m”, “awslambdaric” ]
  • これらのコマンドを実行して、ローカルマシンに Lambda Runtime Interface Emulator をインストールします (例: ~/.aws-lambda-rie)。
mkdir -p ~/.aws-lambda-rie
curl -Lo ~/.aws-lambda-rie/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie
chmod +x ~/.aws-lambda-rie/aws-lambda-rie

Lambda Runtime Interface Emulator がローカルマシンにインストールされている場合は、コンテナを起動する際にマウントできます。コンテナをローカルで起動するコマンドは次のとおりです (Lambda Runtime Interface Emulator が ~/.aws-lambda-rie にあると仮定しています)。

docker run -d -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 \
       --entrypoint /aws-lambda/aws-lambda-rie lambda/python:3.9-alpine3.12
       /lambda-entrypoint.sh app.handler

Python 向けのカスタムイメージのテスト
どちらの方法でも、コンテナがローカルで実行されている場合は、cURL を使用して関数の呼び出しをテストできます。

curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'

期待していた通りに出力されています!

"Hello from AWS Lambda using Python3.9.0 (default, Oct 22 2020, 05:03:39) \n[GCC 9.3.0]!"

画像を ECR にプッシュし、前と同じように関数を作成します。コンソールでのテストは次のとおりです。

コンソールのスクリーンショットです。

Alpine に基づくカスタムコンテナイメージは、Lambda で Python 3.9 を実行しています!

このサービスは、今すぐご利用いただけます
現在、Lambda 関数は、米国東部 (バージニア北部)米国東部 (オハイオ)米国西部 (オレゴン)アジアパシフィック (東京)アジアパシフィック (シンガポール)欧州 (アイルランド)欧州 (フランクフルト)南米 (サンパウロ) で、コンテナイメージを使用して今すぐデプロイできます。他のリージョンでも近いうちにサポートを追加する予定です。コンテナイメージのサポートは ZIP アーカイブに加えて提供されており、ZIP のパッケージ形式を引き続きサポートします。

この機能の使用に追加料金は発生しません。ECR リポジトリと、通常の Lambda の料金のみお支払いいただきます。

AWS Lambda でのコンテナイメージのサポートは、コンソールAWS コマンドラインインターフェイス (CLI)AWS SDKAWS サーバーレスアプリケーションモデル、および AWS パートナーが提供するソリューション (Aqua SecurityDatadogEpsagonHashiCorp TerraformHoneycombLumigoPulumiStackerySumo LogicThundra など) で使用できます。

この新機能によって新しいシナリオが開かれたことで、開発パイプラインとの統合が簡素化され、カスタムイメージや、お好きなプログラミングプラットフォームを使用してサーバーレスアプリケーションを構築することが容易になりました。

詳細を確認し、AWS Lambda でコンテナイメージの使用を開始してください。

Danilo