[AWS Lambda / Docker / Python3] AWS Lambdaでdockerイメージを動かそうとして発生したエラーたちの対応

# 概要

**serverless framework**を使ってDockerイメージをAWS Lambdaにデプロイしたところ、いくつものエラーに遭遇したので、その対応をしたお話。


# 今回のディレクトリ構造

```
root_directory/
　├ Dockerfile
　├ src/
　│　└ handlers/
　│　　　└ filename.py
　└ serverless.yml
```

# エラーたち

## 1. `Error: Either "handler" or "image" property (not both) needs to be set on function "FuncName".`

### 1-1. エラー

**handlerかimageのどっちかにしろ**と怒られております: (´ºωº｀):

```bash:エラー発生状況
$ sls deploy

Deploying func-name to stage stg (ap-northeast-1)ap-northeast-1.amazonaws.com/serverless-func-name-dev:tagname

✖ Stack func-name-dev failed to deploy (0s)
Environment: linux, node 14.18.3, framework 3.25.1, plugin 6.2.2, SDK 4.3.2
Docs:        docs.serverless.com
Support:     forum.serverless.com
Bugs:        github.com/serverless/serverless/issues

Error:
Either "handler" or "image" property (not both) needs to be set on function "FuncName".
```

### 1-2. 原因と対策

Dockerイメージをデプロイして実行したいのであれば、以下のように`handler`を消しましょう。

```yml:serverless.yml
# 修正前: `image:` と `handler:` が両方ある
functions:
  FuncName:
    image:
      name: python_image
      command: ["src.handlers.filename.lambda_handler"]
    handler: src.handlers.filename.lambda_handler

# 修正後
functions:
  FuncName:
    image:
      name: python_image
      command: ["src.handlers.filename.lambda_handler"]
    # image: を使うなら handler は消そう
    # handler: src.handlers.filename.lambda_handler
```

## 2. `virtualenv: error: argument dest: the destination . is not write-able at /home`

### 2-1. エラー

```bash:AWSコンソール画面のログで確認できるエラー
virtualenv: error: argument dest: the destination . is not write-able at /home
```

不適切なDockerイメージをAWS Lambdaにデプロイしていると発生することがある。

自分の場合、`FROM python:3.7.12-slim`とかを使っていたのだけど、その際にこのエラーに遭遇してしまった。

### 2-2. 対策

docker imageはこれを使う。

```Dockerfile:Dockerfile
FROM public.ecr.aws/lambda/python:3.8
```

AWS Lambda専用Dockerイメージみたいなものだろう。
AWS公式ドキュメントでもこのイメージが使用されているよ(*´ｖ｀)

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-image.html

### 2-3. 補足

ちなみに、`public.ecr.aws/lambda/python`を使うと、packageのインストール方法が大きく変わるので注意。
debian系でよく使われる`apt`コマンドではなく、redhat系でよく使われる`yum`コマンドを使ってpackageをインストールしなければならない。

だいたい以下のようになる。

```Dockerfile:Dockerfile
# Ubuntu系
# FROM python:3.7.12-slim
# RUN apt -y update \
#    && apt install -y --no-install-recommends \
#    apt-utils \
#    python-dev \
#    build-essential

# Redhat系
FROM public.ecr.aws/lambda/python:3.8
RUN yum update -y && \
    yum install -y \
    python-devel \
    build-essential
```


## 3. `entrypoint requires the handler name to be the first argument` -> 対応済

### 3-1. エラー

serverlessによるデプロイは成功したのだけど、AWS Lambdaを実行したら以下のようなエラーが発生した。

```bash:AWSコンソール画面のログで確認できるエラー
entrypoint requires the handler name to be the first argument
entrypoint requires the handler name to be the first argument
START RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxwwwwww Version: $LATEST
RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxwwwwww Error: Runtime exited with error: exit status 142
Runtime.ExitError
END RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxwwwwww
REPORT RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxwwwwww	Duration: 19.26 ms	Billed Duration: 20 ms	Memory Size: 512 MB	Max Memory Used: 3 MB	
```

### 3-2. 原因

serverless.ymlの`functions: -> funcname: -> image: -> command:`のところが間違っていた。

### 3-3. 対策

ここには、AWS Lambdaに実行させたいファイルへのパス、そして関数名を記載する。
これでエラーが解消する。

以下のような感じ。

```yml:serverless.yml
# 修正前
functions:
  FuncName:
    description: 説明
    image:
      name: docker_image_tag
      # これはよくない
      command: ["poetry", "run", "python", "-m", "src.handlers.filename.lambda_handler"]
      name: lambda-func-name

# 修正後
functions:
  FuncName:
    description: 説明
    image:
      name: docker_image_tag
      # ↓これでOK: {実行したいpyファイルまでのパス}.{実行する関数名}
      command: ["src.handlers.filename.lambda_handler"]
      name: lambda-func-name
```


## 4. `Unable to import module 'src.handlers.filename': No module named 'src'`

### 4-1. エラー

こちらも、「3.」と同様で、serverlessによるデプロイが成功した後の話。
AWS Lambdaを実行したら以下のようなエラーが発生した。

```bash
[ERROR] Runtime.ImportModuleError: Unable to import module 'src.handlers.filename': No module named 'src'
Traceback (most recent call last):
```

### 4-2. 原因

Dockerfileに `WORKDIR` を指定しない方がいいみたい。
厳密には、`WORKDIR`を使っても対応可能なのではないかと予想しているのだけど、対処方法見つからず。

### 4-3. 対策

とりあえず`WORKDIR`を消そう( \*˙︶˙\*)و

~~それじゃイヤだという場合は、~~

- ~~serverless frameworkの`command:`を使ってる人の場合~~
    ~~`serverless.yml`の`functions: -> funcname: -> image: -> command:` に指定するパスを、必ずrootディレクトリから辿れるようにすればよさそう。~~
- ~~serverless frameworkの`command:`は使ってない場合~~
    ~~Dockerfileの`CMD`に指定するパスを、上記と同様にrootディレクトリから辿れるパスにすればよさそう。~~

~~たとえば、以下のようなディレクトリ構造で、~~

```
root_directory/
　├ Dockerfile
　├ src/
　│　└ handlers/
　│　　　└ filename.py
　└ serverless.yml
```

~~かつ、docker container内の`root_directory/`ディレクトリで`pwd`を実行したときに、`home/username/root_directory/`といった結果が返ってきたのであれば、`CMD`や`command:`に対して、以下のように書けばよさそう。試してないけど！~~


```
["home.username.root_directory.src.handlers.filename.lambda_handler"]
```

↑上記の方法試したけどうまくいかなかった。
面倒だから、`WORKDIR`消した方が早いす～(´^ω^｀)

#### ＊＊ 2022/12/19追記 ＊＊

後になって以下のAWSのドキュメントでも確認してみたのだけど、

https://docs.aws.amazon.com/ja_jp/toolkit-for-visual-studio/latest/user-guide/lambda-creating-project-docker-image.html

デフォルトでは`WORKDIR`が以下のようになる模様。

```Dockerfile:Dockerfile
WORKDIR /var/task
```

確かに、`WORKDIR`を一切設定せずにdockerイメージ(public.ecr.aws/lambda/python:3.8)をbuildしてdocker execで入ってみたところ、自動的に`WORDKDIR`が`/var/task`になってた。

```bash:dockerコンテナにexecした直後にpwdコマンドを打ってみた
# pwd
/var/task
```

この辺をうまく扱えれば解決するのかもしれない。

また、公式ドキュメントには以下のような記述もあり、

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

この記事の「代替ベースイメージからのイメージの作成」という項目の内容によると、`WORKDIR`を変更することもできそう。

今回はその点で頑張る必要はないので、調査はここで終了。


### 4-4. 参考にした記事

https://zenn.dev/panyoriokome/scraps/91fd4ddfbdc117

https://inokara.hateblo.jp/entry/2020/12/02/213617