Service CatalogのTerraform Open Source製品でクロスリージョンのデプロイができるか試してみた

Multiple Providersを使ってクロスリージョンのデプロイを行うTerraform Open Source製品を作成し、起動することができるか試してみました。
2023.04.11

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

AWS事業本部のイシザワです。

先日のアップデートでTerraformテンプレートをService Catalogの製品として登録できるようになりました。

これまではCloudFormationで書く必要があったので、リージョンをまたがるシステムを製品として起動するためにはLambda関数で実装したカスタムリソースを使う必要がありました。

TerraformであればMultiple Providersを使うことでLambda関数を実装せずにクロスリージョンのデプロイをすることができるので、これをService Catalogの製品として起動できるのか試してみました。

結論としては可能ですが、少し癖があります。

Terraform Reference Engine

Terraform Open Source製品を起動するためにはTerraform Reference Engineを事前にService Catalogの管理アカウントにデプロイする必要があります。デプロイする手順は以下の記事を参考にしてください。

製品を起動した際、Terraformコマンドの実行はEC2インスタンス上で行われます。 Terraform Applyが実行される前に以下の内容のprovider_override.tf.jsonが追加されます。これにより、デフォルトのAWS providerが上書きされます。

{
    "provider": {
        "aws": {
            "region": "<Terraform Reference Engineをデプロイしたリージョン>",
            "assume_role": {
                "role_arn": "<製品の起動制約に設定したIAMロール>",
                "session_name": "pp-1234_MyProvisioned_product"
            },
            "default_tags": {
                "tags": {
                    "SERVICE_CATALOG_TERRAFORM_INTEGRATION-DO_NOT_DELETE": "pp-1234"
                }
            }
        }
    }
}

このため、下図のように製品を起動したアカウントのIAMロールをEC2インスタンスがAssumeRoleして、Terraformテンプレートのプロビジョニングが行われます。

クロスリージョンデプロイをやってみた

製品の起動

Terraform Reference Engineの仕組みを考慮すると、Terraformテンプレートで以下の条件を満たすaws providerを定義すればクロスリージョンのデプロイをする製品を作成できそうです。

  • aliasを設定している(デフォルトのproviderだと設定が上書きされるため)
  • 製品を起動するアカウントのIAMロールをAssumeRoleする
  • ↑で設定するIAMロールのロール名がSCLaunch始まりである(TerraformExecutionRole-*による制約のため)

そこで、以下のTerraformテンプレートで定義される簡単なクロスリージョンのVPCピアリングをService Catalogの製品として登録します。製品のプロビジョニングは東京リージョンで行います。

variables.tf

variable "main_vpc_cidr_block" { type = string }
variable "sub_vpc_cidr_block" { type = string }

main.tf

data "aws_caller_identity" "current" {}

provider "aws" {
  alias  = "osaka"
  region = "ap-northeast-3"
  assume_role {
      role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/SCLaunch-VPCPeeringProduct"
  }
}

resource "aws_vpc" "main" {
  cidr_block = var.main_vpc_cidr_block
  tags = {
    Name = "main"
  }
}

resource "aws_vpc" "sub" {
  provider   = aws.osaka
  cidr_block = var.sub_vpc_cidr_block
  tags = {
    Name = "sub"
  }
}

resource "aws_vpc_peering_connection" "peer" {
  peer_owner_id = data.aws_caller_identity.current.account_id
  vpc_id        = aws_vpc.main.id
  peer_vpc_id   = aws_vpc.sub.id
  peer_region   = "ap-northeast-3"
  auto_accept   = false
}

resource "aws_vpc_peering_connection_accepter" "peer" {
  provider                  = aws.osaka
  vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
  auto_accept               = true
}

デフォルトのawsプロバイダの実行プリンシパルは製品を起動するアカウントのIAMロールのため、aws_caller_identityで製品を起動するアカウントの情報を得ることができます。

SCLaunch-VPCPeeringProductは製品の起動制約とします。ここでAssumeRoleするIAMロールと起動制約に設定するIAMロールを一致させる必要はありません。

SCLaunch-VPCPeeringProductに設定する権限はTerraform Open Source製品の起動制約に必須な以下の権限とAmazonVPCFullAccessを設定します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "s3:ExistingObjectTag/servicecatalog:provisioning": "true"
                }
            }
        },
        {
            "Action": [
                "resource-groups:CreateGroup",
                "resource-groups:ListGroupResources",
                "resource-groups:DeleteGroup",
                "resource-groups:Tag"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "tag:GetResources",
                "tag:GetTagKeys",
                "tag:GetTagValues",
                "tag:TagResources",
                "tag:UntagResources"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}

信頼性ポリシーには以下の内容を設定します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "GivePermissionsToServiceCatalog",
            "Effect": "Allow",
            "Principal": {
                "Service": "servicecatalog.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<Service Catalog管理アカウントのアカウントID>:root"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringLike": {
                    "aws:PrincipalArn": [
                        "arn:aws:iam::<Service Catalog管理アカウントのアカウントID>:role/TerraformEngine/TerraformExecutionRole*",
                        "arn:aws:iam::<Service Catalog管理アカウントのアカウントID>:role/TerraformEngine/ServiceCatalogTerraformOSParameterParserRole*"
                    ]
                }
            }
        }
    ]
}

製品を登録したら起動するアカウントに移動して実際に起動してみます。

製品が作成されたら、プロビジョニングされた製品のリソースタブで作成されたリソースの一覧を見れます。しかし、ここでは大阪リージョンで作成したリソースを確認することはできません。

起動した製品の情報からは確認できませんが、実際にはちゃんと大阪リージョンでVPCは作成されています。

プロビジョニングされた製品の削除

起動した製品を削除しようとすると失敗してしまいます。

To work with aws_vpc.sub (orphan) its original provider configuration at provider["registry.terraform.io/hashicorp/aws"].osaka is required, but it has been removed. This occurs when a provider configuration is removed while objects created by that provider still exist in the state. Re-add the provider configuration to destroy aws_vpc.sub (orphan), after which you can remove the provider configuration again.

↑はエラーメッセージです。どうやらaws providerのaws.osakaが見つからないためエラーとなっているようです。

ここで、Terraform Reference Engineの製品起動時と削除時の動作を確認してみます。

Terraform Apply

For the provision/update workflow, the terraform_runner package performs these steps.

  1. Create a temporary directory to serve as a Terraform workspace
  2. Assume the launch role and download the provisioning artifact
  3. Override parameters, assume-role, backend, and tags. The Tag override will be the tracer tag explained in the Limitations section below.
  4. Execute Terraform apply
  5. Clean up the temporary directory

Terraform Destroy

For the terminate workflow, the terraform_runner package performs these steps.

  1. Create a temporary directory to serve as a Terraform workspace
  2. Override assume-role and backend
  3. Execute Terraform destroy
  4. Clean up the temporary directory

起動時には製品として登録したアーティファクト(.tar.gzファイル)をダウンロードして利用するのですが、削除時には利用しないようです。 そのため、aws.osakaプロバイダで作成したリソースを削除しようとしても、製品削除時にはaws.osakaプロバイダの情報が無いため削除できなかったのだと思われます。

ワークアラウンドとして、プロバイダの情報のみを定義した以下のファイルを製品の新たなバージョンとして登録します。

main.tf

data "aws_caller_identity" "current" {}

provider "aws" {
  alias  = "osaka"
  region = "ap-northeast-3"
  assume_role {
      role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/SCLaunch-VPCPeeringProduct"
  }
}

製品を削除する前に先ほど作成したバージョンに製品を更新します。

このバージョンに製品を更新した後に製品の削除を行うと、aws.osakaプロバイダで作成したリソースは既に削除されているため製品の削除に成功します。

まとめ

Terraform Open Source製品でクロスリージョンのデプロイをすることができました。同じ理屈でクロスアカウントの製品のデプロイや、AWS以外のプロバイダと連携した製品のデプロイも可能だと思います。 なお、Terraform Reference EngineのGitHubに↓の注意があります。他プロバイダを追加する際はワーキングディレクトリ外のローカルファイルシステムに書き込みをしないよう注意してください。

The engine overrides the "aws" provider. Other providers are not supported.

If you do include other providers in your Terraform configuration, be sure they are not writing to the local file system outside the current working directory. The engine creates a unique working directory for each provisioned product on the EC2 instance. If the Terraform configuration writes outside this directory, those files could interfere with processes working on other provisioned products on the same instance.