【Terraform】terraform_remote_stateデータソースの使い方
はじめに
こんにちは、佐伯です。今年も残すところ後わずかとなりました。
以下エントリでterraform_remote_stateデータソースに少し触れたので、使用方法について簡単に紹介したいと思います。
公式ドキュメント
公式ドキュメントは下記となります。
Terraform: terraform_remote_state - Terraform by HashiCorp
やってみた
今回は簡単にアカウントB(AccountID:75XXXXXXXXXX)のAWSアカウントIDをTerraformの状態ファイルから取得し、アカウントA(AccountID:58XXXXXXXXXX)にアカウントBを信頼するIAMロールを作成します。
以下のようなディレクトリ構成としてます。
example/ ├── account-a │ └── terraform │ ├── config.tf │ ├── datasource.tf │ └── iam.tf └── account-b └── terraform ├── config.tf ├── datasource.tf └── output.tf
[アカウントB]リモートバックエンド用のS3バケットの作成
アカウントBでTerraformの状態ファイルを保管するリモートバックエンド用のS3バケットを作成します。
このS3バケットはTerraformで作成してもよいのですが、Terraformの管理下にしてしまうと万が一誤ってdestroyしてしまう可能性があることを考慮して、このS3バケットはAWSマネジメントコンソールから手動で作成しています。
S3をリモートバックエンドに設定する場合はバージョニングを有効にしましょう。
S3バケット作成後にバケットポリシーで、アカウントAからの読み取りを許可します。
JSONは以下のようになります。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "ExampleStatement1", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::<許可するAWSアカウントID>:root" }, "Action": [ "s3:GetBucketLocation", "s3:ListBucket", "s3:GetObject" ], "Resource": [ "arn:aws:s3:::<S3バケット名>", "arn:aws:s3:::<S3バケット名>/*" ] } ] }
ついでに、ライフサイクル設定もして90日経過後の古いバージョンは失効するようにしました。
[アカウントB]リモートバックエンド設定
リモートバックエンドをS3に設定します。
terraform { backend "s3" { bucket = "terraform-state-75XXXXXXXXXX" key = "state" region = "ap-northeast-1" acl = "bucket-owner-full-control" } }
リモートバックエンド設定後はterraform init
を実行します。
[saiki.ko@~/example/account-b/terraform] $ terraform init Initializing the backend... Initializing provider plugins... The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking changes, it is recommended to add version = "..." constraints to the corresponding provider blocks in configuration, with the constraint strings suggested below. * provider.aws: version = "~> 1.6" Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.
[アカウントB]アカウントBのAWSアカウントIDを参照できるようにする
terraform_remote_stateデータソースで参照できるようにするためには、値をoutput
する必要があります。output
するとTerraformの状態ファイルに値が出力される形です。
AWSアカウントの取得にはaws_caller_identityデータソースを使いましょう。
data "aws_caller_identity" "my" {}
aws_caller_identityデータソースで取得した、AWSアカウントIDをoutputします。
output "account_id" { value = "${data.aws_caller_identity.my.account_id}" }
[アカウントB]Terraform状態ファイルの更新
データソースとアウトプットを追加したのみなので、AWSリソースの変更は伴いません。そういった場合はterraform refresh
でTerraformの状態ファイルの更新を行うことができます。
[saiki.ko@~/example/account-b/terraform] $ terraform refresh Empty or non-existent state file. Refresh will do nothing. Refresh does not error or return an erroneous exit status because many automation scripts use refresh, plan, then apply and may not have a state file yet for the first run. data.aws_caller_identity.my: Refreshing state... Outputs: account_id = 75XXXXXXXXXX
実行結果の補足として、今回はリモートバックエンドの設定後、terraform init
の実行しかしてないので、S3バケットにTerraformの状態ファイルがない旨のメッセージが出力されています。
[アカウントA]アカウントBのS3バケットが参照できることを確認
[saiki.ko@~/example/account-a] $ aws sts get-caller-identity { "Account": "58XXXXXXXXXX", "UserId": "AIDAXXXXXXXXXXXXXXXXX", "Arn": "arn:aws:iam::58XXXXXXXXXX:user/XXXXX" } [saiki.ko@~/example/account-a] $ aws s3 ls s3://terraform-state-75XXXXXXXXXX 2017-12-31 20:44:35 1299 state
[アカウントA]terraform_remote_stateデータソースでの参照
terraform_remote_stateデータソースで、アカウントBのリモートバックエンドに設定したS3バケットを設定します。
data "terraform_remote_state" "account_b" { backend = "s3" config { bucket = "terraform-state-75XXXXXXXXXX" key = "state" region = "ap-northeast-1" } }
[アカウントA]アカウントBを信頼したIAMロールの作成
aws_iam_policy_documentデータソース内で${data.terraform_remote_state.account_b.account_id}
という形でアカウントBのTerraform状態ファイルから読み込んだ値を使用することができます。
resource "aws_iam_role" "assume_role" { name = "assume_role" assume_role_policy = "${data.aws_iam_policy_document.assume_role_policy.json}" } resource "aws_iam_role_policy_attachment" "attach_administratoraccess_to_assume_role" { role = "${aws_iam_role.assume_role.name}" policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess" } data "aws_iam_policy_document" "assume_role_policy" { statement { effect = "Allow" actions = ["sts:AssumeRole"] principals { type = "AWS" identifiers = ["arn:aws:iam::${data.terraform_remote_state.account_b.account_id}:root"] } } }
[アカウントA]terraform applyの実行
IAMロールを作成するためにterraform apply
を実行します。
[saiki.ko@~/example/account-a/terraform] $ aws sts get-caller-identity { "Account": "58XXXXXXXXXX", "UserId": "AIDAXXXXXXXXXXXXXXXXX", "Arn": "arn:aws:iam::58XXXXXXXXXX:user/XXXXX" } [saiki.ko@~/example/account-a/terraform] $ terraform apply data.terraform_remote_state.account_b: Refreshing state... data.aws_caller_identity.my: Refreshing state... data.aws_iam_policy_document.assume_role_policy: Refreshing state... An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: + aws_iam_role.assume_role id: <computed> arn: <computed> assume_role_policy: "#長いので省略" create_date: <computed> force_detach_policies: "false" name: "assume_role" path: "/" unique_id: <computed> + aws_iam_role_policy_attachment.attach_administratoraccess_to_assume_role id: <computed> policy_arn: "arn:aws:iam::aws:policy/AdministratorAccess" role: "assume_role" Plan: 2 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes aws_iam_role.assume_role: Creating... arn: "" => "<computed>" assume_role_policy: "" => "#長いので省略" create_date: "" => "<computed>" force_detach_policies: "" => "false" name: "" => "assume_role" path: "" => "/" unique_id: "" => "<computed>" aws_iam_role.assume_role: Creation complete after 2s (ID: assume_role) aws_iam_role_policy_attachment.attach_administratoraccess_to_assume_role: Creating... policy_arn: "" => "arn:aws:iam::aws:policy/AdministratorAccess" role: "" => "assume_role" aws_iam_role_policy_attachment.attach_administratoraccess_to_assume_role: Creation complete after 3s (ID: assume_role-20171231121813149700000001) Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
はい、作成できました。
[アカウントB]AWS CLIでAssumeRoleできるか確認
[saiki.ko@~/example/account-b/terraform] $ aws sts get-caller-identity { "Account": "75XXXXXXXXXX", "UserId": "AIDAXXXXXXXXXXXXXXXXX", "Arn": "arn:aws:iam::75XXXXXXXXXX:user/XXXXX" } [saiki.ko@~/example/account-b/terraform] $ aws sts assume-role --role-arn arn:aws:iam::58XXXXXXXXXX:role/assume_role --role-session-name AssumeRoleFrom75XXXXXXXXXX { "AssumedRoleUser": { "AssumedRoleId": "AROAXXXXXXXXXXXXXXXXX:AssumeRoleFrom75XXXXXXXXXX", "Arn": "arn:aws:sts::58XXXXXXXXXX:assumed-role/assume_role/AssumeRoleFrom75XXXXXXXXXX" }, "Credentials": { "SecretAccessKey": "y8KMt+A62YXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "SessionToken": "長いので省略", "Expiration": "2017-12-31T13:23:16Z", "AccessKeyId": "ASIAXXXXXXXXXXXXXXXX" } }
アクセスキー、シークレットキー、セッショントークンが取得できたので問題ないですね。
まとめ
今回作成したIAMロールは正直「AWSアカウントID、変数に入れちゃえばいいじゃん」となり、あまり便利さが感じられないですが、運用や通知系のAWS LambdaをひとつのAWSアカウントにまとめている場合などは便利じゃないでしょうか。
また、workspaceのベストプラクティスにはProduction/Staging/Developmentなどの環境をworkspaceのみで分離せず、小さな単位で分割しterraform_remote_stateデータソースを使用してリンクすることが推奨されています。
State: Workspaces - Terraform by HashiCorp
それでは皆さん良いお年を。