大好きなLambdaでArm64 Graviton2 CPUを利用できるようになったので、既存のPython3.6 + x86_64 から切り替えてみました。
基本的でたいした内容ではないですが、ギリ時事ネタということで、要点をまとめてみたいと思います。
更新内容
とりあえずLambdaの更新内容の確認から。- AWS Graviton2 プロセッサを搭載した AWS Lambda 関数 – Arm で関数を実行し、最大 34% 優れた料金パフォーマンスを実現 | Amazon Web Services ブログ
- AWS Lambdaがarm64アーキテクチャをサポートしました | DevelopersIO
これからLambda + Pythonで構成する場合
CPUアーキテクチャが増えただけでなく、Pythonも1~2年でマイナーバージョンが増えていくので、選択に迷うことがあるかもしれません。もし今から新たにLambda + Pythonで構成するならば、今回私が選択した Python3.9 + Arm64 が現在と未来を見据えてベターな選択になると思います。半歩、運用しやすさに振って Python3.9 + x86_64 というのもアリ。
まずはその辺の理由・考え方から整理していきます。
Pythonのサポート期限
Pythonはサポート期限がキッチリしていて、5年となっています。切れるとどうなるかというと、Lambdaだと単に使えなくなっていきます。
- [Lambda] Python 2.7 のサポートが2021年7月15日に終了します | DevelopersIO
- AWS Lambda Python2 を Python3 に変換した記録 | 外道父の匠
偏見かもですが、Lambdaのコードはそこまで奥深いことをしないと思うので、Python3 として動くのであれば、できるだけ新しく対応されているマイナーバージョンを選択する方がよいでしょう。
で、Lambda の現時点で使える最新は 3.9 かつ、Arm64対応は 3.8 or 3.9 となれば、3.9 でいきましょうとなるわけです。
Python3.9 + Arm64 のデメリット
この選択は、サポート期限まで長く、速く安い!という牛丼みたいなメリットがありますが、デメリットにも触れておきます。まず、現時点で多くのディストリビューションで入るデフォルトのPythonは 3.6 or 3.7 です。そのため、インストール物の用意やコードの開発のために、Python3.9 を準備する必要があります。しかもパッケージがないので、ソースから入れたりパッケージングすることになります。
アーキテクチャについては、最初からArm64なら問題ないですが、x86_64 からの切り替えとなると、pip でのインストール物一式やライブラリファイルを新たに用意することになりますし、それをするための環境がなければEC2でArm64として起動する必要があります。
なので、ベストではなくベター程度なのですが、数年スパンでみた運用を踏まえると、このくらいの舵の切り方をするのが良さげという個人的所感です。
あとはGraviton2が少々特徴的なので、こちらも参考にってところ。
切替時の互換性
あくまでPython3.6 + x86_64 → Python3.9 + arm64 への切り替えで、かつ私の小規模なPythonコードでの話になりますが、Lambdaに収めるzipの中身としては大きく3種類あり、それぞれの互換性はこんな感じという感触。3.6 → 3.9 | x86_64 → arm64 | 役割 | |
*.py , */*.py | ▲ | ○ | メインコードやModuleなど |
python/ | △ | △ | pip3のインストール物一式 |
lib/ | ○ | ☓ | .so などライブラリファイル |
Python3コード自体は、マイナーバージョンを上げてもエラーが出るほどの影響はなく、Warningが少々出た程度でした。もちろん、これはコード次第の話。
python/ は pip3 で同梱するファイル群で、そのまま動くものもあるけど、全く動かなくものもあります。これは、インストール処理時にPythonバージョンやCPUアーキテクチャをファイル名に入れるものがあるためで、当然名前を合わせるだけでなく再インストールする必要があります。
lib/ は私の場合、mysqlclient(MySQLdb)で必要な libmysqlclient.so.18 だけですが、これはCPUアーキテクチャごとに合わせたパッケージのものが必要になるので、名前が同じだけでは動きません。ファイルが存在しても、アーキテクチャが異なれば ”No such file or directory” とわかりづらく怒られます。
メインのLambdaコードのzipは中央アカウント的な所のS3に置いて、複数サービスで共通利用してたりするので、旧式のままにしておくのと、新式にするのとを無駄なく混在運用するのは考えどころとなります。
コード構成の変更
以上のことから、旧式と新式のコード管理は、丸っと分けてしまうのが最も分かりやすく安定します。メインもレイヤーもそれぞれ用意し、Terraformでの指定も完全に切り替えるだけになるからです。ただ、それをやると2つのレポジトリになって、たいした修正を加える必要のないPythonコードが重複することになるので、今回は1つのレポジトリのままやりくりすることにしました。
3.6用と3.9用のディレクトリにそれぞれの python/ lib/ を入れた zip にし、レイヤーとして使うほうを指定する。それ以外のファイルをメインのzipとして共通使用する、という感じです。もちろんzip化やS3アップロードはCI先生にお任せ。
久々に丸っと pip3 install をしてみると、メジャーバージョンが変わったものはゴリッと動かなくなったりするので、修正 or バージョン固定にそれなりに時間をとられます。
付け焼き刃の失敗例
最初は、レイヤーで分けることを思いつく前に、強引に対応できないか考えました。例えば python/ や lib/ は lib-36/ と lib-39/ のように用意して、環境に応じて最初にシンボリックリンクを作成して切り替える──ような手段です。
lambdaの特徴として、書き込み可能なのは /tmp のみであることや、LD_LIBRARY_PATH を途中で変えることはできず、通常ではない読み込み方で実装するくらいなら、素直にレイヤーで分けたほうがいい、という結論にすぐなりました。
PythonバージョンとCPUアーキテクチャに柔軟に対応するなら、もうちょっと堅いことも考えられるでしょうが、社内用システムならそこまでせず、数年に1回、バシッと決め打ちアップデートすればいいと思います。
んでテスト環境で一括変更してみて、CloudWatchメトリクスでLambdaエラー発生数がゼロじゃなければ修正していく。くらいのラフな運用ではあります:-)