【小ネタ】AWS SAMを継続的デリバリする際に便利なオプションのご紹介

はじめに

こんにちは、中山です。

現在私が関わっている仕事ではAWS SAMを使ってサーバーレスアプリケーションで構成管理をしています。構成管理を整えたら当然CD(継続的デリバリ)も回したい要求が出てくるので、CircleCIを使って全体のフローを管理しています。詳細については以前のエントリを参照してください。

AWS SAM/CircleCI/LocalStackを利用した実践的なCI/CD – ClassmethodサーバーレスAdvent Calendar 2017 #serverless #adventcalendar #reinvent

AWS SAMをCDする際ちょっとした問題が発生していました。今回はこの問題点とその解消方法をご紹介したいと思います。

検証環境

  • AWS CLI: 1.14.51
  • Python: 3.6.4

何が問題だったのか

まず大前提の整理を。

AWS SAMをCDするためには基本的にAWS CLIを使います。主に利用するコマンドは以下2つです。

  • aws cloudformation package
    • 主に CodeUri プロパティで指定されたLambda関数のコードをS3バケットにアップロードし、AWS SAMのテンプレートをデプロイ可能な形式に変換する
  • aws cloudformation deploy
    • 上記コマンドで生成されたテンプレートからスタックを作成/更新する

aws cloudformation deploy コマンドは最初にChange Setsを生成してから、それを実行するという流れでスタックを作成/更新させます。デフォルトの動作はChange Setsの実行まで行います。 --no-execute-changeset オプションを指定することにより実行まではさせないことも可能です。

CDする際にこのChange Setsの扱いに難がありました。具体的にいうと、 Change Setsが存在しない状態で aws cloudformation deploy コマンドを実行すると終了ステータスコードが0以外になってしまう という問題がありました。

全てのCI/CDサービスを検証したわけではないので、私が主に利用しているCircleCIを前提に説明します。ただ、どのサービスも基本的に同じような判定になっているので結果は大体同じかと思います。

CircleCIの場合、任意のコマンドを実行するにはrunまたはdeployステップを利用します。先程ご紹介したようなデプロイ系のコマンドであれば基本 deploy ステップです。例えば、以下のような設定を .circleci/config.yml に指定するとAWS SAMのCDが可能になります。

      - deploy:
          name: Deploy AWS SAM
          command: |
            set -x
            
            aws cloudformation package \
              --template-file sam.yml \
              --s3-bucket <_YOUR_S3_BUCKET_NAME_HERE_> \
              --output-template-file packaged.yml
              
            aws cloudformation deploy \
              --stack-name <_YOUR_STACK_NAME_HERE_> \
              --template-file packaged.yml \
              --capabilities CAPABILITY_IAM

これらのステップ内に記載されているコマンドはデフォルトで /bin/bash -eo pipefail シェルで実行されるため、終了ステータスコードが0以外の場合、それは失敗したと判断されてしまいます。ドキュメントから引用します。

-e:

Exit immediately if a pipeline (which may consist of a single simple command), a subshell command enclosed in parentheses, or one of the commands executed as part of a command list enclosed by braces exits with a non-zero status.

つまり、Change Setsが存在しない状態で aws cloudformation deploy を実行すると deploy ステップが失敗したと判断されてしまうということです。例えば、サーバーレスアプリケーションを管理しているリポジトリのREADMEだけ更新したい場合などでこの問題が発生します。リリースのタイミングによっても問題の発生率が変わりますが、今私が関わっている仕事ではGitHubフローで運用しているため master ブランチにマージされるとデプロイが走ります。こういった状態で deploy ステップが実行されると、真っ赤な画面(失敗を通知してくれる画面)が表示されてしまいます。

もちろんドキュメントに記載されているように、ワークアラウンドとして set +e を付ける選択肢もあるのですが、これでは別の原因でコマンドが失敗した場合にそれを検知できない可能性があります。デプロイという重要なコマンドを実行する際には付けるべきではないと考えています。

話が長くなってしまいました、続いてこの問題に対する解消方法をご紹介します。

どうやって問題点を解決したのか

aws cloudformation deploy--no-fail-on-empty-changeset オプションを使いましょう。

名前から推測できるように、このオプションはChange Setsが存在しない場合も終了ステータスコードを0で返すためのものです。デフォルトは --fail-on-empty-changeset 、つまりChange Setsが存在しない場合では終了ステータスコードが0以外で返ってきます。ドキュメントから引用します。

--fail-on-empty-changeset | --no-fail-on-empty-changeset (boolean) Specify if the CLI should return a non-zero exit code if there are no changes to be made to the stack. The default behavior is to return a non-zero exit code.

実際に使ってみましょう。

最初に適当なAWS SAMテンプレートを用意。これを複数回デプロイさせて動作を確認してみます。

---
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: Test

Resources:
  TestFunc:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/handlers/test
      Handler: index.handler
      Runtime: python3.6

まず以下のコマンドを実行。テスト用のスタックを作成しておきます。

# パッケージコマンドの実行
$ aws cloudformation package \
  --template-file sam.yml \
  --s3-bucket <_YOUR_S3_BUCKET_NAME_HERE_> \
  --output-template-file packaged.yml
Uploading to 7fe559078e511ec5f20ef8cc7f0a4078  161 / 161.0  (100.00%)
Successfully packaged artifacts and wrote output template to file packaged.yml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /path/to/packaged.yml --stack-name <YOUR STACK NAME>
# デプロイ
$ aws cloudformation deploy \
  --stack-name test \
  --template-file packaged.yml \
  --capabilities CAPABILITY_IAM

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - test

スタックが作成されました。この状態、つまりChange Setsが存在しない状態で再度デプロイさせてみます。

$ aws cloudformation deploy \
  --stack-name test \
  --template-file packaged.yml \
  --capabilities CAPABILITY_IAM

Waiting for changeset to be created..

No changes to deploy. Stack test is up to date
$ echo $?
255

終了ステータスコードが255で返ってきますね。続いて --no-fail-on-empty-changeset オプションを指定して再度同じコマンドを実行してみます。

$ aws cloudformation deploy \
  --stack-name test \
  --template-file packaged.yml \
  --capabilities CAPABILITY_IAM \
  --no-fail-on-empty-changeset

Waiting for changeset to be created..

No changes to deploy. Stack test is up to date
$ echo $?
0

ちゃんと終了ステータスコードが0で返ってきます。やりましたね。

まとめ

いかがだったでしょうか。

AWS SAMで管理しているサーバーレスアプリケーションをCDする際に便利なオプションをご紹介しました。こういった「痒いところ」もきっちりサポートしてくれるAWS CLIはやっぱり便利だなと再認識した所存です。

ちなみに今回ご紹介したオプションはCHANGELOGには載ってない?ように見えますが、AWS CLIの1.14.28から導入されていたようです。更にいうと、1.14.51以前のバージョンではバグが存在してたようですがこちらのPRで解消されました。最高ですね。

本エントリがみなさんの参考になれば幸いに思います。