コンテンツにスキップ

Phase 1:ネットワーク(VCN)

目標

  • Terraform で最小構成のネットワークを作り、init → plan → apply → destroy の流れに慣れる
  • state(状態ファイル)とは何かを理解する:Terraform は「どのリソースを自分が作ったか」をどうやって把握しているのか

ネットワーク・リソース(VCN、ゲートウェイ(Gateway)、ルート表(Route Table)など)はすべて無料なので、練習に最適です。

構成

flowchart LR
  Internet((Internet)) <--> IGW[インターネット・ゲートウェイ]
  subgraph VCN["VCN 10.0.0.0/16"]
    IGW --- RT["ルート表<br/>0.0.0.0/0 → IGW"]
    RT --- SN["パブリック・サブネット 10.0.1.0/24"]
    SL["セキュリティ・リスト<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(ただしデフォルトはステートフル) サブネット(Subnet)単位のファイアウォール
NSG(ネットワーク・セキュリティ・グループ) Security Group VNIC 単位のファイアウォール。Phase 4 から使用
コンパートメント(Compartment) (近いもの)Resource Group / アカウント分割 権限と費用の境界

ポイント

参照 = 依存関係

resource "oci_core_internet_gateway" "igw" {
  vcn_id = oci_core_vcn.main.id   # VCN の属性を参照
}

他のリソースの属性を参照すると、Terraform は VCN を先に作ってから IGW を作ると判断します。削除時は逆順です。順序を自分で書く必要はありません。

セキュリティ・リストの書き方

  • protocol はプロトコル番号で指定:"6" = TCP、"1" = ICMP、"all" = すべて
  • SSH はローカル PC の IP だけ許可("${var.my_ip}/32")
  • ICMP type 3 code 4(Path MTU Discovery)は許可しておく必要があります。ないと大きなパケットで問題が起きます。OCI のデフォルトのセキュリティ・リストにもこのルールがあります

タグ

すべてのリソースに同じ freeform_tags を付けておくと、Terraform 管理のリソースだとコンソール(GUI)でひと目でわかります。

locals {
  common_tags = {
    project    = "tf-lab"
    managed_by = "terraform"
  }
}

state とは

  • terraform apply のあとに terraform.tfstate が生成されます。「コード上のリソース ↔ OCI 上の実際の OCID」の対応を記録したものです
  • plan のとき、Terraform は コード ↔ state ↔ 実際のリソース の 3 つを比較します
  • state をなくすと、Terraform は作成済みのリソースを認識できなくなり、もう 1 セット作ろうとします
  • ディレクトリ(ルート・モジュール、root module)ごとに state が 1 つ → 01-network と 02-compute は別々に apply します。作るときは 01 → 02、削除するときは 02 → 01
plan の記号 意味
+ 新規作成
~ インプレース変更(OCID は変わらない)
-/+ 削除して再作成(# forces replacement、OCID が変わる)
- 削除

補足

リソースがまだ存在しない段階では、コードをどう変えても plan は + しか表示しません。~ や -/+ はリソースを作成したあとにはじめて現れます。

この段階では state をローカルに置いています。複数人での作業や紛失対策の問題は、Phase 3 でリモート state(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、作成順は次のとおりでした。

VCN → IGW とセキュリティ・リスト(同時に作成。どちらも VCN にだけ依存)→ ルート表 → サブネット

順序は Terraform が参照関係から自動で決めます。削除時はちょうど逆になります。

2. リソースがない状態でコードを変える

apply の前に project_name を変えて plan すると、記号は + のまま、display_name の値だけが変わりました。

→ state が空で比較対象がないため、どう変えても「新規作成」になります。~ や -/+ はリソースを作成したあとにしか現れません。

3. ドリフト(drift):コンソールでリソースを手動変更する

  1. コンソールでセキュリティ・リストに Ingress ルールを手動で 1 つ追加(0.0.0.0/0、TCP 80)
  2. 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 = コンソールで追加したルールを削除しようとしている
    • 旧値 -> null = この値が削除される
    • Terraform は plan のときにまず実際の状態を読み込み(refresh)、コードと比較します。コードにこのルールがないので、実環境をコードどおりに戻そうとします
  3. 対処はどちらか一方です。

    方法 やり方 今回
    手動変更を取り消す terraform apply ✅ こちらを選択 → 再度 plan すると No changes
    変更をコードに取り込む ルールを main.tf に書く → plan が No changes になる(apply 不要)

IaC の基本原則

変更はすべてコードで行い、コンソールで手動変更はしません。手動で変えても、次の apply でコードの内容に戻されます。

4. destroy と再作成

terraform destroy       # 5 to destroy であることを確認してから yes を入力
terraform state list    # → 空
terraform apply         # 作り直す(Phase 2 の VM をこのサブネットに置くため)

作り直すと 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 はローカル PC の 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 はローカル PC の 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 のデフォルト・セキュリティ・リストにも含まれる)
  ingress_security_rules {
    source   = "0.0.0.0/0"
    protocol = "1"
    icmp_options {
      type = 3
      code = 4
    }
  }

  freeform_tags = local.common_tags
}

# パブリック・サブネット:リージョナル・サブネット(Regional Subnet。可用性ドメイン(Availability Domain)を指定せず、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 の割当てを許可(パブリック・サブネット)

  freeform_tags = local.common_tags
}