Help us understand the problem. What is going on with this article?

ServerlessFrameworkでLmabda関数名に柔軟性を持たせようとする時の注意点

Lambda関数名に柔軟性をもたせようとしたら、ロググループ名が変わってしまったり、ログが出力されなくなったりしたのでその時の対応についての注意点になります。

普通にserverless.ymlでLmabdaを定義するとこんな感じになると思います。
serverless.yml
service: demo

provider:
  name: aws
  runtime: python3.7
  stage: ${opt:stage, 'dev'}

functions:
  hello:
    handler: handler.hello

この場合、リソースのロググループ名はServerlessFrameworkが以下のようにしてくれます。
論理IDHelloLogGroup
物理ID/aws/lambda/demo-dev-hello

物理IDはこんな感じですね。
/aws/lambda/${self:service}-${self:provider.stage}-${Lambda関数名}

ログループ名を表示したり使い回すときはこんな感じで。
serverless.yml
resources:
  Outputs:
    HelloLogGroup:
      Value: !Ref HelloLogGroup

ただ、これだと関数名を変更するときはロググループの箇所も変更する必要があります。
また、関数が増えると関数名に合わせてロググループの名前を追加するという面倒な作業が増えます。

関数名を変えたり、増やしたりするときに面倒くさい!!

と考えて、こんな風に置き換えました。

env.yml
service: demo
func1: hello 
func2: goodby
serverless.yml
service: ${self:custom.env.service}

provider:
  name: aws
  runtime: python3.7
  stage: ${opt:stage, 'dev'}

custom:
  env: ${file(../env.yml)}

functions:
  func1:
    name: ${self:custom.env.func1}
    handler: handler.${self:custom.env.func1}

  func2:
    name: ${self:custom.env.func2}
    handler: handler2.${self:custom.env.func2}

resources:
  Outputs:
    Func1LogGroup:
      Value: !Ref Func1LogGroup
    Func2LogGroup:
      Value: !Ref Func2LogGroup

これで変更に柔軟な構成になりましたが、sls deploy -v したところ・・

ログの名前がおかしい?!

Stack Outputs
Func1LogGroup: /aws/lambda/hello
Func2LogGroup: /aws/lambda/goodby

さらにLambdaを実行してもCloudWatch Logsにログが出力されない!!

functonの名前をnameで設定したことによって、今までうまくやってくれていたServerlessFrameworkのレールから外れてしまったんでしょうか。。

ログの名前を戻す&CloudWatchのIAMロールを付与する

CloudFormationのリソースはextensionsでOverrideできるため、以下のように自分で組みたてて更新します。さらにCloudWatchのIAMのロールを明示的に追加します。

common.yml
  Func1LogGroup: !Join
                  - ''
                  - - "/aws/lambda/"
                    - ${self:service}
                    - '-'
                    - ${self:provider.stage}
                    - '-'
                    - ${self:custom.env.func1}
  Func2LogGroup: !Join
                  - ''
                  - - "/aws/lambda/"
                    - ${self:service}
                    - '-'
                    - ${self:provider.stage}
                    - '-'
                    - ${self:custom.env.func2}
serverless.yml
service: ${self:custom.env.service}

provider:
  name: aws
  runtime: python3.7
  stage: ${opt:stage, 'dev'}
#### ↓CloudWatch LogsのIAMロール付与 ####
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - logs:CreateLogGroup
        - logs:CreateLogStream
        - logs:PutLogEvents
        - s3:ListBucket
      Resource: "*"
#### ↑追加 ####

custom:
#### ↓追加 ####
  common: ${file(../../template/common.yml)}
#### ↑追加 ####
  env: ${file(../env.yml)}

functions:
  func1:
    name: ${self:custom.env.func1}
    handler: handler.${self:custom.env.func1}

  func2:
    name: ${self:custom.env.func2}
    handler: handler2.${self:custom.env.func2}

resources:
  Outputs:
    Func1LogGroup:
      Value: !Ref Func1LogGroup
    Func2LogGroup:
      Value: !Ref Func2LogGroup
#### ↓リソースのOverride ####
  extensions:
    Func1LogGroup:
      Properties:
        LogGroupName: ${self:custom.common.Func1LogGroup}
    Func2LogGroup:
      Properties:
        LogGroupName: ${self:custom.common.Func2LogGroup}
#### ↑追加 ####

これでロググループ名は戻り、Lambda実行時にCloudWatchにログが出力されるようになりました。
めでたし、めでたし。。

Stack Outputs
Func1LogGroup: /aws/lambda/demo-dev-hello
Func2LogGroup: /aws/lambda/demo-dev-goodby

・・いや、さらに柔軟にしたい・・!

この状態だと関数の増減のたびに追加、削除する必要があるため、以下のように修正しました。
とりあえず最大4つということにして設定しておきます。
関数の増減があるときにenv.ymlを修正し、まだ作成していない分はdummyといれておきます。

env.yml
service: demo
func1: hello 
func2: goodby
func3: dummy
func4: dummy

Conditionsを追加して関数の存在チェックを作成。dummy以外の場合は存在となみす。

serverless.yml
resources:
  Conditions:
    Func1Exists:
      !Not
        - !Equals
          - ${self:custom.env.func1}
          - dummy
    Func2Exists:
      !Not
        - !Equals
          - ${self:custom.env.func2}
          - dummy
    Func3Exists:
      !Not
        - !Equals
          - ${self:custom.env.func3}
          - dummy
    Func4Exists:
      !Not
        - !Equals
          - ${self:custom.env.func3}
          - dummy

resourcesはこんな感じにします。
!Ref Func3LogGroupみたいな書き方をしてしまうと、そのリソースが存在しないときは、たとえConditionsを使っていてもエラーになります。なのでself:customから取るようにしています。

serverless.yml
〜〜〜〜〜省略〜〜〜〜
resources:
  Conditions:
    Func1Exists:
      !Not
        - !Equals
          - ${self:custom.env.func1}
          - dummy
    Func2Exists:
      !Not
        - !Equals
          - ${self:custom.env.func2}
          - dummy
    Func3Exists:
      !Not
        - !Equals
          - ${self:custom.env.func3}
          - dummy
    Func4Exists:
      !Not
        - !Equals
          - ${self:custom.env.func4}
          - dummy

  extensions:
    Func1LogGroup:
      Condition: Func1Exists
      Properties:
        LogGroupName: ${self:custom.common.Func1LogGroup}
    Func2LogGroup:
      Condition: Func2Exists
      Properties:
        LogGroupName: ${self:custom.common.Func2LogGroup}
    Func3LogGroup:
      Condition: Func3Exists
      Properties:
        LogGroupName: ${self:custom.common.Func3LogGroup}
    Func4LogGroup:
      Condition: Func4Exists
      Properties:
        LogGroupName: ${self:custom.common.Func4LogGroup}

  Outputs:
    Func1LogGroup:
      Condition: Func1Exists
      Value: ${self:custom.common.Func1LogGroup}
    Func2LogGroup:
      Condition: Func2Exists
      Value: ${self:custom.common.Func2LogGroup}
    Func3LogGroup:
      Condition: Func3Exists
      Value: ${self:custom.common.Func3LogGroup}
    Func4LogGroup:
      Condition: Func4Exists
      Value: ${self:custom.common.Func4LogGroup}

これでsls deploy -vすると・・

Error: Func1LogGroup: Sorry, extending the Condition resource attribute at this point is not supported. Feel free to propose support for it in the Framework issue tracker: https://github.com/serverless/serverless/issues

なんかエラーでてる!!

翻訳すると・・

申し訳ありませんが、現時点では Condition リソース属性の拡張はサポートされていません。Framework issue tracker: https://github.com/serverless/serverless/issues でサポートを提案してください。

ということらいいです。
extensionsにConditionは使えないらしいです。OutputsにConditionは使えたのでとりあえずは良かったということで・・。
extensionsは、Conditionが使える実装がされるまでは、関数の増減によってコメントをオン・オフして対応します。

最終的にはこんな感じになりました。

env.ymlに設定した関数の分だけロググループが表示されます。

Stack Outputs
Func1LogGroup: /aws/lambda/demo-dev-hello
Func2LogGroup: /aws/lambda/demo-dev-goodby
serverless.yml
service: ${self:custom.env.service}

provider:
  name: aws
  runtime: python3.7
  stage: ${opt:stage, 'dev'}
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - logs:CreateLogGroup
        - logs:CreateLogStream
        - logs:PutLogEvents
        - s3:ListBucket
      Resource: "*"

custom:
  common: ${file(../../template/common.yml)}
  env: ${file(../env.yml)}

functions:
  func1:
    name: ${self:custom.env.func1}
    handler: handler.${self:custom.env.func1}

  func2:
    name: ${self:custom.env.func2}
    handler: handler2.${self:custom.env.func2}

resources:
  Conditions:
    Func1Exists:
      !Not
        - !Equals
          - ${self:custom.env.func1}
          - dummy
    Func2Exists:
      !Not
        - !Equals
          - ${self:custom.env.func2}
          - dummy
    Func3Exists:
      !Not
        - !Equals
          - ${self:custom.env.func3}
          - dummy
    Func4Exists:
      !Not
        - !Equals
          - ${self:custom.env.func4}
          - dummy

  extensions:
    Func1LogGroup:
#      実装されたらコメント外す
#      Condition: Func1Exists
      Properties:
        LogGroupName: ${self:custom.common.Func1LogGroup}
    Func2LogGroup:
#      実装されたらコメント外す
#      Condition: Func2Exists
      Properties:
        LogGroupName: ${self:custom.common.Func2LogGroup}

# extensionsは、Conditionが実装されるまで、関数の増減の都度コメントのオンオフを行う
#    Func3LogGroup:
#      実装されたらコメント外す
#      Condition: Func3Exists
#      Properties:
#        LogGroupName: ${self:custom.common.Func3LogGroup}
#    Func4LogGroup:
#      実装されたらコメント外す
#      Condition: Func4Exists
#      Properties:
#        LogGroupName: ${self:custom.common.Func4LogGroup}

  Outputs:
    Func1LogGroup:
      Condition: Func1Exists
      Value: ${self:custom.common.Func1LogGroup}
    Func2LogGroup:
      Condition: Func2Exists
      Value: ${self:custom.common.Func2LogGroup}
    Func3LogGroup:
      Condition: Func3Exists
      Value: ${self:custom.common.Func3LogGroup}
    Func4LogGroup:
      Condition: Func4Exists
      Value: ${self:custom.common.Func4LogGroup}
env.yml
service: demo
func1: hello 
func2: goodby
func3: dummy
func4: dummy
common.yml
  Func1LogGroup: !Join
                  - ''
                  - - "/aws/lambda/"
                    - ${self:service}
                    - '-'
                    - ${self:provider.stage}
                    - '-'
                    - ${self:custom.env.func1}
  Func2LogGroup: !Join
                  - ''
                  - - "/aws/lambda/"
                    - ${self:service}
                    - '-'
                    - ${self:provider.stage}
                    - '-'
                    - ${self:custom.env.func2}
  Func3LogGroup: !Join
                  - ''
                  - - "/aws/lambda/"
                    - ${self:service}
                    - '-'
                    - ${self:provider.stage}
                    - '-'
                    - ${self:custom.env.func3}
  Func4LogGroup: !Join
                  - ''
                  - - "/aws/lambda/"
                    - ${self:service}
                    - '-'
                    - ${self:provider.stage}
                    - '-'
                    - ${self:custom.env.func4}

めでたし、めでたし・・と思っていたら

新しい問題発覚!!

上記のやり方はロググループに言及していましたが、Lambdaの関数名にも同じことが言えます。

本来は{service}-{stage}-{function}という形式で作られるはずが、{function}で作られてしまいます。
(上記の例のとおりだと、demo-dev-helloではなく、helloで作られる)
これも同様にextensionsで無理やり変えてしまえば問題ないように思えました。

気づいたきっかけ

ある時、Lambdaの起動をS3をトリガーにするために、以下のように設定を加えるとうまく機能しませんでした。

serverless.yml
functions:
  func1:
    name: ${self:custom.env.func1}
    handler: handler.${self:custom.env.func1}
    events:
      - s3:
          bucket: ${self:custom.common.Func2Bucket1}
          event: s3:ObjectCreated:*
          existing: true

Lambda関数の一覧には(extensionsで無理やり変えたため)demo-dev-helloで作られていますが、S3のトリガを確認するとhelloで設定してます。
この場合、Lambda関数名にhelloは存在していないため、トリガが機能しません。

想像ですが、S3のトリガ設定のほうがextensionsでLambda関数名を書き換えるより順序が早いんですかね・・。

解決方法

結局の以下のような形にすることで問題はなくなりました。

  • extensionsの使用はやめる
  • nameに{service}-{stage}-{function}の形式に沿う形で設定する
serverless.yml
functions:
  func1:
    name: ${self:custom.env.service}-${self:provider.stage}-${self:custom.env.func1}
    handler: handler.${self:custom.env.func1}
    events:
      - s3:
          bucket: ${self:custom.common.Func2Bucket1}
          event: s3:ObjectCreated:*
          existing: true

最初からこうしておけという話ですね。。

ken992
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした