Terraformで複数AWSアカウントをまたぐ設計|provider aliasとassume_roleでマルチアカウント構成を実装する方法

運営:リナックスマスター.JP(代表 宮崎智広)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Terraform > Terraformで複数AWSアカウントをまたぐ設計|provider aliasとassume_roleでマルチアカウント構成を実装する方法
「開発環境と本番環境で別々のAWSアカウントを使っているが、Terraformのコードを2セット書いて管理するのは辛い」
「provider aliasは名前だけ知っているが、assume_roleと組み合わせてクロスアカウント操作をどう書けばいいのかわからない」

複数のAWSアカウントを扱う場合、Terraformのprovider aliasとassume_roleを組み合わせると、1つのコードベースで開発・ステージング・本番の各アカウントにまたがるリソースを同時管理できます。アカウントをまたぐ認証の切り替えはTerraformが自動で行ってくれます。

この記事では、provider aliasの基本設定からassume_roleによるクロスアカウント認証の仕組み、IAMクロスアカウントロールの準備手順、data sourceへのprovider指定、実際のリソース実装例、moduleへのprovider引き渡し設計、さらに「provider aliasで統合管理」と「ディレクトリ分割で分離管理」のどちらを選ぶべきかの判断基準まで解説します。

想定環境:Terraform 1.8.x(RHEL 9.4 / Ubuntu 24.04 LTS)

この記事のポイント

・provider aliasで複数AWSアカウントへのproviderを1つのコードで定義できる
・assume_roleブロックにrole_arnを指定するだけでクロスアカウント認証が切り替わる
・リソース定義でprovider = aws.devのように明示すると作成先アカウントを固定できる
・一度作成したaliasをproviders.tfから削除するとstateが壊れるので慎重に扱う


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜAWSをマルチアカウントで分離するのか

AWSのベストプラクティスでは、用途ごとにAWSアカウントを分離することが強く推奨されています。1つのアカウントに開発・ステージング・本番を同居させると、次のリスクが生まれます。

・IAMロールのミスで開発者が本番のリソースを誤操作するリスク
・本番のコスト増加に開発のトラフィックが影響し、原因特定が難しくなる
・セキュリティインシデントが発生したとき、被害が全環境に広がる

マルチアカウント構成では、アカウントそのものがセキュリティ境界になります。開発アカウントで何が起きても、IAMポリシーの設定ミスが起きても、本番アカウントとは別のアカウントのため影響が遮断されます。

AWS Organizationsを使う組織では、管理アカウント(マスターアカウント)の下に開発・ステージング・本番の各メンバーアカウントを配置する構成が標準です。Terraformでこの構成を管理する場合、大きく2つのアプローチがあります。

・provider aliasで統合管理:1つのTerraformコードで複数アカウントのリソースを同時定義する
・ディレクトリ分割で分離管理:アカウントごとにTerraformのルートモジュールを分けてそれぞれ管理する

この2つの判断基準については後述します。まずprovider aliasの仕組みを理解しましょう。

provider aliasの仕組みと基本的な書き方

Terraformでは通常、1つのprovider(例:aws)を1つだけ定義します。しかしprovider aliasを使うと、同じproviderに複数の設定を持たせることができます。

同一アカウントの異なるリージョンにリソースを配置する場合も、provider aliasを使います。マルチアカウントではこれに加えてassume_roleを組み合わせます。

1. 基本的なprovider alias設定(同一アカウント・リージョン違い)

まず理解しやすいリージョン違いの例で仕組みを確認します。

# providers.tf - 同一アカウントの東京/大阪リージョンにprovider aliasを定義 terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } # デフォルトprovider(東京リージョン) provider "aws" { region = "ap-northeast-1" } # aliasを持つprovider(大阪リージョン) provider "aws" { alias = "osaka" region = "ap-northeast-3" }

aliasが設定されたproviderは、リソース定義でprovider = aws.osakaのように参照します。

# main.tf - リソースにprovider aliasを指定 resource "aws_s3_bucket" "dr_backup" { provider = aws.osaka # 大阪リージョンのproviderを明示 bucket = "my-app-dr-backup-osaka" tags = { Name = "DR用バックアップバケット" } }

2. assume_roleを使ったクロスアカウントprovider alias

別AWSアカウントへのアクセスには、IAMのSTS(Security Token Service)経由でロールを引き受けるassume_roleを使います。assume_roleブロックに切り替え先アカウントのIAMロールARNを指定するだけで、TerraformはAPI呼び出し時に自動でSTS:AssumeRoleを実行して認証を切り替えます。

# providers.tf - マルチアカウント設定 # 管理アカウント(実行元): 111122223333 # 開発アカウント: 123456789012 # 本番アカウント: 987654321098 # 実行元(管理アカウント)のデフォルトprovider provider "aws" { region = "ap-northeast-1" } # 開発アカウントへのprovider alias provider "aws" { alias = "dev" region = "ap-northeast-1" assume_role { role_arn = "arn:aws:iam::123456789012:role/TerraformCrossAccountRole" } } # 本番アカウントへのprovider alias provider "aws" { alias = "prod" region = "ap-northeast-1" assume_role { role_arn = "arn:aws:iam::987654321098:role/TerraformCrossAccountRole" } }

role_arnには「Terraformが引き受けるロール」のARNを指定します。このロールは切り替え先アカウント側(開発・本番)に事前に作成が必要です。

3. data sourceへのprovider参照

data sourceブロックにもproviderを指定できます。特定アカウントの既存リソース情報(AMI IDやVPC IDなど)を参照するときに使います。

# 開発アカウントの既存VPC IDを取得する例 data "aws_vpc" "dev_existing" { provider = aws.dev # 開発アカウントのproviderを指定 tags = { Name = "existing-dev-vpc" } } # 本番アカウントのAMI IDを取得する例(本番アカウントが所有するゴールデンAMI) data "aws_ami" "prod_golden" { provider = aws.prod most_recent = true owners = ["self"] filter { name = "name" values = ["golden-ami-*"] } }

provider指定を忘れると、デフォルトprovider(管理アカウント)のリソースを参照してしまいます。「リソースが見つからない(NotFound)」エラーが出た場合、providerの向き先が違うアカウントになっていないかを確認してください。

IAMクロスアカウントロールの準備(Terraform実行前の設定)

provider aliasのassume_roleが機能するためには、切り替え先アカウントにクロスアカウントロールを作成し、実行元アカウントからのassumeを許可する必要があります。

4. 切り替え先アカウントにIAMロールを作成する

開発・本番の各アカウントに「TerraformCrossAccountRole」を作成します。信頼ポリシー(Trust Policy)で、実行元アカウント(管理アカウント)からのSTS:AssumeRoleを許可します。

# AWSマネジメントコンソールまたはCLIで開発アカウント側に作成する信頼ポリシー # (管理アカウントのアカウントID: 111122223333 を許可) { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:root" }, "Action": "sts:AssumeRole" } ] }

Principalに管理アカウントのroot ARNを指定すると、管理アカウントのすべてのIAMエンティティがこのロールを引き受けられる候補になります。実際にAssumeRoleできるのは、次のステップで「sts:AssumeRole権限」を付与したエンティティだけです。

このロールにはTerraformが操作するリソースに応じた権限ポリシーもアタッチします。開発環境でEC2・VPC・S3を管理する場合は、対象サービスへのフルアクセスか、PowerUserAccessポリシーを付与するのが一般的です。

5. 実行元アカウント側にsts:AssumeRoleを許可する

管理アカウント側では、Terraformを実行するIAMユーザー・ロールに、切り替え先アカウントのロールをAssumeRoleできる権限を付与します。

# 管理アカウントのIAMポリシー(TerraformExecutionRole または実行ユーザーにアタッチ) { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": [ "arn:aws:iam::123456789012:role/TerraformCrossAccountRole", "arn:aws:iam::987654321098:role/TerraformCrossAccountRole" ] } ] }

実践|複数アカウントにVPCとサブネットを構築する

IAMの準備が完了したら、実際にリソースを定義します。

6. provider設定とリソース定義

# main.tf - 開発・本番アカウントにVPCを作成する例 # 開発アカウントにVPCを作成(provider = aws.dev を明示) resource "aws_vpc" "dev" { provider = aws.dev cidr_block = "10.1.0.0/16" tags = { Name = "dev-vpc" Environment = "dev" } } resource "aws_subnet" "dev_public" { provider = aws.dev vpc_id = aws_vpc.dev.id cidr_block = "10.1.1.0/24" availability_zone = "ap-northeast-1a" tags = { Name = "dev-public-subnet" } } # 本番アカウントにVPCを作成(provider = aws.prod を明示) resource "aws_vpc" "prod" { provider = aws.prod cidr_block = "10.0.0.0/16" tags = { Name = "prod-vpc" Environment = "prod" } } resource "aws_subnet" "prod_public" { provider = aws.prod vpc_id = aws_vpc.prod.id cidr_block = "10.0.1.0/24" availability_zone = "ap-northeast-1a" tags = { Name = "prod-public-subnet" } }

7. terraform planで差分を確認する

# terraform init 後に terraform plan を実行 $ terraform plan Terraform will perform the following actions: # aws_subnet.dev_public will be created + resource "aws_subnet" "dev_public" { + arn = (known after apply) + availability_zone = "ap-northeast-1a" + cidr_block = "10.1.1.0/24" + id = (known after apply) + tags = { + "Name" = "dev-public-subnet" } + vpc_id = (known after apply) } # aws_vpc.dev will be created + resource "aws_vpc" "dev" { + arn = (known after apply) + cidr_block = "10.1.0.0/16" + id = (known after apply) + tags = { + "Environment" = "dev" + "Name" = "dev-vpc" } } # aws_subnet.prod_public will be created + resource "aws_subnet" "prod_public" { + arn = (known after apply) + availability_zone = "ap-northeast-1a" + cidr_block = "10.0.1.0/24" + id = (known after apply) + tags = { + "Name" = "prod-public-subnet" } + vpc_id = (known after apply) } # aws_vpc.prod will be created + resource "aws_vpc" "prod" { + arn = (known after apply) + cidr_block = "10.0.0.0/16" + id = (known after apply) + tags = { + "Environment" = "prod" + "Name" = "prod-vpc" } } Plan: 4 to add, 0 to change, 0 to destroy.

planの出力で開発アカウント(aws.dev)と本番アカウント(aws.prod)それぞれのリソースが列挙されます。実行元の管理アカウントはSTSを経由してそれぞれのアカウントにAPIコールを発行するため、planの段階でIAMの設定が正しいかどうかも確認できます。

moduleへのprovider引き渡し設計

Terraformコードをmodule化している場合、provider aliasをmodule内で使うには呼び出し元からproviders引数で明示的に渡す必要があります。

8. providers引数の渡し方

アカウントごとに同じリソース構成を作る場合、moduleを複数回呼び出す設計が一般的です。呼び出し時にproviders引数でprovider aliasを渡します。

# main.tf(呼び出し元) # 同じVPCモジュールを開発・本番の両アカウントに展開する例 module "vpc_dev" { source = "./modules/vpc" providers = { aws = aws.dev # 開発アカウントのproviderを渡す } vpc_cidr = "10.1.0.0/16" env_name = "dev" } module "vpc_prod" { source = "./modules/vpc" providers = { aws = aws.prod # 本番アカウントのproviderを渡す } vpc_cidr = "10.0.0.0/16" env_name = "prod" }

providers引数を省略すると、module内のリソースはすべてデフォルトprovider(管理アカウント)を使います。マルチアカウント設計では必ずこの引き渡しを明示してください。

VPCピアリングのように2つのアカウントにまたがる操作をmodule内で完結させる場合は、複数のproviderを渡す設計も使えます。

# VPCピアリングmodule(管理アカウントと開発アカウントをまたぐ操作) module "vpc_peering" { source = "./modules/vpc-peering" providers = { aws.requester = aws # 管理アカウント(ピアリングをリクエストする側) aws.accepter = aws.dev # 開発アカウント(ピアリングを承認する側) } requester_vpc_id = "vpc-xxx" accepter_vpc_id = "vpc-yyy" }

9. module内でのconfiguration_aliases宣言

複数のprovider aliasを受け取るmoduleでは、受け取るproviderをrequired_providersのconfiguration_aliasesで宣言します。

# modules/vpc-peering/versions.tf terraform { required_version = ">= 1.3" required_providers { aws = { source = "hashicorp/aws" version = ">= 5.0" configuration_aliases = [aws.requester, aws.accepter] } } }

この宣言があることで、「このmoduleはrequesterとaccepterという2つのproviderを要求する」ことが明示されます。module内のリソースではprovider = aws.requesterやprovider = aws.accepterで参照できます。

注意点:configuration_aliasesを宣言しないままmodule内でaliasを使うと、Terraform 1.6以降では警告が出るようになりました。Terraform 1.8以降ではエラーになるケースもあるため、moduleを作成するときは必ず宣言しておきましょう。

また、configuration_aliasesで宣言したaliasは後から気軽に削除できない点にも注意が必要です。stateにそのaliasで作成したリソースが残っている状態でaliasブロックを削除すると、terraform planの段階でエラーが発生します。aliasを削除したい場合は、先にterraform destroyでリソースを削除するか、terraform state rmでstateから除いてから設定を変更してください。

provider alias vs ディレクトリ分割|どちらを選ぶか

マルチアカウント管理の手法は「provider aliasで1つのコードに統合する」か「アカウントごとにディレクトリを分割して独立管理する」かの2択です。それぞれの特徴を比較します。
比較軸 provider alias統合管理 ディレクトリ分割管理
コードの一元化 1か所で全アカウントを定義 アカウントごとに別ディレクトリ
terraform apply範囲 全アカウントが一度に変更対象 ディレクトリごとに独立実行
誤操作リスク 本番provider aliasのミスで本番に意図しない変更 ディレクトリを間違えない限り影響範囲が限定
共通リソースの管理 1つのコードで参照でき整合性が取りやすい remote_stateで参照する設計が必要
向いているケース アカウント間で構成差が小さく共有リソースが多い 本番だけ構成が大きく異なり独立性を重視したい
現場での使い分けの目安:

dev・stg・prodでVPCやサブネットのCIDR設計は同じで、インスタンスサイズだけ違う場合はprovider alias + variableによる統合管理が効率的です。一方、本番環境だけWAFやShieldを導入していて構成が根本的に違う場合、本番リソースを別ディレクトリで分離したほうがterraform applyの影響範囲が明確になります。

「本番への誤操作リスクを最小化したい」という方針の現場では、ディレクトリ分割が選ばれることが多いです。provider aliasは利便性が高い反面、provider = aws.prodの書き忘れが本番への意図しない変更につながるリスクがあります。

トラブルシュート|よくあるエラーと対処

「No valid credential sources found」が出た時の対処

Error: No valid credential sources found with provider["registry.terraform.io/hashicorp/aws"].dev, on providers.tf line 14, in provider "aws": 14: assume_role {

原因は実行環境に管理アカウントのAWS認証情報が設定されていないことです。Terraformはまず実行元アカウントの認証情報でSTSにAssumeRoleをリクエストします。この認証情報がないと処理が始まりません。

・ローカル実行の場合:aws configureで管理アカウントの認証情報を設定するか、AWS_PROFILE環境変数でプロファイルを指定する
・CI/CD実行の場合(GitHub Actions等):実行ランナーにIAMロールをOIDCで割り当てる(アクセスキー不要)か、Secretsにアクセスキーを設定する

「AccessDenied: is not authorized to assume role」が出た時の対処

Error: AuthFailure: AWS was not able to validate the provided access credentials Error: user: arn:aws:iam::111122223333:user/terraform-ci is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::123456789012:role/TerraformCrossAccountRole

2か所の設定を確認します。

・実行元アカウント側:実行IAMユーザーまたはロールにsts:AssumeRoleの権限が付与されているか確認する
・切り替え先アカウント側:TerraformCrossAccountRoleの信頼ポリシーに、実行元アカウントのプリンシパルが正しく記載されているか確認する

信頼ポリシーのPrincipalにarn:aws:iam::111122223333:rootを指定した場合でも、実行元IAMエンティティにsts:AssumeRole権限がなければ失敗します。「許可する側(信頼ポリシー)」と「実行できる側(IAMポリシー)」の両方が必要です。

provider alias指定漏れによる意図しないアカウントへの作成

【要注意】リソース定義でprovider引数を省略すると、デフォルトprovider(実行元アカウント)に作成されます。開発アカウントに作るつもりでprovider = aws.devを書き忘れると、管理アカウントにリソースが作成されてしまいます。この誤操作は気づかないまま進みやすいため注意が必要です。

これを防ぐには次の手順が有効です。

・デフォルトproviderに実運用リソースを置かない:管理アカウントにはTerraformのstateバックエンド(S3+DynamoDB)だけを置き、実リソースはすべてaliasを持つproviderで管理する設計にする
・terraform planの結果を必ず確認する:planの出力にある「provider」の行を確認し、意図したアカウントへの変更になっているか確認してからapplyを実行する

「No configuration for provider with alias...」が出た時の対処

Error: No configuration for provider "aws" with alias "dev" on modules/vpc/main.tf line 3, in resource "aws_vpc" "main": 3: provider = aws.dev

module呼び出し時にproviders引数を渡し忘れたか、alias名が一致していないケースです。呼び出し元のproviders = { ... }ブロックを確認し、module側のconfiguration_aliasesと名前が完全一致しているかを確認します。

alias名のアンダースコアとハイフンは混同しやすい点にも注意が必要です。aws.devとproviders = { aws.dev = aws.dev }の対応関係を確認し、定義と参照で名前を統一してください。

aliasブロックを削除するとstateが壊れるケース

# エラー出力例(環境により異なります) Error: Provider configuration not present To work with aws_vpc.dev its original provider configuration at provider["registry.terraform.io/hashicorp/aws"].dev is required, but it has been removed. Add back its configuration.

一度aliasを持つproviderでリソースを作成すると、そのリソース情報はstateファイルに「このprovider aliasが作ったもの」として記録されます。この状態でproviders.tfからaliasブロックを削除すると、Terraformがstateを読み込む段階で上記エラーが発生し、planもapplyも実行できなくなります。

対処法は2つあります。

・リソースを先に削除する:terraform destroy -target=aws_vpc.dev等でそのaliasを使うリソースを先に削除してから、aliasブロックを消す
・stateから除いてからブロックを削除する:リソースを残したままaliasだけ外したい場合はterraform state rm aws_vpc.devでstateの管理から除いた後にaliasブロックを削除する(この場合、実際のAWSリソースはそのまま残る)

「もう使わないaliasだから消してしまおう」と思ったときは、先にそのaliasで作成したリソースが残っていないかをterraform state listで確認する習慣をつけてください。

本記事のまとめ

やりたいこと 設定・操作
別アカウント用のproviderを定義する provider "aws" { alias = "dev" }
クロスアカウント認証を設定する assume_role { role_arn = "arn:aws:iam::..." }
リソースを特定アカウントに作成する resource "aws_vpc" "dev" { provider = aws.dev }
別アカウントのdata sourceを参照する data "aws_vpc" "dev" { provider = aws.dev }
実行元IAMに権限を付与する sts:AssumeRoleを含むIAMポリシーをアタッチ
切り替え先に信頼ポリシーを設定する 実行元アカウントIDをPrincipalに記載
moduleにprovider aliasを渡す providers = { aws = aws.dev }で引き渡す
module内でaliasを受け取る configuration_aliases = [aws.requester, aws.accepter]で宣言
provider aliasかディレクトリ分割か判断する 構成差が小さい→alias統合、本番を独立させたい→ディレクトリ分割
aliasブロックを安全に削除する terraform destroyまたはterraform state rmでstateから除いてから削除
provider aliasとassume_roleの組み合わせは、マルチアカウント構成を1つのTerraformコードで扱える強力な手法です。一方で、全アカウントが同一のapplyスコープに入るため「本番誤操作リスク」と「aliasを削除するとstateが壊れる問題」を念頭に置いた設計が重要です。実際の現場では、小規模チームはprovider aliasで統合し、本番リソースへのapplyはCI/CDの承認ゲートで制御するパターンがバランスよく使われています。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、Terraformのマルチアカウント設計からmodule化・CI/CDパイプラインまで、ハンズオン形式で体系的に学べるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録

運営

リナックスマスター.JP(代表 宮崎智広)

株式会社イーネットマーキュリーが運営するLinux教育サイトです。代表の宮崎智広は現役のLinuxサーバー管理者として20年以上の実務経験を持ち、累計3,100名以上のエンジニアを指導してきました。実践型のLinuxセミナーや無料マニュアルの配布を通じてLinux人材の育成に取り組んでいます。