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

環境変数を持つLambdaをCloudFormationで管理する

More than 1 year has passed since last update.

はじめに

AWSリソースをCloudFormationで管理すると便利というのは分かってはいるけど、LambdaのソースをCloudFormationで管理するのは色々ハードルがあってなかなか手が出せないという自分のような層向けに、まずは最小限のところからやってみようという意識低めの記事です。
まずはこのあたりから始めつつ、より良いベストプラクティスなどは勉強しながら成長させていきたい。

今回やりたいこと

  • LambdaのソースコードをCloudFormationテンプレートから分離する
  • Lambda環境変数をソースリポジトリから追い出す

今回触れないこと

  • CodeDeployなどのCIとの統合
  • SAMのお話

検証したバージョン

$ aws --version
aws-cli/1.16.28 Python/2.7.15 Darwin/18.6.0 botocore/1.12.18

Lambdaリソースを作成するCloudFormationテンプレート

ディレクトリ構造

今回は以下のような構造のNodeJS関数について考えます。

/project-root
 |- hello-http  #<= lambdaソースディレクトリ
     |- node_modules
     |- index.js
     |- package.json
     |- package-lock.json
 |- template.yml #<= CloudFormationテンプレートファイル
 |- settings.conf #<= パラメータ変数定義
 |- package.sh #<= packageコマンドスクリプト
 |- deploy.sh #<= deployコマンドスクリプト

パラメータ

CloudFormationでは以下のようにパラメータ定義することでaws cloudformation deployコマンド実行時にパラメータを渡すことができます。

AWSTemplateFormatVersion: 2010-09-09
Description: Lambda CloudFormation Template
Parameters:
  BaseStackName:
    Type: String
    Description: Base Stack Name
  ApiUrl:
    Type: String
    Description: API Url
    Default: https://api.example.net/api
  ApiToken:
    Type: String
    Description: API Token
    NoEcho: true

CloudFormationスタック内であまり表示したくないパラメータについては、NoEcho: trueを設定しておくとパスワード扱いとなり、CloudFormationスタック上では非表示になります。
Lambda関数に環境変数として渡した場合はLambda関数上では見れてしまうので、今回のケースに関しては気休め程度という気はします。
Lambda関数内でよりセキュアにトークンなどを扱うにはやはりSecretsManagerあたりを使うのがよいのでしょうかね。
このあたりはまだ勉強中です。

ベースとなるLambdaリソース

Resources:
  HttpCallFunction: 
    Type: 'AWS::Lambda::Function' 
    Properties: 
      Code: hello-http 
      Description: Lambda Function
      FunctionName: !Sub hello-http-${BaseStackName}
      Role: 
        Fn::GetAtt: [ LambdaExecutionRole, Arn ] 
      Runtime: nodejs8.10 
      Timeout: 25 
      Handler: index.handler 

ポイントとなるのは [Properties]の[Code]。
ローカルの相対パスディレクトリを指定しておきます。
この状態で aws cloudformation packageコマンドを実行すると、

  • ローカルファイルを固めてS3にアップロード
  • CloudFormationのテンプレートファイル内のLambdaのコードをS3パスに変更

してくれます。

他には、!Sub hello-http-${BaseStackName}の部分はCloudFormationパラメータで指定して、デプロイ環境ごとにLambda関数を別々にデプロイすることを想定しています。

      Role: 
        Fn::GetAtt: [ LambdaExecutionRole, Arn ]

の部分は、別途作成するLambdaの実行ロールのARNを参照します。

packageコマンド

aws cloudformation package --s3-bucket $BUCKET --s3-prefix $PREFIX --template-file template.yml --output-template-file template-packaged.yml
パラメータ 内容
--s3-bucket ソースをアップロードするバケット名
--s3-prefix ソースをアップロードするプレフィックス(任意)
--template-file 入力元となるテンプレートファイル
--output-template-file 変換後のテンプレート出力先ファイル

変換後のパラメータ

  Properties:
    Code:
      S3Bucket: $BUCKET
      S3Key: $PREFIX/0123456ea0dab860708bb7c79c676278

実行ロールの設定

Lambdaファンクションの実行ロールも作成しておきます。

  LambdaExecutionRole: 
    Type: 'AWS::IAM::Role' 
    Properties: 
      AssumeRolePolicyDocument: 
        Version: 2012-10-17 
        Statement: 
          - 
            Effect: 'Allow' 
            Principal: 
              Service: 
                - 'lambda.amazonaws.com' 
            Action: 
              - 'sts:AssumeRole' 
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

最低限のロールとしてlambdaへの信頼関係の付与と、CloudWatchLogsへログを書き込むマネージドポリシーを設定しておきます。
他の権限が必要な場合はここに追加していきます。

環境変数のパラメータ化

CloudFormationパラメータをそのまま環境変数に渡すことができます。

    Properties: 
      ...
      Environment: 
        Variables:
          ApiUrl: !Ref ApiUrl
          ApiToken: !Ref ApiToken

これで、deploy時に渡したパラメータがLambdaの環境変数として設定されます。

deployコマンド

aws cloudformation deploy --template-file template-packaged.yml --stack-name hello-http-develop --capabilities CAPABILITY_NAMED_IAM CAPABILITY_IAM --parameter-overrides BaseStackName=develop ApiUrl=https://example.com/ ApiToken=xxxxx
パラメータ 内容
--template-file リソースを作成するテンプレートファイル
--stack-name CloudFormationスタック名
--parameter-overrides パラメータに渡す値。Key=Value形式でスペース区切りで複数指定可能。

package/deployシェルの作成

packageコマンド、deployコマンドはシェルスクリプトにしてしておくと便利です。
deployは引数に渡す環境変数を開発/本番環境ごとに分離しておくと事故も減らせて安心です。

packageスクリプト

#!/bin/sh
aws cloudformation package --s3-bucket $BUCKET --s3-prefix $PREFIX --template-file template.yml --output-template-file template-packaged.yml $OPTIONS

deployスクリプト

#!/bin/sh

CONF=$1
CONF=${CONF:=settings.conf}

source $CONF

aws cloudformation deploy --template-file template-packaged.yml --stack-name hello-http-$BaseStackName --capabilities CAPABILITY_NAMED_IAM CAPABILITY_IAM --parameter-overrides $PARAMS $OPTIONS

confファイル (settings.conf)

BaseStackName=develop
OPTIONS=""
PARAMS="BaseStackName=develop ApiVersion=1.0 ApiUrl=http://localhost ApiToken=xxx"

本番環境用にはsettings-production.confなどを用意し、

$ ./deploy.sh settings-production.conf

を実行すると、本番環境用トークンなどが環境変数に設定されたLambdaが新しくデプロイするようにしておきます。

今回検証したテンプレートファイル全体

AWSTemplateFormatVersion: 2010-09-09
Description: Lambda CloudFormation Template
Parameters:
  BaseStackName:
    Type: String
    Description: Base Stack Name
  ApiVersion:
    Type: String
    Description: API Version
    Default: v1
  ApiUrl:
    Type: String
    Description: API Url
    Default: https://api.example.net/api
  ApiToken:
    Type: String
    Description: API Token
    NoEcho: true
Resources:
  HttpCallFunction: 
    Type: 'AWS::Lambda::Function' 
    Properties: 
      Code: hello-http 
      Description: Lambda Function
      FunctionName: !Sub hello-http-${BaseStackName} 
      Role: 
        Fn::GetAtt: [ LambdaExecutionRole, Arn ] 
      Runtime: nodejs8.10 
      Timeout: 25 
      Handler: index.handler 
      Environment: 
        Variables: 
          ApiVersion: !Ref ApiVersion
          ApiUrl: !Ref ApiUrl
          ApiToken: !Ref ApiToken
  LambdaExecutionRole: 
    Type: 'AWS::IAM::Role' 
    Properties: 
      AssumeRolePolicyDocument: 
        Version: 2012-10-17 
        Statement: 
          - 
            Effect: 'Allow' 
            Principal: 
              Service: 
                - 'lambda.amazonaws.com' 
            Action: 
              - 'sts:AssumeRole' 
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

今回作ったコードサンプル全体はこちらにも置いてあります。

まとめ

ここまでできると、今まで手作業で作成していたLambdaの管理がCloudFormationで出来そうな気になってきました。
SAMとか関係ない細々したLambdaをどう管理するのかがよく分かっていなかったので、多少整理できた気がします。
sam packageで出来ることはおおよそaws cloudformation packageでも出来るようですね。

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