コンテンツにスキップ

Phase 6:OKE(Kubernetes)

目標

  • Terraform で OKE(Container Engine for Kubernetes)クラスタを作り、その上で nginx を動かしてロード・バランサ(Load Balancer)経由で公開する
  • マネージド Kubernetes で「誰が何を管理するのか」を理解する
  • Kubernetes の自己修復、スケール、drain を体験する

OKE とは

標準の upstream Kubernetes(CNCF 認定)で、kubectl / Helm / manifest の使い方は他の環境と同じです。≒ AWS EKS、Azure AKS、Google GKE。

OCI に依存しているのは「接続レイヤー」だけです。

K8s のオブジェクト / 機能 OCI 上で起きること
Service type: LoadBalancer cloud-controller-manager が OCI の LB を作成
PersistentVolumeClaim CSI driver が OCI のブロック・ボリューム(Block Volume) を作成
kubeconfig の認証 oci ce cluster generate-token(OCI IAM、≒ aws eks get-token)
Pod ネットワーク flannel(overlay)または VCN-Native(Pod が VCN の IP を直接持つ)

誰が何を管理するか

部分 管理者 説明
コントロール・プレーン(Control Plane、apiserver / etcd / scheduler など) Oracle Oracle 自身のテナンシ(Tenancy)内に作られ、ユーザーからこれらの VM は見えない。VCN(仮想クラウド・ネットワーク)には API エンドポイントの IP が 1 つ現れるだけ
VCN / サブネット(Subnet) / セキュリティ・リスト(Security List) 自分 ルールを間違える → ノードが登録できない、LB がつながらない
ワーカー・ノード(Worker Node) 自分(軽め) シェイプ(Shape)、台数、イメージ(Image)。OS パッチと K8s のアップグレードは自分で開始する(イメージを変更 → ノードを 1 台ずつ入れ替え)。kubelet などは OKE がプリインストール
アプリケーション 自分 kubectl。LB / ブロック・ボリュームは K8s が間接的に作るもので、Terraform の state には含まれない
クラスタ・タイプ コントロール・プレーン 備考
BASIC_CLUSTER 無料、SLA なし 今回はこちらを使用
ENHANCED_CLUSTER 時間課金 SLA、アドオン管理、virtual nodes など
ノード・タイプ 説明
Managed nodes ユーザーがノード・プール(Node Pool)を管理し、OKE が VM の作成と登録を担当(今回はこちらを使用)
Virtual nodes フルマネージドで VM の概念がない(≒ AWS Fargate)。Pod 単位の課金で、Enhanced が必要
Self-managed nodes 自分で VM を作ってクラスタに参加させる

ワーカー・ノードに対する考え方が変わる

Phase 2〜4 では nginx を cloud-init / Ansible で VM にインストールしていました。今は nginx がコンテナの中で動き、VM はコンテナを動かす場所にすぎません。 管理するのはその台数とバージョンで、中に何が入っているかは管理しません。壊れたら別の 1 台に入れ替え、修理はしません。

構成

flowchart LR
  PC[ローカル PC my_ip] -->|:80| LB
  CS[Cloud Shell<br/>private network] -->|kubectl :6443| API
  subgraph OCI_ORACLE["Oracle のテナンシ(見えない)"]
    CP[コントロール・プレーン]
  end
  subgraph VCN["VCN 10.6.0.0/16"]
    subgraph SAPI["api 10.6.0.0/28 プライベート"]
      API[K8s API エンドポイント]
    end
    subgraph SNODES["nodes 10.6.10.0/24 プライベート"]
      N1[ワーカー A1 #1]
      N2[ワーカー A1 #2]
    end
    subgraph SLB["lb 10.6.20.0/24 パブリック"]
      LB[Service LB<br/>flexible 10Mbps]
    end
  end
  CP --- API
  API --> N1 & N2
  LB -->|NodePort| N1 & N2
  • Pod CIDR 10.244.0.0/16(flannel の overlay で、VCN の IP は消費しない)/ Service CIDR 10.96.0.0/16
  • ワーカー:A1.Flex 1 OCPU / 6GB ×2、ブート・ボリューム(Boot Volume)50GB ×2 → Always Free の範囲内

設計上の選択

API エンドポイントはプライベートにし、kubectl は Cloud Shell で実行

  • ローカル PC には OCI CLI をインストールできない(Phase 5b のパス長の問題)が、kubeconfig の認証には oci コマンドが必要
  • Cloud Shell には oci と kubectl が最初から入っている
  • パブリック・エンドポイントにした場合:Cloud Shell の送信元 IP は固定されない → 6443 を 0.0.0.0/0 に開放するしかない(Phase 2 の SSH と同じ問題)
  • → API エンドポイントはプライベート・サブネットに置き、Cloud Shell は private network で nodes サブネットに接続、6443 は nodes の CIDR にだけ開放

サブネットごとにセキュリティ・リストを 1 つ

Phase 4 の network モジュール(module)はすべてのサブネットで 1 つのセキュリティ・リストを共有していましたが、OKE ではサブネットごとに必要なルールが異なります → 今回はモジュールを使わず、リソースを直接書きました。

my_ip ──:80──→ [lb] LB ──:30000-32767 / :10256──→ [nodes] ワーカー ×2
[nodes] ワーカー / Cloud Shell ──:6443 / :12250──→ [api] K8s API エンドポイント
[api] コントロール・プレーン ──all──→ [nodes](kubelet 10250 など)

OKE にセキュリティ・リストを自動変更させない

cloud-controller-manager はデフォルトで LB 用のルールをセキュリティ・リストに自分で追加します → Terraform で管理しているセキュリティ・リストで、plan のたびにドリフト(drift)が出ます。

→ NodePort の範囲(30000-32767)とヘルスチェック・ポート(10256)を Terraform で事前に許可し、manifest で自動管理を無効にします。

metadata:
  annotations:
    service.beta.kubernetes.io/oci-load-balancer-shape: "flexible"
    service.beta.kubernetes.io/oci-load-balancer-shape-flex-min: "10"
    service.beta.kubernetes.io/oci-load-balancer-shape-flex-max: "10"
    oci.oraclecloud.com/security-rule-management-mode: "None"

結果:LB を作成したあとも terraform plan は No changes のまま ✅

ノード・イメージの自動選択

oci_containerengine_node_pool_option の sources を名前で絞り込みます:aarch64 + K8s バージョンが一致 + GPU 版を除外し、名前の降順で先頭を取ります(= 日付が最新のもの)。

node_images = {
  for s in data.oci_containerengine_node_pool_option.this.sources : s.source_name => s.image_id
  if length(regexall("^Oracle-Linux-[0-9.]+-aarch64-.*-OKE-${replace(local.k8s_ver, ".", "\\.")}-", s.source_name)) > 0
  && !strcontains(s.source_name, "GPU")
}
node_image_name = reverse(sort(keys(local.node_images)))[0]

操作手順

1. terraform apply

14 added。クラスタは約 7 分、ノード・プールは約 3 分。

補足

Terraform がノード・プールの "Creation complete" を表示しても、ノードが ready とは限りません。SDK の get_node_pool().nodes で各ノードが ACTIVE であることを確認します。

2. Cloud Shell を VCN に接続し、kubeconfig を生成

Cloud Shell の Network → Ephemeral private network setup → VCN tflab6-vcn / Subnet tflab6-subnet-nodes

oci ce cluster create-kubeconfig --cluster-id <cluster OCID> \
  --file $HOME/.kube/config --region ap-osaka-1 \
  --token-version 2.0.0 --kube-endpoint PRIVATE_ENDPOINT

kubectl get nodes -o wide
NAME          STATUS   ROLES   VERSION   OS-IMAGE                  CONTAINER-RUNTIME
10.6.10.228   Ready    node    v1.36.4   Oracle Linux Server 9.8   cri-o://1.36.3
10.6.10.92    Ready    node    v1.36.4   Oracle Linux Server 9.8   cri-o://1.36.3
  • ワーカーだけで、control-plane ノードはない → マスターはマネージドなので見えない
  • ノード名はプライベート IP。arm64
  • コンテナ・ランタイムは CRI-O(Docker / containerd ではない)。K8s は CRI インターフェース経由で呼び出すだけなので、manifest には影響しない

3. nginx + Service LB をデプロイ

kubectl apply -f nginx.yaml
kubectl get svc nginx -w
NAME    TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)
nginx   LoadBalancer   10.96.189.195   203.0.113.130   80:32248/TCP
  • ローカル PC から http://<EXTERNAL-IP> にアクセス → Welcome to nginx!
  • 32248 は NodePort。トラフィック:ローカル PC → LB:80 → node:32248 → kube-proxy → Pod:80(別のノード上の Pod に転送されることもある)
  • SDK で LB が flexible 10/10 Mbps であることを確認(annotation が有効)
  • この LB の名前は UUID で、タグがなく、Terraform の state にも含まれない → コンソール(GUI)では誰が作ったのかわかりにくい

Kubernetes の実験

自己修復

kubectl delete pod <Pod 名>
kubectl get pods -o wide
  • すぐに新しい Pod が現れる:Deployment の期待値は replicas: 2 で、ReplicaSet が不足に気づいて補充する(reconcile loop)
  • 新しい Pod は名前も IP も変わる → Pod IP を直接使わず、Service 経由でアクセスする
  • Terraform がドリフトを修正するのは apply を実行したときだけだが、K8s は常にバックグラウンドで自動修正している

flannel の IP 割り当て

あるノード上の Pod はすべて 10.244.1.x、もう一方はすべて 10.244.0.x → flannel はノードごとに /24 を 1 つ割り当てる。

スケール

kubectl scale deployment nginx --replicas=4

scheduler が自動的に 2 + 2 に分け、2 台のノードに分散しました。

drain(ノード・メンテナンスの模擬)

kubectl drain <ノード名> --ignore-daemonsets --delete-emptydir-data
  • ノードが Ready,SchedulingDisabled になり、その上の nginx(と coredns)が退避され、もう一方のノードで再作成される
  • これがノード・プールのアップグレード時に OKE が自動で行うこと:cordon → drain → ノード入れ替え

drain 時に表示される DaemonSet = OKE が各ノードに配置しているコンポーネント:

DaemonSet 役割
kube-proxy Service / NodePort の転送
kube-flannel-ds Pod ネットワーク(overlay)
csi-oci-node OCI ブロック・ボリュームのマウント
proxymux-client コントロール・プレーン → ノードの通信経路。kubectl logs/exec はここを通る(セキュリティ・リストの 12250)
oke-node-problem-detector ノードの異常検知
oke-dataplane-observability-agent 監視エージェント

uncordon のあと

kubectl uncordon <ノード名>
kubectl scale deployment nginx --replicas=2
  • Pod は自動では戻りません:K8s がノードを選ぶのは新しい Pod を作るときだけで、自分から再配置はしない
  • 2 にスケールダウンしたとき削除されたのは新しく作られた 2 つ → 結果として 2 つの Pod が同じノード上に = 冗長性がない
  • 本番環境での対策:
    • topologySpreadConstraints / podAntiAffinity:Pod を別々のノードに強制的に分散
    • PodDisruptionBudget:drain 時に最低 N 個の Pod が動いていることを保証
    • すでに偏っている場合:kubectl rollout restart deployment nginx で再配置

クリーンアップ

順序が重要

  1. kubectl delete -f nginx.yaml → OKE が作った LB を OKE 自身に削除させる
  2. LB がなくなったことを確認
  3. Cloud Shell の Network を Public に戻す
  4. terraform destroy

順序が逆だと、LB が残って枠を使い続け(課金されることもあり)、VCN も削除できません。

ハマりどころ

nodes サブネットの削除に 8 分 41 秒かかった

サブネットに IP が 1 つ残っていて、名前は UUID → Cloud Shell の private network の VNIC でした。 Cloud Shell のウィンドウを閉じてから約 95 秒後にようやく解放されました。Terraform は競合があるとリトライし続けるので、最終的には自動で削除されました。

  • 予防:destroy の前に Cloud Shell の Network を Public に戻しておく
  • 調査:list_private_ips(subnet_id=...) でサブネットに何が残っているかを確認

kubectl delete pod 10.6.10.228 → NotFound

それはノード名です。Pod 名は nginx-598968dd58-xxxxx のような形式です。

コード全体

06-oke/oke.tf
# ------------------------------------------------------------
# OKE(Container Engine for Kubernetes):クラスタ = コントロール・プレーン(Control Plane)、ノード・プール(Node Pool)= ワーカー・ノード(Worker Node)
#   費用:BASIC_CLUSTER のコントロール・プレーンは無料。ENHANCED_CLUSTER は時間課金 → 使用しない
#         ワーカー = A1.Flex(Always Free 枠内)。Service LB は annotation で flexible 10Mbps を指定
# ------------------------------------------------------------

data "oci_identity_availability_domains" "this" {
  compartment_id = var.compartment_ocid
}

# ノード用のイメージ(Image):OKE 専用の Oracle Linux(kubelet 等がプリインストール済み)
#   名前の例:Oracle-Linux-9.8-aarch64-2026.09.18-0-OKE-1.36.4-1923
#   条件:aarch64(A1 は ARM)+ K8s バージョンと一致 + GPU 版は除外 → 名前の降順で先頭を取得(= 最新日付)
data "oci_containerengine_node_pool_option" "this" {
  node_pool_option_id = "all"
  compartment_id      = var.compartment_ocid
}

locals {
  k8s_ver = trimprefix(var.kubernetes_version, "v")

  node_images = {
    for s in data.oci_containerengine_node_pool_option.this.sources : s.source_name => s.image_id
    if length(regexall("^Oracle-Linux-[0-9.]+-aarch64-.*-OKE-${replace(local.k8s_ver, ".", "\\.")}-", s.source_name)) > 0
    && !strcontains(s.source_name, "GPU")
  }
  node_image_name = reverse(sort(keys(local.node_images)))[0]
}

resource "oci_containerengine_cluster" "this" {
  compartment_id     = var.compartment_ocid
  name               = "${local.name_prefix}-oke"
  kubernetes_version = var.kubernetes_version
  vcn_id             = oci_core_vcn.this.id
  type               = "BASIC_CLUSTER" # ENHANCED_CLUSTER は有料

  cluster_pod_network_options {
    cni_type = "FLANNEL_OVERLAY"
  }

  # API エンドポイントはプライベート・サブネット(Subnet)に配置し、パブリック IP は割り当てない → VCN(仮想クラウド・ネットワーク)内からのみアクセス可能(Cloud Shell のプライベート・ネットワーク)
  endpoint_config {
    subnet_id            = oci_core_subnet.api.id
    is_public_ip_enabled = false
  }

  options {
    # Service type=LoadBalancer のロード・バランサ(Load Balancer)はこのサブネットに作成
    service_lb_subnet_ids = [oci_core_subnet.lb.id]

    kubernetes_network_config {
      pods_cidr     = "10.244.0.0/16" # flannel のオーバーレイ用。VCN と重複しなければよい
      services_cidr = "10.96.0.0/16"  # ClusterIP 用
    }
  }

  freeform_tags = local.common_tags
}

resource "oci_containerengine_node_pool" "this" {
  compartment_id     = var.compartment_ocid
  cluster_id         = oci_containerengine_cluster.this.id
  name               = "${local.name_prefix}-pool"
  kubernetes_version = var.kubernetes_version

  node_shape = "VM.Standard.A1.Flex"
  node_shape_config {
    ocpus         = 1
    memory_in_gbs = 6
  }

  node_source_details {
    source_type             = "IMAGE"
    image_id                = local.node_images[local.node_image_name]
    boot_volume_size_in_gbs = 50 # 最小値。ブロック・ボリューム(Block Volume)は合計 200GB まで無料
  }

  node_config_details {
    size = var.node_count

    # 大阪は可用性ドメイン(Availability Domain)が 1 つのみ
    placement_configs {
      availability_domain = data.oci_identity_availability_domains.this.availability_domains[0].name
      subnet_id           = oci_core_subnet.nodes.id
    }

    node_pool_pod_network_option_details {
      cni_type = "FLANNEL_OVERLAY"
    }

    freeform_tags = local.common_tags
  }

  freeform_tags = local.common_tags
}
06-oke/security.tf
# ------------------------------------------------------------
# セキュリティ・リスト(Security List):サブネット(Subnet)ごとに 1 つ(ルールは OKE ドキュメントの flannel の例を参考に、Always Free の検証用に最小化)
#
#   my_ip ──:80──→ [lb] LB ──:30000-32767 / :10256──→ [nodes] ワーカー ×2
#   [nodes] ワーカー / Cloud Shell ──:6443 / :12250──→ [api] K8s API エンドポイント
#   [api] コントロール・プレーン ──all──→ [nodes](kubelet 10250 等)
#
# Service LB のルールは OKE に自動変更させない:
#   cloud-controller-manager はデフォルトでセキュリティ・リストに自らルールを追加する → Terraform 管理のセキュリティ・リストで毎回 plan にドリフト(drift)が出る
#   → ここで NodePort 範囲を事前に許可し、manifest 側で annotation により自動管理を無効化(k8s/nginx.yaml)
# ------------------------------------------------------------

locals {
  node_port_min    = 30000
  node_port_max    = 32767
  kube_proxy_hc    = 10256 # LB のヘルスチェックは kube-proxy の /healthz 宛て
  services_cidr_sg = data.oci_core_services.all.services[0].cidr_block
}

# ---- api(K8s API エンドポイント)----

resource "oci_core_security_list" "api" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.this.id
  display_name   = "${local.name_prefix}-sl-api"

  # ワーカーと Cloud Shell(いずれも nodes サブネット)→ kube-apiserver
  ingress_security_rules {
    source      = local.subnet_cidrs.nodes
    protocol    = "6"
    description = "kube-apiserver from nodes / Cloud Shell"
    tcp_options {
      min = 6443
      max = 6443
    }
  }

  # ワーカー → コントロール・プレーン(Control Plane)の通信(OKE 専用ポート)
  ingress_security_rules {
    source      = local.subnet_cidrs.nodes
    protocol    = "6"
    description = "worker to control plane"
    tcp_options {
      min = 12250
      max = 12250
    }
  }

  ingress_security_rules {
    source   = local.subnet_cidrs.nodes
    protocol = "1"
    icmp_options {
      type = 3
      code = 4
    }
  }

  # コントロール・プレーン → ワーカー(kubelet 10250、exec / logs 等)
  egress_security_rules {
    destination = local.subnet_cidrs.nodes
    protocol    = "all"
    description = "control plane to workers"
  }

  # コントロール・プレーン → OKE / OCI の API(サービス・ゲートウェイ(Service Gateway)経由)
  egress_security_rules {
    destination      = local.services_cidr_sg
    destination_type = "SERVICE_CIDR_BLOCK"
    protocol         = "6"
    description      = "to OCI services"
    tcp_options {
      min = 443
      max = 443
    }
  }

  freeform_tags = local.common_tags
}

# ---- nodes(ワーカー・ノード(Worker Node)+ Cloud Shell)----

resource "oci_core_security_list" "nodes" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.this.id
  display_name   = "${local.name_prefix}-sl-nodes"

  # ノード間はすべて許可(flannel のオーバーレイ = UDP 8472 / Pod 間の通信)
  ingress_security_rules {
    source      = local.subnet_cidrs.nodes
    protocol    = "all"
    description = "node to node / pod to pod"
  }

  ingress_security_rules {
    source      = local.subnet_cidrs.api
    protocol    = "6"
    description = "control plane to workers"
  }

  ingress_security_rules {
    source      = local.subnet_cidrs.lb
    protocol    = "6"
    description = "LB to NodePort"
    tcp_options {
      min = local.node_port_min
      max = local.node_port_max
    }
  }

  ingress_security_rules {
    source      = local.subnet_cidrs.lb
    protocol    = "6"
    description = "LB health check to kube-proxy"
    tcp_options {
      min = local.kube_proxy_hc
      max = local.kube_proxy_hc
    }
  }

  ingress_security_rules {
    source   = "0.0.0.0/0"
    protocol = "1"
    icmp_options {
      type = 3
      code = 4
    }
  }

  # エグレス:インターネット(NAT)、OCI サービス(SGW)、API エンドポイントをすべて許可
  egress_security_rules {
    destination = "0.0.0.0/0"
    protocol    = "all"
  }

  freeform_tags = local.common_tags
}

# ---- lb(Service LB)----

resource "oci_core_security_list" "lb" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.this.id
  display_name   = "${local.name_prefix}-sl-lb"

  ingress_security_rules {
    source      = "${var.my_ip}/32"
    protocol    = "6"
    description = "HTTP from my IP"
    tcp_options {
      min = 80
      max = 80
    }
  }

  egress_security_rules {
    destination = local.subnet_cidrs.nodes
    protocol    = "6"
    description = "LB to NodePort"
    tcp_options {
      min = local.node_port_min
      max = local.node_port_max
    }
  }

  egress_security_rules {
    destination = local.subnet_cidrs.nodes
    protocol    = "6"
    description = "LB health check to kube-proxy"
    tcp_options {
      min = local.kube_proxy_hc
      max = local.kube_proxy_hc
    }
  }

  freeform_tags = local.common_tags
}
06-oke/network.tf
# ------------------------------------------------------------
# Phase 6:OKE(Container Engine for Kubernetes)用のネットワーク(flannel オーバーレイ版)
#   VCN(仮想クラウド・ネットワーク) 10.6.0.0/16 ─ IGW / NAT GW / Service GW
#    ├─ api   10.6.0.0/28  private:K8s API エンドポイント(パブリック IP なし)
#    ├─ nodes 10.6.10.0/24 private:ワーカー・ノード(Worker Node)。Cloud Shell のプライベート・ネットワークもここに接続
#    └─ lb    10.6.20.0/24 public :Service type=LoadBalancer が作成する OCI ロード・バランサ(Load Balancer)
#   Pod の IP(10.244.0.0/16)は flannel がノード間でオーバーレイ化するため、VCN の IP は消費しない
#
# 04 のネットワーク・モジュール(module)は「全サブネット(Subnet)で 1 つのセキュリティ・リスト(Security List)を共用」する構成だが、
# OKE はサブネットごとに必要なルールが異なる → ここではモジュールを使わず、リソースを直接記述(セキュリティ・リストは security.tf)
# ------------------------------------------------------------

locals {
  name_prefix = "tflab6"
  vcn_cidr    = "10.6.0.0/16"

  subnet_cidrs = {
    api   = "10.6.0.0/28"
    nodes = "10.6.10.0/24"
    lb    = "10.6.20.0/24"
  }

  common_tags = {
    project    = "tf-lab"
    managed_by = "terraform"
    phase      = "6"
  }
}

resource "oci_core_vcn" "this" {
  compartment_id = var.compartment_ocid
  cidr_blocks    = [local.vcn_cidr]
  display_name   = "${local.name_prefix}-vcn"
  dns_label      = local.name_prefix

  freeform_tags = local.common_tags
}

# ---- Gateway ----

resource "oci_core_internet_gateway" "this" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.this.id
  display_name   = "${local.name_prefix}-igw"
  enabled        = true

  freeform_tags = local.common_tags
}

# ワーカー・ノードが Docker Hub 等の外部イメージを pull するために使用
resource "oci_core_nat_gateway" "this" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.this.id
  display_name   = "${local.name_prefix}-natgw"

  freeform_tags = local.common_tags
}

data "oci_core_services" "all" {
  filter {
    name   = "name"
    values = ["All .* Services In Oracle Services Network"]
    regex  = true
  }
}

# ワーカー・ノードの OKE への登録、OCIR イメージの pull、Cloud Shell のトークン取得(containerengine API)はすべてここを経由
resource "oci_core_service_gateway" "this" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.this.id
  display_name   = "${local.name_prefix}-sgw"

  services {
    service_id = data.oci_core_services.all.services[0].id
  }

  freeform_tags = local.common_tags
}

# ---- Route Table ----

resource "oci_core_route_table" "public" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.this.id
  display_name   = "${local.name_prefix}-rt-public"

  route_rules {
    destination       = "0.0.0.0/0"
    destination_type  = "CIDR_BLOCK"
    network_entity_id = oci_core_internet_gateway.this.id
  }

  freeform_tags = local.common_tags
}

resource "oci_core_route_table" "private" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.this.id
  display_name   = "${local.name_prefix}-rt-private"

  route_rules {
    destination       = "0.0.0.0/0"
    destination_type  = "CIDR_BLOCK"
    network_entity_id = oci_core_nat_gateway.this.id
  }

  route_rules {
    destination       = data.oci_core_services.all.services[0].cidr_block
    destination_type  = "SERVICE_CIDR_BLOCK"
    network_entity_id = oci_core_service_gateway.this.id
  }

  freeform_tags = local.common_tags
}

# ---- Subnet ----

resource "oci_core_subnet" "api" {
  compartment_id             = var.compartment_ocid
  vcn_id                     = oci_core_vcn.this.id
  cidr_block                 = local.subnet_cidrs.api
  display_name               = "${local.name_prefix}-subnet-api"
  dns_label                  = "api"
  route_table_id             = oci_core_route_table.private.id
  security_list_ids          = [oci_core_security_list.api.id]
  prohibit_public_ip_on_vnic = true

  freeform_tags = local.common_tags
}

resource "oci_core_subnet" "nodes" {
  compartment_id             = var.compartment_ocid
  vcn_id                     = oci_core_vcn.this.id
  cidr_block                 = local.subnet_cidrs.nodes
  display_name               = "${local.name_prefix}-subnet-nodes"
  dns_label                  = "nodes"
  route_table_id             = oci_core_route_table.private.id
  security_list_ids          = [oci_core_security_list.nodes.id]
  prohibit_public_ip_on_vnic = true

  freeform_tags = local.common_tags
}

resource "oci_core_subnet" "lb" {
  compartment_id             = var.compartment_ocid
  vcn_id                     = oci_core_vcn.this.id
  cidr_block                 = local.subnet_cidrs.lb
  display_name               = "${local.name_prefix}-subnet-lb"
  dns_label                  = "lb"
  route_table_id             = oci_core_route_table.public.id
  security_list_ids          = [oci_core_security_list.lb.id]
  prohibit_public_ip_on_vnic = false

  freeform_tags = local.common_tags
}
06-oke/k8s/nginx.yaml
# nginx ×2 + Service type=LoadBalancer
#   Cloud Shell で実行:kubectl apply -f nginx.yaml
#   削除時:先に kubectl delete -f nginx.yaml で、OKE が作成した LB を OKE 自身に削除させる → その後 terraform destroy
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.27 # multi-arch イメージ(Image)のため A1(arm64)でも動作
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  annotations:
    # flexible 10Mbps = Always Free のロード・バランサ(Load Balancer)(未指定の場合はデフォルトで 100Mbps の LB となり、Always Free を超過)
    service.beta.kubernetes.io/oci-load-balancer-shape: "flexible"
    service.beta.kubernetes.io/oci-load-balancer-shape-flex-min: "10"
    service.beta.kubernetes.io/oci-load-balancer-shape-flex-max: "10"
    # OKE にセキュリティ・リスト(Security List)を自動変更させない(ルールは Terraform の security.tf に記述済み)
    oci.oraclecloud.com/security-rule-management-mode: "None"
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 80