HCP Terraform の環境分離: stg と prod の workspace の設定

公開

stg への変更はマージしたらそのまま反映し、prod への変更は人が確認してから反映したい。

HCP Terraform では、環境ごとに workspace を作り、同じリポジトリにつなぐ。マージで始まった run を apply まで進めるか、承認を待つかは、workspace ごとに決められる。

この記事では、stg の workspace がつながっているリポジトリに prod の workspace を追加して、次の 3 つを確かめる。

  • 1 回のマージで stg は apply まで進み、prod は plan で止まること
  • 2 つの workspace で同じ値を使う変数を variable set で共有すること
  • 同じ .tf のまま、変数の値で stg にだけリソースを作ること

前提

次の状態から始める。(「HCP Terraform の GitHub 連携: VCS-driven workflow の設定」を参照)

  • workspace tfc-study-stg が GitHub のリポジトリの main ブランチにつながっている
  • この workspace の auto-apply は有効

リポジトリの .tf は、S3 バケットを 1 つ定義している。

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
    ManagedBy   = "terraform"
  }
}

環境を workspace で分ける仕組み

.tf は環境ごとに分けない。同じリポジトリの同じブランチに、環境の数だけ workspace をつなぐ。

環境ごとに違うものは workspace が持つ。

workspace が持つもの stg の workspace prod の workspace
state stg のリソース prod のリソース
Terraform variable environment stg prod
auto-apply 有効 無効

ブランチにマージすると、つながっている workspace のそれぞれで run が始まる。run は、その workspace の state と変数を使う。

設定するもの

HCP Terraform に 2 つ、AWS に 1 つ設定する。

  • prod の workspace
    • stg と同じリポジトリの同じブランチにつなぐ
    • auto-apply は無効のままにする
  • variable set
    • 変数をまとめて名前を付けたもの。workspace の外にあり、複数の workspace に適用できる
    • AWS の認証に使う変数を入れて、2 つの workspace に適用する
  • IAM ロールの信頼ポリシー
    • run が AWS を操作するときに引き受けるロールに付いている条件
    • prod の workspace の run もロールを引き受けられるように、条件を追加する

prod 用の state と変数を持たせるために workspace を作る

HCP Terraform の画面で New → Workspace を押し、次の順に設定する。

  1. workflow に Version Control Workflow を選ぶ
  2. stg の workspace と同じリポジトリを選ぶ
  3. workspace の名前を tfc-study-prod にして Create workspace を押す

作った直後に Configure Terraform variables のページが表示される。HCP Terraform がリポジトリの .tf を読み、default のない変数の入力欄を並べている。

この記事の .tf では environment と owner の 2 つ。environment には prod を入力する。

Terraform のバージョンを stg に合わせる

workspace が使う Terraform のバージョンは、workspace の設定で決まる。画面から作った workspace は、作った時点の最新のバージョンになる。

stg の workspace と違うバージョンになっていたので、Settings → General の Terraform Version を stg と同じ値に変えた。

auto-apply は同じページにある。既定は無効で、prod ではそのままにする。

AWS の認証の変数を共有するために variable set を作る

run が AWS を操作するための Environment variable は、どちらの workspace でも同じ値を使う。

  • TFC_AWS_PROVIDER_AUTH
  • TFC_AWS_RUN_ROLE_ARN

workspace ごとに同じ値を置くと、ロールを変えるときに workspace の数だけ直すことになる。

variable set に置くと、値は 1 か所で済む。

organization の Settings → Variable sets から作る。決めるものは 3 つ。

  • 名前
    • この記事では tfc-study-aws
  • 適用する範囲
    • organization のすべての workspace、project、選んだ workspace の 3 通りから選べる
    • この記事では stg と prod の 2 つの workspace を選んだ
  • 変数
    • 上の Environment variable を 2 つ入れた

適用された variable set は、workspace の Variables のページの下に表示される。

stg の workspace の Variables のページ。Workspace variables は environment と owner の 2 つ。下の Variable sets に tfc-study-aws があり、2 workspaces、2 variables と表示され、TFC_AWS_PROVIDER_AUTH と TFC_AWS_RUN_ROLE_ARN が並んでいる

ページの上の Workspace variables には、環境ごとに値が違う Terraform variable だけが残っている。

同じ Key の変数が workspace と variable set の両方にあると、workspace の変数が使われる。variable set に入れた変数は、workspace には置かない。

stg と prod で IAM ロールを分ける場合、TFC_AWS_RUN_ROLE_ARN は workspace ごとに違う値になる。

その場合は variable set に入れず、workspace の変数として置く。

prod の run が AWS を操作できるように信頼ポリシーに workspace を追加する

新しく作った VCS-driven workflow の workspace は、最初の run を画面から始める必要がある。

run が 1 つもない workspace は、PR やマージの通知が届いても run を始めない。

workspace tfc-study-prod で New run から run を始めると、plan がエラーで終わった。

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

Error: failed to refresh cached credentials, failed to retrieve credentials, operation error STS: AssumeRoleWithWebIdentity, https response error StatusCode: 403, ... api error AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity

AssumeRoleWithWebIdentity は、token を見せて IAM ロールを引き受ける AWS の操作。token は、HCP Terraform が run ごとに発行する身元の証明。

HCP Terraform はロールを引き受けようとして、AWS に断られている。

IAM ロールの信頼ポリシーが、stg の workspace の run だけを許しているため。

信頼ポリシーに prod の workspace を追加する

AWS のコンソールで IAM ロールの信頼ポリシーを開き、app.terraform.io:sub の条件に prod の workspace の行を追加する。

"StringLike": {
    "app.terraform.io:sub": [
        "organization:<organization>:project:Default Project:workspace:tfc-study-stg:run_phase:*",
        "organization:<organization>:project:Default Project:workspace:tfc-study-prod:run_phase:*"
    ]
}

値を配列にすると、どれか 1 つに一致する token が許される。

workspace:tfc-study-* のように * で 1 行にまとめる書き方もある。

その書き方では、名前が tfc-study- で始まる workspace を誰かが作ると、その run もロールを引き受けられる。

この記事では workspace を 1 つずつ書いた。

prod の最初の run は plan で止まる

もう一度 run を始めると、plan が成功した。

  • plan は 2 to add。random_pet.name と S3 バケットを新しく作る
  • run は Planned で止まり、Confirm & Apply のボタンが表示される。auto-apply が無効なため

prod の workspace の state は空なので、同じ .tf から prod 用のリソースが新しく作られる。stg のバケットは plan に出てこない。

Confirm & Apply を押すと apply が実行され、prod のバケットができた。

$ aws s3 ls | grep tfc-study
2026-11-15 10:29:56 tfc-study-prod-engaging-buzzard
2026-11-08 11:31:53 tfc-study-stg-national-starfish

環境ごとの差分をマージ前に確かめるために PR を出す

バケットにタグを 1 つ追加する変更で、main 宛ての PR を作る。

main.tf
  tags = {
    Environment = var.environment
    Owner       = var.owner
    ManagedBy   = "terraform"
    Project     = "tfc-study"
  }

PR を作ると、plan だけの run が workspace ごとに 1 つずつ始まる。PR のチェックの欄のリンクを開くと、結果が workspace ごとに並んでいる。

PR のチェックのリンク先のページ。tfc-study-stg と tfc-study-prod の 2 つの workspace が並び、どちらも Planned and finished で、Terraform plan: 0 to add, 1 to change, 0 to destroy と表示されている
  • stg と prod のどちらも 0 to add, 1 to change, 0 to destroy
  • 行の右の View run details から、その workspace の run のページを開ける

マージしたときに何が起きるかを、環境ごとに確かめられる。

マージして stg と prod の run の進み方を比べる

PR をマージすると、2 つの workspace でそれぞれ run が始まる。

workspace auto-apply run の進み方
tfc-study-stg 有効 plan のあと、そのまま apply に進んで Applied で終わる
tfc-study-prod 無効 plan のあと、Planned で止まる

prod の run のページは次のとおり。

prod の workspace の run のページ。名前は Merge pull request #2、状態は Planned。Plan finished は 1 to change、その下は Apply pending。Please review the following changes before continuing の欄に Confirm & Apply、Add comment、Discard run のボタンがある
  • 状態は Planned。plan は終わり、apply は Apply pending のまま始まっていない
  • Confirm & Apply と Discard run のボタンが表示される

この時点で、2 つのバケットのタグの Key を確かめる。

$ aws s3api get-bucket-tagging --bucket tfc-study-stg-national-starfish --query 'TagSet[].Key'
[
    "Project",
    "Environment",
    "Owner",
    "ManagedBy"
]

$ aws s3api get-bucket-tagging --bucket tfc-study-prod-engaging-buzzard --query 'TagSet[].Key'
[
    "Environment",
    "Owner",
    "ManagedBy"
]

stg のバケットには Project が追加されている。prod のバケットにはまだない。

main には同じコミットが入っているが、prod のバケットは Confirm & Apply を押すまで変わらない。押すと apply が実行され、prod のバケットにも Project が追加される。

Discard run は、止まっている run を apply せずに終わらせる。

main のコミットは残るので、次の run の plan に同じ変更がもう一度出る。変更を取りやめるときは、リポジトリでコミットを revert する。

環境ごとにリソースを出し分けるために bool の変数を使う

新しいリソースを、まず stg にだけ作りたいことがある。

.tf を環境ごとに分けずにこれを行うには、作るかどうかを Terraform variable で受け取る。値は workspace ごとに決める。

変数とリソースの定義

アーカイブ用の S3 バケットを追加する。作るかどうかは、bool の変数 enable_archive_bucket で受け取る。

variables.tf
variable "enable_archive_bucket" {
  description = "アーカイブ用の S3 バケットを作るかどうか"
  type        = bool
  default     = false
}
main.tf
resource "aws_s3_bucket" "archive" {
  count = var.enable_archive_bucket ? 1 : 0

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

  tags = {
    Environment = var.environment
    Owner       = var.owner
    ManagedBy   = "terraform"
    Project     = "tfc-study"
  }
}
  • count は、リソースを作る数。0 のときは作らない
  • var.enable_archive_bucket ? 1 : 0 は、変数が true なら 1、false なら 0 になる
  • default が false なので、値を置いていない workspace ではバケットは作られない

マージしただけではどの環境にも作られない

この変更を PR でマージすると、2 つの workspace で run が始まる。

どちらの run も 0 to add, 0 to change, 0 to destroy で、Planned and finished で終わった。

apply するものがないので、prod の run も承認を待たない。

バケットの定義は main に入ったが、どちらの workspace も enable_archive_bucket に値を置いていない。

stg の workspace の変数を true にする

stg の workspace の Variables に、Terraform variable を 1 つ追加する。

Key Value
enable_archive_bucket true

変数を追加しただけでは run は始まらない。stg の workspace で New run から run を始める。

stg の workspace の run のページ。名前は Triggered via UI、状態は Applied。Plan finished は 1 to add で aws_s3_bucket.archive[0] が作成対象、Apply finished は 1 added で Created と表示されている
  • plan は 1 to add。aws_s3_bucket.archive[0] が作られる
  • リポジトリの .tf は変えていない。変えたのは workspace の変数だけ
  • stg は auto-apply が有効なので、そのまま Applied で終わる

AWS のバケットを確かめると、stg にだけアーカイブ用のバケットができている。

$ aws s3 ls | grep tfc-study
2026-11-15 10:29:56 tfc-study-prod-engaging-buzzard
2026-11-08 11:31:53 tfc-study-stg-national-starfish
2026-11-15 11:02:58 tfc-study-stg-national-starfish-archive

prod にも作るときは、prod の workspace に同じ変数を追加して run を始め、Confirm & Apply を押す。

全体像

2 つの workspace の違いと、その違いを決めているものを並べる。

stg の workspace prod の workspace 決めているもの
run が使う .tf リポジトリの main リポジトリの main VCS connection
マージで始まる run apply まで進む plan で止まり、承認を待つ auto-apply
バケットの名前 tfc-study-stg-... tfc-study-prod-... Terraform variable environment
アーカイブ用のバケット 作る 作らない Terraform variable enable_archive_bucket
AWS の認証 IAM ロール 同じ IAM ロール variable set と信頼ポリシー

1 回のマージで動いたものは次のとおり。

flowchart LR
    me[自分]

    subgraph gh[GitHub のリポジトリ]
        main["main ブランチ"]
    end

    subgraph hcp[HCP Terraform]
        vs["variable set<br>TFC_AWS_PROVIDER_AUTH<br>TFC_AWS_RUN_ROLE_ARN"]
        subgraph stg["workspace: tfc-study-stg<br>auto-apply 有効"]
            runS[["run<br>plan → apply"]]
        end
        subgraph prod["workspace: tfc-study-prod<br>auto-apply 無効"]
            runP[["run<br>plan → 承認待ち → apply"]]
        end
    end

    subgraph aws[AWS]
        role["IAM ロール<br>信頼ポリシーに 2 つの workspace"]
        bS[("stg の S3 バケット")]
        bP[("prod の S3 バケット")]
    end

    me -- "マージする" --> main
    main -- "通知" --> runS
    main -- "通知" --> runP
    me -- "Confirm & Apply を押す" --> runP
    vs -- "変数" --> runS
    vs -- "変数" --> runP
    runS -- "引き受ける" --> role
    runP -- "引き受ける" --> role
    runS -- "タグを追加する" --> bS
    runP -- "タグを追加する" --> bP

図から読み取れることは 3 つ。

  • リポジトリは 1 つで、1 回のマージから run が workspace の数だけ始まる
  • HCP Terraform の画面を操作するのは、prod の run を承認するときだけ
  • stg と prod の違いは .tf にはない。workspace の auto-apply と変数にある

まとめ

  • 環境ごとに workspace を作り、同じリポジトリの同じブランチにつなぐ。state と変数は workspace ごとに別になる
  • マージで始まる run は、auto-apply が有効な workspace では apply まで進む
  • auto-apply が無効な workspace では plan で止まり、Confirm & Apply を押すと apply が実行される
  • 複数の workspace で同じ値を使う変数は variable set に置く。同じ Key の変数が workspace にあると、workspace の変数が使われる
  • workspace を追加したら、IAM ロールの信頼ポリシーにもその workspace を追加する
  • bool の変数と count を組み合わせると、同じ .tf のまま、リソースを作るかどうかを workspace の変数で決められる

参考 URL

Ritolabo

100%