ServerlessFramework を使い AWS Lambda Python のローカル開発

Serverless Frameworkを使い AWS Lambda のPython用のローカル環境の作成と実行について記載した内容です
AWSマネジメントコンソールでプログラミング・コーディングするのでなく、ツールを使い開発者体験をよくしていきたい。

## はじめに
- [Dockerで Serverless Framework 環境構築](https://qiita.com/seiichi_akiba/items/d42bda576f3fc6ac5117) の記事のようなことをします
- https://qiita.com/ssugimoto/items/ebe5b2ab5efbb6c6e541 の記事はdev container（旧、リモートコンテナ）を使った場合として位置づけていますので、今回の内容とは異なります
- [Developing inside a Container](https://code.visualstudio.com/docs/devcontainers/containers)ではなく（.devcontainer/devcontainer.jsonを使わずに）、dockerコンテナとして、docker composeでdockerfileを使った場合の開発を試していきます。
- 個人的にはVS Codeのdev containerを使うことは多いけど、自身でdockerコマンドやdocker composeコマンドを使った場合の方が少ない


## VS Codeにインストールした拡張機能
- SSH接続しコンテナ内のファイルを直接編集することができ、コンテナ内でコマンド実行もできます
- それぞれの関係がよくわからないのですが、ひとまず３つ入れておけばなんとかなる
- [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
- [Remote explorer](https://marketplace.visualstudio.com/items?itemName=ms-vscode.remote-explorer) 
- [Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)

## 1.ローカル開発環境準備
#### サンプルソース
- GitHub https://github.com/ssugimoto/serverless-framework-python-example2
    - README_ja.md と この記事を見てください
- Dockerfile
     - そのまま利用しても良いです
     - nodeのバージョンを変えて良いです
     - PythonはFromのベースイメージで変更可能です
- Windows 11 / docker desktop / Docker Compose V2 / WSL2
#### ディレクトリ・ファイル構成
```sh
│  .env
│  .gitignore
│  compose.yml
│  README.md
├─app
└─serverless
        Dockerfile

```

## 2. docker コマンド実行し、コンテナビルドとコンテナ実行

#### 2-1. 環境変数用 `.env` ファイルを作成し、AWSのIMAユーザー情報を記載する。 gitignoreファイルにしておくことも忘れずに。

```
#AWS keys
AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXX
AWS_SECRET_ACCESS_KEY=YYYYYYYYYYYYYYY
```

- .env や他ファイル
```
cd C:\usr\python\sls-docker-20230903
>tree /f

│  .env
│  .gitignore
│  compose.yml
│  README_ja.md
├─app
└─serverless
        Dockerfile
```

#### 2-2. docker composeを実行

2-2-1. compose.yml ファイルのある場所に移動
2-2-2. `docker compose` のコマンドを実行、今回はバックグラウンド実行のため `-d` を付けて実行。

```
docker compose up -d
```

2-2-3. 実行例
~~serverless Errorの要因は、調査中です、~~ Dockerfileの `image: serverless`をコメントにすると表示されなくなる


```
C:\usr\python\sls-docker-20230903>docker compose up -d
[+] Running 0/1
 - serverless Error                                                                                                2.5s
[+] Building 2.0s (15/15) FINISHED
 => [internal] load build definition from Dockerfile                                                               0.0s
 => => transferring dockerfile: 1.66kB                                                                             0.0s
 => [internal] load .dockerignore                                                                                  0.0s
 => => transferring context: 2B                                                                                    0.0s
 => [internal] load metadata for docker.io/library/python:3.11                                                     1.9s
 => [auth] library/python:pull token for registry-1.docker.io                                                      0.0s
 => [ 1/10] FROM docker.io/library/python:3.11@sha256:02808bfd640d6fd360c30abc4261ad91aacacd9494f9ba4e5dcb0b86506  0.0s
 => CACHED [ 2/10] RUN apt-get update -y && apt-get upgrade -y                                                     0.0s
 => CACHED [ 3/10] RUN wget https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip   && unzip awscli-exe-linux-  0.0s
 => CACHED [ 4/10] RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash   && . $HO  0.0s
 => CACHED [ 5/10] RUN node -v                                                                                     0.0s
 => CACHED [ 6/10] RUN npm -v                                                                                      0.0s
 => CACHED [ 7/10] RUN pip install boto3                                                                           0.0s
 => CACHED [ 8/10] RUN sls config credentials --provider aws --key XXX --secret XXX  0.0s
 => CACHED [ 9/10] RUN mkdir -p /app                                                                               0.0s
 => CACHED [10/10] WORKDIR /app/app                                                                                0.0s
 => exporting to image                                                                                             0.0s
 => => exporting layers                                                                                            0.0s
 => => writing image sha256:251085204bde8ed12576e25079168e9efe7977bde4096922e2c55d3664ae9d83                       0.0s
 => => naming to docker.io/library/serverless                                                                      0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
[+] Running 2/2
 - Network sls-docker-20230903_default  Created                                                                    0.0s
 - Container sls-python3-aws-lambda1    Started                                                                    0.4s
```

- docker psで状況確認
```
C:\usr\python\sls-docker-20230903>docker ps
CONTAINER ID   IMAGE        COMMAND     CREATED         STATUS         PORTS     NAMES
34a9fb347401   serverless   "python3"   3 minutes ago   Up 3 minutes             sls-python3-aws-lambda1
```
- ps コンテナ表示
```
C:\usr\python\sls-docker-20230903>docker compose ps
NAME                      COMMAND             SERVICE             STATUS              PORTS
sls-python3-aws-lambda1   "python3"           serverless          running
```
- images Dockerイメージ表示
```
C:\usr\python\sls-docker-20230903>docker compose images
Container                 Repository          Tag                 Image Id            Size
sls-python3-aws-lambda1   serverless          latest              251085204bde        1.91GB
```

## 3. 起動したコンテナに対してVS Codeから接続する

#### 3-1. VS Codeの拡張機能の 「Remote Explorer」を押して

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1482752/db321de1-ee16-ab80-6b54-9c37175fe2e6.png)

#### 3-2. コンテナ内に接続
DEv Containersの階層の下にある
「 →のボタン Attach In Current Window , + のボタン Attach In New Window」を押す
なお、Other Containersの階層の下には表示されない

#### 3-3. VS Codeeでコンテナを開いた後のイメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1482752/5ddc207e-0252-5a5b-5fc7-6f4849c12f47.png)

左下で 「>< Container serverless(sls-python3-aws-lambda1)」・・・compose.ymlの中にある「container_name: sls-python3-aws-lambda1」の表示がされる

#### 3-4. コンテナの中に入る
- 3-4-1. ツールバーの 「Terminal -> New Terminal 」を押して
- 3-4-2. コマンドでPATH等を確認する
```
root@34a9fb347401:/app# node -v
v18.17.1

root@34a9fb347401:/app# sls -v
Framework Core: 3.34.0
Plugin: 6.2.3
SDK: 4.3.2

root@34a9fb347401:/app# npm -v
9.6.7

root@34a9fb347401:/app# ls -l
total 12
-rwxrwxrwx 1 root root  910 Sep  7 03:06 README_ja.md
drwxrwxrwx 1 root root 4096 Sep  6 11:58 app
-rwxrwxrwx 1 root root  494 Sep  6 11:38 compose.yml
drwxrwxrwx 1 root root 4096 Sep  3 11:15 serverless
```
- ターミナルの表示例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1482752/b8f83fff-4cf8-db46-b379-ffc180407df2.png)

## 4. Serveless Frameworkを使い、AWS Lambda Pythonのひな形作成

#### 4-1 コマンド実行
```
cd /app/app
serverless create --template aws-python3 --name lambda-sample --path ./lambda-sample
```

実行例
```
root@c1ae820b8cb2:/app/app# serverless create --template aws-python3 --name lambda-sample --path ./lambda-sample

✔ Project successfully created in "./lambda-sample" from "aws-python3" template (3s)
```

作成されたファイル一覧
```
.
│  .gitignore
│  handler.py
│  serverless.yml

```

#### 4-2. 作成されたファイルの修正
##### serverless.yml
- Python のランタイムバージョンを3.9から3.11に変更
- リージョンを追加 （aws-profileや引数で指定しても可）
     引数だと、`serverless deploy --stage production --region ap-northeast-1` のような使い方


#### 4-3. AWSにデプロイ（Lambda リソース作成）

```
sls deploy
```

実行例
```
root@c1ae820b8cb2:/app/app/lambda-sample# sls deploy

Deploying lambda-sample to stage dev (us-east-1)

✔ Service deployed to stack lambda-sample-dev (92s)

functions:
  hello: lambda-sample-dev-hello (389 B)

Need a faster logging experience than CloudWatch? Try our Dev Mode in Console: run "serverless dev"
```

## 5. 少し工夫等

### 5-1 serverless.yml 変更
- serverless.ymlでS3バケット指定やstage、memorySize、timeout等を調整
- 事前に、S3バケットをAWSマネジメントコンソール等を使って作成しておき、そのS3バケットを使うようにする
- S3バケットは、serverless frameworkが使うstate情報（cloudformationやLambdaのソースzipファイル）を保持します
- 変更例
```serverless.yml

custom:
  defaultStage: dev

provider:
  name: aws
  runtime: python3.11
  region: us-east-1
  stage: ${opt:stage, self:custom.defaultStage}  #stageの指定を引数と指定無しデフォルト
  deploymentBucket:
    name: sls-lambda-sample-dev-randomabcd12345 # your S3 bucket, Create in advance.
    maxPreviousDeploymentArtifacts: 10


functions:
  hello:
    handler: handler.hello
    memorySize: 128    # default 1024MB
    timeout: 30        # AWS Lambda 15minute.=60*15=900sec

```

### 5-2. Lambda functionの実行

実行コマンド：
```
serverless invoke [local] --function functionName
```

#### 5-2-1. AWS クラウド側のLambdaを実行する

```
sls invoke --function hello
```

実行例
```
# cd /app/app/lambda-sample
# sls invoke --function hello
{
    "statusCode": 200,
    "body": "{\"message\": \"Go Serverless v1.0! Your function executed successfully!\", \"input\": {}}"
}
```


#### 5-2-2. ローカル側のLambdaを実行する

クラウド側で実行しているかローカル実行なのか違いがわからないので、handler.pyのレスポンスとなるbodyのmessageを変えてみます

```py
def hello(event, context):
    body = {
        "first_message": "Hello World!",
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "input": event
    }

...
```

実行例
```

# cd /app/app/lambda-sample
# sls invoke local --function hello
{
    "statusCode": 200,
    "body": "{\"first_message\": \"Hello World!\", \"message\": \"Go Serverless v1.0! Your function executed successfully!\", \"input\": {}}"
}
```

## 6. 2回目以降の対応、コンテナの起動、停止、リビルド

流れとしては
コンテナをdocker composeコマンドを使い実行 -> VS Codeを起動し、File -> Open Recent で Container... のものを選択する。すると、VS Codeのターミナルはコンテナの中になり、左のExplorer（ツリー）は compose.yml ファイルのあるディレクトリになる


#### docker コンテナを停止させるには？
- docker desktop のGUI画面で 停止■ボタンを押す
- `docker compose stop` を実行する

#### docker コンテナを起動させるには？
- docker desktop のGUI画面で 開始再生▶ボタンを押す
- `docker compose start`　を実行する
 なお、`docker compose up -d` でも、Dockefileに変更なければ、start相当になります
 

#### dockerコンテナを消す（削除）は？

`docker-compose down`

##### さらに 停止＆削除（コンテナ・ネットワーク・イメージ）

`docker-compose down --rmi all`


- 下記のコマンドだけではコンテナのビルドできない場合が多々あります、ビルド済イメージを消さないといけない
```
docker-compose up --build

docker-compose up -d --build
```

## 7. `.py` だけでなく pip ライブラリを使う場合

### サンプル
- https://github.com/ssugimoto/serverless-framework-python-example2/tree/develop/app/lambda-lib-sample


### 参考ドキュメント
- https://www.serverless.com/plugins/serverless-python-requirements
- 

### 作成方法

#### 7-1. Pythonテンプレートを利用しひな形から作成
```
cd /app/app
serverless create --template aws-python3 --name lambda-lib-sample --path ./lambda-lib-sample
```

#### 7-2. severless frameworkのpluginをインストール
```
cd lambda-lib-sample
```

```
sls plugin install -n serverless-python-requirements
```
serverless.yml に以下が追加されます、node_modulesディレクトリも作成され package.json、package-lock.jsonファイルも作成されます

```serverless.yml
plugins:
  - serverless-python-requirements
```

#### 7-3. requirements.txt を作成

必要なライブラリとバージョンを指定する
```
boto3>=1.28.43
```

#### 7-4. パッケージングのための調整

```serverless.yml

package:
  individually: false
  patterns:
    - '!**/*'
    - handler.py
```

#### 7-5. サンプルソースでは Lambda function一覧を取得するので、LambdaのIAMロールに権限を付与


```serverless.yml
  iam:
    role:
      statements:
        - Effect: "Allow"
          Action:
            - "lambda:List*"
          Resource: "*"
```

#### 7-6. sls packageで試し
```
sls package
```

#### 7-7. sls deploy でデプロイ

```
sls deploy
```

#### 7-8. sls invokeで AWS Lambdaの実行

```
sls invoke --function hello
```



---

## 余談
### dev containersの説明

```
The Visual Studio Code Dev Containers extension lets you use a container as a full-featured development environment. It allows you to open any folder inside (or mounted into) a container and take advantage of Visual Studio Code's full feature set. A devcontainer.json file in your project tells VS Code how to access (or create) a development container with a well-defined tool and runtime stack. This container can be used to run an application or to separate tools, libraries, or runtimes needed for working with a codebase.

Workspace files are mounted from the local file system or copied or cloned into the container. Extensions are installed and run inside the container, where they have full access to the tools, platform, and file system. This means that you can seamlessly switch your entire development environment just by connecting to a different container.

```
日本語訳
```
Visual Studio Code Dev Containers 拡張機能を使用すると、コンテナをフル機能の開発環境として使用できます。これにより、コンテナ内の（またはコンテナにマウントされた）任意のフォルダを開き、Visual Studio Codeの全機能を利用できるようになります。プロジェクト内のdevcontainer.jsonファイルは、明確に定義されたツールとランタイムスタックを持つ開発コンテナへのアクセス（または作成）方法をVS Codeに伝えます。このコンテナは、アプリケーションを実行したり、コードベースの作業に必要なツール、ライブラリ、ランタイムを分離したりするために使用できます。

ワークスペースのファイルは、ローカルのファイルシステムからマウントされるか、コンテナにコピーまたはクローンされる。拡張機能はコンテナ内にインストールされて実行され、ツール、プラットフォーム、ファイルシステムにフルアクセスできる。つまり、別のコンテナに接続するだけで、開発環境全体をシームレスに切り替えることができる。
```

dev containers（リモートコンテナ）の登場で、ローカルでNodeやPythonを動かすことが極端に減りました。ただ、CPUは8コア以上、メモリは32GB以上（64GBが理想）

###  他のツールは？
- 運用で必要なLambda等でなく、アプリケーションとして動かすならばCI/CDまで構築できたら良い
- 今回は、Serverless Frameworkを使っていますが、AWS Toolkit や AWS SAMとSAM cliとCDKを使う場合もあります
- dockerコンテナでServerless FrameworkとPythonを動かすことで個人の環境に依存しない、ローカル環境を汚さない、バージョン違いでの無用なトラブル防止に役立つ