SageMathコンテナをLambdaで実行する

# 初めに

[SageMath](https://www.sagemath.org/) の [Docker イメージ](https://hub.docker.com/r/sagemath/sagemath)を [AWS Lambda](https://aws.amazon.com/jp/lambda/)で実行する方法を書きます。

## 課題

記載した手順で一応コンテナ実行はできます。しかし Lambda 関数自体の実行はエラー終了します。解決方法についてはまだわかっていません。

## ソースコード

イメージとバージョンは、`sagemath/sagemath:10.1` です。
クローン～ビルド手順は README に記載しています。

https://github.com/quryu-1/sagemath-lambda-container-sample

## 実行結果

次のような sage スクリプトを実行します。

```hello.sage
print('Hello, from sagemath container!')

R.<x> = PolynomialRing(QQ)
f = 'x^2 - 1'
print(f'The factorization of {f} is {R(f).factor()}.')
```

実行結果は次のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/0321dc75-7052-97db-8f08-f0ba2e2841ee.png)

ログには sage スクリプトの実行結果が出力されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/158a0271-e8aa-646e-a486-186525416587.png)


# 手順

以下に記載する手順はSageMathコンテナをLambdaで実行する際の備忘録です。

## 1) Dockerfile、sage スクリプトを作成する

### 1.1) Dockerfile、sage スクリプトを作成する

ディレクトリ構成は以下のようにします。

```
/home/ec2-user/sage-demo
                 |---Dockerfile
                 |---run.sh
                 |---hello.sage
```

以下 3 つのファイルを作成します。

```docker:Dockefile
FROM sagemath/sagemath

WORKDIR /var/task

RUN sudo chown sage:sage /var/task

COPY . /var/task/

CMD ["sh", "run.sh"]
```

```shell:run.sh
sage hello.sage
```

```hello.sage
print('Hello from sagemath.')
```

### 1.2) コンテナ実行

イメージをビルドし、コンテナを実行します。

```
$ cd ~/sage-demo
$ docker build -t sage-demo .
$ docker run sage-demo
output) Hello from sagemath.
```

※標準出力は `output)` で記載しています。不要だと思われる標準出力については省略しています。

## 2) ECR にイメージをプッシュする

ログイン、タグ付け、プッシュを行います。

```
$ export ACCOUNT_ID=111111111111
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
$ docker tag sage-demo:latest ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sage-demo:latest
$ docker push ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sage-demo:latest
```

## 3) Lambda コンテナを実行する

### 3.1) Lambda コンテナを実行する

Lambda 関数を作成し、2) のイメージを参照するように設定します。その後、Lambda 関数をテストします。実行は失敗します。以下エラー部分です。

```
/usr/local/bin/sage-entrypoint: line 8: exec: sage: not found
```

これは sage コマンドのパスが通っていないことによるエラーです。

### 3.2) sage コマンド調査

ひとまず sage コマンドに関係するファイルをコンテナからホストにコピーします。まずはホストからコンテナに入ります。

```
docker run -it sage /bin/bash
```

次に起動したコンテナから `sage` コマンドのパスを調べます。

```
$ which sage
output) /home/sage/sage/src/bin/sage
```

次に起動したコンテナ ID を調べ、コンテナからホストへディレクトリ、ファイルをコピーします。

```
$ docker ps
$ docker cp ab62b0122598:/home/sage/sage/src/bin/ /home/ec2-user/sage-demo/sagebin/
$ docker cp ab62b0122598:/usr/local/bin/sage-entrypoint /home/ec2-user/sage-demo/sagebin/
```

ここで上記の `/home/ec2-user/sage-demo/sagebin/` はホストのディレクトリです。一旦ホストに移動してスクリプトを修正し、ビルド時に COPY で修正を反映します。

※ `ab62b0122598` はコンテナ ID

### 3.3) イメージ修正

`sage` 関連のファイルと自作ファイルが混在するのを防ぐため、ディレクトリ構成を以下のように変更します。

```
/home/ec2-user/sage-demo
                 |---Dockerfile
                 |---src
                 |     |---run.sh
                 |     |---hello.sage
                 |
                 |---sagebin
                       |---sage
                       ・
                       （略）
                       ・
                       |---sage-entrypoint
```

Dockerfile は以下のように修正します。

```docker:Dockerfile
FROM sagemath/sagemath

WORKDIR /var/task

RUN sudo chown sage:sage /var/task

COPY ./src/* /var/task/

# sage コマンド関連ファイルを追加
COPY ./sagebin/* /usr/local/bin/ 

CMD ["sh", "run.sh"]
```

Dockerfile 修正後、ビルド、プッシュします。

## 4) Lambda コンテナを再実行する

### 4.1) Lambda コンテナを再実行する

再実行エラーが発生します。

```
/usr/local/bin/sage-env: line 58: cd: /home/sage/: Permission denied
mkdir: cannot create directory ‘//R’: Read-only file system
```

### 4.2) イメージ修正

エラー修正のため、`/home/sage` ディレクトリの権限を緩くします。Dockerfile を以下のように修正後、ビルド、プッシュします。

```docker:Dockerfile
FROM sagemath/sagemath

WORKDIR /var/task

# /home/sage の権限変更
RUN sudo chown sage:sage /var/task \
 && sudo chmod 777 /home/sage

COPY ./src/* /var/task/
COPY ./sagebin/* /home/sage/sage/src/bin/

CMD ["sh", "run.sh"]
```

## 5) Lambda コンテナを再実行する

### 5.1) Lambda コンテナを再実行する

再実行エラーが発生します。

```
mkdir: cannot create directory ‘/home/sage/.sage/’: Read-only file system
.
（略）
.
OSError: [Errno 30] Read-only file system: '/var/task/tmpdc83oq9o'
```

### 5.2) イメージ修正

以下のように修正してプッシュします。

```docker:Dockerfile
FROM sagemath/sagemath

WORKDIR /var/task

RUN sudo chown sage:sage /var/task \
 && sudo chmod 777 /home/sage \
 && mkdir /home/sage/.sage \
 && sudo chmod 777 /home/sage/.sage

COPY ./src/* /var/task/
COPY ./sagebin/* /home/sage/sage/src/bin/

CMD ["sh", "run.sh"]
```

## 6) Lambda コンテナを再実行する

### 6.1) Lambda コンテナを再実行する

再実行エラーが発生します。

```
mkdir: cannot create directory ‘/home/sage/.sage//R’: Read-only file system
/home/sage/sage/src/bin/sage-env: line 436: /home/sage/.sage//R/Makevars.user: No such file or directory
```

### 6.2) イメージ修正

`/home/sage/sage/src/bin/sage-env: line 436` に対応する箇所をコメントアウトしてプッシュします。具体的には以下のように425行目～441行目をコメントアウトします。

```sh:/home/sage/sage/src/bin/sage-env
   425  # if [ -d "$DOT_SAGE" ] ; then
   426  #     if ! [ -d "$DOT_SAGE/R" ] ; then
   427  #         if ! [ -e  "$DOT_SAGE/R" ] ; then
   428  #             mkdir -p "$DOT_SAGE/R"
   429  #         else
   430  #             >&2 echo "Warning: $DOT_SAGE/R exists and is not a directory : trouble ahead..."
   431  #         fi
   432  #     fi
   433  #     R_MAKEVARS_USER="$DOT_SAGE/R/Makevars.user" && export R_MAKEVARS_USER
   434  #     if ! [ -f "$R_MAKEVARS_USER" ] ; then
   435  #         if ! [ -e "$R_MAKEVARS_USER" ] ; then
   436  #             echo "## Empty user-specific Makevars file for Sage's R" > "$R_MAKEVARS_USER"
   437  #         else
   438  #             >&2 echo "Warning: $R_MAKEVARS_USER exists and is not a file : trouble ahead..."
   439  #         fi
   440  #     fi
   441  # fi
```

## 7) Lambda コンテナを再実行する

### 7.1) Lambda コンテナを再実行する

再実行エラーが発生します。

```
Traceback (most recent call last):
File "/home/sage/sage/src/bin/sage-preparse", line 302, in <module>
do_preparse(f)
File "/home/sage/sage/src/bin/sage-preparse", line 151, in do_preparse
with atomic_write(fname) as f:
File "/home/sage/sage/local/var/lib/sage/venv-python3.10.5/lib/python3.10/site-packages/sage/misc/temporary_file.py", line 349, in __enter__
fd, name = tempfile.mkstemp(dir=self.tmpdir)
File "/home/sage/sage/local/var/lib/sage/venv-python3.10.5/lib/python3.10/tempfile.py", line 341, in mkstemp
return _mkstemp_inner(dir, prefix, suffix, flags, output_type)
File "/home/sage/sage/local/var/lib/sage/venv-python3.10.5/lib/python3.10/tempfile.py", line 256, in _mkstemp_inner
fd = _os.open(file, flags, 0o600)
OSError: [Errno 30] Read-only file system: '/var/task/tmpeo_ubjxu'
```

### 7.2) イメージ修正

sage 関連ファイルを修正します。以下のように `~~~.sage.py` ファイルを作成するディレクトリを `/tmp` ディレクトリに設定します。`/tmp` ディレクトリは Lambda 関数が書き込み可能なディレクトリです。

```diff:/home/ec2-user/sage-demo/sagebin
+ import shutil
+ import glob

fn = sys.argv[1]
opts = sys.argv[2:]


+ python_files = glob.glob(os.path.join(os.getcwd(), '*.py'))
+ for python_file in python_files:
+     shutil.copy(python_file, '/tmp')
+ shutil.copy(fn, '/tmp')
+ fn = os.path.join('/tmp', fn)


if fn.startswith('-'):
    print("sage-run received unknown option: {}".format(fn))
    print("usage: sage [options]")
    print("Try 'sage -h' for more information.")
    sys.exit(1)
```

## 8) Lambda コンテナを再実行する

### 8.1) Lambda コンテナを再実行する

再実行エラーが発生します。

```
Traceback (most recent call last):
File "/tmp/hello.sage.py", line 4, in <module>
from sage.all_cmdline import *   # import sage library
File "/home/sage/sage/local/var/lib/sage/venv-python3.10.5/lib/python3.10/site-packages/sage/all_cmdline.py", line 19, in <module>
from sage.all import *
File "/home/sage/sage/local/var/lib/sage/venv-python3.10.5/lib/python3.10/site-packages/sage/all.py", line 124, in <module>
from sage.misc.all       import *         # takes a while
File "/home/sage/sage/local/var/lib/sage/venv-python3.10.5/lib/python3.10/site-packages/sage/misc/all.py", line 8, in <module>
from .misc import (BackslashOperator,
File "/home/sage/sage/local/var/lib/sage/venv-python3.10.5/lib/python3.10/site-packages/sage/misc/misc.py", line 286, in <module>
os.makedirs(SAGE_DB, exist_ok=True)
File "/home/sage/sage/local/var/lib/sage/venv-python3.10.5/lib/python3.10/os.py", line 225, in makedirs
mkdir(name, mode)
OSError: [Errno 30] Read-only file system: '/home/sage/.sage/db'
```

### 8.2) イメージ修正

`sage` コマンドにオプション `--nodotsage` を追加します。

```sh:/home/ec2-user/sage-demo/sagebin/sage-python
#!/bin/sh
sage --nodotsage -python "$@"
```

## 9) Lambda コンテナを再実行する

### 9.1) Lambda コンテナを再実行する

再実行エラーが発生します。

```
Traceback (most recent call last):
File "/tmp/hello.sage.py", line 4, in <module>
from sage.all_cmdline import *   # import sage library
File "/home/sage/sage/local/var/lib/sage/venv-python3.10.5/lib/python3.10/site-packages/sage/all_cmdline.py", line 19, in <module>
from sage.all import *
File "/home/sage/sage/local/var/lib/sage/venv-python3.10.5/lib/python3.10/site-packages/sage/all.py", line 124, in <module>
from sage.misc.all       import *         # takes a while
File "/home/sage/sage/local/var/lib/sage/venv-python3.10.5/lib/python3.10/site-packages/sage/misc/all.py", line 8, in <module>
from .misc import (BackslashOperator,
File "/home/sage/sage/local/var/lib/sage/venv-python3.10.5/lib/python3.10/site-packages/sage/misc/misc.py", line 290, in <module>
os.makedirs(os.environ["MPLCONFIGDIR"], exist_ok=True)
File "/home/sage/sage/local/var/lib/sage/venv-python3.10.5/lib/python3.10/os.py", line 225, in makedirs
mkdir(name, mode)
OSError: [Errno 30] Read-only file system: '/home/sage/.sage//matplotlib-1.5.1'
```

### 9.2) イメージ修正

以下のように環境変数 `MPLCONFIGDIR` の定義をコメントアウトします。なお、この前後の if ブロックもコメントアウトする必要があります。

```sh:/home/ec2-user/sage-demo/sagebin/sage-env
  407  # export MPLCONFIGDIR="$DOT_SAGE/matplotlib-1.5.1"
```

## 10) Lambda コンテナを再実行する

### 10.1) Lambda コンテナを再実行する

エラー終了していますが、`Hello from sagemath.` と出力されました。

```
OpenBLAS WARNING - could not determine the L2 cache size on this system, assuming 256k
Hello from sagemath.
OpenBLAS WARNING - could not determine the L2 cache size on this system, assuming 256k
Hello from sagemath.
START RequestId: fe894660-f0ac-46e6-9985-bdb8dec7beaf Version: $LATEST
RequestId: fe894660-f0ac-46e6-9985-bdb8dec7beaf Error: Runtime exited without providing a reason
Runtime.ExitError
END RequestId: fe894660-f0ac-46e6-9985-bdb8dec7beaf
REPORT RequestId: fe894660-f0ac-46e6-9985-bdb8dec7beaf	Duration: 1647.46 ms	Billed Duration: 1648 ms	Memory Size: 4096 MB	Max Memory Used: 119 MB	
```

# 参考記事

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/images-create.html

https://hub.docker.com/r/sagemath/sagemath