OKE の設計と実践¶
ひとことで
Oracle Kubernetes Engine(OKE)= 標準の Kubernetes + OCI への接続レイヤー(LB、ブロック・ボリューム、IAM)です。 設計の核心は 外部に出すのは LB だけ、API エンドポイントはプライベート、セキュリティ・リストは Terraform だけが管理する こと。運用の核心は 3 つの層(Terraform / K8s オブジェクト / K8s が間接的に作る OCI リソース)を区別し、削除は 上の層から 行うことです。
このページは Phase 6 と Phase 11(第 8 課と第 2 部の実測)をまとめたものです。K8s そのものの原理は Kubernetes の原理、VCN(仮想クラウド・ネットワーク)/ セキュリティ・リストの基本は ネットワーク設計 を参照してください。
「要確認」について
「要確認」と書いた項目はまだ実測しておらず、ドキュメントまたは推測に基づいて書いています。
1. まず構成要素を知る¶
| OKE | AWS | ひとことで |
|---|---|---|
| OKE クラスタ | EKS クラスタ | マネージドの K8s。コントロール・プレーンは Oracle 自身のテナンシ(Tenancy)内に作られ、ユーザーからは見えない |
BASIC_CLUSTER / ENHANCED_CLUSTER |
— | Basic:コントロール・プレーンは無料、SLA なし。Enhanced:時間課金(SLA、アドオン管理、virtual nodes など) |
| ノード・プール(Node Pool、managed nodes) | Managed node group | ワーカー・ノード(VM)のグループ。OKE が VM の作成と登録を担当 |
| virtual nodes | Fargate | VM の概念がなく、Pod 単位の課金。Enhanced が必要 |
| API エンドポイント | 同左 | apiserver の入口。VCN にはその IP が 1 つ現れるだけ |
oci ce cluster generate-token |
aws eks get-token |
kubeconfig の認証:OCI IAM でトークンを取得 |
誰が何を管理するか¶
| 部分 | 管理者 | 説明 |
|---|---|---|
| コントロール・プレーン(apiserver、etcd、scheduler、CCM) | Oracle | バックアップ、アップグレードは Oracle が行う |
| VCN / サブネット(Subnet) / セキュリティ・リスト(Security List) | 自分 | ルールを間違える → ノードが登録できない、LB がつながらない |
| ワーカー・ノード | 自分(軽め) | シェイプ(Shape)、台数、イメージ。kubelet などは OKE がプリインストール。OS パッチと K8s のアップグレードは自分で開始する(イメージを変更 → 1 台ずつ入れ替え) |
| アプリケーション | 自分 | kubectl。LB / ブロック・ボリュームは K8s が間接的に作る |
ワーカー・ノードの考え方は VM とは変わります。nginx はコンテナの中で動き、VM はコンテナを動かす場所にすぎません。管理するのは台数とバージョンで、壊れたら入れ替え、修理はしません。
2. 原理:3 つの層¶
同じ nginx のサイトが 3 つの層にまたがっています。問題が起きたら、まずどの層かを切り分けます。
flowchart TB
subgraph L1["① クラウド基盤:Terraform"]
VCN["VCN / サブネット<br/>セキュリティ・リスト"]
CL["OKE クラスタ<br/>ノード・プール"]
end
subgraph L2["② K8s オブジェクト:kubectl"]
DEP["Deployment<br/>→ ReplicaSet → Pod"]
SVC["Service<br/>type=LoadBalancer"]
end
subgraph L3["③ 間接的に作られる OCI リソース:state 外"]
LB["OCI LB<br/>CCM が作成"]
BV["ブロック・ボリューム<br/>PVC → CSI が作成"]
end
SVC -->|"CCM"| LB
L1 --> L2
| 層 | 何を管理するか | 誰が担当するか |
|---|---|---|
| ① クラウド基盤 | VCN、サブネット、クラスタ、ノード・プール | Terraform(06-oke/) |
| ② K8s オブジェクト | Deployment、Pod、Service | kubectl で期待する状態を送信。コントローラがオブジェクトを作り、scheduler が node を選び、kubelet がコンテナを起動 |
| ③ 間接的に作られるクラウド・リソース | Service の OCI LB、PVC のブロック・ボリューム | cloud-controller-manager(CCM)/ CSI。Terraform はこれらを知らない |
ここから 2 つのルールが導かれます。
- 削除は上の層から:先に ② の Service / PVC を削除し、③ の LB / ブロック・ボリュームが消えたことを確認してから ① を destroy する(第 8 節)
- ③ の層が ① の層のものを変更しに行く:CCM はデフォルトでセキュリティ・リストにルールを追加する → Terraform と衝突する(第 4 節)
3. ネットワーク設計¶
VCN 10.6.0.0/16
├─ api 10.6.0.0/28 private API エンドポイント(IP は数個で足りる → /28)
├─ nodes 10.6.10.0/24 private ワーカー・ノード(+ Cloud Shell の private network)
└─ lb 10.6.20.0/24 public LB だけ
Pod CIDR 10.244.0.0/16(flannel の overlay)/ Service CIDR 10.96.0.0/16
public は lb だけです:インターネットから直接届くのは LB だけで、node と API エンドポイントにはパブリック IP がありません(ネットワーク設計 と同じ考え方)。nodes のルーティング:0.0.0.0/0 → NAT ゲートウェイ(Docker Hub のイメージを pull)、Oracle サービス → サービス・ゲートウェイ(OKE への登録など)。
サブネットごとにセキュリティ・リストを 1 つ¶
3 つのサブネットは役割が違い、必要なルールも違います → Phase 4 の network モジュール(すべてのサブネットで 1 つのセキュリティ・リストを共有)は再利用せず、サブネットごとに書きました。
| 方向 | ポート | 理由 |
|---|---|---|
ローカル PC → lb |
TCP 80 | サイトの入口(ローカル PC の IP のみ許可) |
lb → nodes |
TCP 30000-32767 | LB から NodePort へ転送 |
lb → nodes |
TCP 10256 | LB のヘルスチェックが kube-proxy にアクセス |
nodes / Cloud Shell → api |
TCP 6443、12250 | kubelet / kubectl → apiserver(6443)、ワーカー → コントロール・プレーン(12250、proxymux の通り道で、kubectl logs/exec はここを通る) |
api → nodes |
TCP すべて | コントロール・プレーン → kubelet(10250 など) |
nodes ↔ nodes |
すべて | flannel の VXLAN(UDP 8472)、Pod 間の通信 |
# 06-oke/security.tf(抜粋):nodes サブネットのイングレス・ルール
ingress_security_rules {
source = local.subnet_cidrs.nodes
protocol = "all" # flannel の overlay(UDP 8472)/ Pod 間
description = "node to node / pod to pod"
}
ingress_security_rules {
source = local.subnet_cidrs.lb
protocol = "6"
description = "LB to NodePort"
tcp_options {
min = local.node_port_min # 30000
max = local.node_port_max # 32767
}
}
nodes ↔ nodes の全許可を削除したら(講義での分析)
VXLAN がブロックされる → node をまたぐ Pod 同士が通信できなくなります。厄介なのは一見すべて正常に見えることです。kubectl get nodes はすべて Ready(kubelet → apiserver は nodes → api を通る)なのに、サイトは開けたり、タイムアウトしたりし(別の node 上の Pod が選ばれたときに失敗)、一部の Pod では名前解決ができません(CoreDNS が片方の node にしかない)。
API エンドポイントはプライベートにし、kubectl は Cloud Shell で使う¶
# 06-oke/oke.tf(抜粋)
endpoint_config {
subnet_id = oci_core_subnet.api.id
is_public_ip_enabled = false # パブリック IP を割り当てない → VCN 内からしかアクセスできない
}
- クラスタ全体を操作できる入口をインターネットに置かない
- パブリック・エンドポイントにした場合:Cloud Shell の送信元 IP は固定されない → 6443 を
0.0.0.0/0に開放するしかない(SSH と同じ問題) - 代償:kubectl は VCN 内でしか使えない → Cloud Shell の private network で
nodesサブネットに接続。ローカル PC には OCI CLI もインストールできない(kubeconfig の認証にはociコマンドが必要)が、Cloud Shell にはociとkubectlが最初から入っている
4. OKE にセキュリティ・リストを自動変更させない¶
CCM は LB を作るとき、デフォルトでセキュリティ・リストにルールを自分で追加します → Terraform で管理しているセキュリティ・リストで、plan のたびにドリフト(drift)が出ます。
対策:NodePort の範囲とヘルスチェック・ポートを Terraform で事前に許可し(第 3 節)、manifest で自動管理を無効にします。
# 06-oke/k8s/nginx.yaml(抜粋)
metadata:
annotations:
# flexible 10Mbps = Always Free の LB(書かないとデフォルトの 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.tf に書いてある)
oci.oraclecloud.com/security-rule-management-mode: "None"
結果:LB を作成したあとも terraform plan は No changes のままです。
原則:1 つのリソースは 1 つのツールだけに管理させる。 同じセキュリティ・リストを Terraform と CCM が同時に変更すると、両者がずっと上書きし合います。
5. ノード・イメージの自動選択¶
OKE の node は専用の Oracle Linux イメージ(kubelet などがプリインストール)を使い、名前は Oracle-Linux-9.8-aarch64-2026.09.18-0-OKE-1.36.4-1923 のような形です。
# 06-oke/oke.tf(抜粋):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]
イメージの OCID を固定しないので、コードは変更不要です。Phase 6 のときは 2026.09.18 版が選ばれ、Phase 11 で再度 apply したときは自動で新しい 2026.10.16 版が選ばれました。
6. 実測:Service LB で実際に何が作られたか¶
Phase 11 で、Service type=LoadBalancer によって作られた LB を SDK で調べました。
| 項目 | 実測 |
|---|---|
| シェイプ | flexible、min = max = 10Mbps(annotation が有効 = Always Free) |
| リスナー | 名前は TCP-80、プロトコルは TCP(L4)。LB 製品は L7 に対応しているが、ここでは L4 として使われている |
| バックエンド | node 2 台の NodePort、ポリシーは ROUND_ROBIN |
| ヘルスチェック | HTTP :10256/healthz(kube-proxy)。アプリの 80 番ポートではない |
| 名前 / タグ | 名前は UUID、タグなし、Terraform の state にも入っていない → コンソール上では誰が作ったのかわかりにくい |
| node のフォルト・ドメイン | FD を指定しなかったところ、2 台はそれぞれ FD-1 / FD-3 に。観察は 1 回だけなので、毎回分散が保証されるとは言えない |
トラフィック:ローカル PC → LB:80 → node:NodePort → kube-proxy(iptables)→ Pod:80。別の node 上の Pod に転送されることもあります(原理は Kubernetes の原理 を参照)。
Events で見えたコンポーネントの連鎖:Deployment controller → ReplicaSet controller → scheduler → kubelet(Pulling / Started)。CCM:EnsuringLoadBalancer → already in progress で 1 回失敗して再試行 → EnsuredLoadBalancer。
Pod ネットワーク:flannel が node ごとに範囲を割り当てる¶
- 通常の Pod の IP は
10.244.x.x。flannel、kube-proxy、CSI などのシステム Pod は hostNetwork で、node の IP をそのまま使う - 各 node は Pod CIDR の一部を割り当てられる:Phase 6 は /24(1 台が
10.244.1.x、もう 1 台が10.244.0.x)、Phase 11 は /25(10.244.0.0/25と10.244.0.128/25) - なぜ変わったのかはまだ確認していない(node ごとの maxPods 設定に関係すると推測)
7. A1 の容量不足とノード・プール¶
Phase 11 の apply:クラスタは作成できましたが、ノード・プールが Out of host capacity になりました(無料の A1 リソースが一時的に足りない)。
- OKE は失敗したノード・プールを自動で削除し、VM は残らない。再度 plan するとノード・プールの
1 to addだけになる - エラーの詳細は
ContainerEngineClient.list_work_request_errorsで確認 - 対応:plan がノード・プールの 1 to add だけであることを確認してから apply。1 回目とすぐの再試行はどちらも失敗し、その後 10 分ごとに再試行して 3 回目(約 25 分後)に成功。待っている間、クラスタとネットワークは課金されない
- Terraform がノード・プールの「Creation complete」を表示しても、node が ready とは限らない → SDK の
get_node_pool().nodesで全台がACTIVEであることを確認
8. 削除の順序¶
上の層から削除する
kubectl delete -f nginx.yaml(PVC なども)→ K8s が自分で作った LB / ブロック・ボリュームを K8s 自身に削除させる- SDK / コンソールで LB が消えたことを確認
- Cloud Shell の Network を Public に戻す(private network の VNIC が
nodesサブネットに付いている) terraform destroy
順序を間違えるとどうなるか:
kubectl deleteを忘れた場合:Terraform がlbサブネットを削除しようとしたとき、LB がまだそれを使っている → 削除できない。LB は Terraform の管理外の孤立したリソースになり、枠を占有し続ける(第 8 課での分析)- Public に戻し忘れた場合:Phase 6 で実際に起き、
nodesサブネットの削除に 8 分 41 秒かかった。Cloud Shell を閉じてから約 95 秒後に VNIC が解放され、Terraform が再試行し続けてようやく削除できた。誰が使っているかはlist_private_ips(subnet_id=...)で確認できる
9. 設計判断と Always Free のトレードオフ¶
| 判断 | 選んだもの | 選ばなかったもの | 理由 |
|---|---|---|---|
| クラスタ・タイプ | BASIC_CLUSTER |
ENHANCED_CLUSTER |
コントロール・プレーンが無料。学習用に SLA は不要 |
| node | A1 1 OCPU / 6GB × 2、ブート 50GB × 2 | より大きく / より多く | Always Free(A1 4 OCPU / 24GB、ブロック・ボリューム 200GB)の中で余裕を残す → PVC をあと 2 つ作れる |
| AD / FD | 大阪の AD は 1 つだけ。FD は指定しない | FD を指定 | 実測では今回は別々の FD に配置された(観察 1 回) |
| Pod ネットワーク(CNI) | flannel(overlay) | VCN-Native | シンプルで、VCN の IP を消費しない。小さな node でも VNIC 数の制限を受けない。代償:NetworkPolicy が効かない、LB から Pod へ直接転送できない |
| セキュリティ・ルール | サブネットごとにセキュリティ・リスト 1 つ | 共有 1 つ / NSG | OKE のルールはサブネットの役割ごとに自然に分かれる |
| API エンドポイント | プライベート + Cloud Shell の private network | パブリック・エンドポイント | クラスタの操作入口を 0.0.0.0/0 にさらさない |
| セキュリティ・リストの管理 | Terraform が専有、annotation None |
CCM に自動でルールを追加させる | ドリフトと上書き合戦を避ける |
| Service LB | flexible 10Mbps(annotation) | デフォルトのシェイプ | Always Free はこれだけ |
| ノード・イメージ | data source で名前から最新を絞り込み | OCID を固定 | バージョン更新時にコードを変えなくてよい |
| イメージ・レジストリ | Docker Hub(NAT ゲートウェイ経由) | OCIR(サービス・ゲートウェイ経由) | 公式の nginx マルチアーキテクチャ・イメージを使っている |
10. 検証¶
| 確認したいこと | 方法 | 結果 |
|---|---|---|
| node が本当に ready か | SDK get_node_pool().nodes |
2 台 ACTIVE。kubectl get nodes で 2 台 Ready(ワーカーだけで、コントロール・プレーンは見えない) |
| サイトにアクセスできるか | ローカル PC のブラウザ → LB:80 | Welcome to nginx! |
| LB が無料のシェイプか | SDK で LB を確認 | flexible 10 / 10Mbps |
| CCM がセキュリティ・リストを変更していないか | LB 作成後に terraform plan |
No changes |
| きれいに削除できたか | destroy 後に SDK でコンパートメントを確認 | VCN / LB / インスタンス / ブート・ボリューム / ブロック・ボリュームはすべてなくなった |
oci-bv の reclaimPolicy |
kubectl get storageclass |
要確認(Delete のはず) |
| テナンシの管理者が自動で cluster-admin になるか | — | 要確認 |
| どの Ingress 実装を使うか | — | 要確認(OCI Native Ingress Controller / Gateway API) |
11. ハマりどころ¶
| 現象 | 原因 | 対策 |
|---|---|---|
ノード・プールが失敗:Out of host capacity |
無料の A1 リソースが一時的に足りない | plan がノード・プールだけであることを確認して再試行 |
| ノード・プールは「complete」なのに node がまだ ready でない | Terraform は API の応答しか待たない | SDK で node の状態を確認 |
kubectl dial tcp 10.6.0.x:6443: i/o timeout |
Cloud Shell が private network に接続していない | nodes サブネットにつながる private network に切り替える |
| private network に接続できない | 以前の定義が削除済みのサブネットを指している | private network の定義を作り直す |
| LB 作成後に Terraform でドリフト | CCM がセキュリティ・リストにルールを追加 | 事前に許可 + annotation None |
terraform destroy でサブネットが削除できない |
LB が state に入っていない / Cloud Shell の VNIC が残っている | 先に kubectl delete。Network を Public に戻す |
kubectl describe pod に Events の節がない |
原因は未確認(kubectl 1.36.4) | kubectl events --for pod/<name> |
| Events がしばらくすると消える | Event はデフォルトで 1 時間しか保持されない | 見るなら早めに |
12. 確認問題¶
1. OKE のコントロール・プレーンはどこにある?なぜ kubectl get nodes に出てこない?
Oracle 自身のテナンシ内にあり、Oracle が管理しています。get nodes に表示されるのは kubelet が自分から登録した Node オブジェクトで、コントロール・プレーン側には登録しに来る kubelet がいません。
2. kubectl delete を忘れてそのまま terraform destroy すると何が起きる?
Terraform は CCM が作った LB を知りません。lb サブネットを削除しようとしたとき LB がまだそれを使っているため、サブネットが削除できず、LB は孤立したリソースとして枠を占有し続けます。先に kubectl delete し、LB が消えたことを確認してから destroy します。
3. なぜ manifest で security-rule-management-mode を「None」にするのか?
CCM はデフォルトで LB 用のルールをセキュリティ・リストに追加しますが、このセキュリティ・リストは Terraform が管理しています → plan のたびにドリフトが出て、両者が上書きし合います。そこでポートは Terraform で事前に許可し、CCM には触らせません。
4. Service LB のヘルスチェックはなぜ node の 80 ではなく 10256 にアクセスする?
LB のバックエンドは Pod ではなく node の NodePort です。10256 は kube-proxy の /healthz で、この node がこの Service のトラフィックを処理できるかを知っているのは kube-proxy だけです。
5. nodes ↔ nodes の全許可を削除すると、kubectl get nodes はエラーになる?どんな現象が起きる?
エラーにはならず、node はすべて Ready です。しかし flannel の VXLAN がブロックされ、node をまたぐ Pod 同士が通信できません。サイトがつながったりつながらなかったりし、一部の Pod で名前解決ができなくなります。
6. API エンドポイントをパブリックにすればずっと簡単なのに、なぜそうしないのか?
クラスタ全体を操作できる入口がさらされます。しかも Cloud Shell の送信元 IP は固定されないので、6443 を 0.0.0.0/0 に開放するしかありません。プライベート・エンドポイント + Cloud Shell の private network のほうが安全です。
7. VCN-Native ではなく flannel を選んだ代償は?
VCN から Pod が見えません:Pod ごとに NSG を設定できず、LB から Pod へ直接転送できません(node を 1 回余計に経由)。さらに flannel は NetworkPolicy を実装していません。その代わりにシンプルで、VCN の IP を消費しません。
8. 2 台の node が今回は FD-1 と FD-3 に配置された。OKE は必ず分散してくれると言える?
言えません。観察は 1 回だけです。保証が必要なら明示的に設定し、Pod のレベルでも topologySpreadConstraints を書く必要があります。
コード全文¶
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/network.tf(VCN / サブネット / ルーティング)
# ------------------------------------------------------------
# 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/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/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