コンテンツにスキップ

AWS Lambda を ECR で開発してみる

参考サイト

AWS Lambda の Docker イメージの作成

ベースイメーは?

$ docker pull public.ecr.aws/lambda/python:3.9
$ docker image history --no-trunc public.ecr.aws/lambda/python:3.9

CREATED BY
ENTRYPOINT [ "/lambda-entrypoint.sh" ]
ENV LAMBDA_RUNTIME_DIR=/var/runtime
ENV LAMBDA_TASK_ROOT=/var/task
ENV LD_LIBRARY_PATH=/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib
ENV PATH=/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin
ENV TZ=:/etc/localtime
ENV LANG=en_US.UTF-8
WORKDIR /var/task
ADD file:8c16de5f53b2e8dc79c19e968c6b594543ed51875e9d41a1a62f4e851f432f1e /
ADD file:f2796bda02b6239b210873de241a89a8a5bbe8f3548dff55e1c93c172eec288f /
ADD file:1418415b0a5dee47572cce73a4d3e2f2b53c4d236d28c472a3963f6a5bb00816 /
ADD file:df68587c4258347be050cd551c0a5354cc4ae6d7f3c82441d080332e9f6dcbc7 /
ADD file:0a074f8b3d7acf6e1019114115e33a5c892fb3207525b967b0b3e3b767388af7 /
ADD file:1655d3dcb32e40d18a0f64a09d0352375aa23fea183c99158bb2435cd5a8fd6e /
ARCHITECTURE amd64

ENTRYPOINT は "/lambda-entrypoint.sh"だった。 中身を見てみる。

まずは起動してコンテナの中に入る。

# まずは起動
$ docker run --rm -it public.ecr.aws/lambda/python:3.9 test
# 別ターミナルで入る
$ docker container exec -it a88733e45400 /bin/bash

/lambda-entrypoint.sh

#!/bin/sh
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.

if [ $# -ne 1 ]; then
  echo "entrypoint requires the handler name to be the first argument" 1>&2
  exit 142
fi
export _HANDLER="$1"

RUNTIME_ENTRYPOINT=/var/runtime/bootstrap
if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
  exec /usr/local/bin/aws-lambda-rie $RUNTIME_ENTRYPOINT
else
  exec $RUNTIME_ENTRYPOINT
fi

/var/runtime/bootstrap

#!/bin/bash
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.

export AWS_EXECUTION_ENV=AWS_Lambda_python3.9

if [ -z "$AWS_LAMBDA_EXEC_WRAPPER" ]; then
  exec /var/lang/bin/python3.9 /var/runtime/bootstrap.py
else
  wrapper="$AWS_LAMBDA_EXEC_WRAPPER"
  if [ ! -f "$wrapper" ]; then
    echo "$wrapper: does not exist"
    exit 127
  fi
  if [ ! -x "$wrapper" ]; then
    echo "$wrapper: is not an executable"
    exit 126
  fi
    exec -- "$wrapper" /var/lang/bin/python3.9 /var/runtime/bootstrap.py
fi

/var/runtime/bootstrap.py

"""
Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
"""

import json
import logging
import os
import site
import sys
import time
import traceback
import warnings
import awslambdaric.__main__ as awslambdaricmain


def is_pythonpath_set():
    return "PYTHONPATH" in os.environ


def get_opt_site_packages_directory():
    return '/opt/python/lib/python{}.{}/site-packages'.format(sys.version_info.major, sys.version_info.minor)


def get_opt_python_directory():
    return '/opt/python'


# set default sys.path for discoverability
# precedence: LAMBDA_TASK_ROOT -> /opt/python/lib/pythonN.N/site-packages -> /opt/python
def set_default_sys_path():
    if not is_pythonpath_set():
        sys.path.insert(0, get_opt_python_directory())
        sys.path.insert(0, get_opt_site_packages_directory())
#     'LAMBDA_TASK_ROOT' is function author's working directory
#     we add it first in order to mimic the default behavior of populating sys.path and make modules under 'LAMBDA_TASK_ROOT'
#     discoverable - https://docs.python.org/3/library/sys.html#sys.path
    sys.path.insert(0, os.environ['LAMBDA_TASK_ROOT'])


def add_default_site_directories():
#     Set 'LAMBDA_TASK_ROOT as site directory so that we are able to load all customer .pth files
    site.addsitedir(os.environ["LAMBDA_TASK_ROOT"])
    if not is_pythonpath_set():
        site.addsitedir(get_opt_site_packages_directory())
        site.addsitedir(get_opt_python_directory())

def set_default_pythonpath():
    if not is_pythonpath_set():
#         keep consistent with documentation: https://docs.aws.amazon.com/lambda/latest/dg/lambda-environment-variables.html
        os.environ["PYTHONPATH"] = os.environ["LAMBDA_RUNTIME_DIR"]


def main():
    set_default_sys_path()
    add_default_site_directories()
    set_default_pythonpath()
    awslambdaricmain.main([os.environ["LAMBDA_TASK_ROOT"], os.environ["_HANDLER"]])

if __name__ == '__main__':
    main()

ベースイメージのまとめ

以下のLAMBDA_TASK_ROOTをカレントディレクトリとして、_HANDLER(DockerfileのCMD)の Python モジュールを実行する。 /var/task に app.py をおいて、CMD に app.hanlerをセットするのは、そういうこと。

  • LAMBDA_TASK_ROOT = Dockerfile の/var/task
  • _HANDLER = Dockerfile の CMD

ログ出力する Lambda を作る

ローカル環境構築

Python 環境を調べる

# バージョン
bash-4.2# python --version
Python 3.9.11

# インストール済みのパッケージ
bash-4.2# pip list
Package    Version
---------- -------
pip        22.0.4
setuptools 58.1.0

Python 環境を作る

3.9.11 が見つからなかったので、3.9.9 をインストール。

$ pyenv install 3.9.9
$ pyenv global 3.9.9
$ pyenv versions
  system
  3.10.1
  3.7.12
  3.8.12
* 3.9.9 (set by /home/masa86/.pyenv/version)

venv もつくる。

$ python -m venv .venv

サンプル Lambda

import json

def handler(event, context):
    print(json.dumps(event))
    return dict(result="Hello World")

Dockerfile

FROM public.ecr.aws/lambda/python:3.9
COPY app.py /var/task/
CMD [ "app.handler" ]

ビルド

$ docker build -t lambda-hello .

ローカルテスト

起動

$ docker run -p 9000:8080 lambda-hello

呼び出し

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

結果

02 Apr 2022 00:17:30,208 [INFO] (rapid) exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)
02 Apr 2022 00:17:34,125 [INFO] (rapid) extensionsDisabledByLayer(/opt/disable-extensions-jwigqn8j) -> stat /opt/disable-extensions-jwigqn8j: no such file or directory
02 Apr 2022 00:17:34,125 [WARNING] (rapid) Cannot list external agents error=open /opt/extensions: no such file or directory
START RequestId: 72d026ce-fb60-4ffb-9311-b6f1ec3f5bd3 Version: $LATEST
{"payload": "hello world!"}
END RequestId: 72d026ce-fb60-4ffb-9311-b6f1ec3f5bd3
REPORT RequestId: 72d026ce-fb60-4ffb-9311-b6f1ec3f5bd3  Init Duration: 0.24 ms  Duration: 62.30 ms      Billed Duration: 63 ms  Memory Size: 3008 MB    Max Memory Used: 3008 MB

デプロイ

ECR のリポジトリ作成

ECR のリポジトリを作成する。 ECR のリポジトリ名は docker のイメージ名なので揃える必要あり。

demo という名前にした。

プッシュ

コンソールで作成したリポジトリを選んで、「プッシュコマンドの表示」で手順が確認できる。 aws cli が必要なので、先にインストールする。

インストール方法はバージョン 2 のユーザーガイド

$ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
$ unzip awscliv2.zip
$ sudo ./aws/install
$ aws --version
aws-cli/2.5.2 Python/3.9.11 Linux/5.10.16.3-microsoft-standard-WSL2 exe/x86_64.ubuntu.20 prompt/off

本筋に戻って、プッシュしていく。

# 認証トークンを取得し、レジストリに対して Docker クライアントを認証します。
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin xxx.dkr.ecr.ap-northeast-1.amazonaws.com
WARNING! Your password will be stored unencrypted in /home/masa86/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded

# 以下のコマンドを使用して、Docker イメージを構築します。
$ docker tag lambda-hello:latest xxx.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-hello:latest

# 以下のコマンドを実行して、新しく作成した AWS リポジトリにこのイメージをプッシュします。
$ docker push xxx.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-hello:latest
The push refers to repository [xxx.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-hello]
566c94023e30: Pushed
0a2ffc791a55: Pushed
af2bca515e37: Pushed
5f96311c404e: Pushed
87bc6f0d5aac: Pushed
f2ae3f427fe6: Pushed
c662e800f5c9: Pushed
latest: digest: sha256:e4fbe169bbaf9587b1bb7c62a365ff818c1e2fc8812bd413e144d49f0203f2ea size: 1787

関数の作成

コンソールで、「コンテナイメージ」から作成すれば OK。 後は、ZIP 形式と同じ。

以上。


最終更新日: 2022年4月2日