HCP Terraform の変数: 2 種類の変数の置き場所と run での使われ方

公開

HCP Terraform の run は HashiCorp のクラウドの仮想マシンで動くので、AWS の認証情報や環境ごとに変えたい値は、.tf に書かずに HCP Terraform に渡しておく。その置き場所が workspace の変数で、種類は Environment variable と Terraform variable の 2 つ。

この記事では、workspace の変数に AWS のアクセスキーとバケット名の一部を置き、AWS provider で S3 バケットを 1 つ作る。2 種類の変数が run の中でどう使われるかを、次の 3 つで見る。

  • 変数が足りないときの run の失敗
  • 変数が揃ったときの plan と apply
  • .tf を変えずに、画面の値だけを変えた run

前提

次の状態から始める。

  • HCP Terraform の organization に workspace tfc-study-stg がある(作り方は「HCP Terraform の workspace と run: plan から apply までの流れと state の置き場所」を参照)
  • その state に random_pet.name が 1 つ入っていて、名前は national-starfish
  • workspace の Terraform のバージョンは 1.13.3
  • ローカルの開発環境に terraform v1.13.3 がある
  • AWS のアカウントがあり、コンソールにログインできる IAM ユーザーと、ローカルの aws コマンドが使える

workspace の変数

workspace には変数を置ける。種類は 2 つあり、違いは run の中でその値を誰が読むか。

種類run の中で誰が読むか.tf との関係例
Environment variableterraform を動かす前に run の実行環境の shell に export され、provider や terraform 自体が環境変数として読む.tf には出てこないprovider の認証情報(AWS_ACCESS_KEY_ID など)、terraform の動作を変える値(TF_LOG など)
Terraform variableterraform が variable ブロックの値として受け取り、.tf が var.名前 で読む.tf に variable ブロックの宣言があるバケットの名前、タグ、リージョン、リソースの数など、.tf の中で使う値

迷ったときは、ローカルで terraform apply を実行するなら export で渡す値か、-var で渡す値か、で分けられる。前者が Environment variable、後者が Terraform variable。

どちらも workspace ごとに置くので、環境ごとに値を変えられる点は同じ。stg と prod で別の AWS アカウントを使うなら、認証情報の Environment variable も workspace ごとに別の値を置く。

どちらの変数にも Sensitive の印を付けられる。付けると値は書き込み専用になり、画面でも API でも読めなくなる。

provider の認証情報の渡し方

HCP Terraform の run が AWS を操作するには、AWS provider に認証情報が要る。渡し方は 2 つある。

  • static credentials
    • IAM ユーザーのアクセスキーを、workspace の sensitive な Environment variable に置く
    • 設定が簡単で、ほとんどの provider で使える
    • アクセスキーを定期的に入れ替える必要があり、漏れたときの影響範囲が大きい
  • Dynamic provider credentials
    • アクセスキーを置かない。run のたびに HCP Terraform が AWS に身元を証明し、一時的な認証情報を受け取る
    • 公式ドキュメントが推奨する形

この記事は static credentials で進める。workspace の変数に何を置くと run の中で何が起きるかを、変数の種類ごとに見るため。

Dynamic provider credentials は、この記事の変数を消して置き換える形で別の記事に書く。

AWS に run 用の IAM ユーザーを作る

run が AWS に入るための身元を、AWS のコンソールで作る。作るものは 3 つ。

  1. IAM ポリシー tfc-study: 名前が tfc-study- で始まる S3 バケットに対して、すべての操作を許可する
  2. IAM ユーザー tfc-study: run が名乗るユーザー。1. のポリシーを付ける
  3. アクセスキー: 2. のユーザーの鍵。HCP Terraform に置く

IAM ポリシー

IAM の ポリシー → ポリシーの作成 で、JSON タブに次を貼り、ポリシー名を tfc-study にして作成する。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "StudyBuckets",
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::tfc-study-*",
        "arn:aws:s3:::tfc-study-*/*"
      ]
    }
  ]
}
  • Action
    • s3:* は S3 のすべての操作
  • Resource
    • 操作してよい相手。ARN(AWS の中のものを一意に指す文字列)で書く
    • arn:aws:s3:::tfc-study-* は、名前が tfc-study- で始まるバケットそのもの
    • arn:aws:s3:::tfc-study-*/* は、その中のオブジェクト

ポリシーに書いていない操作は拒否される。ほかのサービスや、ほかの名前のバケットには何もできない。

IAM ユーザーとアクセスキー

IAM ユーザー tfc-study を、コンソールへのアクセスなしで作り、ポリシー tfc-study を直接アタッチする。そのユーザーのアクセスキーを、ユースケース「サードパーティーサービス」で作る。

アクセスキーとシークレットアクセスキーの 2 つは、.tf にもローカルの ~/.aws にも書かない。置く場所は workspace の変数だけ。

S3 バケットと変数の定義

Terraform に作らせる S3 バケットと、workspace から値を受け取る変数を .tf に定義する。ファイルは役割ごとに 5 つに分ける。Terraform は同じディレクトリの .tf をすべて読むので、分け方は人が読みやすいように決めてよい。

versions.tf
terraform {
  cloud {
    organization = "ritolabo"

    workspaces {
      name = "tfc-study-stg"
    }
  }

  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.9"
    }
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

cloud ブロックは、すでにある workspace tfc-study-stg を指す。required_providers に hashicorp/aws を追加した。

providers.tf
provider "aws" {
  region = "ap-northeast-1"
}

AWS provider には、操作するリージョンだけを書く。認証情報は書かない。

AWS provider は、設定に認証情報がなければ、環境変数 AWS_ACCESS_KEY_ID と AWS_SECRET_ACCESS_KEY を探す(認証情報を探す順番では環境変数が最初)。

variables.tf
variable "environment" {
  description = "環境の名前。S3 バケットの名前と Environment タグに使う"
  type        = string
}

variable "owner" {
  description = "Owner タグの値。バケットを管理している人"
  type        = string
}

変数は 2 つ。どちらも default がないので、値がどこからも来なければ terraform plan はエラーで止まる。値は workspace の Terraform variable として置く。

main.tf
resource "random_pet" "name" {
  length = 2
}

resource "aws_s3_bucket" "study" {
  bucket = "tfc-study-${var.environment}-${random_pet.name.id}"

  tags = {
    Environment = var.environment
    Owner       = var.owner
  }
}

data "aws_caller_identity" "current" {}
  • random_pet.name
    • state にあるものと同じ定義。plan に差分は出ない
  • aws_s3_bucket.study
    • この記事で作るもの。名前は tfc-study-<environment>-<random_pet の名前>
    • tags はバケットに付ける キー = 値 のメモ。値は 2 つとも変数から来る
  • data.aws_caller_identity.current
    • AWS に「今、誰として入っているか」を問い合わせる。何も作らない
    • run がどの身元で AWS に入ったかを出力で確かめるために置く
outputs.tf
output "pet_name" {
  value = random_pet.name.id
}

output "bucket_name" {
  value = aws_s3_bucket.study.bucket
}

output "caller_arn" {
  description = "run が AWS に入ったときの身元(誰として API を呼んだか)"
  value       = data.aws_caller_identity.current.arn
}

アクセスキーを sensitive な Environment variable として置く

HCP Terraform の画面(ブラウザで https://app.terraform.io を開き、workspace tfc-study-stg を開いた状態)で、左のメニューの Variables を開く。

Workspace variables の + Add variable で、次の 2 つを追加する。

KeyValue種類Sensitive
AWS_ACCESS_KEY_IDアクセスキーEnvironment variable付ける
AWS_SECRET_ACCESS_KEYシークレットアクセスキーEnvironment variable付ける

保存すると、一覧の Value は Sensitive - write only になり、自分でも値を読めない。

Workspace variables の一覧。AWS_ACCESS_KEY_ID と AWS_SECRET_ACCESS_KEY の 2 行があり、Key の下に Sensitive の印、Value は Sensitive - write only、Category は env

この変数は、run の実行環境で terraform が動く前に、shell の export で環境変数になる。run の中では、次を実行してから terraform plan を実行したのと同じ状態になる。

$ export AWS_ACCESS_KEY_ID="(アクセスキーの値)"
$ export AWS_SECRET_ACCESS_KEY="(シークレットアクセスキーの値)"

AWS provider はこの環境変数を読んで AWS の API を呼ぶ。.tf には何も書かなくてよい。

Sensitive にすると変わること

  • 値が書き込み専用になる。画面でも API でも誰も読めない。新しい値での上書きはできる
  • Key や Description のような値以外の属性は変えられない。変えたいときは削除して作り直す
  • Sensitive でもそうでなくても、HCP Terraform は変数の値を暗号化して保存する。Sensitive は保存後に読めるかどうかの違い
  • Description は暗号化されず、平文で保存される。秘密を Description に書かない

認証情報を Environment variable に置く理由

Terraform variable に置いて、provider "aws" の access_key に var.〜 で渡すこともできる。それでも公式ドキュメントが Environment variable を推奨する理由は次のとおり。

  • Terraform variable の値は terraform に全文が渡る。output やリソースの引数に使うと、state やログに残ることがある
  • Environment variable の値は state に入らない

Sensitive の印が守るのは、Variables の画面と API から値を読まれること。state からは守らない。

Terraform variable を Sensitive にしても、その値を使ったリソースの属性は state に平文で入る。

Terraform variable がないと plan は失敗する

.tf を置いたディレクトリで terraform init を実行する。workspace はすでにあるので新しく作られず、provider が 2 つローカルに入る。

$ terraform init
Initializing HCP Terraform...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 6.0"...
- Finding hashicorp/random versions matching "~> 3.9"...
- Installing hashicorp/aws v6.72.0...
- Installed hashicorp/aws v6.72.0 (signed by HashiCorp)
- Installing hashicorp/random v3.9.1...
- Installed hashicorp/random v3.9.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

HCP Terraform has been successfully initialized!

この時点で workspace には Environment variable しかない。variables.tf の 2 つの変数に値がない状態で terraform plan を実行する。

$ terraform plan
Running plan in HCP Terraform. Output will stream here. Pressing Ctrl-C
will stop streaming the logs, but will not stop the plan running remotely.

Preparing the remote plan...

To view this run in a browser, visit:
https://app.terraform.io/app/ritolabo/tfc-study-stg/runs/run-k9s2yFhX1WcyhBet

Waiting for the plan to start...

Terraform v1.13.3
on linux_amd64
Initializing plugins and modules...
╷
│ Error: No value for required variable
│
│   on variables.tf line 1:
│    1: variable "environment" {
│
│ The root module input variable "environment" is not set, and has no default
│ value. Use a -var or -var-file command line argument to provide a value for
│ this variable.
╵
╷
│ Error: No value for required variable
│
│   on variables.tf line 6:
│    6: variable "owner" {
│
│ The root module input variable "owner" is not set, and has no default
│ value. Use a -var or -var-file command line argument to provide a value for
│ this variable.
╵
Operation failed: failed running terraform plan (exit 1)

エラーを出したのは、ローカルの terraform コマンドではなく、run の実行環境の terraform。

  • ローカルの terraform コマンドは、変数が足りないことを確かめずに .tf 一式を送る
  • 実行環境の terraform が .tf を読んだ時点で、default のない変数に値がないことに気づく

run は Plan errored の状態で終わり、Runs の一覧に赤く残る。run のページを開くと、Diagnostics の欄(terraform が出したエラーと警告の一覧)に、ターミナルと同じ 2 つのエラーが出ている。

Plan errored で終わった run のページ。Diagnostics に Error: No value for required variable が environment と owner の 2 つ表示されている

エラーの文面は、-var か -var-file で値を渡すよう言っている。ローカルの terraform plan -var="environment=stg" のように渡した値は、その run だけの変数として HCP Terraform に送られる。

ただし、run を GitHub への push で始める形にすると、ローカルの terraform コマンドは使わなくなる。そのときに値を置ける場所は workspace の変数だけなので、この記事も workspace に置く。

Terraform variable を置いて plan を通す

Variables の + Add variable で、Terraform variable を 2 つ追加する。Sensitive と HCL のチェックは付けない(HCL は list や map のような値を書くときに付ける)。

KeyValue種類
environmentstgTerraform variable
ownerrioTerraform variable

一覧は 4 行になる。Category の列で、Environment variable は env、Terraform variable は terraform と見分けられる。

Workspace variables の一覧。environment が stg、owner が rio で Category は terraform、AWS_ACCESS_KEY_ID と AWS_SECRET_ACCESS_KEY は Sensitive - write only で Category は env

同じ .tf のまま、もう一度 terraform plan を実行する。

$ terraform plan
Running plan in HCP Terraform. Output will stream here. Pressing Ctrl-C
will stop streaming the logs, but will not stop the plan running remotely.

Preparing the remote plan...

To view this run in a browser, visit:
https://app.terraform.io/app/ritolabo/tfc-study-stg/runs/run-FBYcbQF8LqPM8Pxz

Waiting for the plan to start...

Terraform v1.13.3
on linux_amd64
Initializing plugins and modules...
random_pet.name: Refreshing state... [id=national-starfish]
data.aws_caller_identity.current: Refreshing...
data.aws_caller_identity.current: Refresh complete after 1s [id=123456789012]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_s3_bucket.study will be created
  + resource "aws_s3_bucket" "study" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "tfc-study-stg-national-starfish"
      + bucket_domain_name          = (known after apply)
      + bucket_namespace            = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_region               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = "ap-northeast-1"
      + request_payer               = (known after apply)
      + tags                        = {
          + "Environment" = "stg"
          + "Owner"       = "rio"
        }
      + tags_all                    = {
          + "Environment" = "stg"
          + "Owner"       = "rio"
        }
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + cors_rule (known after apply)

      + grant (known after apply)

      + lifecycle_rule (known after apply)

      + logging (known after apply)

      + object_lock_configuration (known after apply)

      + replication_configuration (known after apply)

      + server_side_encryption_configuration (known after apply)

      + versioning (known after apply)

      + website (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + bucket_name = "tfc-study-stg-national-starfish"
  + caller_arn  = "arn:aws:iam::123456789012:user/tfc-study"

このとき run の実行環境で起きたことは 4 つ。

sequenceDiagram
    participant tf as terraform コマンド<br>(ローカル)
    participant hcp as HCP Terraform
    participant vm as run の実行環境<br>(Linux の仮想マシン)
    participant aws as AWS

    tf->>hcp: .tf 一式を送る(configuration version)
    hcp->>vm: Environment variable を export する<br>AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY
    hcp->>vm: Terraform variable を渡す<br>environment = stg、owner = rio
    vm->>vm: terraform plan を実行する
    vm->>aws: AWS provider が環境変数の鍵で<br>「今、誰か」を問い合わせる(aws_caller_identity)
    aws-->>vm: user/tfc-study
    vm-->>tf: plan のログ
  1. HCP Terraform が、workspace の Environment variable を実行環境の shell に export する
  2. HCP Terraform が、workspace の Terraform variable を terraform に渡す。.tf の var.environment と var.owner に値が入る
  3. AWS provider が環境変数の鍵を使い、data "aws_caller_identity" の問い合わせを AWS に送る。Refresh complete ... [id=123456789012] の行がそれ。plan の時点で認証が通っている
  4. bucket が tfc-study-stg-national-starfish になる。var.environment の stg と random_pet の名前をつないだ値。tags の 2 つの値も Terraform variable から来ている

apply で S3 バケットができる

terraform apply を実行する。plan の内容は上と同じなので、承認待ちから先を示す。

$ terraform apply

(plan の内容)

Plan: 1 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + bucket_name = "tfc-study-stg-national-starfish"
  + caller_arn  = "arn:aws:iam::123456789012:user/tfc-study"

Do you want to perform these actions in workspace "tfc-study-stg"?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_s3_bucket.study: Creating...
aws_s3_bucket.study: Creation complete after 5s [id=tfc-study-stg-national-starfish]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:
bucket_name = "tfc-study-stg-national-starfish"
caller_arn = "arn:aws:iam::123456789012:user/tfc-study"
pet_name = "national-starfish"

caller_arn が user/tfc-study になっている。run は、コンソールにログインしている自分のユーザーではなく、IAM ユーザー tfc-study として AWS に入っていた。

この身元は、workspace の Environment variable に置いた鍵で決まっている。

ローカルの aws コマンドで、バケットができたことを確かめる。

$ aws s3 ls | grep tfc-study
2026-11-08 11:31:53 tfc-study-stg-national-starfish

$ aws s3api get-bucket-tagging --bucket tfc-study-stg-national-starfish
{
    "TagSet": [
        {
            "Key": "Environment",
            "Value": "stg"
        },
        {
            "Key": "Owner",
            "Value": "rio"
        }
    ]
}

AWS のコンソールの S3 でも、バケットのプロパティに ARN とリージョン、タグに 2 つの値が見える。

S3 コンソールのバケット tfc-study-stg-national-starfish のプロパティ。バケットの概要に AWS リージョン ap-northeast-1 と ARN、ユーザー定義タグに Environment = stg と Owner = rio

HCP Terraform の workspace は、Resources が 3 になる。state に入っているものの数で、内訳は次の 3 つ。

  • random_pet.name
  • aws_s3_bucket.study
  • data.aws_caller_identity.current

data ブロックの結果も state に入るので、この数に含まれる。無料プランの上限 500 の対象になる managed resources(organization の Usage に出る数)は data を含まず、2 になる。

変数の値を変えて run し直す

.tf を変えずに、workspace の Terraform variable の値を変えるだけで、バケットのタグを変える。

Variables で owner の行の … → Edit を開き、Value を rio から ritolabo に変えて保存する。

この時点では、バケットのタグはまだ rio のまま。変数は次の run が読む値で、保存しただけでは run は始まらない。

$ aws s3api get-bucket-tagging --bucket tfc-study-stg-national-starfish --query "TagSet[?Key=='Owner'].Value" --output text
rio

ローカルの .tf は変わっていないので、terraform apply を実行し直す理由はない。画面の + New run → Plan and apply (standard) → Start run で run を始める。

公式ドキュメントも、変数の値を変えたが構成は変わっていないときの手動の run を、この用途として挙げている。

plan の結果は aws_s3_bucket.study の 1 to change。

  • tags と tags_all の Owner が "rio" → "ritolabo" に変わる
  • ほかの属性は変わらない
  • 作り直しではなく、その場で属性だけを変える update in-place の変更

plan が終わると承認待ちで止まるので、Confirm & Apply を押す。

画面から始めた run のページ。Triggered via UI、Applied、Resources changed は +0 ~1 -0。Plan finished に aws_s3_bucket.study の tags と tags_all の Owner が rio から ritolabo への変更、Apply finished に Updated、State versions created に新しい state へのリンク

Applied になったあとにタグを見ると、値が変わっている。

$ aws s3api get-bucket-tagging --bucket tfc-study-stg-national-starfish --query "TagSet[?Key=='Owner'].Value" --output text
ritolabo

この run の前後で変わったものと変わっていないものを分ける。

  • configuration version
    • apply のときに送ったもののまま
  • state
    • apply の結果のまま
  • var.owner に入る値
    • rio から ritolabo に変わった

変わったのは workspace の変数 1 つだけで、それで plan に差分が出た。

同じ .tf を複数の workspace で使い、変数だけを変えて環境ごとに違う結果にする形は、この延長にある。

もし environment のほうを変えていたら、バケットの名前が変わる。名前は後から変えられない属性なので、plan は消して作り直す -/+ destroy and then create replacement になる。

全体像

この記事で置いた 4 つの変数が、run の中でどう扱われたかを整理する。表の内容はこの 4 つについてのもので、種類の一般的な決まりではない。

AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY(Environment variable)environment、owner(Terraform variable)
run の中でどうなったかterraform を動かす前に、shell の環境変数として export されたterraform が variable ブロックの値として受け取った
値を読んだプログラムAWS provider。環境変数から認証情報を探したterraform。.tf の var.environment と var.owner に入れた
.tf に書いたものなしvariable ブロックの宣言。値は書いていない
足りないとどうなるかAWS provider が認証情報を見つけられず、plan が失敗するterraform が No value for required variable のエラーを出し、plan が失敗する
Sensitive付けた。値は誰も読めない付けていない。値は画面で見える
state に入ったか入っていない使った先の属性(bucket、tags)として入った

Environment variable を読むのは AWS provider に限らない。run の実行環境の環境変数なので、ほかの provider も terraform 自体も読める(TF_LOG を置けば terraform がログの量を変える)。この記事では、環境変数を読む側に AWS provider しかいなかった。

apply のときに動いたものは次のとおり。

flowchart LR
    me[自分]
    tfcmd[["terraform コマンド"]]

    subgraph hcp[HCP Terraform]
        subgraph ws["workspace: tfc-study-stg"]
            envvars["Environment variables<br>AWS_ACCESS_KEY_ID(Sensitive)<br>AWS_SECRET_ACCESS_KEY(Sensitive)"]
            tfvars["Terraform variables<br>environment = stg<br>owner = rio"]
            cv["configuration version<br>(送った .tf の写し)"]
            state[state]
        end
        subgraph vm["run の実行環境(Linux)"]
            terraform[["terraform v1.13.3"]]
            provider[["AWS provider"]]
        end
    end

    subgraph aws[AWS]
        user["IAM ユーザー tfc-study<br>+ ポリシー tfc-study"]
        bucket[("S3 バケット<br>tfc-study-stg-national-starfish")]
    end

    me -- "apply を実行" --> tfcmd
    me -- "変数を置く・変える" --> tfvars
    tfcmd -- ".tf 一式を送る" --> cv
    cv -- "読む" --> terraform
    tfvars -- "var.〜 として渡る" --> terraform
    envvars -- "export される" --> provider
    terraform -- "aws_s3_bucket を作る指示" --> provider
    provider -- "tfc-study として API を呼ぶ" --> user
    user -- "許可の範囲で作る" --> bucket
    terraform -- "読む・書く" --> state

Environment variable は AWS provider に、Terraform variable は terraform に渡る。どちらもローカルの .tf には書かれておらず、HCP Terraform の中にだけある。

まとめ

  • workspace の変数には Environment variable と Terraform variable の 2 種類がある
    • Environment variable は run の実行環境に export され、provider が読む
    • Terraform variable は .tf の variable ブロックの値として terraform が受け取る
  • AWS provider の認証情報は、IAM ユーザーのアクセスキーを Sensitive な Environment variable に置けば、.tf に何も書かずに渡せる。run はこの鍵の持ち主として AWS に入る
  • Sensitive にした値は画面でも API でも読めなくなる。守られるのは Variables の画面と API からで、state からではない
  • default のない変数に値がないと、run の実行環境の terraform が No value for required variable を出し、run は Plan errored で終わる
  • 画面で Terraform variable の値を変えても、run を始めるまでは何も変わらない。.tf が同じでも、変数が変われば plan に差分が出る
  • workspace の Resources の数は data ブロックの結果も含む。無料プランの上限の対象になる managed resources は data を含まない

参考 URL

Ritolabo

100%