CloudFormation StackSetsで作成したグローバルリソースを同一Stackから参照する際は実行リージョンとリージョンの実行順序を意識しよう

グローバルリソースはどのリージョンで管理しているか意識しよう
2023.07.30

StackSetsでAWS Configの設定をばら撒きたいのにできないな

こんにちは、のんピ(@non____97)です。

皆さんは「CloudFormation StackSets(以降StackSets)でAWS Configの設定をばら撒きたいのにできないな」と思ったことはありますか? 私はあります。

2023年4月ごろまでは以下記事で紹介れているテンプレートを全リージョンに対してStackSetsでばら撒いても動作しました。

しかし、現在は同じテンプレートを同じ方法で実行しても「作ろうとしているService-linked Roleは既に存在しているため作成できない」と怒られるようになりました。

「それなら特定リージョンのみStack Instanceを作成するように設定すれば良いじゃん」と思われるかもしれません。最もです。

ですが、上述のAWS Configの有効化をする際に「1つのリージョンでService-linked Roleを作成」と「全リージョンでAWS Configの有効化」と2つのStackSetsとテンプレートを用意することになります。なるべくStackSetsをまとめたい場合は、どのように対応すれば良いのでしょうか。

そんな時は「一部リソースは特定リージョンのみ作成する」と「Stack Instanceのリージョンの実行順序の指定する」ことによって対応することが可能です。

実際に検証してみます。

いきなりまとめ

  • StackSetsのパラメーターのRegionOrderでリージョンの実行順序を指定可能
  • 全てのリージョンを指定する必要はない
  • RegionConcurrencyType=PARALLELと同時に指定することはできない

StackSetsの作成

まず、適当にService-linked Roleを作成するテンプレートをStackSetsで複数リージョンにばら撒いてみます。

使用するテンプレートは以下のとおりです。

service-linked-role.yml

AWSTemplateFormatVersion: "2010-09-09"

Resources:
  AWSServiceRoleForGlobalAccelerator:
    Type: "AWS::IAM::ServiceLinkedRole"
    Properties:
      AWSServiceName: globalaccelerator.amazonaws.com

まず、StackSetsを作成します。

$ aws cloudformation create-stack-set \
    --stack-set-name service-linked-role-globalaccelerator \
    --description "Service-linked Role Global Accelerator" \
    --template-body file://service-linked-role.yml \
    --permission-model SELF_MANAGED \
    --administration-role-arn arn:aws:iam::<AWSアカウントID>:role/AWSCloudFormationStackSetAdministrationRole \
    --execution-role-name AWSCloudFormationStackSetExecutionRole
{
    "StackSetId": "service-linked-role-globalaccelerator:f65d6fd6-b499-4590-a799-04f5c1578212"
}

次にStack Instanceを作成します。Stack Instanceは以下4リージョンを同時並行して作成するようにします。

  1. ap-northeast-1
  2. us-east-1
  3. us-east-2
  4. us-west-1
$ aws cloudformation create-stack-instances \
    --stack-set-name service-linked-role-globalaccelerator \
    --accounts $(aws sts get-caller-identity --query Account --output text) \
    --regions ap-northeast-1 us-east-1 us-east-2 us-west-1 \
    --operation-preferences RegionConcurrencyType=PARALLEL
{
    "OperationId": "591553fe-36ab-4d11-bfbc-964d08642f70"
}

作成したStackSetsのオペレーションの状態を確認します。

$ aws cloudformation describe-stack-set-operation \
    --stack-set-name service-linked-role-globalaccelerator \
    --operation-id 591553fe-36ab-4d11-bfbc-964d08642f70
{
    "StackSetOperation": {
        "OperationId": "591553fe-36ab-4d11-bfbc-964d08642f70",
        "StackSetId": "service-linked-role-globalaccelerator:f65d6fd6-b499-4590-a799-04f5c1578212",
        "Action": "CREATE",
        "Status": "FAILED",
        "OperationPreferences": {
            "RegionConcurrencyType": "PARALLEL",
            "RegionOrder": []
        },
        "AdministrationRoleARN": "arn:aws:iam::<AWSアカウントID>:role/AWSCloudFormationStackSetAdministrationRole",
        "ExecutionRoleName": "AWSCloudFormationStackSetExecutionRole",
        "CreationTimestamp": "2023-07-29T02:43:59.153000+00:00",
        "EndTimestamp": "2023-07-29T02:46:47.637000+00:00",
        "StatusDetails": {
            "FailedStackInstancesCount": 3
        }
    }
}

失敗していますね。

各Stack Instanceの状態を確認します。

aws cloudformation list-stack-instances \
    --stack-set-name service-linked-role-globalaccelerator
{
    "Summaries": [
        {
            "StackSetId": "service-linked-role-globalaccelerator:f65d6fd6-b499-4590-a799-04f5c1578212",
            "Region": "ap-northeast-1",
            "Account": "<AWSアカウントID>",
            "StackId": "arn:aws:cloudformation:ap-northeast-1:<AWSアカウントID>:stack/StackSet-service-linked-role-globalaccelerator-40bfa296-e857-41b9-a379-211d3237286a/c783fe20-2db9-11ee-a7bc-0649a590e823",
            "Status": "OUTDATED",
            "StatusReason": "ResourceLogicalId:AWSServiceRoleForGlobalAccelerator, ResourceType:AWS::IAM::ServiceLinkedRole, ResourceStatusReason:Resource handler returned message: \"Service role name AWSServiceRoleForGlobalAccelerator has been taken in this account, please try a different suffix. (Service: Iam, Status Code: 400, Request ID: 03b4fb80-fe39-49f0-9bc2-4226ff03ff3e)\" (RequestToken: cf679c29-382a-f92e-0cc3-250c3c38654a, HandlerErrorCode: AlreadyExists).",
            "StackInstanceStatus": {
                "DetailedStatus": "FAILED"
            },
            "OrganizationalUnitId": "",
            "DriftStatus": "NOT_CHECKED",
            "LastOperationId": "591553fe-36ab-4d11-bfbc-964d08642f70"
        },
        {
            "StackSetId": "service-linked-role-globalaccelerator:f65d6fd6-b499-4590-a799-04f5c1578212",
            "Region": "us-east-1",
            "Account": "<AWSアカウントID>",
            "StackId": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/StackSet-service-linked-role-globalaccelerator-ed2eb2ef-effb-417d-b358-54462de2ba87/c78b0300-2db9-11ee-821f-1223dd1d788d",
            "Status": "OUTDATED",
            "StatusReason": "ResourceLogicalId:AWSServiceRoleForGlobalAccelerator, ResourceType:AWS::IAM::ServiceLink
edRole, ResourceStatusReason:Resource handler returned message: \"Service role name AWSServiceRoleForGlobalAccelerato
r has been taken in this account, please try a different suffix. (Service: Iam, Status Code: 400, Request ID: fd9bef1
c-c8c2-4eeb-bc1f-a663b6f89a50)\" (RequestToken: 61ae0347-425f-c8ae-e71a-ffb6ab4336c9, HandlerErrorCode: AlreadyExists
).",
            "StackInstanceStatus": {
                "DetailedStatus": "FAILED"
            },
            "OrganizationalUnitId": "",
            "DriftStatus": "NOT_CHECKED",
            "LastOperationId": "591553fe-36ab-4d11-bfbc-964d08642f70"
        },
        {
            "StackSetId": "service-linked-role-globalaccelerator:f65d6fd6-b499-4590-a799-04f5c1578212",
            "Region": "us-east-2",
            "Account": "<AWSアカウントID>",
            "StackId": "arn:aws:cloudformation:us-east-2:<AWSアカウントID>:stack/StackSet-service-linked-role-globalaccele
rator-be908822-2bfd-480e-9333-0d9271724fb7/c7a9ae90-2db9-11ee-83c6-06265f1183a3",
            "Status": "CURRENT",
            "StackInstanceStatus": {
                "DetailedStatus": "SUCCEEDED"
            },
            "OrganizationalUnitId": "",
            "DriftStatus": "NOT_CHECKED",
            "LastOperationId": "591553fe-36ab-4d11-bfbc-964d08642f70"
        },
        {
            "StackSetId": "service-linked-role-globalaccelerator:f65d6fd6-b499-4590-a799-04f5c1578212",
            "Region": "us-west-1",
            "Account": "<AWSアカウントID>",
            "StackId": "arn:aws:cloudformation:us-west-1:<AWSアカウントID>:stack/StackSet-service-linked-role-globalaccele
rator-62619545-cf53-4795-8c91-7474776ec0c3/c7b40ed0-2db9-11ee-8410-06eda47e7443",
            "Status": "OUTDATED",
            "StatusReason": "ResourceLogicalId:AWSServiceRoleForGlobalAccelerator, ResourceType:AWS::IAM::ServiceLink
edRole, ResourceStatusReason:Resource handler returned message: \"Service role name AWSServiceRoleForGlobalAccelerato
r has been taken in this account, please try a different suffix. (Service: Iam, Status Code: 400, Request ID: a6dbfc2
d-9cc4-4ed6-b7b7-5de84a441130)\" (RequestToken: 07e59d04-d077-121a-dfaf-dd6801283f37, HandlerErrorCode: AlreadyExists
).",
            "StackInstanceStatus": {
                "DetailedStatus": "FAILED"
            },
            "OrganizationalUnitId": "",
            "DriftStatus": "NOT_CHECKED",
            "LastOperationId": "591553fe-36ab-4d11-bfbc-964d08642f70"
        }
    ]
}

us-east-2のみ成功していますね。

us-east-2以外は「作ろうとしている AWSServiceRoleForGlobalAccelerator は既にこのアカウントにあるぞ。違うサフィックスを付けてトライしろ。」とエラーになっています。

マネジメントコンソールから見ると以下のとおりです。

StackSetsのStack Instanceのステータス確認

Stack Instanceの更新順番を指定して更新する

このような時はどのように対応したら良いのでしょうか。

まず、考えられるのは「CloudFormationのConditionを使って、現在の特定のリージョンのみ作成する」ことです。

例えば、us-east-1のみService-linked Roleを作成する場合は以下のようなテンプレートになります。

service-linked-role.yml

AWSTemplateFormatVersion: "2010-09-09"

Conditions:
  IsControlPlaneRegion: !Equals [!Ref AWS::Region, us-east-1]

Resources:
  AWSServiceRoleForGlobalAccelerator:
    Condition: IsControlPlaneRegion
    Type: "AWS::IAM::ServiceLinkedRole"
    Properties:
      AWSServiceName: globalaccelerator.amazonaws.com

ただし、それだけだとStack Instance更新時に失敗すると予想します。

先ほど作成したService-linked RoleはStack Instanceのus-east-2で作成されました。このままStack Instanceの更新を行い、仮にus-east-1から走った場合、us-east-2で作成したService-linked Roleが既に存在するためまたエラーとなってしまいます。

また、Stack Instanceを新規に作成する場合も困ります。

例として挙げたテンプレートはService-linked Roleのみを作成するシンプルなものです。ただし、同じテンプレート内でこのService-linked Roleを使用する場合、us-east-1でService-linked Roleが作成されることを待ってからでなければ他のリージョンのStack Instanceは「Service-linked Roleがない」として失敗してしまいます。

その対応としてリージョンの実行順序RegionOrderを指定しましょう。Stack Instanceの作成時やStackSetsの更新時に指定できます。

今回の場合だとus-east-1でエラーなくService-linked Roleを作成するためには、us-east-2で既存のService-linked Roleを削除してから、us-east-1でService-linked Roleを作成する必要があります。

そのため、StackSets更新時にRegionOrder=us-east-2,us-east-1を指定してあげます。全てのリージョンを指定する必要はないようです。

$ aws cloudformation update-stack-set \
    --stack-set-name service-linked-role-globalaccelerator \
    --description "Service-linked Role Global Accelerator" \
    --template-body file://service-linked-role.yml \
    --operation-preferences RegionOrder=us-east-2,us-east-1
{
    "OperationId": "c932f341-f641-4ba6-9524-8286e48642ce"
}

ちなみに、以下のようにRegionOrderRegionConcurrencyType=PARALLELを同時に指定することはできません。

$ aws cloudformation update-stack-set \
    --stack-set-name service-linked-role-globalaccelerator \
    --description "Service-linked Role Global Accelerator" \
    --template-body file://service-linked-role.yml \
    --operation-preferences RegionConcurrencyType=PARALLEL,RegionOrder=us-east-2,us-east-1
An error occurred (ValidationError) when calling the UpdateStackSet operation: Cannot specify both RegionOrder and RegionConcurrencyType PARALLEL in operation preferences

RegionConcurrencyType=PARALLELは各リージョンのStack Instanceを並列で更新するパラメーターです。「リージョンの実行順序を指定した箇所は直列で、以降は並列で更新する」といったことはできないようです。

さて、StackSetsのオペレーションの状態を確認しましょう。

$ aws cloudformation describe-stack-set-operation \
    --stack-set-name service-linked-role-globalaccelerator \
    --operation-id c932f341-f641-4ba6-9524-8286e48642ce
{
    "StackSetOperation": {
        "OperationId": "c932f341-f641-4ba6-9524-8286e48642ce",
        "StackSetId": "service-linked-role-globalaccelerator:f65d6fd6-b499-4590-a799-04f5c1578212",
        "Action": "UPDATE",
        "Status": "SUCCEEDED",
        "OperationPreferences": {
            "RegionOrder": [
                "us-east-2",
                "us-east-1"
            ]
        },
        "AdministrationRoleARN": "arn:aws:iam::<AWSアカウントID>:role/AWSCloudFormationStackSetAdministrationRole",
        "ExecutionRoleName": "AWSCloudFormationStackSetExecutionRole",
        "CreationTimestamp": "2023-07-29T02:56:42.054000+00:00",
        "EndTimestamp": "2023-07-29T02:57:14.538000+00:00",
        "StatusDetails": {
            "FailedStackInstancesCount": 0
        }
    }
}

更新に成功していますね。

各Stack Instanceの状態も確認します。

aws cloudformation list-stack-instances \
    --stack-set-name service-linked-role-globalaccelerator
{
    "Summaries": [
        {
            "StackSetId": "service-linked-role-globalaccelerator:f65d6fd6-b499-4590-a799-04f5c1578212",
            "Region": "ap-northeast-1",
            "Account": "<AWSアカウントID>",
            "StackId": "arn:aws:cloudformation:ap-northeast-1:<AWSアカウントID>:stack/StackSet-service-linked-role-globalaccelerator-40bfa296-e857-41b9-a379-211d3237286a/9b103910-2dbb-11ee-938d-0aa510102155",
            "Status": "CURRENT",
            "StackInstanceStatus": {
                "DetailedStatus": "SUCCEEDED"
            },
            "OrganizationalUnitId": "",
            "DriftStatus": "NOT_CHECKED",
            "LastOperationId": "c932f341-f641-4ba6-9524-8286e48642ce"
        },
        {
            "StackSetId": "service-linked-role-globalaccelerator:f65d6fd6-b499-4590-a799-04f5c1578212",
            "Region": "us-east-1",
            "Account": "<AWSアカウントID>",
            "StackId": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/StackSet-service-linked-role-globalaccelerator-ed2eb2ef-effb-417d-b358-54462de2ba87/971034a0-2dbb-11ee-b006-0a8d6d22a55b",
            "Status": "CURRENT",
            "StackInstanceStatus": {
                "DetailedStatus": "SUCCEEDED"
            },
            "OrganizationalUnitId": "",
            "DriftStatus": "NOT_CHECKED",
            "LastOperationId": "c932f341-f641-4ba6-9524-8286e48642ce"
        },
        {
            "StackSetId": "service-linked-role-globalaccelerator:f65d6fd6-b499-4590-a799-04f5c1578212",
            "Region": "us-east-2",
            "Account": "<AWSアカウントID>",
            "StackId": "arn:aws:cloudformation:us-east-2:<AWSアカウントID>:stack/StackSet-service-linked-role-globalaccele
rator-be908822-2bfd-480e-9333-0d9271724fb7/c7a9ae90-2db9-11ee-83c6-06265f1183a3",
            "Status": "CURRENT",
            "StackInstanceStatus": {
                "DetailedStatus": "SUCCEEDED"
            },
            "OrganizationalUnitId": "",
            "DriftStatus": "NOT_CHECKED",
            "LastOperationId": "c932f341-f641-4ba6-9524-8286e48642ce"
        },
        {
            "StackSetId": "service-linked-role-globalaccelerator:f65d6fd6-b499-4590-a799-04f5c1578212",
            "Region": "us-west-1",
            "Account": "<AWSアカウントID>",
            "StackId": "arn:aws:cloudformation:us-west-1:<AWSアカウントID>:stack/StackSet-service-linked-role-globalaccele
rator-62619545-cf53-4795-8c91-7474776ec0c3/9d707030-2dbb-11ee-94c1-02595b0bb097",
            "Status": "CURRENT",
            "StackInstanceStatus": {
                "DetailedStatus": "SUCCEEDED"
            },
            "OrganizationalUnitId": "",
            "DriftStatus": "NOT_CHECKED",
            "LastOperationId": "c932f341-f641-4ba6-9524-8286e48642ce"
        }
    ]
}

いずれのStack InstanceもSUCCEEDEDとなっていますね。

マネジメントコンソールから確認すると以下のとおりです。

Stack Instanceの作成成功

Stack Instanceのイベントも確認しましょう。

まず、us-east-2のイベントです。

11:56:45に更新がかかり、11:56:59にService-linked Roleの削除に成功していますね。

us-east-2のStack Instanceのイベント

次にus-east-1のイベントです。

11:57:01にStackの作成が開始され、11:57:05にService-linked Roleの作成に成功していますね。

us-east-1のStack Instanceのイベント

RegionOrderで設定した順序でStack Instanceが更新・作成されていることが分かりました。

念の為、マネジメントコンソールからService-linked Roleが作成されていることを確認しておきましょう。

AWSServiceRoleForGlobalAccelerator

Global AcceleratorのService-linked Roleがありますね。

グローバルリソースはどのリージョンで管理しているか意識しよう

「CloudFormation StackSetsで作成したグローバルリソースを同一Stackから参照する際は実行リージョンとリージョンの実行順序を意識しよう」というお話をしました。

「ただ、サービス有効化したいだけなのにIAMロールを作成するStackSetsを分離するのは何だか避けたい」という場合はConditionRegionOrderを使ってみると良いでしょう。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!