ServerlessFrameworkを触ってみた

ServerlessFrameworkのインストールから基本的な設定・操作方法を確認していきたいと思います。
今回はAWSをプロバイダとしてPython3でサービスを作成します。

# ■ インストール
### # Serverless Frameworkのインストール
node.jsのインストールはnvmで行います。

```bash
# nvm インストール
#   https://github.com/nvm-sh/nvm#installing-and-updating
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash

# 最新のltsをインストールしてグローバルに設定
nvm install --lts
nvm use --lts

# インストールされているか確認
node -v
npm --version

# serverless frameworkのインストール
sudo npm install -g serverless
sls --version
```

### # dockerのインストール
Pythonの外部ライブラリを利用する場合に必要になるのでインストールしておきます。

[Amazon ECS における Docker の基本 - Dockerのインストール | aws](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/docker-basics.html#install_docker)

```bash
# Pythonの外部ライブラリをパッケージングするときに必要になる
sudo amazon-linux-extras install docker
sudo systemctl enable docker.service
sudo systemctl start docker.service

# ec2-userをdockerグループに追加してsudoなしでdockerを操作できるようにする
sudo usermod -a -G docker ec2-user

# 一度ログアウトして再ログイン
```

### # プロバイダアカウントのセットアップ

https://serverless.com/framework/docs/providers/aws/guide/credentials

AWSのコンソールからServerlessFramework用のIAMユーザーを発行します。
一応何でもできるように AdministratorAccess ポリシーをつけておきます。


# ■ 基本的な操作
https://serverless.com/framework/docs/providers/aws/cli-reference

### # プロジェクトの作成

```bash
# --template: AWSをプロバイダとしてPython3でプロジェクトを作成する場合は aws-python3 を指定
# --name: 命名規則は ^[a-zA-Z][0-9a-zA-Z-]+$
# --path: プロジェクトを作成するパスを指定します。
sls create --template aws-python3 --name translator --path translator
```

### # ローカルでのファンクションの実行
デプロイ前の関数をローカルで実行します。

```bash
# --function <FUNCTION_NAME>: 実行する関数名を指定します。
# -p <EVENT_FILE>: ファンクション実行時に引数にわたるevent(json形式)を指定することができます。
sls invoke local --function hello -p event.json
```

### # デプロイ

```bash
# --stage <STAGE_NAME>: servers.ymlのprovider.stageをデプロイ時に指定することができます。(default=dev)
# --region <REGION>: servers.ymlのprovider.regionをデプロイ時に指定することができます。
sls deploy --stage prod --region ap-northeast-1 

# ファンクション単位でデプロイしたい場合
sls deploy function -f <yourfunction>
```

### # デプロイした関数をターミナルから実行
デプロイした関数をターミナルから実行します。

```bash
# --function <FUNCTION_NAME>: 実行する関数名を指定します
# -p <EVENT_FILE>: ファンクション実行時に引数にわたるevent(json形式)を指定することができます
sls invoke --function hello -p event.json
```

### # ログ
指定した関数のログを閲覧します。

```bash
# --function <FUNCTION_NAME>: 対象の関数名を指定します
# -t: ログをtailします
sls logs --function hello
```
### # Github上のサービスをローカルにインポート

```bash
# ふつうにgit cloneしてもOK
sls install -u <GITHUB URL OF SERVICE>

cd <PROJECT>
npm install
```

### # プラグインのインストール
```bash
sls plugin install -n <PLUGIN_NAME>
```

### # サービスの削除
```bash
sls remove -v
```

# ■ Pythonの外部ライブラリを利用したい場合
serverless-python-requirements プラグインを利用します。

[Serverless Python Requirements | Serverless](https://www.serverless.com/plugins/serverless-python-requirements)

### # serverless-python-requirements のインストール

```bash
# インストール
sls plugin install -n serverless-python-requirements
```

インストールしたら下記設定を追記します。

```serverless.yml
plugins:
    - serverless-python-requirements
custom:
    PythonRequirements: # deploy時のパッケージングの際にpythonライブラリのビルドをdockerコンテナ上で行う設定です。
        dockerizePip: true
```

### # venvのセットアップ
ServerlessFramework自体の仕組みとしてvenvでPythonライブラリを管理する必要はないですが、管理がしやすくなるのでvenvを利用します。
venv仮想環境をactivateしておけば `sls invoke local` でローカルで関数を実行することもできます。

```bash
cd <PROJECT_NAME>

# .venvディレクトリに仮想環境を作成して、仮想環境を有効化します。
python3.8 -m venv .venv
source .venv/bin/activate

# ライブラリをインストールして、requirements.txtに書き込見ます。
pip install numpy boto3
pip freeze > requirements.txt
```

### # デプロイ
あとは、デプロイコマンドを実行するだけで、requirements.txtに書かれているライブラリを良しなにパッケージングしてデプロイしてくれます。
※ numpyみたいなバイナリのビルドが必要なパッケージはdockerコンテナ上(最初にインストールしたやつ)でビルドされて、Lambda上でも問題なく動作します。

```bash
sls deploy
```

# ■ serverless.yml ファイルの設定項目
### # service
サービス名を定義します。初期状態のままでOKです。

```servers.yml
service: translator
```

### # frameworkVersion
フレームワークのバージョンを定義します。初期状態のままでOKです。

```servers.yml
frameworkVersion: '2'
```

### # provider
プロバイダ名やランタイムやリージョンなど、プロジェクトの基本的な情報を定義します。

```serveless.yml
provider:
  name: aws
  runtime: python3.8
  lambdaHashingVersion: 20201221
  region: ap-northeast-1 # デプロイするリージョンを定義します。deployコマンドの--regionでデプロイ時に指定することも可能
  stage: dev # デプロイするステージ情報を定義します。deployコマンドの--stageでデプロイ時に指定することも可能
 ```

### # package

https://serverless.com/framework/docs/providers/aws/guide/packaging

パッケージに含めるファイルをパターンで定義します。
定義されたパターンは後勝ちなので、最初にすべてのファイルを除外して、必要なファイルだけ指定すると使いやすそうです。

```servers.yml
package:
  patterns:
    - '!**' # すべてのファイルをexclude
    - 'static/**' # staticディレクトリは以下をinclude
    - 'handler.py' # handler.pyをinclude
```

### # functions
https://serverless.com/framework/docs/providers/aws/guide/functions

関数の実行の起点となるイベントと、イベントが発生したときに実行する関数を定義します。

- handler  
`ファイル名.関数名` 形式で関数を指定します。
- events  
API Gatewayを使用したhttpイベントや、S3の操作をトリガーとしたイベントなどいろいろなイベントを定義することができます。  
https://serverless.com/framework/docs/providers/aws/events

```serverless.yml
functions:
  translate:
    handler: handler.translate # handler.pyのtranslate関数を呼び出します。
    role: TranslateRole # resourceセクション(後述)で作成したIAMロールをlambdaに設定します。(未指定の場合はデフォルトのロールが利用されます)
    events: 
      - http: # API Gatewayを利用したhttpイベントを設定します。
          path: /translate # /translateにPOSTアクセスされたときに関数を実行します。
          method: post
          cors: true # CORSを許可するオプションみたいですが、効いていない気がします、、、
  static:
    handler: handler.static # handler.pyのstatic関数を呼び出します。
    role: BaseRole
    events:
      - http:
          path: "/static/{any+}" # {hoge} でプレースホルダを設定できます。{hoge+} とするとワイルドカードになります。
          method: get
```

### # resources

https://serverless.com/framework/docs/providers/aws/guide/resources

CloudFormationのテンプレートをそのまま記述することができます。
今回はLambda関数用のIAMロールを定義しています。

```serverless.yml
resources:
  Resources:
    BaseRole: # handler.static関数にアタッチするロール
      Type: AWS::IAM::Role
      Properties:
        RoleName: translator-role-lambda-base
        ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        AssumeRolePolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action:
                - sts:AssumeRole
              Principal:
                Service:
                  - lambda.amazonaws.com
    TranslateRole: # handler.translate関数にアタッチするロール
      Type: AWS::IAM::Role
      Properties:
        RoleName: translator-role-lambda-translate
        ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
          - arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole
          - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
          - arn:aws:iam::aws:policy/TranslateFullAccess
        AssumeRolePolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action:
                - sts:AssumeRole
              Principal:
                Service:
                  - lambda.amazonaws.com
```

### # plugins

https://serverless.com/framework/docs/providers/aws/guide/plugins

利用するプラグインを定義します。
`sls plugin install` コマンドでプラグインをインストールすると自動的に追記されます。

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

### # custom
Documentに項目が見当たらないが、プラグインなどの個別設定を記述するセクションだろうか、、、

```servers.yml
custom:
  pythonRequirements:
    dockerizePip: true
```

# ■ 簡単な翻訳アプリ
学習した内容を元にちょっとした翻訳アプリを作ってみました。

https://github.com/ng3rdstmadgke/translator

![スクリーンショット 2021-09-10 203541.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/3d4d8d10-ba07-871a-8ca5-8a6dc7700725.png)

![スクリーンショット 2021-09-10 203652.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/e1134e26-473e-9354-b577-09e12f42f67e.png)



# ■ 参考記事
- https://serverless.com/
- https://serverless.co.jp/blog/25/