はじめに
ローカルでLambdaを開発する際に、いざデプロイしようと思った時に困った方も少なくないと思います。
というのも、デフォルトでプリインストールされていないライブラリをimportしている場合はzipファイルにしてAWSコンソール上でアップロードする必要があります。
もしくはServerless FrameworkやSAMを導入されている方が多いのかと思います。
個人的にはちょっとした関数をLambdaで実装しようとした時にServerless FrameworkやSAMはゴツすぎるというかやりたいことに対して出来ることと、開発環境や設定が手間になると思い敬遠していました。
そこで今回Lambdaがコンテナイメージをサーポートしたので慣れ親しんだDockerfileでデプロイできるなら良いのではと考え試してみました。
使用環境とバージョン
- macOS Catalina
- aws-cli/2.0.28
- Docker version 19.03.13
- Lambdaの使用言語 Python3.8
記事の対象
- ある程度AWSとDockerの知識がある方
- ECSもしくはEKSを利用してアプリケーションを作成している方
事前準備
- AWS CLIを利用できること
- IAMユーザもしくはスイッチロール先に
ecr:GetAuthorizationToken
権限が付与されていること ※1
Amazon ECR の作成
dockerイメージをpushするリポジトリを事前に作成します。
AWS CLIで作成します。以下のコマンドをローカルで実行して下さい。
aws ecr create-repository \
--repository-name lambda-container
実行ファイルの作成
今回作成するLambdaには以下の要素を含めようと思います。
- Lambda実行環境に標準で含まれないライブラリを使用する
- boto3により、AWSリソースにアクセスする
自分の経験則から、Lambdaを使用する際にpythonのライブラリを追加したいシーンがよくありました。
今まではpython -m venv {環境名}
でローカルに仮想環境を構築し、追加ライブラリをinstallしてからzipにしてAWSコンソールからLambdaにアップロードしていました。
その辺りがdockerイメージを使用することで改善できることを期待しています。
また、LambdaではよくAWSリソースを参照することがあるのでboto3での権限周りを整理できればより実用的なものになると思います。
以下のpythonファイルを使用することとします。
import os
import logging
import requests
import json
import boto3
rds = boto3.client('rds')
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# 返却用クラス
class LambdaResponse:
# コンストラクタ
def __init__(self, db, zip):
self.db = db
self.zip = zip
# json形式で返却
def json(self):
db = {}
instances = []
for instance in self.db['DBInstances']:
res = {}
res['Identifier'] = instance['DBInstanceIdentifier']
res['Status'] = instance['DBInstanceStatus']
instances.append(res)
db['Instances'] = instances
return {
'db': db,
'zip': self.zip
}
# メインハンドラー
def lambda_handler(event, context):
logger.info('event: {}'.format(event))
# お試し: RDSインスタンスをDescribe
describe = rds.describe_db_instances(DBInstanceIdentifier='edu-demodb-rds01-postgres11')
logger.info('describe db instances: {}'.format(describe))
# お試し: Defaultライブラリーに含まれない機能(郵便番号から住所を検索)
response = requests.get('https://zipcloud.ibsnet.co.jp/api/search?zipcode={}'.format(event['zip']))
logger.info('Status: {}, Body: {}'.format(response.status_code, json.dumps(response.json(), ensure_ascii=False)))
return LambdaResponse(describe, response.json()).json()
requirements.txtの作成
こちらはpip
での一般的なインストール方法のため説明を省きます。
詳しく知りたい方は以下を参照して下さい。
pip install - pip documentation v20.3.3
###### Requirements without Version Specifiers ######
requests
# boto3 <- Lambdaのdockerイメージに含まれているため不要です。無くても動きます
###### Requirements with Version Specifiers ######
Dockerfileの作成
以下に使用するDockerfileになります。
pip install -r requirements.txt
にて追加のライブラリをインストールします。
インストール先はDockerコンテナ内になるため、ローカル環境を汚すことはありませんでした。
CMD
には実行対象となるハンドラーを指定して下さい。
FROM public.ecr.aws/lambda/python:3.8
COPY lambda-container.py requirements.txt ${LAMBDA_TASK_ROOT}/
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
RUN pip list
CMD [ "lambda-container.lambda_handler" ]
dockerイメージは2種類ありDocker Hubにも用意されています。
基本的に同じものでリポジトリがAWSかDockerかの違いだと思うので基本的には手順通りのAWS側を使用すれば良いと思います。
amazon/aws-lambda-python - Docker Hub
FROM amazon/aws-lambda-python:3.8
また、dockerイメージから以下の環境変数が提供されています。
${LAMBDA_TASK_ROOT}
に必要なファイルをCOPY
して下さい。
The AWS base images provide the following environment variables:
- LAMBDA_TASK_ROOT=/var/task
- LAMBDA_RUNTIME_DIR=/var/runtime
Creating Lambda container images - AWS Lambda
ローカル環境でLambdaを実行
以下のディレクトリに成果物を配置することにします。事前に作成しておいて下さい。
$ mkdir lambda-container
$ cd lambda-container
$ ls
Dockerfile lambda-container.py requirements.txt
以下の手順でローカル環境にてLambdaを実行することができます。
$ docker build -t lambda-container .
$ docker run -p 9000:8080 lambda-container
$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"zip":"1310045"}'
{
"db": {
"Instances": [
{
"Identifier": "xxxxxxxxxx",
"Status": "stopped"
}
]
},
"zip": {
"message": null,
"results": [
{
"address1": "東京都",
"address2": "墨田区",
"address3": "押上",
"kana1": "トウキョウト",
"kana2": "スミダク",
"kana3": "オシアゲ",
"prefcode": "13",
"zipcode": "1310045"
}
],
"status": 200
}
}
boto3の認証について
上記の通りにローカル実行すると、以下のようなエラーが発生します。
これはdockerコンテナ内にboto3用のCredentialsが存在しないためです。
{
"errorMessage": "Unable to locate credentials",
"errorType": "NoCredentialsError",
"stackTrace": [
" File \"/var/task/lambda-container.py\", line 37, in lambda_handler\n describe = rds.describe_db_instances(DBInstanceIdentifier='xxxxxxxxx')\n",
" File \"/var/runtime/botocore/client.py\", line 357, in _api_call\n return self._make_api_call(operation_name, kwargs)\n",
" File \"/var/runtime/botocore/client.py\", line 662, in _make_api_call\n http, parsed_response = self._make_request(\n",
" File \"/var/runtime/botocore/client.py\", line 682, in _make_request\n return self._endpoint.make_request(operation_model, request_dict)\n",
" File \"/var/runtime/botocore/endpoint.py\", line 102, in make_request\n return self._send_request(request_dict, operation_model)\n",
" File \"/var/runtime/botocore/endpoint.py\", line 132, in _send_request\n request = self.create_request(request_dict, operation_model)\n",
" File \"/var/runtime/botocore/endpoint.py\", line 115, in create_request\n self._event_emitter.emit(event_name, request=request,\n",
" File \"/var/runtime/botocore/hooks.py\", line 356, in emit\n return self._emitter.emit(aliased_event_name, **kwargs)\n",
" File \"/var/runtime/botocore/hooks.py\", line 228, in emit\n return self._emit(event_name, kwargs)\n",
" File \"/var/runtime/botocore/hooks.py\", line 211, in _emit\n response = handler(**kwargs)\n",
" File \"/var/runtime/botocore/signers.py\", line 90, in handler\n return self.sign(operation_name, request)\n",
" File \"/var/runtime/botocore/signers.py\", line 162, in sign\n auth.add_auth(request)\n",
" File \"/var/runtime/botocore/auth.py\", line 357, in add_auth\n raise NoCredentialsError\n"
]
}
幾つか解決方法があると思いますが、簡単な方法として以下のように、docker run
する時に環境変数を設定します。
$ docker run \
-e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
-e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
-e AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \
-p 9000:8080 lambda-container
以下のドキュメントにあるように、boto3はConfigオブジェクトを使用して明示的に上書しない限り、環境変数を使用して認証を行うようです。
Using environment variables
Configurations can be set through the use of system-wide environment variables. If set, these configurations are global and will affect all clients created unless explicitly overwritten through the use of a Config object.
Configuration — Boto3 Docs 1.16.49 documentation
Amazon ECRにpush
無事ローカルでLambdaを実行することができたのでECRにpushします。
ECRへpushする方法については、以下の記事で解説していますので、手順のみ記載します。
Amazon ECRのDockerイメージをローカルにpull、pushする
$ aws ecr get-login-password --region ap-northeast-1 \
| docker login --username AWS --password-stdin {aws_account_id}.dkr.ecr.{region}.amazonaws.com
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
lambda-container latest 9ec9f9c8cabf 5 months ago 402MB
$ docker tag 9ec9f9c8cabf {aws_account_id}.dkr.ecr.{region}.amazonaws.com/lambda-container
$ docker push {aws_account_id}.dkr.ecr.{region}.amazonaws.com/lambda-container
Lmabdaをdockerイメージで作成
dockerイメージの準備が整ったので、Lambdaを作成していきます。
AWSコンソールにログインしてLambdaの作成でコンテナイメージを選択して下さい。
Lambdaの作成が完了しました。現状ではAWSコンソールではコードが表示されないようです。
まとめ
ちょっとしたLambdaの場合は不要かと思いますが、何度も修正が想定される場合は良いと思いました。
また、CodeCommitで履歴管理し、CodePipelineで自動デプロイすればより使いやすいと思います。