Serverless Framework 初めて触ってみた

Serverless Framework を利用するプロジェクトにアサインされました。

今まで全く触ったことがなかったのですが、いきなり「serverless.yaml 書いて」と言われたので急遽勉強するための環境を整えます。

インストール

www.serverless.com

node, npmバージョン確認

$ node -v
v18.12.1

$ npm -v
8.19.2

インストール

$ npm install -g serverless

serverless バージョン確認

$ serverless -v
Framework Core: 3.27.0
Plugin: 6.2.3
SDK: 4.3.2

プロジェクト作成

どうやら serverless でインタラクティブにプロジェクトが初期化できるようなのですが、選べるテンプレートが少なく、Lambda(Python) がなかったので、create コマンドで作成するようにします。

www.serverless.com

プロジェクト初期化

$ serverless create \
--template aws-python3 \ # 上記ページの "Available Templates" から選択
--path tutorial \ # コマンド実行ディレクトリに作成するプロジェクトディレクトリ名を指定
--name tutorial #  serverless.yml に記載されるプロジェクト名を指定

# GitHub等からテンプレートを持ってくる場合には `--template-url` を指定します。

AWS 権限設定

www.serverless.com

serverless でAWS APIを実行するための権限設定を行います。手順は以下の通り。

  1. IAMユーザ作成
  2. AdministratorAccessポリシーを付与・・・※補足1参照
  3. アクセスキーを作成

次に aws configure をします。serverless config credentials でラップされているようなのでこちらを使います。

$ serverless config credentials \
--provider aws \
--profile talkeyboid-serverless-user \
--key XXXXXXXXXXXXXX \
--secret XXXXXXXXXXXXXXXXXXX

プロファイル環境変数指定

$ export AWS_PROFILE=talkeyboid-serverless-user

補足1

※ドキュメントチュートリアルでは「AdministratorAccess を付与」と書いてありますが、怖すぎます。一応注意書きはある模様。

Note that the above steps grant Serverless Framework administrative access to your account. While this makes things simple when starting out, we recommend that you create and use more fine-grained permissions once you determine the scope of your serverless applications and move them into production.

権限を絞って与えるには以下が参考になりそうです。皆さん苦労されている様子。

blog.n-t.jp

qiita.com

ポリシージェネレータのリポジトリもありました。これですべてができるとは思いませんが、かなりの時短になりそうな予感。

github.com

github.com

補足2

以下のように serverless.yml を記載することで、--stage オプションに応じて、利用するプロファイルを変えることができるようです。便利。

service: new-service
provider:
  name: aws
  runtime: nodejs14.x
  profile: ${self:custom.profiles.${sls:stage}}
custom:
  profiles:
    dev: devProfile
    prod: prodProfile

また、定義した各リソースを CloudFormation 側でデプロイするときに利用するロール(サービスロール)を指定できるようでした。

provider:
  iam:
    deploymentRole: arn:aws:iam::123456789012:role/deploy-role

ここら辺は問題もある模様(カスタムリソースが勝手に作られそれにサービスロールが当たってしまう)。余裕があればここら辺もキャッチアップしたいですね。特に本番では気になる。

go-to-k.hatenablog.com

プロジェクト確認

ここで一度プロジェクトの内容を確認します。serverless create で作成したプロジェクトはこんな感じ。

$ tree
.
└── tutorial
    ├── handler.py
    └── serverless.yml

1 directory, 2 files

handler.py は Lambda デフォルトとほぼ同じコード、関数名が hello になってますが、yaml側でハンドラー定義もされてるんでしょうか。

handler.py
import json


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

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response

    # Use this code if you don't use the http event with the LAMBDA-PROXY
    # integration
    """
    return {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "event": event
    }
    """

serverless.yml はこんな感じ。やたらと親切にコメントでいろいろと書いてくれています。必要なところだけコメント外して書き換えるだけでいろいろとできそうですね。

serverless.yml
# Welcome to Serverless!
#
# This file is the main config file for your service.
# It's very minimal at this point and uses default values.
# You can always add more config options for more control.
# We've included some commented out config examples here.
# Just uncomment any of them to get that config option.
#
# For full config options, check the docs:
#    docs.serverless.com
#
# Happy Coding!

service: tutorial
# app and org for use with dashboard.serverless.com
#app: your-app-name
#org: your-org-name

# You can pin your service to only deploy with a specific Serverless version
# Check out our docs for more details
frameworkVersion: '3'

provider:
  name: aws
  runtime: python3.8

# you can overwrite defaults here
#  stage: dev
#  region: us-east-1

# you can add statements to the Lambda function's IAM Role here
#  iam:
#    role:
#      statements:
#        - Effect: "Allow"
#          Action:
#            - "s3:ListBucket"
#          Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ]  }
#        - Effect: "Allow"
#          Action:
#            - "s3:PutObject"
#          Resource:
#            Fn::Join:
#              - ""
#              - - "arn:aws:s3:::"
#                - "Ref" : "ServerlessDeploymentBucket"
#                - "/*"

# you can define service wide environment variables here
#  environment:
#    variable1: value1

# you can add packaging information here
#package:
#  patterns:
#    - '!exclude-me.py'
#    - '!exclude-me-dir/**'
#    - include-me.py
#    - include-me-dir/**

functions:
  hello:
    handler: handler.hello
#    The following are a few example events you can configure
#    NOTE: Please make sure to change your handler code to work with those events
#    Check the event documentation for details
#    events:
#      - httpApi:
#          path: /users/create
#          method: get
#      - websocket: $connect
#      - s3: ${env:BUCKET}
#      - schedule: rate(10 minutes)
#      - sns: greeter-topic
#      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
#      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
#      - iot:
#          sql: "SELECT * FROM 'some_topic'"
#      - cloudwatchEvent:
#          event:
#            source:
#              - "aws.ec2"
#            detail-type:
#              - "EC2 Instance State-change Notification"
#            detail:
#              state:
#                - pending
#      - cloudwatchLog: '/aws/lambda/hello'
#      - cognitoUserPool:
#          pool: MyUserPool
#          trigger: PreSignUp
#      - alb:
#          listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/
#          priority: 1
#          conditions:
#            host: example.com
#            path: /hello

#    Define function environment variables here
#    environment:
#      variable2: value2

# you can add CloudFormation resource templates here
#resources:
#  Resources:
#    NewResource:
#      Type: AWS::S3::Bucket
#      Properties:
#        BucketName: my-new-bucket
#  Outputs:
#     NewOutput:
#       Description: "Description for the output"
#       Value: "Some output value"

コメント外すとたったこれだけです。予想通りハンドラ定義ありましたね。ソースを変更するときはここを書き換えればよさそうです。

serverless.yml
service: tutorial
frameworkVersion: '3'

provider:
  name: aws
  runtime: python3.8

functions:
  hello:
    handler: handler.hello

デプロイ

デプロイ

$ serverless deploy

CloudFormationスタックが作成されたことを確認。どうやらデフォルトで dev がつくようですね。custom.profiles を指定すれば任意に設定できるのでしょうかね(知らんけど)。

Lambda, ロググループ, IAMロール, S3バケット が作成されているようです。serverless.yaml では Lambda しか定義していなかったですが良しなに作ってくれました。ただ、ここでリージョンが us-east-1 になっていることに気づきました。IAM権限設定の際に serverless config をしましたが、ここでおそらくリージョンを設定しないといけなかった気がします。もしくはテンプレート内にリージョン書けばできるかな?

とりあえず、各リソースを確認していきます。

Lambda

ソースが反映されていることがわかります。

設定はマネジメントコンソールで作成するときとかなり違いますね。デフォルトだとタイムアウトが長く、メモリも大きいです。

ロール

ポリシーは以下が当たってました。Lambdaの一番シンプルな組み込みロールと同じ感じですね。

tutorial-dev-us-east-1-lambdaRole

tutorial-dev-lambdaポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:CreateLogStream",
                "logs:CreateLogGroup"
            ],
            "Resource": [
                "arn:aws:logs:us-east-1:XXXXXXXXXXXX:log-group:/aws/lambda/tutorial-dev*:*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:us-east-1:XXXXXXXXXXXX:log-group:/aws/lambda/tutorial-dev*:*:*"
            ],
            "Effect": "Allow"
        }
    ]
}

S3

バケットには以下のファイルが格納されていました。

ファイル 中身
compiled-cloudformation-template.json CloudFormationに変換したテンプレート
serverless-state.json serverless.ymlで定義していない値をデフォルト値で埋めたテンプレート
tutorial.zip デプロイ資材(ここでは handler.py

これらは serverless deploy 時にコンパイルされた .serverless ディレクトリ内のファイルと "ほぼ" 同じもののようです。

$ ls .serverless/
cloudformation-template-create-stack.json  serverless-state.json
cloudformation-template-update-stack.json  tutorial.zip

どのような対応か、わかりやすく説明してくださってるページがありました。

blog.giftee.dev

どうやらこんな感じの流れなようです。

  • serverless.yml を解析
  • 埋まっていない値をデフォルト値で補完し serverless-state.json を出力
  • 定義ファイルやソースを格納するS3バケットを作成するためのテンプレートを cloudformation-template-create-stack.json として出力
  • 実際に作成するテンプレートを cloudformation-template-update-stack.json として出力。これはS3バケット上の compiled-cloudformation-template.json と同じ。※同じものがアップロードされるという意味なのかは不明

お掃除

全削除。CloudFormationスタックが削除されます。

$ serverless remove

serverless 実行用に作成したアクセスキーを無効化しておきます。

感想

全くの0からなんとなくイメージはつかめました。

serverless.yml の書き方、IAM権限の絞り込み周りを勉強していきます。