Phase 1:网络(VCN)¶
目标¶
- 用 Terraform 建一套最小的网络,熟悉
init → plan → apply → destroy的流程 - 理解 state 是什么:Terraform 怎么知道"哪些资源是我建的"
网络资源(VCN、Gateway、Route Table 等)本身全部免费,适合用来练手。
架构¶
flowchart LR
Internet((Internet)) <--> IGW[Internet Gateway]
subgraph VCN["VCN 10.0.0.0/16"]
IGW --- RT["Route Table<br/>0.0.0.0/0 → IGW"]
RT --- SN["Public Subnet 10.0.1.0/24"]
SL["Security List<br/>SSH: my_ip / VCN 内部<br/>ICMP 3/4"] --- SN
end
和 AWS 的对应¶
| OCI | AWS | 说明 |
|---|---|---|
| VCN | VPC | |
| Internet Gateway | Internet Gateway | |
| Route Table | Route Table | |
| Security List | NACL(但默认是 stateful) | Subnet 级别的防火墙 |
| NSG(Network Security Group) | Security Group | VNIC 级别的防火墙,Phase 4 开始用 |
| Compartment | (近似)Resource Group / 账号划分 | 权限和费用的边界 |
要点¶
引用 = 依赖关系¶
引用了别的资源的属性,Terraform 就知道要先建 VCN 再建 IGW,删的时候反过来。不用自己写顺序。
Security List 的写法¶
protocol用协议号:"6"= TCP、"1"= ICMP、"all"= 全部- SSH 只放行本机 IP(
"${var.my_ip}/32") - ICMP type 3 code 4(Path MTU Discovery)要放行,否则大包会出问题。OCI 默认的 Security List 里也有这一条
标签¶
所有资源都打上同样的 freeform_tags,在 Console 上一眼就能看出是 Terraform 管理的:
state 是什么¶
terraform apply后生成terraform.tfstate:记录"代码里的资源 ↔ OCI 上实际的 OCID"plan时 Terraform 做三方比较:代码 ↔ state ↔ 实际资源- state 丢了,Terraform 就不认识已经建好的资源了,会想再建一套
- 一个目录(root module)一份 state →
01-network和02-compute要分别 apply,建的时候 01 → 02,删的时候 02 → 01
| plan 里的符号 | 意思 |
|---|---|
+ |
新建 |
~ |
原地修改(OCID 不变) |
-/+ |
删掉重建(# forces replacement,OCID 会变) |
- |
删除 |
Note
资源还不存在时,不管怎么改代码,plan 都只显示 +。~ 和 -/+ 要等资源建好之后才会出现。
这一阶段的 state 放在本机。多人操作、防丢失的问题在 Phase 3 用 remote state 解决。
实际操作¶
1. 先 plan,再 apply¶
cd 01-network
terraform init
terraform plan # → Plan: 5 to add, 0 to change, 0 to destroy.
terraform apply # 再显示一次 plan,输入 yes 才执行
看 plan 的要点:
- 先看最后一行
Plan: X to add, Y to change, Z to destroy。出现了没想到的 change 或 destroy,就先停下来 (known after apply):OCID 这类值要等资源建好才知道- 输出太长时:
terraform plan -no-color > plan.txt保存下来慢慢看
apply 的结果是 5 added,创建顺序是:
顺序是 Terraform 根据引用关系自动算出来的。删除时正好相反。
2. 资源还不存在时改代码¶
apply 之前把 project_name 改掉再 plan,符号还是 +,只有 display_name 的值变了。
→ state 是空的,比较对象不存在,所以不管怎么改都是"新建"。~ 和 -/+ 只有在资源建好之后才会出现。
3. drift:在 GUI 里手动改资源¶
- 在 Console 上给 Security List 手动加一条 Ingress Rule(
0.0.0.0/0、TCP 80) -
terraform plan:~ resource "oci_core_security_list" "public" { - ingress_security_rules { - protocol = "6" -> null - source = "0.0.0.0/0" -> null ... } # (3 unchanged blocks hidden) } Plan: 0 to add, 1 to change, 0 to destroy.~= 原地修改,- ingress_security_rules= 要删掉 GUI 里加的那条规则旧值 -> null= 这个值要被删掉- Terraform 在 plan 时先读一遍实际状态(refresh),再和代码比较。代码里没有这条规则,所以要把实际环境改回代码的样子
-
处理方法二选一:
方法 做法 这次 撤销手动改动 terraform apply✅ 选这个 → 再 plan 是 No changes把改动收进代码 把规则写进 main.tf→ plan 变成No changes(不用 apply)
IaC 的基本原则
变更都通过代码来做,不在 GUI 里手动改。手动改了,下次 apply 时会被代码覆盖回去。
4. destroy 和重建¶
terraform destroy # 先确认是 5 to destroy,再输入 yes
terraform state list # → 空
terraform apply # 建回来(Phase 2 的 VM 要放在这个 Subnet 里)
重建后 OCID 全部变了:Terraform 管理的是"代码里的地址 ↔ OCID"的对应关系,OCID 本身每次都是新的。
完整代码¶
01-network/main.tf
# ------------------------------------------------------------
# Phase 1:只建网络(VCN 及相关网络资源都免费)
# VCN ─ Internet Gateway
# └─ Public Subnet ─ Route Table(0.0.0.0/0 → IGW)
# └─ Security List(SSH 只放行本机 IP)
# ------------------------------------------------------------
locals {
# 所有资源共用的标签,方便在 GUI 里筛选、确认是 Terraform 管理的资源
common_tags = {
project = "tf-lab"
managed_by = "terraform"
}
}
# VCN:相当于 AWS 的 VPC
resource "oci_core_vcn" "main" {
compartment_id = var.compartment_ocid
cidr_blocks = [var.vcn_cidr]
display_name = "${var.project_name}-vcn"
dns_label = var.project_name # 只能用字母和数字,以字母开头,15 个字符以内
freeform_tags = local.common_tags
}
# Internet Gateway:Public Subnet 访问互联网的出入口
resource "oci_core_internet_gateway" "igw" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.main.id # 引用其他资源的属性 → Terraform 会自动判断创建顺序
display_name = "${var.project_name}-igw"
enabled = true
freeform_tags = local.common_tags
}
# Route Table:默认路由指向 IGW
resource "oci_core_route_table" "public" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.main.id
display_name = "${var.project_name}-rt-public"
route_rules {
destination = "0.0.0.0/0"
destination_type = "CIDR_BLOCK"
network_entity_id = oci_core_internet_gateway.igw.id
}
freeform_tags = local.common_tags
}
# Security List:Subnet 级别的防火墙
resource "oci_core_security_list" "public" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.main.id
display_name = "${var.project_name}-sl-public"
# 出站:全部放行
egress_security_rules {
destination = "0.0.0.0/0"
protocol = "all"
}
# 入站:SSH 只允许本机 IP(protocol 用协议号表示:6=TCP, 1=ICMP)
ingress_security_rules {
source = "${var.my_ip}/32"
protocol = "6"
tcp_options {
min = 22
max = 22
}
}
# 入站:SSH 允许 VCN 内部(Cloud Shell 的 Private Network 会拿到这个 VCN 里的私有 IP)
ingress_security_rules {
source = var.vcn_cidr
protocol = "6"
description = "SSH from inside VCN (Cloud Shell private network)"
tcp_options {
min = 22
max = 22
}
}
# 入站:ICMP type 3 code 4(Path MTU Discovery 需要,OCI 默认 Security List 里也有)
ingress_security_rules {
source = "0.0.0.0/0"
protocol = "1"
icmp_options {
type = 3
code = 4
}
}
freeform_tags = local.common_tags
}
# Public Subnet:Regional Subnet(不指定 AD,可以跨 AD 使用)
resource "oci_core_subnet" "public" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.main.id
cidr_block = var.public_subnet_cidr
display_name = "${var.project_name}-subnet-public"
dns_label = "public"
route_table_id = oci_core_route_table.public.id
security_list_ids = [oci_core_security_list.public.id]
prohibit_public_ip_on_vnic = false # false = 允许分配公网 IP(Public Subnet)
freeform_tags = local.common_tags
}