AWS Lambda を Python2 からPython3に移行しました

こんにちは。Data Engineer の @shase です。

何かしらの移行ばかり今年はしているのですが...前回はElasticsearchの移行で今回はAWS Lambdaの話です。

2020年1月にPython2がEOLになり、4月に最終バージョンの Python 2.7.18 がリリースされ、Python2自体の開発は終了しました。

AWS LambdaのRuntimeとしては、多少紆余曲折ありました。現在のアナウンスでは、Python2のRuntimeに対するサポートは2021年6月までは継続するものの、ユーザに対しては速やかなPython3への移行が求めらています。

今回は、自分が担当しているAWS Lambdaの既存システムを、順次Python3に移行させたので、その記録を紹介したいと思います。

システム概要

f:id:quipper-ja:20201120111917p:plain
aws lambda

  • Kinesis Data Streamに、各種WebアプリケーションからログがPUTされています。
  • Kinesis Data StreamをTriggerにLambda Functionが起動され、Transformして、外部のシステムにLoadしているものです。
  • いわゆるストリームデータのETLを担っています。

パッケージング&デプロイ方式

作られた時代が古いこともあり、デプロイ用のシェルスクリプトでzipパッケージを作成し、AWS CLIでデプロイされています。

(ちょっとこの辺に手を入れたかったのですが、余裕がなく後日やりたいと思っています。後述。)

移行方式

移行にあたって考慮したのは以下の点です。

  • 極力ダウンタイムは小さくしたい
  • リリースした場合(エラー時の)切り戻しを容易にしたい

いろいろ検討したのですが、Python2 Runtimeとは別に、Python3 Runtimeを用意して、Lambdaの event-source-mapping のupdate(切り替え)で対応することにしました。

一時的にRuntimeが2つになってしまう煩雑さはあるのですが、コードの互換性があまりないことと、ダウンタイムを小さくしたいのを考えた場合に、今回はこれがベターかなと判断しました。

AWS SAMに準拠しているなど、前提が違えば、移行方式も変わってくるとは思います)

ちなみに、当初考えていた移行案として、AWS LambdaのAlias機能で、Function間のAliasを設定できればいいのかな、というのがありました。しかし、LambdaのAlias機能は、特定Functionのバージョニングの切り替えのみサポートしているため、今回の想定ではマッチしませんでした。

Python2からPython3へのコード変換

Pythonには、2to3 という公式のコンバートツールがあるので、基本的にはこちらを使いました。 2to3.8 -w example.py とすることで、変換前のbackupファイルが作成され、example.py自身はPython3のコードになります。

概ね、2to3で自動変換されたのですが、一部ネストが深いところなど、自動で変換されない箇所がありました。また、当たり前ですが使っているライブラリのバージョンアップ等で挙動が変わっている箇所は当然手動で修正しています。

ちなみに自動で変換された例としては、もともと以下の様なコードがあったのですが

# dはdict
for k, v in d.items():
  # dictに対する操作

Python2の dict.items() はリストそのものを構築していたのですが、Python3では、パフォーマンスのためにイテレータを返すように変更になっています(Python2でいう、dict.viewitems() )。止む無くPython2と同じ様な挙動をさせたいときは、 list(d.items()) しなさい的なことを自動でやってくれました。

こんな感じで、比較的シンプルなETLのコードということもあり、(リファクタリングは別途必要なものの)全体的にすんなり変換できています。

実際の移行作業

新しいPython3 Runtimeをデプロイ後の実際の移行作業は簡単で、 aws lambda update-event-source-mapping --uuid "xxx" --function-name "arn:xxx" で、新旧のmappingを切り替えるだけでした。

update-event-source-mapping に必要なfunctionに紐づくuuidは aws lambda list-event-source-mappings --function-name xxx で確認することができます。また、functionのarnは aws lambda get-function --function-name xxx で確認できます。

移行後のモニタリング

移行前後で特にモニタリング内容が変わるわけではありませんが、DatadogのMonitorを使って(取得できるものはすべて見ているものの)主に以下のようなメトリクスを私は見ていました。

  • Kinesis Data Stream
    • GetRecords.IteratorAgeMilliseconds
    • GetRecords.Success
  • Lambda Function
    • IteratorAge
    • Error count and success rate

一部 Function の切り替え時に、ちょっとしたトラブルがあったのですが、今回のリリース方式とこれらの監視のおかげで、すぐに切り戻すことができました(その後無事作業は終わっています)。

終わりに

正直、今回の移行で一番苦労したのは、独自のデプロイスクリプトをなんとかする所でした。最初はシンプルな仕組みも、数年が経過し、様々な人の手が入るとどうしても複雑化してしまうと思います。また、現状のAWS CLIベースのデプロイはLambdaのConfigurationの管理の課題も残っています。

安全なデプロイと構成管理のために、AWS SAMに準拠した形に変えていくのか、Serverless Frameworkのようなものを今後導入するのか、このあたりが今後の検討事項かなと思っています。

Python2からPython3への変更のような破壊的な変更はそうそうあることでもありませんが、Python3のRuntimeはなるべく(楽に)最新版に追随できるように、今後デプロイ周りの整備をしていきたいと思っています。