EC2 上で docker コンテナを使用した Lambda 関数の開発

EC2(Amazon Linux2) から dockerコンテナを使用して lambda 関数を作成する手順を記録しておきます。
それぞれについて、軽く説明しておきます


## docker
docker の使い方として、以下二つの方法があると思います。今回は docker 環境で開発をしていきます。
1. ローカルで開発して、docker 環境でアプリケーションを動作させる
2. docker 環境で開発して、アプリケーションを docker で動作させる

具体的には、docker とボリュームをマウントした環境で開発を行った後に、Docker image を ECR に Push して、Lambda 関数を作成します。

## 手順
0. EC2 にSSH接続する
1. docker をインストールします
    ```
    $ sudo yum install -y docker
    $ sudo service docker start
    $ sudo usermod -a -G docker ec2-user
    $ sudo docker info
    ```

2. aws cli を version 2 系にしておき、再起動します。
    https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/getting-started-install.html
    ```
    $ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
    $ unzip awscliv2.zip
    $ sudo ./aws/install
    $ sudo reboot
    ```


3. もう一度 SSH 接続してからdockerを起動します
    ```
    $ sudo service docker start 
    ```


4. 一旦開発環境用の Dockerfile を作成します
    ```
    $ vi Dockerfile
    ```
    ```
    # 公式の Python イメージを使用
    FROM python:3.9
    
    # コードを配置するディレクトリを作成
    WORKDIR /app
    ```


5. docker image を build します
    ```
    $ docker build -t myenv .
    ```

6. ボリュームをマウントして、コンテナに入ります
    ```
    $ docker run -it -v $PWD:/app --name docker-poetry-env myenv bash
    ```

    ここまでの操作で、コンテナの中に入ることができました。
    このコンテナの中で色々と開発を行っていきます。
    今回は簡単なpandasを動かすだけのコードを作成します。

7. poetry をインストール
    ```
    # pip install poetry
    ```

8. 色々と開発を行います
    ```
    # poetry new poetry_docker_project
    # cd poetry_docker_project/poetry_docker_project
    # poetry add pandas
    # vi main.py
    ```
    ```
    import pandas as pd
    
    def lambda_handler(event, context):
        # サンプルデータを作成
        data = {
            'Name': ['John', 'Emily', 'Michael', 'Jessica'],
            'Age': [25, 30, 35, 28],
            'City': ['New York', 'Los Angeles', 'Chicago', 'Houston']
        }
        df = pd.DataFrame(data)
        
        # 平均年齢を計算
        avg_age = df['Age'].mean()
        
        # 適当に表示させる
        print("avg_age:", avg_age)
    ```


9. デプロイ用のDockerfileを作成する
    ```
    # cd ../
    # vi Dockerfile
    ```
    ```
    # Dockerイメージのベース
    FROM public.ecr.aws/lambda/python:3.9
    
    # poetryをインストール
    RUN pip install poetry
    
    # コードのみをコンテナにコピー
    COPY poetry_docker_project/ ${LAMBDA_TASK_ROOT}
    COPY poetry.lock pyproject.toml ./
    
    # 依存関係をインストール
    RUN poetry export --format requirements.txt --without-hashes > requirements.txt
    RUN poetry config virtualenvs.create false \
      && poetry install --no-dev --no-root
    
    # Lambda関数のエントリーポイントを設定
    CMD ["main.lambda_handler"]
    ```

10. コンテナから抜けます
    コンテナに再度入る方法などは後述します。コンテナ環境で作成したファイルへのアクセス権限が EC2 上ではない点に注意してください。
    ```
    # exit
    ```

11. `aws configure`を設定(アクセスキーなどを設定します)
    ```
    $ aws configure
    ```

12. ECR にログイン(これ以降 Region や <AWS アカウント id> は適時修正してください)
    ```
    $ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin <AWS アカウント ID>.dkr.ecr.ap-northeast-1.amazonaws.com
    ```

13. ECR リポジトリの作成(まだ作成していない場合)
    ```
    $ aws ecr create-repository --repository-name test-docker-poetry-lambda-repo --region ap-northeast-1
    ```

14. Docker イメージのビルドします
    ```
    $ docker build -t <AWS アカウント id>.dkr.ecr.ap-northeast-1.amazonaws.com/test-docker-poetry-lambda-repo:latest .
    ```
    
15. Docker イメージを ECR に Push します
    ```
    $ docker push <AWS アカウント id>.dkr.ecr.ap-northeast-1.amazonaws.com/test-docker-poetry-lambda-repo:latest
    ```



16. 先ほどECRにPushしたdockerを使用してLambda 関数の作成。
    (なお、Lambda で使用する IAM Role は作成しているものとします。)

    ```
    $ aws lambda create-function \
      --function-name deploy_docker_test \
      --package-type Image \
      --code ImageUri=<AWS アカウント id>.dkr.ecr.ap-northeast-1.amazonaws.com/test-docker-poetry-lambda-repo:latest \
      --role arn:aws:iam::<AWS アカウント id>:role/service-role/<role 名>
    ```

    Lambda 関数の更新だと以下になります。
    ```
    $ aws lambda update-function-code --function-name deploy_docker_test --image-uri <AWS アカウント id>.dkr.ecr.ap-northeast-1.amazonaws.com/test-docker-poetry-lambda-repo:latest
    ```

17. Lambda 関数をテスト的に起動してみます
    ```
    $ aws lambda invoke --function-name deploy_docker_test --log-type Tail outputfile.txt --cli-binary-format raw-in-base64-out
    $ cat outputfile.txt
    ```
    -> 以下のようになり、Lambda 関数の起動が確認できました。
    {"statusCode": 200, "body": "Average age: 29.5"}


### 補足1: docker を更新して、Lambda関数を更新する手順
下記を実行して、docker image を更新して、Lambda 関数も更新できます。
```
$ docker build -t <AWS アカウント id>.dkr.ecr.ap-northeast-1.amazonaws.com/test-docker-poetry-lambda-repo:latest .

$ docker push <AWS アカウント id>.dkr.ecr.ap-northeast-1.amazonaws.com/test-docker-poetry-lambda-repo:latest

$ aws lambda update-function-code --function-name deploy_docker_test --image-uri <AWS アカウント id>.dkr.ecr.ap-northeast-1.amazonaws.com/test-docker-poetry-lambda-repo:latest
```


### 補足2: Docker コンテナを起動し、コンテナの中に入るコマンド
この例では poetry-docker-env を起動して、その中に入ります。
```
$ docker start poetry-docker-env
$ docker exec -it poetry-docker-env bash
```

終わったらコンテナから出て、コンテナを停止させます。
```
$ docker stop poetry-docker-env
```

動作してるかは下記コマンドで確認できます。
```
$ docker ps
```