Phase 3:Module / Remote State / import¶
目標¶
Phase 2 までで見えてきた 3 つの問題を解決します。
| 問題 | 対策 |
|---|---|
| state(状態ファイル)が 1 台の PC にしかない(紛失のおそれ、複数人で操作できない、ロックがない) | リモート state(remote state):オブジェクト・ストレージ(Object Storage)+ backend "oci" |
| 環境を複数作るにはコードをコピー&ペーストするしかない | モジュール(module) + for_each |
| コンソール(GUI)で手動作成したリソースを Terraform で管理できない | import |
構成¶
03-modules/
├── bootstrap/ ① state を保存するバケット(このディレクトリ自身は local state)
├── modules/ ② network / compute の部品
└── env/ ③ module を呼び出す。state はバケットに保存
① bootstrap:state を保存するバケット¶
- state を保存するバケット(Bucket)は、自分自身の state を自分の中に保存できない → 別ディレクトリにして local state を使う(「鶏が先か卵が先か」問題)
- バケットの設定:
NoPublicAccess、versioningEnabled(state を壊しても旧バージョンを取り戻せる)、prevent_destroy = true(誤削除防止) - Always Free:オブジェクト・ストレージ 20GB + 月 5 万回の API リクエスト。state の使用量は無視できる程度
② module¶
modules/network:VCN / IGW / Route Table / Security List / Subnet(for_each)
modules/compute:NSG / NSG rule / VM
- module の中には
provider "oci" {}を書かず、required_providersだけを書く。認証とリージョン(Region)は呼び出し側(ルート・モジュール、root module)が決める - 同種のリソースが複数あるときは
for_each、「0 個か 1 個か」はcount(参照時はthis[0]) ignore_changes = [source_details[0].source_id]:新しいイメージ(Image)が公開されても、data source が新イメージを返したせいで VM が再作成される、ということが起きない
for_each と count の違い¶
| アドレス | 途中の 1 つを削除したとき | |
|---|---|---|
for_each |
this["public"](key で指定) |
その 1 つだけが削除される |
count |
this[0]、this[1](番号で指定) |
後ろの番号がすべてずれる → 再作成される |
→ 複数のリソースは原則 for_each を使います。
③ env:remote state¶
# backend.hcl(Git には入れず、backend.hcl.example をコミット)
bucket = "tflab-tfstate"
namespace = "<Object Storage namespace>"
key = "03-modules/env/terraform.tfstate"
region = "ap-osaka-1"
- backend ブロックでは variable が使えない → 値は別ファイルに書き、init 時に渡す(partial configuration)
- 以前は S3 互換 API + 顧客秘密キー(Customer Secret Key)が必要だったが、1.12 以降は
~/.oci/configの認証をそのまま使える - 確認:
terraform state list/terraform state pull
remote state の効果
2 人で同じ state を使う場合:A が先に apply し、そのあと B が apply すると結果は 0 changed になります。B が読むのは A が更新したあとの state だからです。
2 人が同時に apply した場合は、あとから実行したほうが state lock で止められます。
④ import:手動で作ったリソースを管理下に入れる¶
コンソールで NSG(ネットワーク・セキュリティ・グループ)+ ルールを手動で作成し、それを Terraform の管理下に移しました。
-
importblock を書く: -
terraform plan "-generate-config-out=generated.tf"→ resource のコードが自動生成される - 生成されたコードを整理する:OCID を変数や参照に置き換え、Oracle が自動で付ける
defined_tagsを削除し、common_tags を追加 plan→2 to import, 1 to change(タグをインプレース更新)→apply→ もう一度planするとNo changes- import block を削除する(import が終われば不要)
import は自動で検索しない
Terraform は OCI 上にどんなリソースがあるかを探しに行きません。「どの OCID がコード上のどのアドレスに対応するか」は人が指定する必要があります。 コンパートメント(Compartment)単位でまとめてエクスポートしたい場合は、OCI リソース・マネージャ(Resource Manager)のリソース検出(Resource Discovery)か Terraformer を使います。
ハマりどころ¶
NSG ルールの import ID がコンソールで見えない
形式は networkSecurityGroups/<NSG OCID>/securityRules/<ルール ID> ですが、ルール ID(例:675A86)はコンソールに表示されません。
→ 一時ディレクトリで data source oci_core_network_security_group_security_rules を使って調べます。
import ID の形式はリソースの種類ごとに異なるので、provider ドキュメントの Import の節を確認します。
PowerShell 5.1 が引数を . の位置で分割する
-backend-config=backend.hcl が 2 つの引数に分割される → 引用符で囲んで "-backend-config=backend.hcl" にします。
-out=xxx.tfplan、-generate-config-out=generated.tf も同様です。
No configuration files
plan はルート・モジュール(env/、bootstrap/)で実行します。03-modules/ で実行するとこのエラーになります。
コード全体¶
03-modules/env/main.tf
# ------------------------------------------------------------
# Phase 3-③:モジュール(module)を呼び出して環境を 1 セット作成(state はオブジェクト・ストレージ(Object Storage))
# 01/02 の環境(tflab-*、10.0.0.0/16)はそのまま残し、ここでは別に tflab3-*(10.1.0.0/16)を作成
# ------------------------------------------------------------
locals {
name_prefix = "tflab3"
vcn_cidr = "10.1.0.0/16"
common_tags = {
project = "tf-lab"
managed_by = "terraform"
phase = "3"
}
# 作成する VM:1 行追加するごとに 1 台増える(Always Free の上限に注意:A1 は合計 4 OCPU / 24GB、ブート・ボリューム(Boot Volume)は合計 200GB)
instances = {
web = { subnet = "public", assign_public_ip = true }
}
}
module "network" {
source = "../modules/network"
compartment_ocid = var.compartment_ocid
name_prefix = local.name_prefix
vcn_cidr = local.vcn_cidr
vcn_dns_label = local.name_prefix
subnets = {
public = { cidr = "10.1.1.0/24", dns_label = "public", is_public = true }
private = { cidr = "10.1.2.0/24", dns_label = "private", is_public = false }
}
# 利用中のネットワークで 22 番ポートがブロックされている → VCN(仮想クラウド・ネットワーク)内部(Cloud Shell の Private Network)のみ許可
ssh_allowed_cidrs = [local.vcn_cidr]
freeform_tags = local.common_tags
}
# module でも for_each が使える:local.instances の件数分だけ呼び出される
# state 上は module.compute["web"].oci_core_instance.this のようなアドレスになる
module "compute" {
source = "../modules/compute"
for_each = local.instances
compartment_ocid = var.compartment_ocid
name = "${local.name_prefix}-${each.key}"
vcn_id = module.network.vcn_id
subnet_id = module.network.subnet_ids[each.value.subnet]
assign_public_ip = each.value.assign_public_ip
ssh_public_key = file(var.ssh_public_key_path)
user_data = file("${path.module}/cloud-init.yaml")
http_allowed_cidrs = ["${var.my_ip}/32"]
freeform_tags = local.common_tags
}