Serverless Framework を使い Python で記述した Lambda 関数を、外部モジュールを含めてデプロイする方法

2023.10.31

はじめに

アノテーション テクニカルサポートの川崎です。

社内育成の素材として、Serverless Framework を使用してデプロイするアプリを、アプリ作成の課題として、チームのメンバーに取り組んでいただいております。

その過程で経験したことを、Serverless Framework のデプロイ手順としてまとめてみます。

エラーの内容

Serverless Framework を使い Python で書かれた Lambda 関数をデプロイしようとしたところ、 import している外部モジュールがデプロイ対象に含まれずにエラーが発生しました。

実施した対処方法

Python で書かれた Lambda 関数を、Serverless Framework を利用してデプロイする際に、 Python の外部モジュールをデプロイに含めたい場合には、 serverless-python-requirements という Serverless Framework のプラグインを使用する必要があります。

外部モジュール requests を使ったサンプルをデプロイして、エラーを再現してみた

Serverless Framework の以下のバージョンで動作確認していきます。

% python3 -V
Python 3.11.6
% node -v
v18.18.0
% npm install serverless -g
% sls -v
Framework Core: 3.36.0
Plugin: 7.1.0
SDK: 4.4.0

Serveless サービスを新規作成します。

% mkdir python-lambda-sample1
% cd python-lambda-sample1
% sls create --template aws-python3
% rm handler.py
% touch lambda_function.py

使用するテンプレートは「aws-python3」を指定します。

こちらに、指定可能なテンプレートの一覧があります。

ここでは、テンプレートから生成された handler.py は削除し、lambda_function.py というファイル名で、Lambda Python のサンプルコードを記述します。(そのまま handler.py を使っても問題ありません)

外部モジュールとして Requests を利用しています。テストサイト「httpbin.org」に HTTP の GET メソッドでアクセスします。

lambda_function.py

import requests

def lambda_handler(event, context):
    # テストサイトにGETでアクセス
    response = requests.get("https://httpbin.org/get", params={"param1": "value1"})
    return response.json()

テンプレートから生成された serverless.yml には、コメント行が多数含まれています。不要なコメント行を削除するため、以下の内容で上書きします。

7行目は、デプロイ先として東京リージョンを指定しています。

10-11行目は、Lambda 関数名(get-access-sample)と、サンプルコードに合わせて、ファイル名と handler を指定しています。

serverless.yml

service: python-lambda-sample1
frameworkVersion: '3'

provider:
  name: aws
  runtime: python3.11
  region: ap-northeast-1

functions:
  get-access-sample:
    handler: lambda_function.lambda_handler

なお、テンプレートでは Python ランタイムのバージョンは 3.9 が指定されておりましたので、現時点で最新の 3.11 に変更しておきます。

上記の設定ができたら、デプロイを実行します。

ここでは、profile を指定して実行します。

% sls deploy --aws-profile sls-user

デプロイが成功したら、invoke コマンドで、デプロイした Lambda 関数を実行してみます。

すると、「Unable to import module」 (モジュールをインポートできません) というエラーが発生するのが確認できました。

sls invoke -f get-access-sample --aws-profile sls-user
{
    "errorMessage": "Unable to import module 'lambda_function': No module named 'requests'",
    "errorType": "Runtime.ImportModuleError",
    "requestId": "976f3153-3159-4d73-b1a8-377cad87b3bc",
    "stackTrace": []
}
Environment: darwin, node 18.18.0, framework 3.36.0, plugin 7.1.0, SDK 4.4.0
Credentials: Local, "sls-user" profile
Docs:        docs.serverless.com
Support:     forum.serverless.com
Bugs:        github.com/serverless/serverless/issues

Error:
Invoked function failed

serverless-python-requirements プラグインを使用する

上記のエラーを回避するために、プラグインをインストールして、外部モジュールをデプロイに含める設定を行っていきます。

既存の状態(エラー内容)を保存しておくため、別の Serveless サービスを作成し、そちらで確認していきます。

% mkdir python-lambda-sample2
% cd python-lambda-sample2
% sls create --template aws-python3
% rm handler.py
% touch lambda_function.py
  • lambda_function.py
  • serverless.yml

は、先ほどと同じ内容を記述します。

次のコマンドを実行して、プラグインをインストールします。

% sls plugin install -n serverless-python-requirements

「-n」は「--name」オプションで、プラグイン名を指定しています。

serverless.yml の内容を確認します。末尾にプラグインについての記述が追加されているのが確認できました。(13-14行目)

serverless.yml

service: python-lambda-sample2
frameworkVersion: '3'

provider:
  name: aws
  runtime: python3.9
  region: ap-northeast-1

functions:
  get-access-sample:
    handler: lambda_function.lambda_handler

plugins:
  - serverless-python-requirements

requirements.txt:Pythonプロジェクトで必要となるパッケージ(とそのバージョン情報)を記述するファイル

を作成し、外部モジュール requests を指定します。今回は、パッケージ名だけ記述して、ファイルを作成しています。

% echo requests > requirements.txt

デプロイを実行します。

% sls deploy --aws-profile sls-user

デプロイが成功したら、invoke コマンドで、デプロイした Lambda 関数を実行してみます。

% sls invoke -f get-access-sample --aws-profile sls-user
Running "serverless" from node_modules
{
    "args": {
        "param1": "value1"
    },
    "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate",
        "Host": "httpbin.org",
        "User-Agent": "python-requests/2.31.0",
        "X-Amzn-Trace-Id": "Root=x-xxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"
    },
    "origin": "xx.xxx.xxx.xxx",
    "url": "https://httpbin.org/get?param1=value1"
}

外部モジュール requests を使用した lambda_function.py が、実行できました。

外部モジュールを Lambda Layer としてデプロイする

外部モジュールを Lambda Layer としてデプロイする方法についても、確認しておきます。

serverless.yml にハイライトの行を追記します。

custom プロパティで、layer : true を指定するだけで、依存関係(requirements.txt)から Lambda Layer が作成されます。

16-17行目では、Lambda 関数から、作成した Layer への参照を追加しています。

serverless.yml

service: python-lambda-sample3
frameworkVersion: '3'

provider:
  name: aws
  runtime: python3.9
  region: ap-northeast-1

custom:
  pythonRequirements:
    layer: true

functions:
  get-access-sample:
    handler: lambda_function.lambda_handler
    layers:
      - Ref: PythonRequirementsLambdaLayer

plugins:
  - serverless-python-requirements

デプロイを実行します。

sls deploy --aws-profile sls-user

デプロイが成功したら、invoke コマンドで、デプロイした Lambda 関数を実行してみます。

% sls invoke -f get-access-sample --aws-profile sls-user
Running "serverless" from node_modules
{
    "args": {
        "param1": "value1"
    },
    "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate",
        "Host": "httpbin.org",
        "User-Agent": "python-requests/2.31.0",
        "X-Amzn-Trace-Id": "Root=x-xxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"
    },
    "origin": "xx.xxx.xxx.xxx",
    "url": "https://httpbin.org/get?param1=value1"
}

簡単にデプロイできますね!

参考資料

[1] AWS Python Example

[2] Serverless Framework: Plugins

[3] Serverless Framework Commands - AWS Lambda - Create

[4] Requests: HTTP for Humans™ — Requests 2.31.0 documentation

[5] httpbin.org

[6] [アップデート] AWS LambdaのサポートランタイムにPython 3.11が追加されました | DevelopersIO

[7] Lambda レイヤーでの作業 - AWS Lambda

アノテーション株式会社について

アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。「らしく働く、らしく生きる」のスローガンを掲げ、様々な背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。少しでもご興味あれば、アノテーション株式会社WEBサイトをご覧ください。