Serverless Frameworkのserverless.ymlが長くなってきたので外部ファイルから読み込むようにしてみる

どうも!大阪オフィスの西村祐二です。

サーバーレスアプリケーションを開発するときにServerless Frameworkを使って開発している人は多いのではないでしょうか。

Lambda関数、プラグインの設定、AWSリソースが増えてくるとserverless.ymlがどんどん長くなってきます。

拡張やメンテナンスなど行いやすくするために、今回、serverless.ymlの設定を外部のファイルから読み込むようにしてみたいと思います。

環境

  • OS : macOS Mojave 10.14.3
  • Node.js : v10.15.1
  • Serverless Framework: 1.37.0

対象のserverless.yml

今回は下記のようなサンプルを用意しました。

複数のLambda関数、プラグイン、S3、DynamoDBのAWSリソースを管理した構成になっています。

下記のサンプルをもとに設定していきます。

service: test-blog # NOTE: update this with your service name

provider:
  name: aws
  runtime: python3.7
  stage: v1
  apiName: ${self:custom.env}-${self:service}
  environment:
    DYNAMODB_TABLE: ${self:custom.env}-${self:service}
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"

plugins:
  - serverless-dynamodb-local
  
custom:
  env: ${opt:env, 'itg'}
  dynamodb:
    start:
      port: 8000
      inMemory: true
      migrate: true
      seed: true
    seed:
      development:
        sources:
          - table: ${self:provider.environment.DYNAMODB_TABLE}
            sources: [./data/test.json]

package:
  individually: true
  include:
    - handler.py
  exclude:
    - "**"

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: sample/test
          method: get
  create:
    handler: handler.create
    events:
      - http:
          path: todos
          method: post
          cors: true

  list:
    handler: handler.list
    events:
      - http:
          path: todos
          method: get
          cors: true

  get:
    handler: handler.get
    events:
      - http:
          path: todos/{id}
          method: get
          cors: true

  update:
    handler: handler.update
    events:
      - http:
          path: todos/{id}
          method: put
          cors: true

  delete:
    handler: handler.delete
    events:
      - http:
          path: todos/{id}
          method: delete
          cors: true

resources:
  Resources:
    testS3Bucket:
      Type: "AWS::S3::Bucket"
      Properties:
        BucketName: hoge-ynishimura-bucket
    testTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${self:custom.env}-${self:service}
        AttributeDefinitions:
          - AttributeName: email
            AttributeType: S
        KeySchema:
          - AttributeName: email
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1

IAMに関する設定を外部ファイルから読み込むようにする

configというディレクトリを作り、そこの配下にiam.ymlを作成します。(これは任意で各々で適切な形で作成してください。)

そこに、IAMの設定を書き出します。

嬉しい点としては外部から設定を読み込む形にしても${self:provider.environment.DYNAMODB_TABLE}など変数をきちんとパースして認識してくれるところです。

- Effect: Allow
  Action:
    - dynamodb:Query
    - dynamodb:Scan
    - dynamodb:GetItem
    - dynamodb:PutItem
    - dynamodb:UpdateItem
    - dynamodb:DeleteItem
  Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"

serverless.ymlは下記のようにfileプロパティを使うことで読み込むことができます。

  environment:
    DYNAMODB_TABLE: ${self:provider.custom.env}-${self:service}
  iamRoleStatements: ${file(./config/iam.yml)}

plugins:
  - serverless-dynamodb-local

どのように読込されるかの動作確認としては下記コマンドで簡単に確認できます。

$ sls print

service: test-blog
provider:
  stage: v1
  apiName: itg-test-blog
  name: aws
  runtime: python3.7
  environment:
    DYNAMODB_TABLE: itg-test-blog
  iamRoleStatements:
    - Effect: Allow
      Action:
        - 'dynamodb:Query'
        - 'dynamodb:Scan'
        - 'dynamodb:GetItem'
        - 'dynamodb:PutItem'
        - 'dynamodb:UpdateItem'
        - 'dynamodb:DeleteItem'
      Resource: 'arn:aws:dynamodb:us-east-1:*:table/itg-test-blog'
plugins:
  - serverless-dynamodb-local
.
.
.

プラグインの設定を外部から読み込む

先程と同様に下記のようにプラグインの設定を外出しします。

start:
  port: 8000
  inMemory: true
  migrate: true
  seed: true
seed:
  development:
    sources:
      - table: ${self:provider.environment.DYNAMODB_TABLE}
        sources: [./data/test.json]

serverless.ymlは下記のようにfileプロパティを使うことで読み込むことができます。

plugins:
  - serverless-dynamodb-local

custom:
  env: 
  dynamodb: ${file(./config/plugin/serverless-dynamodb-local.yml)}

package:
・
・
・

Lambda関数のイベントに関する設定を外部から読み込む

今回、configディレクトリ配下にevents.ymlを作成し、そこから読み込むようにしてみました。

下記のように、各Lambdaのイベントの設定を書き出します。

hello_event:
  - http:
      path: sample/test
      method: get

create_event:
  - http:
      path: todos
      method: post
      cors: true

list_event:
  - http:
      path: todos
      method: get
      cors: true

get_event:
  - http:
      path: todos/{id}
      method: get
      cors: true

update_event:
  - http:
      path: todos/{id}
      method: put
      cors: true

delete_event:
  - http:
      path: todos/{id}
      method: delete
      cors: true

serverless.ymlは下記のようにfileプロパティのあとにキーを指定することで個別に読み込むことができます。

functions:
  hello:
    handler: handler.hello
    events: ${file(./config/events.yml):hello_event}

  create:
    handler: handler.create
    events: ${file(./config/events.yml):create_event}

  list:
    handler: handler.list
    events: ${file(./config/events.yml):list_event}

  get:
    handler: handler.get
    events: ${file(./config/events.yml):get_event}

  update:
    handler: handler.update
    events: ${file(./config/events.yml):update_event}

  delete:
    handler: handler.delete
    events: ${file(./config/events.yml):delete_event}

resources:

AWSリソースのcfnテンプレートを外部から読み込む

今回、configディレクトリ配下にcfnディレクトリを作りs3.ymldynamodb.ymlに分けて、そこから読み込むようにしてみました。

下記のように、cfnテンプレートの設定を書き出します。

  Resources:
    testS3Bucket:
      Type: "AWS::S3::Bucket"
      Properties:
        BucketName: hoge-ynishimura-bucket
Resources:
  testTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: ${self:custom.env}-${self:service}
      AttributeDefinitions:
        - AttributeName: email
          AttributeType: S
      KeySchema:
        - AttributeName: email
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1

注意点としてはResourcesではじまるように記述しないといけないところです。

https://serverless.com/framework/docs/providers/aws/guide/variables#multiple-configuration-files

serverless.ymlは下記のようにfileプロパティを使ってファイルを指定するだけでcfnテンプレートを読み込むことができます。

  delete:
    handler: handler.delete
    events: ${file(./config/events.yml):delete_event}

resources:
  - ${file(./config/cfn/s3.yml)}
  - ${file(./config/cfn/dynamodb.yml)}

最終的なserverless.yml

最終的なserverless.ymlは下記のようになりました。

ある程度スッキリしたのではないでしょうか。

service: test-blog # NOTE: update this with your service name

provider:
  name: aws
  runtime: python3.7
  stage: v1
  apiName: ${self:custom.env}-${self:service}
  environment:
    DYNAMODB_TABLE: ${self:custom.env}-${self:service}
  iamRoleStatements: ${file(./config/iam.yml)}

plugins:
  - serverless-dynamodb-local

custom:
  env: ${opt:env, 'itg'}
  dynamodb: ${file(./config/plugin/serverless-dynamodb-local.yml)}

package:
  individually: true
  include:
    - handler.py
  exclude:
    - "**"

functions:
  hello:
    handler: handler.hello
    events: ${file(./config/events.yml):hello_event}

  create:
    handler: handler.create
    events: ${file(./config/events.yml):create_event}

  list:
    handler: handler.list
    events: ${file(./config/events.yml):list_event}

  get:
    handler: handler.get
    events: ${file(./config/events.yml):get_event}

  update:
    handler: handler.update
    events: ${file(./config/events.yml):update_event}

  delete:
    handler: handler.delete
    events: ${file(./config/events.yml):delete_event}

resources:
  - ${file(./config/cfn/s3.yml)}
  - ${file(./config/cfn/dynamodb.yml)}

また、分割した設定ファイルとserverless.ymlのディレクトリ構成は下記のようになっています。(他の関連ファイルは非表示)

.
├── config
│   ├── cfn
│   │   ├── dynamodb.yml
│   │   └── s3.yml
│   ├── events.yml
│   ├── iam.yml
│   └── plugin
│       └── serverless-dynamodb-local.yml
└── serverless.yml

さいごに

いかがだったでしょうか。

serverless.ymlの設定を外部のファイルから読み込むようにしてみました。

イベント、AWSリソースだけでも外部ファイルから読み込むようにするとかなりスッキリするのではないでしょうか。

また、他にもいろいろ試していたのですが、かなり柔軟性があり、コマンド引数によって動的に読み込む設定を切り替えたりもできますので、興味のある方は是非試してみてください。

誰かの参考になれば幸いです。