Phase 3:Module / Remote State / import¶
目标¶
解决到 Phase 2 为止暴露出来的 3 个问题:
| 问题 | 对策 |
|---|---|
| state 只在一台 PC 上(会丢、不能多人操作、没有锁) | remote state:Object Storage + backend "oci" |
| 要建多套环境就得复制粘贴代码 | module + for_each |
| Console 上手动建的资源 Terraform 管不了 | import |
结构¶
03-modules/
├── bootstrap/ ① 存 state 的 bucket(这个目录自己用 local state)
├── modules/ ② network / compute 零件
└── env/ ③ 调用 module,state 放在 bucket 里
① bootstrap:存 state 的 bucket¶
- 存 state 的 bucket 没法把自己的 state 存进自己 → 单独一个目录,用 local state("先有鸡还是先有蛋"问题)
- bucket 设置:
NoPublicAccess、versioningEnabled(state 改坏了可以找回旧版本)、prevent_destroy = true(防止误删) - Always Free:Object Storage 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]:新镜像发布后,VM 不会因为 data source 查到新镜像而被重建
for_each 和 count 的区别¶
| 地址 | 删掉中间一个时 | |
|---|---|---|
for_each |
this["public"](按 key) |
只删那一个 |
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 的效果
两个人用同一份 state:A 先 apply 了,B 再 apply 时结果是 0 changed,因为 B 读到的是 A 更新后的 state。
两个人同时 apply 的话,后执行的会被 state lock 挡住。
④ import:把手动建的资源纳入管理¶
在 Console 手动建了一个 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 在 Console 上看不到
格式是 networkSecurityGroups/<NSG OCID>/securityRules/<规则 ID>,规则 ID(例:675A86)Console 不显示。
→ 在临时目录里用 data source oci_core_network_security_group_security_rules 查。
import ID 的格式每种资源都不一样,要看 provider 文档的 Import 一节。
PowerShell 5.1 把参数在 . 处拆开
-backend-config=backend.hcl 会被拆成两个参数 → 要加引号 "-backend-config=backend.hcl"。
-out=xxx.tfplan、-generate-config-out=generated.tf 也一样。
No configuration files
plan 要在 root module(env/、bootstrap/)里执行,在 03-modules/ 里执行会报这个错。
完整代码¶
03-modules/env/main.tf
# ------------------------------------------------------------
# Phase 3-③:调用 module 建一套环境(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:加一条就多建一台(注意 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
}