はじめに

クラウドインテグレーション事業部 MSP開発セクション 大阪オフィス所属の上地 航平です。

今回は、m1 Macで開発中のアプリケーションの一部コンポーネント(Lambda関数)をServerless Framework経由で更新デプロイした際に発生した問題についてお話しします。

具体的には、Intel Macから更新デプロイすると正常に動作するLambda関数が、m1 Macでは「OSError: Cannot load native module ‘Crypto.Cipher._raw_ecb’」というエラーを出して動作しないという事象が発生しました。

この問題はM1(ARM) ベースのMacとIntel(x86) Macの命令セットアーキテクチャの違いにより、利用しているライブラリのビルド内容に差がでたのが原因でした。
本記事では、この事象の解消方法や実際に取った回避策について詳しく説明します。

開発体制について

私たちのチームでは、m1 MacとIntel Macを持つメンバーが併用してアプリケーションの開発を行っています。
そのため開発PCは2パターン存在します。

パターン①

チップ:Apple M1
命令セットアーキテクチャ:arm64

パターン②

チップ:Intel
命令セットアーキテクチャ:x86_64

Lambda関数

ランタイム:Python3.9
命令セットアーキテクチャ:x86_64

命令セットアーキテクチャとは?

「命令セットアーキテクチャ」とは、システムやコンピュータの基本設計を指します。具体的には、コンピュータのハードウェアやソフトウェアの構成を意味します。
そしてm1 MacとIntel Macでは、命令セットアーキテクチャが異なります。

  • m1 Mac: Appleの独自設計によるarm64命令セットアーキテクチャを使用
  • Intel Mac: x86_64命令セットアーキテクチャを使用

Lambda関数の命令セットアーキテクチャ差異による互換性については、公式ドキュメントに詳しく記載されています。
参考:arm64 アーキテクチャとの関数コードの互換性

更新デプロイの方法

一部の社内向けシステムにはIaCツールとしてServerless Frameworkを使用しており、更新デプロイは以下の手順で行います。

  1. GitHubリポジトリをローカルの開発PCにPULL
  2. デプロイ作業用のDockerコンテナをビルドします。
  3. コンテナ内からServerless Frameworkで更新デプロイを実施します。

問題の概要

・Intel Macからアプリケーションの更新デプロイを実施すると、Lambda関数は正常でエラーはなし。
・m1 Macからアプリケーションの更新デプロイを実施すると、Lambda関数からエラーメッセージが発生する。

[ERROR] OSError: Cannot load native module 'Crypto.Cipher._raw_ecb': Not found '_raw_ecb.cpython-39-x86_64-linux-gnu.so', Cannot load '_raw_ecb.abi3.so': /var/task/Crypto/Util/../Cipher/_raw_ecb.abi3.so: cannot open shared object file: No such file or directory, Not found '_raw_ecb.so

・調査を進める中で、m1 MacからビルドしたコンテナとLambda関数の命令セットアーキテクチャに差異があることが分かった。(以下画像参照)

・なぜ、m1 Macでビルドしたコンテナの命令セットアーキテクチャがarm64となるのか?
Dockerコンテナのビルドは、実行環境の命令セットアーキテクチャに依存します。そのためm1 Macでビルドするとコンテナの命令セットアーキテクチャもarm64となります。
これにより、Lambda関数の命令セットアーキテクチャと差異が発生してしまいます。

次のセクションでは、実際にDockerコンテナのビルドで、実行環境(開発PCの命令セットアーキテクチャ)の影響を受けるか確認したいと思います。

検証してみた。
(以降の手順で使っている開発PCは”m1 Mac”です。)

※前提:以下の単語の組み合わせは同じ意味です。
aarch64 == arm64
amd64 == x86_64

手順① ローカルに以下内容のDockerfileを作成

FROM amazonlinux:2023

手順② m1 Macの実行環境でビルドする。

docker build -t test-m1-mac .

手順③ コンテナの命令セットアーキテクチャがaarch64(arm64)であることを確認する。

docker run -it --rm test-m1-mac /bin/sh
uname -m

実際に確認したターミナル画面

手順④ Intel Macと同じ命令セットアーキテクチャのamd64(x86_64)実行環境でビルドする。

--platform=linux/amd64 の箇所で実行環境の命令セットアーキテクチャを x86_64 になるよう指定しています。
これにより、Dockerは特定の命令セットアーキテクチャ向けにコンテナをビルドすることができます。
詳細は→マルチCPU命令セットアーキテクチャ

docker build -t test-intel-mac --platform=linux/amd64 .

手順⑤ コンテナの命令セットアーキテクチャがx86_64であることを確認する。

docker run --rm test-intel-mac
uname -m

実際に確認したターミナル画面

この結果からdockerfileで指定したイメージは同じものだが、
ビルドされたDockerコンテナの命令セットアーキテクチャは、、実行環境(開発PCの命令セットアーキテクチャ)の影響を受けることが分かる。

エラーメッセージの分析

[ERROR] OSError: Cannot load native module 'Crypto.Cipher._raw_ecb': Not found '_raw_ecb.cpython-39-x86_64-linux-gnu.so', Cannot load '_raw_ecb.abi3.so': /var/task/Crypto/Util/../Cipher/_raw_ecb.abi3.so: cannot open shared object file: No such file or directory, Not found '_raw_ecb.so

Lambda関数のエラーメッセージから、Crypto.Cipher._raw_ecb’モジュールが見つからないためにエラーが発生していることが分かりました。
これは、今回のアプリケーションで暗号化処理部分をPyCryptodomeライブラリで実装しており、このライブラリで使用されるためと推察しました。

なぜPyCryptodomeライブラリの参照エラーが発生したのか?

デプロイが行われるコンテナ環境とLambda関数の命令セットアーキテクチャの差異により、PyCryptodomeライブラリが参照すべきパスが見つからなかったためです。

具体的な挙動を推察:
1. m1 Mac上でarm64のDockerコンテナを作成
2. PyCryptodomeのライブラリがarm64命令セットアーキテクチャ用にビルドされる
3. このarm64用のPyCryptodomeライブラリは、arm64命令セットアーキテクチャで動作する
4. arm64用のPyCryptodomeライブラリは、動作するためにarm64用のパスを探す
5. しかし、Lambda関数はx86_64命令セットアーキテクチャで実行されるため、x86_64用のパスを探し、見つからないエラーを出力する→ Not found '_raw_ecb.cpython-39-x86_64-linux-gnu.so',

では、どのように解消できるのか?

Lambda関数を正常に動作させるためにPyCryptodomeライブラリがパスを参照できるようにするには、実行環境とビルド環境の命令セットアーキテクチャを一致させる必要がありました。

解消・回避案

今回は3つの案を考えました。

【案1】 従来どおりIntel Macから、デプロイする。

実行環境とビルド環境の命令セットアーキテクチャをx86_64で統一できます。

【案2】 Lambda関数の命令セットアーキテクチャをarm64に指定しm1 Macから、デプロイする。

Serverless Frameworkではarchitectureパラメータを使用して命令セットアーキテクチャを指定できますが、従来の書き方ではarchitectureパラメータを使用せずデフォルト設定(x86_64)でデプロイしていました。

設定ファイル 例

# Serverless Frameworkのserverless.yml

provider:
  name: aws
  runtime: python3.9
  architecture: arm64  ←未指定だとx86_64となる。

上記の設定ファイルを使用することで、DockerコンテナとLambda関数の命令セットアーキテクチャが一致し、PyCryptodomeライブラリがパスを参照でき、m1 Mac経由の更新デプロイ時にエラーが解消されるようになりました。

【案3】 マルチCPU命令セットアーキテクチャ でDockerコンテナをビルドする。

m1 Macで、x86_64命令セットアーキテクチャのコンテナをビルドできるようになる。
実行環境とビルド環境の命令セットアーキテクチャをarm64で統一することができます。

結論:【案1】を採用し事象を回避しました。

以降では、不採用となった【案2】・【案3】とともに【案1】の選定理由について解説します。

解説

【案2】 の不採用理由

Lambda関数の命令セットアーキテクチャをarm64に変更することでエラーは解消されましたが、
既存の他のライブラリや処理が正常に動作する保証がなく、運用保守のリスクが高まるため、【案2】 は断念しました。
そのため、Intel Macからデプロイを行うことで問題を回避しました。

【案3】の不採用理由

--platformフラグを付与してdockerfileをビルドした際に、依存関係エラーが多発しました。
正常にビルドするため全てのエラーを解消するには時間を要すると考え、【案3】 は断念しました。

CI/CD環境への移行などを引き続き検討

最終的にIntel Macからデプロイを行うことで問題を回避しましたが、これは暫定的な対応です。
将来的な運用保守を見据え、ビルド&デプロイをCI/CD環境に移行することを検討しています。
これにより、m1やIntelといった開発マシンに依存せず、一貫したビルド環境を確保することが可能となるためです。

最後に

開発マシンがIntel Macからm1 Macに移行する中で、同様の問題に直面している方々の参考になれば幸いです。
最後までご覧いただき、ありがとうございました。