TERRAFORM REGION 解説

リージョンはどこに書くべきか

CloudFront はバージニア北部、他は東京 — その理解は半分正しく、半分ずれています。別の会社で同じ構成をもう一度作るときに効くのは、region を変数にすることではありません。

TL;DR

  1. リージョンを書く必要があるのはリソースの一部だけ。グローバル / us-east-1 固定 / 通常のリージョナルの 3 種類がある。
  2. 事故が起きるのは region を書いたことではなく、部品の中に散らして書いたこと。引っ越しても警告なく元のリージョンに作られる。
  3. 移植性を担保するのは変数化ではなく、modules が region を知らないこと

01リージョンの 3 分類

「どこに置かれるか」はリソースの種類で決まる

AWS リソースをグローバル / バージニア北部固定 / 通常のリージョナルの 3 つの棚に仕分ける図
リージョンを書く必要があるのは 3 番目の棚だけ

AWS のリソースは region の扱いで 3 種類に分かれます。全部に region を指定する必要はありません。

種類region の扱い
グローバルIAM / Route 53 / CloudFront distributionregion の概念がない。どの provider から作っても同じ 1 個ができる
us-east-1 固定要求CloudFront に付ける ACM 証明書CloudFront 本体はグローバルだが、証明書は us-east-1 の在庫しか参照されない
通常のリージョナルVPC / EC2 / RDS / ECS / ALB / S3provider の region に作られる — ここだけが本当の可変点

北部バージニアが要るのは 2 番目だけです。 CloudFront 本体はグローバルなので region を書く必要がありません。実際この repo でも modules/aws/cloudfront/main.tfroute53_unit/main.tf に region の記述は 0 件で、これが正しい状態です。

02事故を決めるのは「書いた場所」

黙って東京に作られるのは、どういうときか

部品ごとに東京の荷札が貼られた Before と、司令塔から一括で指示される After の対比図
危険なのは region を書いたことではなく、書いた場所が自己申告しないこと

削る前は、security_group / route_table / internet_gateway の各リソースに region が散っていました。

BEFORE — 部品の中にリージョンが埋まっている

resource "aws_security_group" "db" {
  name   = "cp-db-stg"
  region = "ap-northeast-1"   // ← 部品が自分で東京を指している
  vpc_id = "vpc-xxxxxxxxxxxxxxxxx"
}

これが移植時の事故になります。 他社案件にコピーして provider を大阪に変えても、この 9 リソースだけ黙って東京に作られます。plan はエラーも警告も出しません — 正常に見えるまま間違ったリージョンに立ちます。

AFTER — 部品はリージョンを知らない

resource "aws_security_group" "db" {
  name   = "cp-db-${var.env}"
  vpc_id = var.vpc_id
}

削った後、region を書いている場所は 2 つだけになりました。既定は 1 箇所、例外は名前で分かる形です。

場所意味移植時
stg/versions.tf の providerこの構成の既定リージョン1 行変えれば全体が移る
stg/aws.tfacm_..._us_east_1意図的な例外 (証明書)変えてはいけない値

03変数にすべきか

判断軸は「1 つの state で複数リージョンを扱うか」ひとつだけ

環境ディレクトリがリージョンを宣言し、その下の部品箱はリージョンを持たない上下 2 層の図
移植性を生むのは変数化ではなく、部品がリージョンを知らないこと

扱わないなら、provider に直書きで構いません。 stg/ prd/ のような環境別ディレクトリ構成では root module = 環境そのものなので、環境固有値がそこに直書きされているのはむしろ正しい設計です。変数化しても「変数のデフォルト値をどこかに直書きする」に化けるだけで、可変点は増えません。

扱う場合の手段は 2 つあります。

手段書き方備考
provider aliasprovider = aws.us_east_1 をリソースごとに指す従来からの方法。alias の定義が必要
resource ごとの region 属性region = var.region をリソースに直接書くAWS provider 6 系。この repo が使っている方法

acm_unit — alias 定義ゼロで東京とバージニアを作り分けている

resource "aws_acm_certificate" "main" {
  region                    = var.region   // ← 呼び出し側が us-east-1 を渡す
  domain_name               = var.domain_name
  subject_alternative_names = var.subject_alternative_names
  validation_method         = "DNS"
}

modules 側で region を知っているのは acm_unit だけです。 そこは「呼び出し側がリージョンを選ぶ module」という設計意図が var.region として表現されている — 知っていていい唯一の場所です。

04この repo に残っている 1 箇所

「ベタ書きのせいで本当は違うところなのに」の実例

modules/aws/alb/outputs.tf に、Route 53 の alias レコードが ALB を指すためのホストゾーン ID がベタ書きされています。

// 東京リージョンのゾーンID(固定)
output "zone_id_ap_northeast_1" {
  value = "Z14GRHDCWA56QT"   // ← この値はリージョンごとに違う
}

まさに懸念どおりの形です。 大阪や北米に移植すると、コード上は東京の ID を指したままレコードが作られます。しかも provider を変えても追随しません。

terraform state show で確認したところ、aws_lb.cloud_pratica.zone_id 属性が同じ値を持っていました。定数をやめて属性参照にすれば、リージョンが変わっても自動で追随します。

output "zone_id_cloud_pratica" {
  value = aws_lb.cloud_pratica.zone_id   // ← リージョンに追随する
}
定数リージョン依存実害
ALB (alb/outputs.tf)Z14GRHDCWA56QTする移植時に誤ったゾーンを指す
CloudFront (cloudfront/outputs.tf)Z2FDTNDATAQYW2しない (全リージョン共通)なし — output 名が紛らわしいだけ

結論

  1. リージョンを書く必要があるのは「通常のリージョナル」だけ。グローバルと us-east-1 固定は別扱いにする。
  2. 既定は provider に 1 箇所、例外は名前で宣言する。部品 (modules/) には書かない。
  3. 変数化が要るのは 1 つの state で複数リージョンを扱うときだけ。今回は不要 — 海外に出る予定がないから、ではなく、state が 1 リージョンで閉じているから。