こしあん
2025-02-09

CloudFormation StackSetsでOUのアカウントに対してリソースを自動デプロイ


25{icon} {views}

Terraformを仲介し、CloudFormation StackSetsのSERVICE_MANAGEDモードを使うことで、OU配下の各アカウントへ自動的にS3バケットをデプロイする例。OUに新規アカウントを追加してもスタックが自動生成され、IAMロールの管理も不要となるため運用負荷が大幅に軽減される。

はじめに

  • こちらの記事で個人用Organizationsを導入できたので、特定のOUに対してCloudFormation Stacksetsを使ってリソースを自動デプロイしてみる
  • CloudFormation StacksetsはCloudFormationの機能の一つ。シングルアカウントに適用するのはStackと呼ばれるが、複数アカウントのユースケースを想定されていて、複数アカウントに対してStackを生み出せるのがStackSets
  • CloudFormationのデプロイは相変わらずネイティブのAWS CLIで行うと取り回しが悪いので、こちらの記事のように、Terraformを仲介させる。
  • StackSetsのように、複数アカウントに対してリソースを自動的に生み出すためのリソースは、おそらくTerraformのネイティブ機能だとできないので(CI/CDやTerragruntなどの他のツールを併用すればおそらく可能)、AWS公式の機能でこれができるというとややCloudFormationの優位性が出てくるところ。

AWS Organizationsの信頼されたアクセスの有効化

管理側・子側のIAMロールを作成せずにCloudFormation StacksetができるSERVICE_MANAGEDモードがある。これを使うとIAMロールの管理の煩雑さがなくなる。

SERVICE_MANAGEDモードを使うには、AWS OrganizationsでCloudFormation Stacksetsの信頼されたアクセスを有効化しておく必要がある。管理側のアカウントのマネジメントコンソールで、AWS Organizations→サービスで、「CloudFormation Stacksets」を有効化しておく。

有効化されていないと以下のようなエラーが出るはず。

api error ValidationError: You must enable organizations access to operate a service managed stack set

特定OUの全アカウントに対しS3バケットを作るStackSets

以下のようなOrganizationの構成になっているとしたときに、「dev OU」に対してS3バケットをデプロイするStackSetsを考える

AWS Organization (ルート)
├── management アカウント
└── OU: dev
    └── develop アカウント

Terraform

CloudFormationを取り回しやすくするために、デプロイ用にTerraformを使う。ポイントは、

  • 管理アカウント側だけのプロバイダで良い。子のアカウントの認証情報や承認はいらない
  • StackSetsはSERVICE_MANAGEDでデプロイする。SELF_MANAGEDだとIAMロールが面倒なことになる
###############################
# プロバイダー定義
###############################
# 管理アカウント用プロバイダー (profile: management)
provider "aws" {
  profile = "management"
  region  = "ap-northeast-1"
}

###############################
# 変数定義 (variables.tf)
###############################
variable "bucket_suffix" {
  description = "CloudFormation テンプレートへ渡す S3 バケット名のサフィックス(ユニークにするための値)"
  type        = string
  default     = "sample"
}

# 管理アカウント側から Organizations 情報を取得
data "aws_organizations_organization" "org" {
}

# ルート OU 配下の「dev」OU を取得
data "aws_organizations_organizational_unit" "dev" {
  name      = "dev"
  parent_id = data.aws_organizations_organization.org.roots[0].id
}

# CloudFormation StackSet の作成 (SERVICE_MANAGED モード)
resource "aws_cloudformation_stack_set" "example" {
  name             = "example-stackset"
  template_body    = file("${path.module}/template.yaml")
  permission_model = "SERVICE_MANAGED"  # OU 単位のターゲット指定が利用可能

  parameters = {
    BucketSuffix = var.bucket_suffix
  }

  capabilities = ["CAPABILITY_NAMED_IAM"]

  # AutoDeployment を有効化(新規アカウント加入時に自動展開)
  auto_deployment {
    enabled                          = true
    retain_stacks_on_account_removal = false
  }
}

# StackSet インスタンスの作成 (OU をデプロイ対象)
resource "aws_cloudformation_stack_set_instance" "example_instance" {
  stack_set_name = aws_cloudformation_stack_set.example.name
  region         = "ap-northeast-1"

  deployment_targets {
    organizational_unit_ids = [data.aws_organizations_organizational_unit.dev.id]
  }
}

aws_cloudformation_stack_set リソースで StackSet を作成

  • name で StackSet 名を指定
  • template_body に読み込む CloudFormation テンプレート (template.yaml) を指定
  • permission_model = “SERVICE_MANAGED” により、AWS が StackSet の操作権限を管理し、OU 単位でのデプロイが可能
    • SERVICE_MANAGED にすれば、AWS Organizationsと連携してOU単位でのデプロイが可能になる。
    • SELF_MANAGED モードで利用していた administration_role_arn や execution_role_name は不要になり、AWS側で権限管理が行われるようになる。
  • auto_deployment を enabled = true にすることで、新規アカウントが OU に追加されたときに自動的にスタックが作成される

aws_cloudformation_stack_set_instance リソースで StackSet の実際のデプロイ先を指定

  • stack_set_name に上で作成した StackSet 名を指定
  • region は “ap-northeast-1” として、東京リージョンにデプロイ
  • deployment_targets.organizational_unit_ids に、対象となる OU の ID を指定 (data.aws_organizations_organizational_unit.dev.id)

CloudFormationのコード(template.yaml)

S3バケットをデプロイするだけ。ただ、S3バケット名はグローバルでユニークでないといけないので、最後にアカウントIDをつけてStackSets間で差を出すようにする。ここはデプロイされる側(子アカウント)のIDが入る。AWS::AccountIdで取れる。

AWSTemplateFormatVersion: '2010-09-09'
Description: >
  シンプルな S3 バケットを作成する CloudFormation テンプレート
Parameters:
  BucketSuffix:
    Type: String
    Description: "バケット名のサフィックス(ユニークになるよう設定)"
Resources:
  MyS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'stack-sets-bucket-${BucketSuffix}-${AWS::AccountId}'
Outputs:
  BucketName:
    Description: "作成された S3 バケットの名前"
    Value: !Ref MyS3Bucket

結果

子アカウント側

  • S3バケットができている
  • スタックはできているが、StackSetsはできていない

親アカウント側

  • StackSetsはできているが、スタックはできていない

ということで、いずれも想定通りの挙動。StackSets→スタックを生んでいる

所感

  • これのすごいところは、OUにあとからアカウントを追加してもよしなにStackSets側でデプロイしてくれること。この機能はさすがにTerraform単体だと再現できなそう。
  • ただ、CloudFormation単体のメンテンスコストが異様に高いので、それで打ち消し合ってイーブンなのかなという印象。Terraformでこのへんがよしなにできたら自分はTerraform使うかな。
  • デプロイするリソースはそこまで面倒ではないが、アカウント数が異様に多くて数百とかだとこの手法は有効そう


Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

北海道の駅巡りコーナー


Add a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です