Thanks to visit codestin.com
Credit goes to www.sambaiz.net

同じリポジトリにある複数の SAM プロジェクトで Python のパッケージを共有する

awspython

複数の SAM プロジェクトで Python のパッケージを共有する方法を考える。

$ tree .
.
├── project1
│   ├── function
│   │   ├── app.py
│   │   └── requirements.txt
│   └── template.yaml
├── project2
│   ├── function
│   │   ├── app.py
│   │   └── requirements.txt
│   └── template.yaml
...
.
└── mylib
    └── mylib.py

デプロイの際のパッケージングを考慮すると共有パッケージのディレクトリを各プロジェクトにコピーするという方法が思いつく。BuildMethod: makefile を指定するとビルド時に CustomMakeWorkflow によって関数のコードが ARTIFACTS_DIR にコピーされ make build-{logicalId} が呼ばれるので、これを自動で行うことができる。なお親ディレクトリを相対パスで参照するには –build-in-source フラグを渡す必要がある。

$ cat template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  TestFunction:
    Type: AWS::Serverless::Function
    Properties:
      ...
    Metadata:
      BuildMethod: makefile

$ cat function/Makefile
build-TestFunction:
    pip install -r requirements.txt -t "$(ARTIFACTS_DIR)"
    cp -r ../../mylib "$(ARTIFACTS_DIR)/"

ただ、この方法は開発時に import 先のパッケージが見つからないのと、共有コードに依存がある場合それを呼び元で管理しなくてはならず変更に対して弱いという問題があるので、pyproject.toml を置いて pip install するのが望ましい。ただしデフォルトの PythonPipWorkflow は –build-in-source に対応していないので BuildMethod: makefile は必要。

$ cat mylib/pyproject.toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "mylib"
version = "0.1.0"
dependencies = [
    "numpy>=1.24.0,<2.0.0",
]

しかし相対パスでのインストールは親ディレクトリがコンテナにマウントされない sam build –use-container では使えないという問題がある。他の方法としては Git リポジトリを pip install するというのもある。 pip install git+(リポジトリ) すると git clone して #subdirectory= で指定したディレクトリのライブラリがインストールされる。

$ cd example-app
$ cat requirements.txt
git+https://github.com/sambaiz/pip-install-from-git-test.git@main#subdirectory=stats-lib
git+https://github.com/sambaiz/pip-install-from-git-test.git@main#subdirectory=text-lib

$ pip install -r requirements.txt
Collecting git+https://github.com/sambaiz/pip-install-from-git-test.git@main#subdirectory=stats-lib (from -r requirements.txt (line 1))
  Cloning https://github.com/sambaiz/pip-install-from-git-test.git (to revision main) to /tmp/pip-req-build-rss0j5g6
  Running command git clone --filter=blob:none --quiet https://github.com/sambaiz/pip-install-from-git-test.git /tmp/pip-req-build-rss0j5g6
  Resolved https://github.com/sambaiz/pip-install-from-git-test.git to commit 605fa82f41ecf763308190fb6b0ae5113c05d893
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... done
Collecting git+https://github.com/sambaiz/pip-install-from-git-test.git@main#subdirectory=text-lib (from -r requirements.txt (line 2))
  Cloning https://github.com/sambaiz/pip-install-from-git-test.git (to revision main) to /tmp/pip-req-build-ivwe3ami
  Running command git clone --filter=blob:none --quiet https://github.com/sambaiz/pip-install-from-git-test.git /tmp/pip-req-build-ivwe3ami
  Resolved https://github.com/sambaiz/pip-install-from-git-test.git to commit 605fa82f41ecf763308190fb6b0ae5113c05d893
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... done
Requirement already satisfied: numpy>=1.24.0 in ./venv/lib/python3.14/site-packages (from stats-lib==0.1.0->-r requirements.txt (line 1)) (2.4.0)

同じリポジトリであっても clone が必要で CI/CD で行う場合そのための権限を与える必要があるが、別のリポジトリに移動してもそのまま動く。そうする予定があるなら CodeArtifactなどの外部リポジトリに上げる選択肢もある。

あるいは Lambda Layer にしてもよい。sam local invoke もそのまま動く。ただ、開発時やテストの際には pip install するので他の方法とそれほど違いはないかもしれない。これなら相対パスでも問題なさそうだが、そうすると共有コードのバージョンを固定するのが難しくなってしまう。そういう点では相対パスではなくリリースバージョンを指定したいところ。その上で各プロジェクトを共有コード込みでデプロイするか Lambda Layer を共有するかというのは、容量が大きくなければどちらでもいいのではないか。

Lambda Layerでバイナリやライブラリを切り出す - sambaiz-net

Resources:
  MyLibLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: mylib-layer
      ContentUri: ../mylib/
      CompatibleRuntimes:
        - python3.11
    Metadata:
      BuildMethod: makefile

  TestFunction:
    Type: AWS::Serverless::Function
    Properties:
      ...
      Layers:
        - !Ref MyLibLayer
    Metadata:
      BuildMethod: makefile