
AWS Transform custom で Lambda のランタイムを Python 3.10 から 3.13 にアップグレードしてみた
製造ビジネステクノロジー部の小林です。
現在、AWS Lambda では、Python 3.10 のサポート終了が告知されています。
運用中のシステムに Python 3.10 の Lambda 関数が残っていて、サポート終了までにランタイムを上げないといけなくなりました。
1 本ずつ手で直してもいいのですが、せっかくなので AWS Transform custom の AWS マネージド変換を使って、Python 3.10 から Python 3.13 へのアップグレードを試してみます。
Python 3.10 ランタイムの期限
まず、どのくらい時間が残っているのかを整理します。Lambda のランタイム一覧に載っている Python 系の予定は次のとおりです。
| ランタイム | OS | 非推奨日 | 関数作成のブロック | 関数更新のブロック |
|---|---|---|---|---|
| python3.10 | Amazon Linux 2 | 2026 年 10 月 31 日 | 2027 年 2 月 1 日 | 2027 年 3 月 3 日 |
| python3.11 | Amazon Linux 2 | 2027 年 6 月 30 日 | 2027 年 7 月 31 日 | 2027 年 8 月 31 日 |
| python3.12 | Amazon Linux 2023 | 2028 年 10 月 31 日 | 2028 年 11 月 30 日 | 2029 年 1 月 10 日 |
| python3.13 | Amazon Linux 2023 | 2029 年 6 月 30 日 | 2029 年 7 月 31 日 | 2029 年 8 月 31 日 |
非推奨日を過ぎても関数の呼び出し自体は続けられます。止まるのはセキュリティパッチの提供とテクニカルサポートで、加えてコンソールからの作成・更新ができなくなります。
さらに期日が来ると CLI や CloudFormation 経由の更新もブロックされるので、パイプラインからのデプロイが通らなくなります。
運用中のシステムだと、実質ここが締め切りだと思っておいた方がよさそうです。
Python 3.11 は Amazon Linux 2 ベースの最後の Python ランタイムで、非推奨日が 2027 年 6 月 30 日。3.10 の 2026 年 10 月 31 日から 8 か月しか延びないため、ここに上げても近いうちに同じ作業をもう一度やることになります。
残る 2 つのうち、3.12 は 2028 年 10 月 31 日、3.13 は 2029 年 6 月 30 日まで猶予があります。今回は次の移行までの間隔をできるだけ長く取りたかったので、3.13 を選びました。
AWS Transform custom とは
AWS Transform は、エージェント型の AI でアプリケーションのモダナイゼーションを支援するサービスです。.NET やメインフレーム、VMware の移行といった用途が用意されていますが、今回使うのはその中の AWS Transform custom です。
AWS Transform custom は、任意の Git リポジトリを対象にコードを変換してくれる機能です。やってほしいことを「変換定義」として指定すると、エージェントがコードを解析して、修正・ビルド・検証まで進めてくれます。
変換定義は自分で書くこともできますが、よくあるパターンは AWS が用意した「AWS マネージド変換」として提供されていて、名前を指定するだけで呼び出せます。Lambda のランタイム更新も、公式ドキュメントでユースケースとして挙げられています。
| 言語 | 変換定義名 |
|---|---|
| Python | AWS/python-version-upgrade |
| Node.js | AWS/nodejs-version-upgrade |
| Java | AWS/java-version-upgrade |
対応リージョン
利用できるのは次の 8 リージョンで、東京リージョンも含まれています。
- us-east-1 / ca-central-1
- eu-central-1 / eu-west-2
- ap-northeast-1 / ap-northeast-2 / ap-south-1 / ap-southeast-2
料金
エージェントが計画・分析・コード修正を行っている時間に対して課金されます。ローカルでのビルドやテスト実行を待っている時間は課金対象外です。
事前準備: AWS Transform CLI をインストールする
AWS Transform CLI(atx)の動作条件は次のとおりです。
- Node.js 22 以降
- Git(変換の対象ディレクトリが Git リポジトリである必要があります)
- AWS 認証情報(
AWSTransformCustomFullAccessなどの IAM ポリシー)
インストールは公式のスクリプトを使います。Service management 画面に表示されていたコマンドと同じものです。
curl -fsSL https://transform-cli.awsstatic.com/install.sh | bash
Node.js のバージョンチェックからチェックサムの検証、~/.local/bin/atx へのシンボリックリンク作成まで、一通り自動でやってくれます。

今回入ったのは 3.14.1 でした。~/.local/bin が PATH に入っていない場合は追加しておきます。
リージョンは環境変数や AWS CLI の設定から解決されるので、東京リージョンで動かすなら次のように指定しておきます。
export AWS_REGION=ap-northeast-1
export AWS_PROFILE=your_profile_name
AWS Transform サービスを作成する
AWS Transform を使うにあたって、まずマネジメントコンソールでウェブアプリを有効化しておきます。
正確にはこの操作は「AWS Transform のウェブアプリの有効化」で、CLI(atx)だけで完結させるなら必須ではありません。ただし有効化しておくと、CLI のインストールコマンドやジョブの実行状況をコンソール側から確認できるので、初回は通しておくのが分かりやすいと思います。
マネジメントコンソールで AWS Transform を開くと、「Enable web application」の画面になります。

セットアップ方法は 2 つあります。
| Quick start | Manual setup | |
|---|---|---|
| 認証 | IAM-only access(既存の AWS 認証情報) | IAM Identity Center による SSO も選択可 |
| 暗号化 | AWS 管理キー | KMS カスタマー管理キーを指定 |
| ストレージ | AWS Transform マネージドストレージ | 自前の S3 バケットなど |
今回は検証用アカウントなので Quick start を選びました。ストレージ設定は後からでも変更できます。
一方、組織で SSO を必須にしている場合や、ソースコードの暗号化キー・保管先を自社管理にする必要がある場合は Manual setup を選びます。顧客環境や本番相当のアカウントで使うなら、こちらを検討することになりそうです。
「有効にする」を押すと、「Service management」画面に情報が表示されます。

ここで確認できるのは次の情報です。
- Web application URL … ウェブアプリのサインイン URL(
https://<テナントID>.transform.<リージョン>.on.aws/signin/形式) - CLI install command … AWS Transform CLI のインストール用コマンド
- Service profile ARN / Tenant ID … サービスの識別子
User access の Access model が IAM-only access になっていれば、IAM Identity Center を別途用意しなくても、いま使っている IAM ユーザー / ロールのままウェブアプリにアクセスできます。

Python 3.10 の Lambda 関数をコンソールで用意する
Lambda コンソールの「関数の作成」から、次の設定で作ります。
- 一から作成
- 関数名:
python310-upgrade-demo - ランタイム: Python 3.10
変換の効果が見えるように、Python 3.12 で削除された distutils と、非推奨になった datetime.utcnow() を含むコードを置いてみます。
import json
import os
from datetime import datetime
from distutils.util import strtobool # Python 3.12 で標準ライブラリから消えるモジュール
from typing import Dict, List
def collect_targets(event: Dict) -> List[str]:
"""イベントから処理対象の名前だけを取り出す。変換で型注釈がどう変わるかを見たいので、あえて typing 版で書いている。"""
return [item["name"] for item in event.get("items", [])]
def lambda_handler(event, context):
# 環境変数の "true"/"false" を真偽値にするのに strtobool を使っている。ここが移行時に引っかかる
verbose = strtobool(os.environ.get("VERBOSE", "false"))
targets = collect_targets(event)
body = {
"processed_at": datetime.utcnow().isoformat(), # naive な datetime を返すため 3.12 以降は非推奨警告が出る
"targets": targets,
"count": len(targets),
}
if verbose:
print(json.dumps(body))
return {"statusCode": 200, "body": json.dumps(body)}
「Deploy」したあと、テストイベントを作って動かしておきます。変換後に同じイベントで比べられるようにしておくと、確認が楽になります。
{
"items": [
{ "name": "order-001" },
{ "name": "order-002" }
]
}
Python 3.10 のまま実行して、正常に返ってくることを確認しました。

関数コードをダウンロードして Git リポジトリにする
コンソールの関数画面で「アクション」→「関数のダウンロード」→「.zip ファイル」を選ぶと、デプロイパッケージを取得できます。

展開して Git リポジトリとして初期化します。AWS Transform custom は変更を Git のコミットとして積んでいくので、この作業が必要になります。
mkdir python310-upgrade-demo && cd python310-upgrade-demo
unzip ~/Downloads/python310-upgrade-demo.zip
git init
git add .
git commit -m "Initial commit (python3.10)"
補足: CDK / SAM で管理している場合
今回はコンソールで作った関数を題材にしたため .zip をダウンロードしていますが、CDK や SAM でコード管理しているなら、このステップは不要です。
変換を実行する
リポジトリのルートで atx を起動します。
cd python310-upgrade-demo
atx
起動すると、参照しているリージョンと「Trusted Tools」の一覧が表示されます。

デフォルトで自動実行が許可されているのは file_read や grep といった読み取り専用のツールだけです。ファイルの書き込みやコマンド実行は、その都度確認を求められます。-t を付けるとすべてを許可でき、許可設定は ~/.aws/atx/trust-settings.yaml で管理されています。
日本語で依頼できる
セッションの中は自然言語でやり取りできます。英語だけでなく、日本語でもそのまま通りました。

この Python 3.10 の Lambda 関数を Python 3.13 にアップグレードしてください。
エージェントが変換定義を選んでくれる
送信すると、エージェントがまず list_available_transformations_from_registry でレジストリを検索し、使える変換定義を探し始めました。

この時点で AWS マネージド変換が 36 個登録されていて、Java の SDK v1→v2 や .NET のモダナイゼーション、MuleSoft からサーバーレスへの移行など、かなり幅広い変換が揃っています。
検索結果から AWS/python-version-upgrade が今回の用途に合うと判断して、次の 2 つを提示してきました。
- この AWS マネージド変換をそのまま適用する(リポジトリのパスを教える)
- Lambda 固有の要件があるなら、カスタム変換定義を新規作成する

変換定義名をこちらから指定しなくても、やりたいことを日本語で伝えるだけで適切なものを選んでくれました。
カスタム変換定義のほうは、AWS マネージド変換ではカバーされない自社固有のルール(共通ロガーへの差し替え、Powertools の導入、禁止ライブラリの置換など)を手順書として定義し、レジストリに登録して再利用するための仕組みです。今回は素のランタイム更新だけなので、マネージド変換をそのまま使います。
リポジトリのパスを聞かれるので、カレントディレクトリを指定します。リポジトリのルートで atx を起動しているので . で足ります(絶対パスでも問題ありません)。
. のリポジトリに適用してください。
ツールの実行は都度確認される
パスを答えると、エージェントがシェルコマンドの実行許可を求めてきました。

起動時の Trusted Tools に含まれていたのは読み取り専用のツールだけなので、shell はここで確認が入ります。
選択肢は次の 4 つです。
y… この 1 回だけ許可n… 拒否t… このセッション中はshellを信頼する- 文章を入力 … 承認せず、別の方法を指示する
この後 shell は何度も呼ばれるので、内容を確認したうえで t を選びました。非対話モードの -t オプションは、これを最初から全ツールに適用する指定にあたります。
コードを読んで論点を整理してくれる
リポジトリの構成を把握したあと、file_read で lambda_function.py を読み、状況をまとめてくれました。

仕込んでおいた 3 点が、そのまま指摘されています。
from distutils.util import strtobool…distutilsは Python 3.12 で削除されたため、ローカル実装への置き換えが必要datetime.utcnow()… Python 3.12 で非推奨。datetime.now(datetime.timezone.utc)に置き換えるfrom typing import Dict, List… 組み込みのdict/listを使う書き方に近代化できる
加えて「テスト・依存関係ファイル・CI/CD 設定・Dockerfile はいずれも存在しない」と、周辺のファイル構成まで把握したうえで報告してくれました。単にコードを機械的に置換するのではなく、プロジェクト全体を見てから計画を立てていることが分かります。
最後に移行先のバージョンを聞かれたので、3.13 と答えて進めます。
3.13 でお願いします。
検証まで済ませて完了する
修正が終わると、変換定義の完了条件(exit criteria)を判定した結果を報告してくれました。

変更内容
distutils.util.strtobool(3.12 で削除)をローカル実装に置き換えdatetime.utcnow()(3.12 で非推奨)をdatetime.now(timezone.utc)に置き換え- 型ヒントを
typing.Dict/typing.Listから組み込みのdict/listに近代化
検証結果
- ターゲットバージョンでコンパイルできる … PASS
- テスト・依存関係ファイル・ドキュメント・デプロイスクリプトの更新 … NON-APPLICABLE(いずれも存在しない)
該当しない項目を NON-APPLICABLE として明示したうえで、全ての exit criteria を満たしたと判定しています。何をもって完了としたのかが追えるので安心感がありますね。
検証結果は Markdown としても保存されるため、後からレビューに回せます。
~/.aws/atx/custom/<ジョブID>/artifacts/validation_summary.md

差分を確認する
main との差分を見てみます。
git diff main

変更は lambda_function.py 1 ファイル、狙った 3 点だけでした。
1. distutils.util.strtobool の置き換え
-from distutils.util import strtobool
+def strtobool(val: str) -> bool:
+ """Convert a string representation of truth to True or False.
+
+ Raises ValueError if val is anything else.
+ """
+ val = val.strip().lower()
+ if val in ("y", "yes", "t", "true", "on", "1"):
+ return True
+ if val in ("n", "no", "f", "false", "off", "0"):
+ return False
+ raise ValueError(f"invalid truth value {val!r}")
元の distutils.util.strtobool と同じ真偽値セットを網羅していて、不正値で ValueError を投げる挙動まで再現されています。呼び出し側を書き換えずに済む形になっているのが良いですね。
2. datetime.utcnow() の置き換え
-from datetime import datetime
+from datetime import datetime, timezone
...
- "processed_at": datetime.utcnow().isoformat(),
+ "processed_at": datetime.now(timezone.utc).isoformat(),
import への timezone 追加も漏れていません。
3. 型注釈の近代化
-from typing import Dict, List
-def collect_targets(event: Dict) -> List[str]:
+def collect_targets(event: dict) -> list[str]:
不要になった from typing import 自体も削除されています。
コメントや docstring は追従しない
一方で、collect_targets の docstring はそのまま残りました。
def collect_targets(event: dict) -> list[str]:
"""イベントから処理対象の名前だけを取り出す。変換で型注釈がどう変わるかを見たいので、あえて typing 版で書いている。"""
型注釈は dict / list に変わったのに、説明文は「typing 版で書いている」と言い続けています。コードは正しく直っても、コメントや docstring の記述が古いままになるケースがあるので、レビュー時はこのあたりも見ておきたいところです。
挙動が変わる点に注意
今回の変換で気をつけたいのは日時まわりです。utcnow() は naive な datetime を返していましたが、now(timezone.utc) は aware な datetime を返します。isoformat() の出力も +00:00 が付くようになるため、後続の処理で文字列比較や datetime 同士の比較をしている場合は結果が変わる可能性があります。
コンソールでランタイムを更新する
今回は単一ファイルで依存ライブラリもないので、コンソールのエディタに変換後のコードを貼り付けて反映します。
- 対象の Lambda 関数を開く
- 「コード」タブのエディタで
lambda_function.pyを変換後の内容に差し替えて「Deploy」 - 「ランタイム設定」→「編集」でランタイムを Python 3.13 に変更

コードを先に Deploy してからランタイムを変更するのがおすすめです。逆にすると、3.13 に切り替わった直後だけ distutils を import する古いコードが載った状態になり、そのタイミングで呼び出されると ModuleNotFoundError になります。
なお、依存ライブラリがある場合や複数ファイル構成の場合はコンソールエディタでは扱えないので、「アップロード元」→「.zip ファイル」で変換後のパッケージをアップロードします。
zip -r ../python310-upgrade-demo.zip . -x '*.git*' -x '__pycache__/*'
Python 3.13 でテストする
ランタイムを 3.13 に変更して、ステップ 1 と同じテストイベントを実行します。

{
"statusCode": 200,
"body": "{\"processed_at\": \"2026-09-21T06:45:33.412114+00:00\", \"targets\": [\"order-001\", \"order-002\"], \"count\": 2}"
}
targets と count は変換前と同じで、ロジックは壊れていません。一方 processed_at には +00:00 が付いています。datetime.utcnow() は naive な datetime を返していましたが、datetime.now(timezone.utc) は aware な datetime を返すため、isoformat() の出力にオフセットが含まれるようになったためです。
まとめ
AWS Transform custom の AWS/python-version-upgrade を使って、Python 3.10 の Lambda 関数を Python 3.13 へ上げてみました。
普通に AI に頼むのと何が違うか
完了条件の自己検証、Git コミットへの積み上げ、はどちらも普通の AI コーディングエージェントでも実現できる部分ではあります。とはいえ、変換定義に完了条件がセットで入っていて、それに沿って毎回同じ基準で判定してくれるのは安心感がありました。
一番の価値だと感じたのは、変換定義がレジストリでバージョン管理されている点です。同じ定義を複数の関数・複数のリポジトリに流しても「同じ定義・同じ根拠で変換した」ことが後から追える。1 つのリポジトリの中に対象の関数が何本もあるようなケースでも、この一貫性はそのまま効いてきます。
今回は 1 関数だけの検証なので、このスケールメリットは複数関数・複数リポジトリで試して確かめたいところです。
この記事がどなたかの参考になれば幸いです。





