Lambda関数名に柔軟性をもたせようとしたら、ロググループ名が変わってしまったり、ログが出力されなくなったりしたのでその時の対応についての注意点になります。
普通にserverless.ymlでLmabdaを定義するとこんな感じになると思います。
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関数名}
ログループ名を表示したり使い回すときはこんな感じで。
resources:
Outputs:
HelloLogGroup:
Value: !Ref HelloLogGroup
ただ、これだと関数名を変更するときはロググループの箇所も変更する必要があります。
また、関数が増えると関数名に合わせてロググループの名前を追加するという面倒な作業が増えます。
関数名を変えたり、増やしたりするときに面倒くさい!!
と考えて、こんな風に置き換えました。
service: demo
func1: hello
func2: goodby
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のロールを明示的に追加します。
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}
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
といれておきます。
service: demo
func1: hello
func2: goodby
func3: dummy
func4: dummy
Conditionsを追加して関数の存在チェックを作成。dummy以外の場合は存在となみす。
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
から取るようにしています。
〜〜〜〜〜省略〜〜〜〜
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
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}
service: demo
func1: hello
func2: goodby
func3: dummy
func4: dummy
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をトリガーにするために、以下のように設定を加えるとうまく機能しませんでした。
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}の形式に沿う形で設定する
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
最初からこうしておけという話ですね。。