OKE 的设计和实践¶
一句话
Oracle Kubernetes Engine(OKE)= 标准的 Kubernetes + 一层 OCI 接入(LB、Block Volume、IAM)。 设计的核心:只有 LB 对外、API endpoint 私有、Security List 只由 Terraform 管;运维的核心:分清三层(Terraform / K8s 对象 / K8s 间接建的 OCI 资源),删除时从上往下删。
本页整理自 Phase 6 和 Phase 11(第 8 课和第 2 部分的实测)。K8s 本身的原理见 Kubernetes 原理,VCN / Security List 的基础见 网络设计。
关于「待确认」
标"待确认"的项目还没有实测,是按文档或推测写的。
1. 先认识这些组件¶
| OKE | AWS | 一句话 |
|---|---|---|
| OKE cluster | EKS cluster | 托管的 K8s。control plane 建在 Oracle 自己的 tenancy 里,用户看不到 |
BASIC_CLUSTER / ENHANCED_CLUSTER |
— | Basic:control plane 免费、无 SLA;Enhanced:按小时收费(SLA、add-on 管理、virtual nodes 等) |
| node pool(managed nodes) | Managed node group | 一组 worker node(VM)。OKE 负责建 VM 和注册 |
| virtual nodes | Fargate | 没有 VM 的概念,按 Pod 收费,要 Enhanced |
| API endpoint | 同左 | apiserver 的入口,VCN 里只出现它的一个 IP |
oci ce cluster generate-token |
aws eks get-token |
kubeconfig 的认证:用 OCI IAM 换 token |
谁管什么¶
| 部分 | 谁管 | 说明 |
|---|---|---|
| control plane(apiserver、etcd、scheduler、CCM) | Oracle | 备份、升级由 Oracle 做 |
| VCN / subnet / Security List | 自己 | 规则写错了 → node 注册不进去、LB 连不通 |
| worker node | 自己(轻量) | shape、台数、镜像。kubelet 等由 OKE 预装;OS 补丁和 K8s 升级要自己发起(换镜像 → 逐台替换) |
| 应用 | 自己 | kubectl。LB / Block Volume 由 K8s 间接建 |
worker node 的思路和 VM 不一样了:nginx 跑在容器里,VM 只是跑容器的地方,管的是数量和版本,坏了就换,不去修。
2. 原理:三层结构¶
同一个 nginx 网站跨越三层。出了问题,先分清是哪一层:
flowchart TB
subgraph L1["① 云基础设施:Terraform"]
VCN["VCN / subnet<br/>Security List"]
CL["OKE cluster<br/>node pool"]
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["Block Volume<br/>PVC → CSI 建"]
end
SVC -->|"CCM"| LB
L1 --> L2
| 层 | 管理什么 | 谁负责 |
|---|---|---|
| ① 云基础设施 | VCN、subnet、cluster、node pool | Terraform(06-oke/) |
| ② K8s 对象 | Deployment、Pod、Service | kubectl 提交期望;controller 建对象、scheduler 选 node、kubelet 启动容器 |
| ③ 间接建的云资源 | Service 的 OCI LB、PVC 的 Block Volume | cloud-controller-manager(CCM)/ CSI;Terraform 不知道它们 |
由此得出两条规则:
- 删除从上往下:先删 ② 的 Service / PVC,确认 ③ 的 LB / Block Volume 没了,再 destroy ①(第 8 节)
- 第 ③ 层会去改第 ① 层的东西:CCM 默认会往 Security List 里加规则 → 和 Terraform 打架(第 4 节)
3. 网络设计¶
VCN 10.6.0.0/16
├─ api 10.6.0.0/28 private API endpoint(只要几个 IP → /28)
├─ nodes 10.6.10.0/24 private worker node(+ 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
只有 lb 是 public:从 Internet 能直接到的只有 LB,node 和 API endpoint 都没有公网 IP(和 网络设计 同样的思路)。nodes 的路由:0.0.0.0/0 → NAT Gateway(拉 Docker Hub 的镜像),Oracle 服务 → Service Gateway(向 OKE 注册等)。
每个 subnet 一个 Security List¶
三个 subnet 的角色不同,要的规则也不同 → 没有复用 Phase 4 的 network module(它所有 subnet 共用一个 Security List),每个 subnet 单独写。
| 方向 | 端口 | 为什么 |
|---|---|---|
本机 → lb |
TCP 80 | 网站入口(只允许本机 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)、worker → control plane(12250,proxymux 的通道,kubectl logs/exec 走这里) |
api → nodes |
TCP 全部 | control plane → kubelet(10250 等) |
nodes ↔ nodes |
全部 | flannel 的 VXLAN(UDP 8472)、Pod 之间的通信 |
# 06-oke/security.tf(摘录):nodes subnet 的入站规则
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 只在某一台上)。
API endpoint 私有,kubectl 在 Cloud Shell 里用¶
# 06-oke/oke.tf(摘录)
endpoint_config {
subnet_id = oci_core_subnet.api.id
is_public_ip_enabled = false # 不分配公网 IP → 只能从 VCN 里访问
}
- 能操作整个集群的入口不放到公网
- 如果用公网 endpoint:Cloud Shell 的出口 IP 不固定 → 只能对
0.0.0.0/0开 6443(和 SSH 是同一个问题) - 代价:kubectl 只能在 VCN 里用 → Cloud Shell 的 private network 接进
nodessubnet。本机也装不了 OCI CLI(kubeconfig 的认证要用oci命令),而 Cloud Shell 自带oci和kubectl
4. 不让 OKE 自动改 Security List¶
CCM 建 LB 时,默认会自己往 Security List 里加规则 → Terraform 管理的 Security List 每次 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 List(规则已经在 security.tf 里写好了)
oci.oraclecloud.com/security-rule-management-mode: "None"
结果:建了 LB 之后 terraform plan 还是 No changes。
原则:一份资源只让一个工具管。 同一个 Security List 被 Terraform 和 CCM 同时改,两边会一直互相覆盖。
5. node 镜像自动选择¶
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 用 SDK 查了 Service type=LoadBalancer 建出来的 LB:
| 项目 | 实测 |
|---|---|
| shape | flexible,min = max = 10Mbps(annotation 生效 = Always Free) |
| listener | 名字 TCP-80,协议 TCP(L4)。LB 产品支持 L7,但这里当 L4 用 |
| backend | 2 台 node 的 NodePort,策略 ROUND_ROBIN |
| 健康检查 | HTTP :10256/healthz(kube-proxy),不是业务的 80 端口 |
| 名字 / 标签 | 名字是 UUID,没有标签,不在 Terraform 的 state 里 → Console 上很难看出是谁建的 |
| node 的 fault domain | 没指定 FD,两台分别在 FD-1 / FD-3。只观察了 1 次,不能当作每次都保证分散 |
流量:本机 → 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 失败重试 → EnsuredLoadBalancer。
Pod 网络:flannel 给每个 node 分一段¶
- 普通 Pod 的 IP 是
10.244.x.x;flannel、kube-proxy、CSI 等系统 Pod 是 hostNetwork,直接用 node 的 IP - 每个 node 分到 Pod CIDR 的一段:Phase 6 是 /24(一台
10.244.1.x、另一台10.244.0.x),Phase 11 是 /25(10.244.0.0/25和10.244.0.128/25) - 为什么变了还没确认(推测和每个 node 的 maxPods 设定有关)
7. A1 容量不足和 node pool¶
Phase 11 的 apply:cluster 建好了,node pool 报 Out of host capacity(免费的 A1 资源暂时不够)。
- OKE 会自动删掉失败的 node pool,不留 VM;再 plan 就只剩 node pool
1 to add - 错误详情用
ContainerEngineClient.list_work_request_errors查 - 做法:确认 plan 正好是 node pool 1 to add 才 apply,第一次和立刻重试都失败,之后每 10 分钟重试一次,第 3 次(约 25 分钟后)成功;cluster 和网络在等待期间不收费
- Terraform 显示 node pool "Creation complete" 时,node 不一定 ready → 用 SDK 的
get_node_pool().nodes确认每台都是ACTIVE
8. 删除顺序¶
从上往下删
kubectl delete -f nginx.yaml(和 PVC 等)→ 让 K8s 自己删掉它建的 LB / Block Volume- 用 SDK / Console 确认 LB 已经没了
- Cloud Shell 的 Network 切回 Public(private network 的网卡挂在
nodessubnet 上) terraform destroy
顺序反了会怎样:
- 忘了
kubectl delete:Terraform 删到lbsubnet 时,LB 还占着它 → 删不掉;LB 成了 Terraform 不管的孤立资源,继续占额度(第 8 课的分析) - 忘了切回 Public:Phase 6 实际碰到了,
nodessubnet 删了 8 分 41 秒。关掉 Cloud Shell 约 95 秒后网卡才释放,Terraform 一直重试才删掉。用list_private_ips(subnet_id=...)能查到是谁占着
9. 设计决策和 Always Free 的取舍¶
| 决策 | 选了 | 没选 | 理由 |
|---|---|---|---|
| cluster 类型 | BASIC_CLUSTER |
ENHANCED_CLUSTER |
control plane 免费;学习用不需要 SLA |
| node | A1 1 OCPU / 6GB × 2,boot 50GB × 2 | 更大 / 更多 | Always Free(A1 4 OCPU / 24GB、Block Volume 200GB)里留出余量 → PVC 还能建 2 个 |
| AD / FD | 大阪只有 1 个 AD;FD 不指定 | 指定 FD | 实测这次分到了不同 FD(1 次观察) |
| Pod 网络(CNI) | flannel(overlay) | VCN-Native | 简单、不占 VCN 的 IP;小 node 也不受 VNIC 数限制。代价:NetworkPolicy 不生效、LB 不能直接转给 Pod |
| 安全规则 | 每个 subnet 一个 Security List | 共用一个 / NSG | OKE 的规则按 subnet 的角色天然分开 |
| API endpoint | 私有 + Cloud Shell private network | 公网 endpoint | 不把集群的操作入口暴露在 0.0.0.0/0 |
| Security List 的管理 | Terraform 独占,annotation None |
让 CCM 自动加规则 | 避免 drift 和互相覆盖 |
| Service LB | flexible 10Mbps(annotation) | 默认 shape | Always Free 只有这个 |
| node 镜像 | data source 按名字筛选最新 | 写死 OCID | 版本更新时代码不用改 |
| 镜像仓库 | Docker Hub(经 NAT Gateway) | OCIR(经 Service Gateway) | 用的是官方 nginx 多架构镜像 |
10. 验证¶
| 要确认的事 | 怎么做 | 结果 |
|---|---|---|
| node 真的 ready | SDK get_node_pool().nodes |
2 台 ACTIVE;kubectl get nodes 2 台 Ready(只有 worker,看不到 control plane) |
| 网站能访问 | 本机浏览器 → LB:80 | Welcome to nginx! |
| LB 是免费的规格 | SDK 查 LB | flexible 10 / 10Mbps |
| CCM 没改 Security List | 建 LB 后 terraform plan |
No changes |
| 删干净了 | destroy 后用 SDK 查 compartment | VCN / LB / 实例 / boot volume / block volume 都没有了 |
oci-bv 的 reclaimPolicy |
kubectl get storageclass |
待确认(应该是 Delete) |
| tenancy 管理员是否自动 cluster-admin | — | 待确认 |
| Ingress 用哪个实现 | — | 待确认(OCI Native Ingress Controller / Gateway API) |
11. 坑¶
| 现象 | 原因 | 对策 |
|---|---|---|
node pool 失败:Out of host capacity |
免费的 A1 资源暂时不够 | 确认 plan 只剩 node pool 再重试 |
| node pool "complete" 了,node 还没 ready | Terraform 只等 API 返回 | 用 SDK 确认 node 状态 |
kubectl dial tcp 10.6.0.x:6443: i/o timeout |
Cloud Shell 没接 private network | 切到接 nodes subnet 的 private network |
| private network 连不上 | 以前的定义指向已删除的 subnet | 新建 private network 的定义 |
| 建了 LB 后 Terraform 有 drift | CCM 往 Security List 里加规则 | 提前放行 + annotation None |
terraform destroy 删不掉 subnet |
LB 不在 state 里 / Cloud Shell 的网卡还在 | 先 kubectl delete;Network 切回 Public |
kubectl describe pod 没有 Events 节 |
原因未确认(kubectl 1.36.4) | kubectl events --for pod/<name> |
| Events 过一会儿就没了 | Event 默认只保留 1 小时 | 要看就趁早 |
12. 自测问题¶
1. OKE 的 control plane 在哪?为什么 kubectl get nodes 看不到它?
在 Oracle 自己的 tenancy 里,由 Oracle 管理。get nodes 列的是 kubelet 主动报到的 Node 对象,control plane 那边没有 kubelet 来报到。
2. 忘了 kubectl delete 就直接 terraform destroy,会发生什么?
Terraform 不知道 CCM 建的 LB。删到 lb subnet 时 LB 还占着它,subnet 删不掉;LB 成为孤立资源继续占额度。要先 kubectl delete,确认 LB 没了再 destroy。
3. 为什么 manifest 里要把 security-rule-management-mode 设成「None」?
CCM 默认会往 Security List 里加 LB 用的规则,而这个 Security List 是 Terraform 管的 → 每次 plan 都有 drift,两边互相覆盖。所以端口在 Terraform 里提前放行,让 CCM 不要动。
4. Service LB 的健康检查为什么打 node 的 10256,而不是 80?
LB 的后端是 node 的 NodePort,不是 Pod。10256 是 kube-proxy 的 /healthz,只有 kube-proxy 知道这个 node 能不能处理这个 Service 的流量。
5. 把 nodes ↔ nodes 的全部放行删掉,kubectl get nodes 会报错吗?会出现什么现象?
不会报错,node 都是 Ready。但 flannel 的 VXLAN 被挡,跨 node 的 Pod 之间不通:网站时好时坏,有的 Pod 名字解析不了。
6. API endpoint 用公网的会简单很多,为什么不这么做?
能操作整个集群的入口会暴露出来;而且 Cloud Shell 的出口 IP 不固定,只能对 0.0.0.0/0 开 6443。私有 endpoint + Cloud Shell private network 更安全。
7. 选 flannel 而不是 VCN-Native,代价是什么?
VCN 看不到 Pod:不能按 Pod 设 NSG、LB 不能直接转给 Pod(多跳一次 node);而且 flannel 不实现 NetworkPolicy。换来的是简单、不占 VCN 的 IP。
8. 两台 node 这次分到了 FD-1 和 FD-3,能说明 OKE 一定会分散吗?
不能,只观察了 1 次。需要保证的话要明确配置,Pod 层面也要写 topologySpreadConstraints。
完整代码¶
06-oke/oke.tf(cluster + node pool)
# ------------------------------------------------------------
# OKE:cluster(control plane)+ node pool(worker node)
# 费用:BASIC_CLUSTER 的 control plane 免费;ENHANCED_CLUSTER 按小时收费 → 不用
# worker = A1.Flex(Always Free 额度内);Service LB 用 annotation 指定 flexible 10Mbps
# ------------------------------------------------------------
data "oci_identity_availability_domains" "this" {
compartment_id = var.compartment_ocid
}
# node 用的镜像: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 endpoint 放在 private subnet,不分配公网 IP → 只能从 VCN 里访问(Cloud Shell private network)
endpoint_config {
subnet_id = oci_core_subnet.api.id
is_public_ip_enabled = false
}
options {
# Service type=LoadBalancer 的 LB 建在这个 subnet
service_lb_subnet_ids = [oci_core_subnet.lb.id]
kubernetes_network_config {
pods_cidr = "10.244.0.0/16" # flannel 的 overlay,和 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
# 大阪只有 1 个 AD
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 / subnet / 路由)
# ------------------------------------------------------------
# Phase 6:OKE 用的网络(flannel overlay 版)
# VCN 10.6.0.0/16 ─ IGW / NAT GW / Service GW
# ├─ api 10.6.0.0/28 private:K8s API endpoint(没有公网 IP)
# ├─ nodes 10.6.10.0/24 private:worker node(+ Cloud Shell 的 private network 也接这里)
# └─ lb 10.6.20.0/24 public :Service type=LoadBalancer 建的 OCI LB
# Pod 的 IP(10.244.0.0/16)由 flannel 在节点之间做 overlay,不占 VCN 的 IP
#
# 04 的 network module 是"所有 Subnet 共用一个 Security List",
# OKE 每个 Subnet 要的规则都不一样 → 这里不用 module,直接写资源(Security List 在 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
}
# worker node 拉 Docker Hub 等外部镜像要用
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
}
}
# worker node 向 OKE 注册、拉 OCIR 镜像,以及 Cloud Shell 拿 token(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(每个 subnet 的 Security List)
# ------------------------------------------------------------
# Security List:每个 Subnet 一个(规则参考 OKE 文档的 flannel 示例,按 Always Free 实验需要精简)
#
# my_ip ──:80──→ [lb] LB ──:30000-32767 / :10256──→ [nodes] worker ×2
# [nodes] worker / Cloud Shell ──:6443 / :12250──→ [api] K8s API endpoint
# [api] control plane ──all──→ [nodes](kubelet 10250 等)
#
# Service LB 的规则不让 OKE 自动改:
# cloud-controller-manager 默认会自己往 Security List 里加规则 → Terraform 管的 SL 每次 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 endpoint)----
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"
# worker 和 Cloud Shell(都在 nodes subnet)→ 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
}
}
# worker → 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
}
}
# control plane → worker(kubelet 10250、exec / logs 等)
egress_security_rules {
destination = local.subnet_cidrs.nodes
protocol = "all"
description = "control plane to workers"
}
# control plane → OKE / OCI 的 API(走 Service GW)
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 的 overlay = 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
}
}
# 出:Internet(NAT)、OCI 服务(SGW)、API endpoint 全部放行
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)→ 再 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 镜像,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 的 LB(不写的话默认是 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