AWS ECRを使ってNode.js/PythonのコンテナイメージをAWS Lambdaへデプロイしてみた

2022年9月29日掲載

キービジュアル

AWS ECRを使ってNode.js/PythonのコンテナイメージをAWS Lambdaへデプロイしてみた。

目次

  • Node.jsとPythonをサーバレスで実行する環境を作りたい方向けの記事です
  • AWSの環境にデプロイする方法をステップバイステップで解説します
  • この記事を読むと、AWSででLambdaにどのようにデプロイするか全体感を把握することが可能です

はじめに

AWS Lambdaはサーバレスコンピューティングプラットフォームであり、開発ユーザは小さな関数コードを作成するだけで、開発ユーザはサーバをプロビジョニングすることなく、その関数コードを実行することができます。

本記事では、AWS Lambdaのデプロイ方法の1つである、コンテナイメージとしてパッケージ化されたコンテナイメージファイルをECR(Amazon Elastic Container Registry)リポジトリにアップしながら、ECRイメージをLambdaコード関数としてデプロイする方法を説明します。ECR(Amazon Elastic Container Registry)はソースコードを管理するフルマネージドなコンテナレジストリです。ECRを使うと外部ネットワークを介せずにコンテナイメージを簡単に保持・管理できることや、Lambda以外にAWSサービスとの連携やIAMユーザーごとの操作権限等セキュリティ設定が簡単になります。

1. 全体構成図

Node.jsおよびPythonそれぞれのコードをコンテナイメージにしたうえでECR経由でAWS Lambdaへデプロイする際の流れ、構成図としては次の通りになります。

2. DockerとAWS CLIの準備 - 1. Install Docker desktop

ソースコードをDockerコンテナイメージでLambdaへデプロイするためのコンテナイメージを構築するために、Dockerの作業環境を準備します。Dockerは複数のプラットフォームをサポートしており、公式サイトよりインストールガイドラインに沿ってインストール、Docker作業環境を構築することができます。

著者はWindows PCを使用しているので、Docker Desktop for Windowsを使用します。

3. DockerとAWS CLIの準備 - 2. AWS CLI のインストールと設定

AWS CLIコマンドツールを導入します。AWS CLIコマンドツールを使用するには、Install Guidelineのガイドラインに従ってCLIコマンドツールをインストールし、設定します。著者はWindowsPCを使用しているので、Windowsを例に説明します。Windowsの場合、AWS CLI V2からインストールパッケージをダウンロードした後、ウィザードを使用してインストールプロセスをステップごとに完了できます。

<参考>
https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html

インストールが完了したら、`aws --version`コマンドを実行してステータスを確認できます。ツールが正常にインストールされていれば、関連するバージョン情報が表示されます。

`aws configure` コマンドを実行して、AWS CLIインストールをセットアップするための設定を行います。以下の情報が既にあることを確認します。

  • Access key ID
  • Secret access key
  • AWS Region
  • Output format

既存のアクセスキーがない場合は、IAM > User > Access Keys に移動し、`Create access key` ボタンをクリックして新しいアクセスキーを作成し、キー情報をコピーし、先ほどのセットアップ情報に入力します。

4. Nodejsスクリプトをデプロイ - 1. Node.jsソースコードと構成の準備

AWS LambdaでデプロイしたいNode.jsソースコードを準備します。本記事では、node_test_container_awsというソースコードフォルダを作成し、Lambdaのハンドラーが含まれるindex.jsを作成します。ハンドラー(handler)はサーバレスとして利用する言語の関数の開始位置となるもので、HTTPトリガー、イベントトリガーなどさまざまなトリガーによって実行される際に呼び出すエントリポイントです。このハンドラーは必須項目であり、ハンドラーを呼び出すことで、サーバレスとしての関数コード(以降スクリプト)が実行開始されます。ここでは、event handlerを例とします。

LambdaのNode.jsランタイム環境は、一般的なNode.jsモジュールと、使用可能な共通モジュールを提供します。カスタマイズされたScriptには、サードパーティの依存関係を必要とする場合があります。 上記のソースコードでは、日付を操作するためのmomentと呼ばれるパッケージが必要であり、デプロイ前にpackage.jsonで準備・管理する必要があります。これらのパッケージはnpmでローカルに管理します。node_test_container_awsフォルダにはindex.jspackage.json が格納されています。

上記のソースコード index.js は次の通りです。

'use strict';
const moment = require('moment');

exports.handler = (event, context, callback) => {
  console.log('hello world');
  callback(null, 'Container Node: ' + moment().utcOffset(9).format('YYYY-MM-DD HH:mm:ss'));
}
操作画面

上記のpackage.jsonは次の通りです。

{
  "dependencies": {
    "moment": "^2.29.4"
  }
}

5. Nodejsスクリプトをデプロイ - 2. ローカルにイメージのデプロイ

AWS Lambda用コード関数のコンテナイメージを構築するには、AWSベースイメージからLambdaの特定のランタイム用イメージを作成する必要があります。AWSベースイメージには以下のような環境変数が用意されているので、Dockerfileで直接使用することができます。

  • LAMBDA_TASK_ROOT=/var/task
  • LAMBDA_RUNTIME_DIR=/var/runtime

今回、要件に基づいてDockerfileを変更するので、以下の対応をします。

  • 1行目で特定のランタイムのベースイメージを設定します。ここではランタイムを Nodejs 16.x とします。他のベースイメージはAWS base images for Lambdaで確認することができます。

  • 4行目で、プロジェクトフォルダ配下のファイル名情報に基づいて、ファイル名を変更します。node_test_container_awsフォルダ配下にはindex.js、package.json が格納されているので、index.js package.jsonのファイル名を記載します。このコード関数に属する全てのファイルは、必要に応じてpackage.jsonを含め、`LAMBDA_TASK_ROOT=/var/task` のディレクトリ配下へコピーされます。

  • 7行目は、依存関係が必要ない場合は削除します。

  • 9行目でコード関数のHandlerを設定します。この設定は、コンソール内のコンテナイメージに基づいて、作成・処理中に上書きすることができます。

以下はDockerfileの内容です。

FROM public.ecr.aws/lambda/nodejs:16

# Assumes your function is named "app.js", and there is a package.json file in the app directory
COPY index.js package.json  ${LAMBDA_TASK_ROOT}

# Install NPM dependencies for function
RUN npm install

# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "index.handler" ]  

Dockerfileの準備ができたら、`docker build -t <LOCAL_IMAGE> `コマンドを実行し、ローカルイメージをビルドします。AWSベースのイメージを取得し、サードパーティの依存関係をインストールするため多少時間がかかります。

6. Nodejsスクリプトをデプロイ - 3. ビルドイメージファイルをECRにプッシュ

AWSコマンドライン(CLI)とDockerコマンドを使用して、ビルドしたコンテナイメージをECRへアップロード(Push)します。
以下のコマンドでは、<AWS_ACCOUNT_ID>と<REGION>を自分用に変更します。次のコマンドでAWS ECRレジストリにログインします。

aws ecr get-login-password --region <REGION> | docker login --username AWS --password-stdin <AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com

ログインが無事完了すると、関連する成功メッセージが表示されます。
次に、以下のように `create-repository` コマンドを使用して、コンテナイメージを管理するリポジトリを作成します。

aws ecr create-repository --repository-name <REPOSITORY_NAME> --image-scanning-configuration scanOnPush=true --image-tag-mutability MUTABLE

AWSコンソール上のECR画面にて、作成されたリポジトリを確認します。

最後に、ビルドしたコンテナイメージにリポジトリ名と同じタグを付け、以下のように `docker push` コマンドで AWS ECR にデプロイします。
なお、<LOCAL_IMAGE>:<TAG>は、上記の手順でビルドしたイメージの情報と一致させる必要があります。

docker tag <LOCAL_IMAGE>:<LOCAL_TAG> <AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/<IMAGE>:<TAG>


docker push <AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/<IMAGE>:<TAG>   

AWSコンソール上のECR画面にて、ECRリポジトリへアップロード(Push)したコンテナイメージを確認します。

7. Nodejsスクリプトをデプロイ - 4. ECRからのイメージをもとにLambda コード関数を作成

AWS Lambda コンソール画面に移動し、ECRのコンテナイメージからコード関数を作成します。

  1. `Create Function` ボタンをクリックして、コード関数を新規作成します。

  2. タイプとして`Container Image` を選択します。

  3. 特定の関数名を入力します。

  4. `Browse Images` ボタンをクリックして、ECR内のイメージをスキャンします。

  5. 上記の手順で作成したリポジトリを選択します。

  6. 上記の手順で作成したタグを持つターゲットイメージを選択します。

  7. `Select Images` ボタンをクリックして、イメージURIを保存します。

  8. `Create Function` ボタンをクリックして、作成プロセスを開始します。


`Container image overrides` 選定バーにて、以下の設定項目を更新することができます。

  • ENTRYPOINT

  • CMD

  • WORKDIR

8. Nodejsスクリプトをデプロイ - 5. デプロイ結果の確認及びテスト

Lambdaコンソール画面にて、作成したコード関数を確認するとコンテナイメージから作成されているため、インラインエディタでコードを表示することができません。

コンソールからテストイベントを使用してコード関数をテストします。

その結果、現在の日付情報が特定の形式で返されます。

以上が、AWS Elastic Container Registry (ECR) を使って、Node.jsソースコードのコンテナイメージをAWS Lambdaへデプロイする方法になります。

9. Pythonスクリプトをデプロイ - 1. Pythonソースコードと構成の準備

python_test_container_awsという名前のローカルプロジェクトフォルダを作成します。本記事では、Pythonスクリプトにrequestsというサードパーティパッケージが含まれています。pythonの場合、依存関係は requirements.txt で管理されます。

index.py requirements.txt を次のように準備します。

上記の index.py ソースコードは次の通りです。

import requests

def handler(event, context):
    response = requests.get('http://google.com/')
    print("Access Google home successfully!".format(response.status_code))
    return "Response status_code: {0}".format(response.status_code)

上記の requirements.txt 依存関係ファイルは次の通りです。

requests

10. Pythonスクリプトをデプロイ - 2. ローカルにイメージのデプロイ

AWS Lambda関数のローカルイメージを構築するために、関連するDockerfileを準備します。

今回、要件に基づいてDockerfileを変更するので、以下の対応をします。

  • 1行目で特定のランタイムのベースイメージを設定します。ここではランタイムを python 3.9 とします。他のベースイメージはAWS base images for Lambdaで確認することができます。

  • 4行目で、プロジェクトフォルダ配下のファイル名情報に基づいて、ファイル名を変更します。python_test_container_awsフォルダ配下にはindex.py、requirements.txt が格納されているので、index.py requirements.txtのファイル名を記載します。このコード関数に属する全てのファイルは、必要に応じて9行目のrequirements.txtを含め、`LAMBDA_TASK_ROOT=/var/task` のディレクトリ配下へコピーされます。

  • 10行目は、依存関係がもし必要ない場合は削除します。

  • 13行目でコード関数のHandlerを設定します。この設定は、コンソール内のコンテナイメージに基づいて、作成・処理中に上書きすることができます。

以下はDockerfileの内容です。

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

# Copy function code
COPY index.py ${LAMBDA_TASK_ROOT}

# Install the function's dependencies using file requirements.txt
# from your project folder.

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

# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "index.handler" ]


Dockerfileの準備ができたら、`docker build -t <LOCAL_IMAGE> `コマンドを実行し、ローカルイメージをビルドします。AWSベースのイメージを取得し、サードパーティの依存関係をインストールするため多少時間がかかります。

11. Pythonスクリプトをデプロイ - 3. ビルドイメージファイルをECRにプッシュ

AWSコマンドライン(CLI)とDockerコマンドを使用して、ビルドしたコンテナイメージをECRへアップロード(Push)します。
以下のコマンドでは、<AWS_ACCOUNT_ID>と<REGION>を自分用に変更します。次のコマンドでAWS ECRレジストリにログインします。


aws ecr get-login-password --region <REGION> | docker login --username AWS --password-stdin <AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com

ログインが無事完了すると、関連する成功メッセージが表示されます。
次に、以下のように `create-repository` コマンドを使用して、コンテナイメージを管理するリポジトリを作成します。

aws ecr create-repository --repository-name <REPOSITORY_NAME> --image-scanning-configuration scanOnPush=true --image-tag-mutability MUTABLE

AWSコンソール上のECR画面にて、作成されたリポジトリを確認します。

最後に、ビルドしたコンテナイメージにリポジトリ名と同じタグを付け、以下のように `docker push` コマンドで AWS ECR にデプロイします。
なお、<LOCAL_IMAGE>:<TAG>は、上記の手順でビルドしたイメージの情報と一致させる必要があります。

 

docker tag <LOCAL_IMAGE>:<LOCAL_TAG> <AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/<IMAGE>:<TAG>


docker push <AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/<IMAGE>:<TAG>   

AWSコンソール上のECR画面にて、ECRリポジトリへアップロード(Push)したコンテナイメージを確認します。

12. Pythonスクリプトをデプロイ - 4. ECRからのイメージをもとにLambda コード関数を作成

AWS Lambda コンソール画面に移動し、ECRのコンテナイメージからコード関数を作成します。

 

  1. `Create Function` ボタンをクリックして、コード関数を新規作成します。

  2. タイプとして`Container Image` を選択します。

  3. 特定の関数名を入力します。

  4. `Browse Images` ボタンをクリックして、ECR内のイメージをスキャンします。

  5. 上記の手順で作成したリポジトリを選択します。

  6. 上記の手順で作成したタグを持つターゲットイメージを選択します。

  7. `Select Images` ボタンをクリックして、イメージURIを保存します。

  8. `Create Function` ボタンをクリックして、作成プロセスを開始します。


`Container image overrides` 選定バーにて、以下の設定項目を更新することができます。

  • ENTRYPOINT

  • CMD

  • WORKDIR

13. Pythonスクリプトをデプロイ - 5. デプロイ結果の確認及びテスト

Lambdaコンソール画面にて、作成したコード関数を確認するとコンテナイメージから作成されているため、インラインエディタでコードを表示することができません。

コンソールからテストイベントを使用してコード関数をテストします。上記のデプロイする関数コードの中身は、Googleホームページにアクセスし、関連するHTTPステータスコードを返すものです。

以上が、AWS Elastic Container Registry (ECR) を使って、PythonソースコードのコンテナイメージをAWS Lambdaへデプロイする方法になります。

14. 補足事項

・関数コードの更新方法

コード関数はコンテナイメージからデプロイされるので、コードを更新するためにコンテナイメージを更新することができます。例えば、上記のPythonサンプルの処理結果・応答メッセージにいくつかの単語を追加したとして、それに伴いコンテナイメージを更新構築します。コンテナイメージのタグが以前と同じ場合、前のコンテナイメージはリポジトリで「タグなし」としてマークされます。

操作画面

Lambdaコンソール画面にて、作成したコード関数を確認するとコンテナイメージから作成されているため、インラインエディタでコードを表示することができません。

ECRの詳細ページに移動し、新規イメージからコード関数をデプロイします。

成功メッセージが表示されたら、コード関数を再度呼び出し、新たに単語を追加した応答メッセージを確認します。

・Dockerfileのエントリポイントが間違っている場合、コード関数を修正する方法

Dockerfileで定義された`CMD [ "xxxxx" ]`がコード内の実際のエントリポイントと一致しなかった場合、関数を呼び出すとエラーが発生します。

これは、Dockerfileの設定がindex.handlerだが、index.pyの実際の関数はlambda_handlerと呼ばれるためです。

コンテナイメージを再構築したくない場合は、コード関数設定を使用してDockerfileの値をオーバーライドすることができます。

  1. `Image` タブを開き、イメージの設定を確認します。

  2. `Edit` ボタンをクリックします。

  3. CMD に正しい値を設定します。ここでは index.lambda_handler を使用します。

  4. `Save` ボタンをクリックして変更を処理します。


完了すると、成功メッセージが表示され、上記の手順と同様にコンテナイメージの構成設定が更新されます。

コード関数を再度呼び出すと、正しいPythonスクリプト内の正しいコード関数が呼び出されます。

・コード関数作成中にKMS設定エラーを修正する方法

コード関数作成中にKMS設定のエラーが発生する場合があります。
これは、ECRとLambda実行ロールとの同期によって発生する事象なので、しばらくしてからもう一度実行すると、正常に完了します。

15. 最後に

それぞれをコンテナイメージファイルとしてAWS Lambdaへデプロイする方法をご紹介しました。参考として、この手法であれば、LambdaをEKS、あるいはECS+Fargateに置き換えてデプロイすることもできますし、または別記事で紹介しているようにServerless Devsを使って他クラウドのサーバレス基盤からコンテナイメージを使ってデプロイすることもできます。

サーバレスアプリケーションはコードを記述しながら実行できるクラウドサービスなので、エンジニアリングリソースを小さくしてくれるため非常に便利です。別途、AWS CLI版でLambdaをデプロイする方法も紹介していますが、依存関係を含めた開発・デバッグ・展開・運用・保守の完全なライフサイクルを満たしたい場合や、コンテナイメージを使ったCI/CD運用を目指している場合は本記事をご参考になれば幸いです。

関連記事リンク

サーバレスってなに?Alibaba Cloud, AWS, Azure, Google Cloud のサーバレスサービスを比べてみました

AWS

AWS LambdaにZIPファイル化したNode.jsおよびPythonのコードをデプロイしてみた(GUI編)

AWS LambdaをAWS CLIベースでNode.jsおよびPythonそれぞれをデプロイしてみた(CLI編)

AWS ECRを使ってNode.js/PythonのコンテナイメージをAWS Lambdaへデプロイしてみた   ←  本記事

Google Cloud

Google Cloud FunctionにZIPファイル化したNode.jsおよびPythonのコードをデプロイしてみた(GUI編)  

Google Cloud FunctionをGoogle Cloud CLIベースでNode.jsおよびPythonそれぞれをデプロイしてみた(CLI編)

Azure

Azure FunctionにZIPファイル化したNode.jsおよびPythonのコードをデプロイしてみた(GUI編)

Azure FunctionをAzure CLIベースおよびAzure Function Core ToolsでNode.jsおよびPythonそれぞれをデプロイしてみた(CLI編)

Azure FunctionをAzure CLIおよびAzure Function Core ToolsでNode.jsおよびPythonそれぞれをデプロイしてみた 

Alibaba Cloud

Alibaba Cloud Function ComputeにZIPファイル化したNode.jsおよびPythonのコードをデプロイしてみた(GUI編)

Alibaba Cloud FunctionComputeをCLIベースでNode.jsおよびPythonそれぞれをデプロイしてみた(CLI編)

Serverless Devsを使ってNode.js/PythonコードをコンテナイメージにしながらAlibaba Cloud FunctionComputeへデプロイしてみた

関連サービス

Amazon Web Services (AWS)

ソフトバンクはAWS アドバンストティアサービスパートナーです。「はじめてのAWS導入」から大規模なサービス基盤や基幹システムの構築まで、お客さまのご要望にあわせて最適なAWS環境の導入を支援します。

MSPサービス

MSP(Managed Service Provider)サービスは、お客さまのパブリッククラウドの導入から運用までをトータルでご提供するマネージドサービスです。

おすすめの記事

条件に該当するページがございません