Serverless Framework で resources に定義したリソースを環境変数で参照しようとした時に、エラーが発生した場合の対処方法

Serverless Framework で resources に定義したリソースを環境変数で参照できない時の対処方法の一つ
2022.07.06

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは、サービスグロースチームの筧です。

Serverless Framework には resources というプロパティがあります。 このプロパティを使用すると、serverless deploy した際に、SNS トピック や S3 バケット などのリソースを作成することができます。

Serverless Framework - AWS Infrastructure Resources

If you are using AWS as a provider for your Service, all are other AWS infrastructure resources which the AWS Lambda functions in your depend on, like AWS DynamoDB or AWS S3. Using the Serverless Framework, you can define the infrastructure resources you need in serverless.yml, and easily deploy them.

では resources プロパティ経由で作成したリソースの情報を、 同じ Serverless Framework で定義している AWS Lambda の環境変数として参照するには、どうしたらよいでしょうか。 一つの方法として、cf:stackName.outputKey というシンタックスを利用する方法があります。

Serverless Framework Variables

Variables allow users to dynamically replace config values in serverless.yml config. They are especially useful when providing secrets for your service to use and when you are working with multiple stages. To use variables, you will need to reference values enclosed in ${} brackets.

このシンタックスを利用した方法を試したところ、初回デプロイ時以外は問題なく動作しました。 ただ初回デプロイ時のみ、Value not found at "cf" sourceというエラーメッセージが表示されてデプロイ失敗になってしまいました。 原因調査したところ、以下の Issue を発見しました。

Default values for cf references · Issue #4940 · serverless/serverless

This is a (Bug Report / Feature Proposal) Using cf references it's not possible to provide default values. Example: ${cf:non-existing-stack.foo, 'bar'} Description For bug reports: What went wrong? Default value was not picked. Instead o...

どうやら、cf:stackName.outputKey シンタックスを利用する場合、初回デプロイ時のみ、依存関係サイクルの問題が発生してしまうようです。 そして Issue の中で紹介されていた方法の一つで、問題解消することができたので、その内容について本ブログでご紹介します。

前提

筆者の環境

ターミナル

% sw_vers
ProductName:    macOS
ProductVersion: 12.2.1
BuildVersion:   21D62
% sls --version
Framework Core: 3.19.0 (local) 3.19.0 (global)
Plugin: 6.2.2
SDK: 4.3.2
% pipenv --version
pipenv, version 2022.6.7

resources プロパティで SNS トピックを定義して、そのARNを環境変数で参照することをゴールとします

やってみた

失敗パターン: cf:stackName.outputKey シンタックスを利用して初回デプロイした場合

cf:stackName.outputKey シンタックスを利用して、resources の Outputs から SNS トピックの ARN を参照します。

serverless.yml

service: hello
frameworkVersion: '3'
provider:
  name: aws
  runtime: python3.9
  lambdaHashingVersion: 20201221
  stage: ${opt:stage, 'dev'}
  region: ${opt:region, "ap-northeast-1"}

:
snip
:

  environment: ${self:custom.environment.${self:provider.stage}}
functions:
  hello:
    handler: src/handlers/hello.handler

:
snip
:

custom:
  environment:
    dev:
      SNS_TOPIC_ARN: ${cf:${self:service}-${self:provider.stage}.SnsTopicArn}

:
snip
:

resources:
  Resources:
    HelloTopic:
      Type: "AWS::SNS::Topic"
      Properties:
        DisplayName: "HelloTopic-${self:provider.stage}-${self:provider.region}"
        TopicName: "HelloTopic-${self:provider.stage}-${self:provider.region}"
  Outputs:
    SnsTopicArn:
      Value: !Ref HelloTopic
      Export:
        Name: SnsTopicArn

このような書き方だと、初回デプロイ時のみ、Value not found at "cf" sourceというエラーメッセージが表示されてデプロイ失敗します。

ターミナル

% aws-vault exec personal -- sls deploy --stage dev
Running "serverless" from node_modules

Error:
Cannot resolve serverless.yml: Variables resolution errored with:
  - Cannot resolve variable at "custom.environment.dev.SNS_TOPIC_ARN": Value not found at "cf" source

成功パターン: 環境変数で Ref を利用して初回デプロイした場合

Ref を利用して、resources の Resources から SNS トピックの ARN を参照します。

serverless.yml

service: hello
frameworkVersion: '3'
provider:
  name: aws
  runtime: python3.9
  lambdaHashingVersion: 20201221
  stage: ${opt:stage, 'dev'}
  region: ${opt:region, "ap-northeast-1"}

:
snip
:

  environment: ${self:custom.environment.${self:provider.stage}}
functions:
  hello:
    handler: src/handlers/hello.handler

:
snip
:

custom:
  environment:
    dev:
      SNS_TOPIC_ARN:
        Ref: HelloTopic

:
snip
:

resources:
  Resources:
    HelloTopic:
      Type: "AWS::SNS::Topic"
      Properties:
        DisplayName: "HelloTopic-${self:provider.stage}-${self:provider.region}"
        TopicName: "HelloTopic-${self:provider.stage}-${self:provider.region}"

初回デプロイ時でも、問題なくデプロイできます。

ターミナル

% aws-vault exec personal -- sls deploy --stage dev
Running "serverless" from node_modules

Deploying hello to stage dev (ap-northeast-1)

✔ Service deployed to stack hello-dev (160s)

hello.py の handler 関数です。

src/handlers/hello.handler

import logging
import os

logger = logging.getLogger()
logger.setLevel(logging.INFO)

SNS_TOPIC_ARN = os.environ["SNS_TOPIC_ARN"]


def handler(event, context):
    logger.info(SNS_TOPIC_ARN)
    return

hello.py の handler 関数を実行すると、resourcesで定義した SNS トピックの ARN が参照できていることを確認できました!

% aws-vault exec personal -- sls invoke local -f hello --stage dev
Running "serverless" from node_modules
INFO:root:arn:aws:sns:ap-northeast-1:123456789012:HelloTopic-dev-ap-northeast-1

null

おわりに

最後まで読んでいただきありがとうございます。

本ブログが少しでも皆さんの役立っていると幸いです。 それではまた!