フリーランチ食べたい

No Free Lunch in ML and Life. Pythonや機械学習のことを書きます。

CloudFormationのOutputsを活用してServerlessプロジェクト内の情報を外部と共有する

Serverless内で使っている環境変数を外部と共有する方法を紹介します。Serverlessでは設定ファイル内でCloudFormationを書くことができ、そこで作成したS3などのResourceをServerlessプロジェクトから使うことができます。この機能はとても便利なのですが、作りたいアプリケーションが、常にServerlessだけで完結できるとは限らず、ときにはServerlessプロジェクト外のスクリプトから、Resourceの値を参照したい場合があります。

例えば、以下の記事ではSageMakerの学習にあたってServerlessプロジェクト外の環境からSageMakerのAPIを呼び出しています。具体的には、「ローカルスクリプトで学習」「Serveless(Lambda)から予測」という感じです。

blog.ikedaosushi.com

こんなときに、どうやってServerlessプロジェクト外のスクリプトからServerless内のResourceを参照すれば良いか、について書きたいと思います。

問題設定

まず、「解決したいのがどんな問題なのか」を明確にしたいと思います。 想定するのは、下記のようにServerlessを使って作成したS3 Bucketに、ローカルのPythonスクリプトでファイルをアップロードするような処理です。

f:id:ikedaosushi:20190505192345p:plain

このとき、ローカルのPythonスクリプトはS3 Bucketの名前を知らないと、当然ですがファイルをアップロードすることが出来ません。 このS3 Bucketの名前のようなServerlessで作成したResource情報にローカルのPythonスクリプトからアクセスしたいと思います。

サンプルとして下記のserverless.ymlを作成してデプロイしてみます。

serverless.yml

service: sharing-env

custom:
  default_stage: dev
  stage: ${opt:stage, self:custom.default_stage}
  s3_bucket: ${self:service}-${self:custom.stage}

provider:
  name: aws
  runtime: python3.7
  stage: ${self:custom.stage}
  region: ${self:custom.region}

functions:
  hello:
    handler: handler.hello

resources:
  Resources:
    S3Bucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:custom.s3_bucket}
        AccessControl: Private
sls deploy

デプロイが成功するとS3 Bucketが作成されます。

f:id:ikedaosushi:20190505185509p:plain

このS3 Bucketの名前(sharing-dev)をローカルのPythonスクリプトから取得するのがゴールです。

CloudFormationにアクセスする

このS3 Bucketの名前を取得するために、boto3を使ってCloudFormationのStackにアクセスします。Serverlessは「 service に設定した名前+stage」 を名前としてStackを作成するので、そのStackの名前を指定し、ローカルのPythonからアクセスします。アクセスにあたってCloudFormationのOutputsを使うのが良いでしょう。

f:id:ikedaosushi:20190505192245p:plain

それでは先程作成したサンプルを使って実証してみます。まず、先ほどのserverless.ymlにOutputsを追加します。

serverless.yml

resources:
  Resources:
    S3Bucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:custom.s3_bucket}
        AccessControl: Private
  Outputs: # この行以降を追加
    S3Bucket: # <= この名前がKeyになる
      Value: ${self:custom.s3_bucket}

これでデプロイするとCloudFormationのOutputsにS3 Bucketの名前が出力されます。

f:id:ikedaosushi:20190505185936p:plain

このOutputsにアクセスします。boto3を使って下記のように取得することができます。

upload.py

import os 
import boto3

# CloudFormationから環境変数を読み出し
## CFのStack設定
SERVICE_NAME = "sharing-env"
ENV = os.environ.get("ENV", "dev")
STACK_NAME = f"{SERVICE_NAME}-{ENV}"

## Outputsを{Key: Valueの形で読み出し}
stack = boto3.resource('cloudformation').Stack(STACK_NAME)
outputs = {o["OutputKey"]: o["OutputValue"] for o in stack.outputs}

S3_BUCKET = outputs["S3Bucket"]
print(S3_BUCKET)
# => sharing-env-dev (Bucket名)

# ...アップロード処理(略)

Serverlessで作成したResourceにアクセスすることができました。 今回は単純にS3 Bucketの名前を共有しましたが、 GetAttRef などのCloudFormationの関数を使ってARNなどの値を共有することも可能です。

さいごに

Serverlessプロジェクト内の情報を外部と共有する方法を紹介しました。 出来てしまうと簡単なことなのですが、意外と同じ内容を記載している記事が見当たらなかったかつ、よくありそうなユースケースなのでまとめました。 使ったコードは↓のリポジトリに置いてあります。

github.com