【Terraform】terraform_remote_stateデータソースの使い方

はじめに

こんにちは、佐伯です。今年も残すところ後わずかとなりました。

以下エントリでterraform_remote_stateデータソースに少し触れたので、使用方法について簡単に紹介したいと思います。

Terraform v0.9→v0.11までの変更点をまとめてみた

公式ドキュメント

公式ドキュメントは下記となります。

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 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データソースを使いましょう。

aws_caller_identityデータソースで取得した、AWSアカウントIDをoutputします。

[アカウント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バケットを設定します。

[アカウントA]アカウントBを信頼したIAMロールの作成

aws_iam_policy_documentデータソース内で${data.terraform_remote_state.account_b.account_id}という形でアカウントBのTerraform状態ファイルから読み込んだ値を使用することができます。

[アカウント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

それでは皆さん良いお年を。