はじめに
サーバーワークスの宮本です。今回は本番運用していた AWS Lambda 関数が何も変更していないのに突然動かなくなった話を共有します。一見すると信じられない話ですが、最後までお読みいただけると幸いです。
前提
対象の Lambda 関数に関する基本情報(今回の話に関係ある部分のみ)は以下の通りです。
- 2023/01 に初回デプロイし、運用を続けていた
- ランタイムは Python3.9
- 依存ライブラリは Lambda Layer にまとめている
- 月に数回動かすようなバッチ処理
ある日のこと
4月某日のことです。当該 Lambda の実行でエラーが発生したことが通知されました。以下はエラー内容の抜粋です。
[ERROR] Runtime.ImportModuleError: Unable to import module '{モジュール名}': cannot import name 'DEPRECATED_SERVICE_NAMES' from 'botocore.docs' (/opt/python/botocore/docs/__init__.py) Traceback (most recent call last):
おやおや... 知らない子ですね。当該 Lambda は前月まで問題なく動作しており、設定もプログラムも変更していません。一体何が起きたのでしょう?botocore
で問題が起きているようですが全く心あたりがありません。
ググってみる
とりあえず、エラーメッセージでググってみました。すると以下 boto3 の Issue がヒットしました。boto3 のインポートでエラーが出ており、botocore と boto3 のバージョンが競合していることに起因する事象のようです。(これを書いている今はすんなり理解できるのですが、当時はあまりピンと来ていませんでした。)
botocore、boto3 のバージョンを確認してみる
なんとなく調査ポイントが掴めたので、Lambda の状況を確認してみることにします。当該プロジェクトでは、依存ライブラリを Pipenv で管理していたので、Pipfile
の内容を確認します。
# 一部抜粋 [packages] aws-lambda-powertools = "*" pynamodb = "*" pydantic = "*" # 以下略 [dev-packages] boto3 = "==1.20.32" black = "*" pytest = "*" # 以下略
boto3 は dev-package
に含まれており、デプロイ時のパッケージング対象ではないようです。これは、Lambda 環境にデフォルトでセットアップされている boto3 が使われるということを意味します。botocore は boto3 の依存として、同じくデフォルトでセットアップされているはずなので、なぜバージョンの競合が発生するのかよくわかりません。
試しに手元の環境で、新規に Python3.9 の Lambda を作成し、バージョンの確認をしてみることに。バージョンは以下のコードで確認することができます。
def lambda_handler(event, context): import botocore print(botocore.__version__) import boto3 print(boto3.__version__)
これを実行すると以下のログが出力されました。botocore、boto3 ともにバージョンは 1.34.42
です。
INIT_START Runtime Version: python:3.9.v51 Runtime Version ARN: arn:aws:lambda:ap-northeast-1::runtime:850b605499cb99649454b5f45a401c538f93f8eb6e094569c572200f6773b28e START RequestId: 8b6ad0a0-1b53-4902-9e86-ba25cade32a6 Version: $LATEST 1.34.42 1.34.42 END RequestId: 8b6ad0a0-1b53-4902-9e86-ba25cade32a6
が、ここである事実に気がつきました。1行目の Runtime Version: python:3.9.v51
の部分です。2023/01 に発表されたランタイム管理制御機能により、ランタイムバージョン管理が行われるようになったのです。
検証用に作成した Lambda では、ランタイムバージョンが v51 ですが、事象が発生している Lambda では異なるランタイムバージョンを使用している可能性もありそうです。
徐々に核心に
事象が発生している Lambda のランタイムバージョンをログから確認します。まずは4月某日のエラー発生時のランタイムバージョンです。
INIT_START Runtime Version: python:3.9.v47 Runtime Version ARN: arn:aws:lambda:ap-northeast-1::runtime:10c0a712b86e00044c2a7684d5440555c68f84a2c1426a98dcccee6f5f9eb905
ランタイムバージョンは v47 でした。検証用Lambda のランタイムバージョンを v51 -> v47 に変更(マネジメントコンソールから実施可能です。以下画像参照)します。
その上で、botocore、boto3 のバージョンを確認したところ以下の結果でした。
- botocore==1.34.42
- boto3==1.34.42
同様に、成功した前月以前のランタイムバージョンを確認します。
INIT_START Runtime Version: python:3.9.v46 Runtime Version ARN: arn:aws:lambda:ap-northeast-1::runtime:785596225436995dcc561b9cc5101b0b76da2573cf322ec7ca1d619abb3fc581
ランタイムバージョンは v46 でした。冒頭で何も変更していない旨を書きましたが、ランタイムバージョンがAWSによって自動的にアップデートされていたのです...!!こちらも同様に botocore、boto3 のバージョンを確認したところ以下の結果でした。
- botocore==1.29.90
- boto3==1.26.90
しかしながら、ランタイムバージョン v47 に同梱される botocore、boto3 のバージョンは共に 1.34.42 で競合が出るとは思えません。念の為、手元の適当な環境で boto3==1.34.42 が要求する botocore のバージョンを確認します。
$ pipenv install boto3==1.34.42 $ pipenv graph boto3==1.34.42 ├── botocore [required: >=1.34.42,<1.35.0, installed: 1.34.94] │ ├── jmespath [required: >=0.7.1,<2.0.0, installed: 1.0.1] │ ├── python-dateutil [required: >=2.1,<3.0.0, installed: 2.9.0.post0] │ │ └── six [required: >=1.5, installed: 1.16.0] │ └── urllib3 [required: >=1.25.4,<1.27, installed: 1.26.18]
boto3==1.34.42 は botocore 1.34.42 以上、1.35.0 より前のバージョンに対応していることがわかります。よって v47 に同梱されているバージョンは問題ありません。(当たり前)
なぜバージョン競合が発生するのか
他に botocore、boto3 のバージョンが変わる要因を考えてみた結果、他の依存ライブラリが botocore、boto3 に依存している可能性がありそうだと考えました。pipenv graph
で同様に確認します。
$ pipenv graph pynamodb==5.3.4 - botocore [required: >=1.12.54, installed: 1.29.51] - jmespath [required: >=0.7.1,<2.0.0, installed: 1.0.1] - python-dateutil [required: >=2.1,<3.0.0, installed: 2.8.2] - six [required: >=1.5, installed: 1.16.0] - urllib3 [required: >=1.25.4,<1.27, installed: 1.26.14] # 以下略
PynamoDB が botocore に依存していることが判明しました...!! バージョンは 1.29.51 で、boto3==1.34.42 と互換性がないバージョンです。
ちなみに、Lambda Layer のソースコードは以下コマンドでダウンロードURLを得られるので、ダウンロード・解凍してバージョン確認ができます。
$ aws lambda get-layer-version \ --layer-name {Lambda Layer 名} \ --version-number {バージョン番号} { "Content": { "Location": "{ダウンロードURL}", "CodeSha256": "5O9DQ69ESEReYe3rDumATsrw9XNCzComASZzae+OgAE=", "CodeSize": 48265363 }, # 以下略 }
原因まとめ
まとめると、以下の原因で botocore、boto3 のバージョン競合が発生していました。
- boto3 はランタイム同梱のものを使用。バージョンは 1.34.42
- botocore は PynamoDB が依存しており、Lambda Layer に含まれる。バージョンは 1.29.51 で boto3 のバージョンと互換性がないためインポート時にエラーが発生する
- ランタイム同梱の botocore であれば競合は発生しないが、ランタイム同梱のものより Lambda Layer のライブラリが優先されるようです
- ランタイムバージョン v46 では競合は発生しなかったが、自動更新で v47 になったことにより競合が発生するようになった
対応
まず、暫定対応として当該 Lambda のランタイムバージョンを正常に動く v46 に変更・固定しました。これによりバージョン競合の問題はひとまず解決します。
しかしながら、バージョンを固定してしまうと Lambda 基盤のアップデートが行われず、少なからずセキュリティリスク等を持つ状態になってしまうため、恒久的にはランタイムバージョンの更新は自動で行われるようにするのが好ましいです。ランタイムバージョンが自動更新される状態で、競合を発生させないようにするため、ランタイム同梱の boto3 は使用せず Lambda Layer に含むようにする予定です。
おわりに
ソフトウェアは何もしないと壊れるを肌で感じた体験でした。長期的な運用を考慮した設計、継続的なメンテナンスが大事ですね。この記事がどなたかのお役に立てば幸いです。