はじめに

前の記事の続きです。CloudFront ディストリビューションの Promote (昇格) を Step Functions で構成する方法を紹介します。

なお今回も、記事の煩雑さを軽減する目的で以下のように省略しています。

  • プライマリディストリビューション -> プライマリ
  • ステージングディストリビューション -> ステージング

フロー

やはり基本的に Lambda 不要、設定のみで実現できました。前回と違うのはステータス遷移待ちのループを多く含む点です。

ステート名 API 説明
Get staging distribution id Systems Manager: GetParameter ステージングの ID を取得
Check primary distribution status CloudFront: GetDistribution プライマリのステータスが Deployed になっているか確認
なっていない場合はなるまで待つ
Check staging distribution status CloudFront: GetDistribution ステージングのステータスが Deployed になっているか確認
なっていない場合はなるまで待つ
Get primary distribution CloudFront: GetDistribution プライマリから ETag など必要な情報を取得
Get primary distribution CloudFront: GetDistribution ステージングから ETag など必要な情報を取得
Promote CloudFront: UpdateDistributionWithStagingConfig ステージングの設定をプライマリに上書きすることでリリースする (昇格)
Create primary distribution invalidation CloudFront: CreateInvalidation 事後処理としてキャッシュ無効化する
Check invalidation status CloudFront: GetInvalidation キャッシュ無効化の完了を確認
完了していない場合は完了するまで待つ

初期パラメータ

ステートマシンに渡す初期パラメータは以下を想定しています。

{
  "ParameterKeyStagingDistributionId": "<ステージングの ID 情報が入った SSM パラメータの名前>",
  "PrimaryDistributionId": "<プライマリの ID>"
}

詳細

ループ処理

今回のループは基本的に以下の構造を持っています。ステータス遷移待ちの基本的な構造です。

  1. GetXXX 系の API でステータスを取得
  2. Choice を使って取得したステータスが完了ステータスかどうかを判定し分岐させる
  3. 完了ステータスだったらループを抜ける
  4. 完了ステータス以外だったら Wait 入れて最初に戻る

条件はこんな感じで設定しています。

補足

今回はプライマリのデプロイ待ちループの次にステージングに対しても同じループを回しています。これは以下のようにパラレルで処理することもできます。

しかし、以下の理由で採用していません。

  • パラレルで実行した場合は各処理の結果が配列に格納されるため、その後のステートで配列の要素を指定する手間が発生し面倒
  • 待機処理を並列で行う必然性がない

全体像

ASL はこんな感じになりました。

{
  "Comment": "Promote",
  "StartAt": "Get staging distribution id",
  "States": {
    "Get staging distribution id": {
      "Type": "Task",
      "Next": "Check primary distribution status",
      "Parameters": {
        "Name.$": "$.ParameterKeyStagingDistributionId"
      },
      "Resource": "arn:aws:states:::aws-sdk:ssm:getParameter",
      "ResultSelector": {
        "Id.$": "$.Parameter.Value"
      },
      "ResultPath": "$.StagingDistribution"
    },
    "Promote": {
      "Type": "Task",
      "Parameters": {
        "Id.$": "$.PrimaryDistribution.Id",
        "StagingDistributionId.$": "$.StagingDistribution.Id",
        "IfMatch.$": "States.Format('{},{}', $.PrimaryDistribution.ETag, $.StagingDistribution.ETag)"
      },
      "Resource": "arn:aws:states:::aws-sdk:cloudfront:updateDistributionWithStagingConfig",
      "Next": "Create primary distribution invalidation",
      "ResultSelector": {
        "Id.$": "$.Distribution.Id"
      },
      "ResultPath": "$.PromotedDistribution"
    },
    "Create primary distribution invalidation": {
      "Type": "Task",
      "Next": "Check invalidation status",
      "Parameters": {
        "DistributionId.$": "$.PrimaryDistribution.Id",
        "InvalidationBatch": {
          "CallerReference.$": "$.PrimaryDistribution.DistributionConfig.CallerReference",
          "Paths": {
            "Quantity": 1,
            "Items": ["/*"]
          }
        }
      },
      "Resource": "arn:aws:states:::aws-sdk:cloudfront:createInvalidation",
      "ResultSelector": {
        "Id.$": "$.Invalidation.Id",
        "Status.$": "$.Invalidation.Status"
      },
      "ResultPath": "$.Invalidation"
    },
    "Check invalidation status": {
      "Type": "Task",
      "Next": "Choice for invalidation",
      "Parameters": {
        "DistributionId.$": "$.PrimaryDistribution.Id",
        "Id.$": "$.Invalidation.Id"
      },
      "Resource": "arn:aws:states:::aws-sdk:cloudfront:getInvalidation",
      "ResultSelector": {
        "Id.$": "$.Invalidation.Id",
        "Status.$": "$.Invalidation.Status"
      },
      "ResultPath": "$.Invalidation"
    },
    "Choice for invalidation": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.Invalidation.Status",
          "StringEquals": "Completed",
          "Next": "Success"
        }
      ],
      "Default": "Wait completing invalidation"
    },
    "Wait completing invalidation": {
      "Type": "Wait",
      "Seconds": 3,
      "Next": "Check invalidation status"
    },
    "Check primary distribution status": {
      "Type": "Task",
      "Next": "Choice for primary distribution",
      "Parameters": {
        "Id.$": "$.PrimaryDistributionId"
      },
      "Resource": "arn:aws:states:::aws-sdk:cloudfront:getDistribution",
      "ResultSelector": {
        "Status.$": "$.Distribution.Status"
      },
      "ResultPath": "$.PrimaryDistribution"
    },
    "Check staging distribution status": {
      "Type": "Task",
      "Next": "Choice for staging distribution",
      "Parameters": {
        "Id.$": "$.StagingDistribution.Id"
      },
      "Resource": "arn:aws:states:::aws-sdk:cloudfront:getDistribution",
      "ResultSelector": {
        "Id.$": "$.Distribution.Id",
        "Status.$": "$.Distribution.Status"
      },
      "ResultPath": "$.StagingDistribution"
    },
    "Choice for primary distribution": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.PrimaryDistribution.Status",
          "StringEquals": "Deployed",
          "Next": "Check staging distribution status"
        }
      ],
      "Default": "Wait deploying primary distribution"
    },
    "Choice for staging distribution": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.StagingDistribution.Status",
          "StringEquals": "Deployed",
          "Next": "Get primary distribution"
        }
      ],
      "Default": "Wait deploying staging distribution"
    },
    "Get primary distribution": {
      "Type": "Task",
      "Next": "Get staging distribution",
      "Parameters": {
        "Id.$": "$.PrimaryDistributionId"
      },
      "Resource": "arn:aws:states:::aws-sdk:cloudfront:getDistribution",
      "ResultSelector": {
        "Id.$": "$.Distribution.Id",
        "DistributionConfig.$": "$.Distribution.DistributionConfig",
        "ETag.$": "$.ETag"
      },
      "ResultPath": "$.PrimaryDistribution"
    },
    "Get staging distribution": {
      "Type": "Task",
      "Next": "Promote",
      "Parameters": {
        "Id.$": "$.StagingDistribution.Id"
      },
      "Resource": "arn:aws:states:::aws-sdk:cloudfront:getDistribution",
      "ResultSelector": {
        "Id.$": "$.Distribution.Id",
        "DistributionConfig.$": "$.Distribution.DistributionConfig",
        "ETag.$": "$.ETag"
      },
      "ResultPath": "$.StagingDistribution"
    },
    "Wait deploying primary distribution": {
      "Type": "Wait",
      "Seconds": 3,
      "Next": "Check primary distribution status"
    },
    "Wait deploying staging distribution": {
      "Type": "Wait",
      "Seconds": 3,
      "Next": "Check staging distribution status"
    },
    "Success": {
      "Type": "Succeed"
    }
  }
}

おわりに

Promote 処理に関しても、煩雑なスクリプトを書き下すまでもなく JSON の定義ファイルに置き換えることができました。今回の検証で Step Functions の苦手意識が消えました。いちど慣れてしまえば著しく省力化できることがわかったので、これからもどんどん利用していきたいと思います。

次回は CDK と絡めながらこの処理を実際のパイプラインに組み込んでみたいと思います。