跳转至

Kubernetes 原理

一句话

Kubernetes(K8s)的核心只有三句: ① 写下期望状态,控制器一直去接近它(reconcile);② 对象之间靠 Label 关联;③ K8s 是抽象层,背后干活的是云资源(LB、Block Volume、IAM)。 觉得乱的时候,回到这三句。

本页整理自 Phase 11 的讲义(第 1〜8 课)和 Phase 6 的实验。在 OCI 上怎么建、实测看到了什么,见 OKE 的设计和实践。

1. 先认识这些对象

K8s 对象 一句话 类比(OCI / 之前做过的)
Pod 最小单位:1 个或多个容器,共用一个 IP —
Deployment / ReplicaSet 维持"N 个同样的 Pod",负责换版本 Phase 4 的"VM ×2 + LB 健康检查",但自动补
Service 给一直在变的 Pod 一个固定的 IP 和名字 LB 的 Backend Set
Ingress HTTP 的分流规则(域名、路径 → 哪个 Service) LB 的 L7 路由规则
ConfigMap / Secret 放在镜像外面的配置 / 机密 Function 的 config / Vault 的 Secret
PersistentVolumeClaim(PVC) "要一块 50GB 的盘"的申请 由 CSI 建出 Block Volume
Namespace 集群里的分区 Compartment
Role / RoleBinding(RBAC) 谁在哪个 Namespace 能做什么 IAM Policy
ServiceAccount Pod 自己的身份 Resource Principal / Instance Principal
Job / CronJob 跑完就结束的任务 / 定时任务 Function / cron

2. 原理

2.1 容器:被"罩住"的进程

容器不是小 VM,它就是宿主机上的一个进程,只是被罩住了,看不到外面。

VM 容器
隔离的是什么 整台机器(各自有内核) 进程(所有容器共用宿主机的内核)
启动 几十秒〜几分钟 几十毫秒〜几秒
大小 几 GB 几 MB〜几百 MB
隔离强度 强 比 VM 弱(内核有漏洞会影响所有容器)

靠 Linux 的三个功能罩住:namespace 决定"能看到什么"(自己的 PID、IP、文件系统),cgroups 决定"最多能用多少"(超过内存上限 → OOMKilled),分层文件系统让镜像的只读层共享、容器只在最上面加一层可写层。

和镜像有关的三件事:

  • tag 会变,digest 不会:nginx:1.27 的 tag 可以被重新指向别的镜像;要绝对固定用 nginx@sha256:...
  • CPU 架构:OKE 的 node 是 A1(arm64)。官方镜像大多是"多架构"的,pull 时自动选;只有 amd64 的镜像在 A1 上跑不起来
  • 数据和配置放外面:容器删掉可写层就没了;同一个镜像在开发 / 生产用不同的配置

这背后的原则叫 cattle, not pets(把服务器当牲口,不当宠物):坏了就换,不去修。不在运行中的容器里改东西,要改就 build 新镜像替换。

2.2 声明式 + 持续修正(reconcile)

你告诉 K8s"想要什么状态",K8s 一直盯着,一发现实际不一样就自动修正。

Terraform K8s
写法 声明式(.tf) 声明式(YAML manifest)
什么时候修正 drift 执行 apply 的时候(一次) 一直在修,不用人触发
期望状态存在哪 state 文件 etcd

所有对象都是同样的结构:spec 是你写的期望,status 是 K8s 写的实际,控制器让 status 追上 spec。

2.3 架构

flowchart TB
  U["kubectl"] -->|"HTTPS :6443"| API
  subgraph CP["Control Plane(OKE 上由 Oracle 管)"]
    API["kube-apiserver<br/>唯一的入口"]
    ETCD[("etcd<br/>全部状态")]
    CTL["scheduler<br/>controller-manager<br/>cloud-controller-manager"]
    CTL <-->|"盯着变化、写回结果"| API
    API --- ETCD
  end
  subgraph N1["Worker Node(每台都有)"]
    K1["kubelet"] --> R1["容器运行时<br/>CRI-O"]
    R1 --> P1["Pod"] & P2["Pod"]
    KP1["kube-proxy<br/>flannel"]
  end
  API <-->|"分配给我的 Pod?"| K1
组件 在哪 做什么
kube-apiserver Control Plane 所有操作的唯一入口:认证 → 权限检查 → 存进 etcd。组件之间不直接对话,都通过它
etcd Control Plane 集群全部状态的键值数据库
scheduler Control Plane 新 Pod 放哪个 node。只做决定,不负责启动
controller-manager Control Plane Deployment、ReplicaSet、Node、Job 等几十个控制器,各跑各的 reconcile loop
cloud-controller-manager(CCM) Control Plane 和云打交道:Service type=LoadBalancer → 建 OCI LB
kubelet 每个 node 问 apiserver"分配给我的 Pod",让运行时启动容器,把状态报回去
kube-proxy 每个 node 把"访问 Service → 实际 Pod"的转发规则写进 node
CNI 插件 每个 node 给 Pod 分 IP,打通 node 之间的 Pod 网络(本仓库:flannel)

2.4 组件链:kubectl apply 之后发生了什么

以"2 个副本的 nginx Deployment"为例:

步骤 谁 做了什么
① kubectl → apiserver 把期望写进 etcd(认证用 OCI IAM 的 token → RBAC 检查)。kubectl 到这里就返回了
② Deployment 控制器 建一个 ReplicaSet
③ ReplicaSet 控制器 0 个、要 2 个 → 建 2 个 Pod 对象(这时还没有 node)
④ scheduler 给没有 node 的 Pod 挑 node,把 node 名写进去
⑤ 那台 node 的 kubelet 发现分配给自己的 Pod → 让 CRI-O 拉镜像、启动容器;让 CNI 分配 IP
⑥ kubelet 报告"Running、IP 是 10.244.x.x"
⑦ 其他控制器 / kube-proxy Pod 就绪 → 更新 Service 的转发目标

最容易搞错:容器是谁启动的

controller 建的只是 Pod 对象(etcd 里的一条记录)。真正启动容器(进程)的是那台 node 上的 kubelet。 controller 建对象 → scheduler 选 node → kubelet 干活,三者互不调用,都是"盯着 apiserver、发现差距、改数据"。

Phase 11 里亲眼看到了这条链:node 还没加入时,coredns 的 Pod 对象已经存在(controller 做完了),但状态是 Pending、NODE 是 <none>,Events 是 no nodes available to schedule pods(scheduler 卡住),也没有 kubelet 来启动它。node 加入后它自动变成 Running。

控制平面停了 ≠ 服务停了:

scheduler 停了 5 分钟 结果
已经在跑的 Pod 不受影响(scheduler 只在"放哪"那一刻有用)
运行中的容器崩溃 kubelet 在原地重启
新 Pod / 自愈要补的 Pod 停在 Pending,scheduler 恢复后自动调度

用户的请求走 LB → node → Pod(数据平面),不经过控制平面。

3. 核心对象:Pod、Label、Deployment、Service

Pod

  • 一个 Pod 里的容器共用 IP 和 Volume。绝大多数是一个 Pod 一个容器;放日志转发等辅助容器时叫 sidecar
  • Pod 也是用完即扔的:死了换新的,名字和 IP 都会变(Phase 6:10.244.1.4 → .5)→ 不要直接用 Pod IP,也不要直接建单个 Pod
状态 意思 常见原因
Pending 还没分到 node 资源不够、没有 node
ImagePullBackOff 拉镜像失败 名字写错、要认证、没有对应架构
CrashLoopBackOff 一启动就崩溃 程序报错、配置错、exec format error(架构不对)
OOMKilled 超过内存上限被杀 limits.memory 太小

Label 和 Selector:K8s 的粘合剂

对象之间不靠名字,靠标签关联:Deployment 管理 app=nginx 的 Pod,Service 把流量转给 app=nginx 的 Pod。 和 OCI 的 freeform_tags 不同:OCI 的标签只是备注,K8s 的标签直接决定"谁管谁、流量给谁"。手动给别的 Pod 贴上 app=nginx,ReplicaSet 会认为多了一个而删掉一个,Service 也会把流量转给它。

Deployment → ReplicaSet → Pod

# 06-oke/k8s/nginx.yaml(摘录)
spec:
  replicas: 2              # 期望:保持 2 个
  selector:
    matchLabels:
      app: nginx           # ① 我管"带 app=nginx 的 Pod"
  template:
    metadata:
      labels:
        app: nginx         # ② 新 Pod 贴这个标签(必须和 ① 一致,否则 apply 时被拒绝)
    spec:
      containers:
        - name: nginx
          image: nginx:1.27  # 改这里 → 滚动更新

为什么分两层?ReplicaSet 管"数量",Deployment 管"版本"。改了 image,Deployment 建一个新 ReplicaSet,新的一点点加、旧的一点点减(滚动更新);旧 ReplicaSet 以副本数 0 保留,kubectl rollout undo 马上能退回。 Pod 名 nginx-598968dd58-xxxxx = Deployment 名 - 模板的 hash - 随机字符串。

Service:固定入口

type 从哪里访问 实际做了什么
ClusterIP(默认) 只能集群内部 分配虚拟 IP(本仓库从 10.96.0.0/16)
NodePort 外部用 node IP:30000〜32767 ClusterIP + 每个 node 开同一个端口
LoadBalancer 外部通过云 LB NodePort + CCM 建 OCI LB

大多数 Service 是 ClusterIP(Web → API 用 http://api 这样的名字访问,CoreDNS 解析),对外入口通常只有 1 个。 Service 能访问但没响应时,先看 kubectl get endpointslices -l kubernetes.io/service-name=nginx:转发目标为空 = selector 没匹配到 Pod。

其他工作负载

类型 用途 和 Deployment 的区别
StatefulSet 有状态(DB、消息队列) Pod 名固定(db-0)、每个 Pod 有专用存储、按顺序启停
DaemonSet 每个 node 跑 1 个 不指定副本数(kube-proxy、flannel、csi-oci-node)
Job / CronJob 跑完就结束 / 定时 成功后不重启

实际项目里 DB 一般不放进 K8s,用托管 DB(ADB 等),K8s 里只跑无状态应用。

4. 网络

集群里的 4 种地址(06-oke 的配置)

地址 范围 谁分配 在 VCN 里真实存在吗
node 的 IP 10.6.10.0/24 OCI(VCN) ✅
Pod 的 IP 10.244.0.0/16 flannel(CNI) ❌(overlay,VCN 不知道)
Service 的 IP(ClusterIP) 10.96.0.0/16 K8s ❌ 哪里都不存在,只是 node 上的转发规则
LB 的公网 IP — OCI(CCM 去申请) ✅

Pod 网络:K8s 只规定"每个 Pod 有自己的 IP、Pod 之间不经过 NAT 直接通",怎么实现交给 CNI 插件。

flannel(本仓库) VCN-Native(OCI 专有)
Pod 的 IP overlay(10.244.x.x) VCN subnet 的 IP
跨 node 外面再套一层 node → node 的包(VXLAN,UDP 8472) 不封包
VCN 能看到 Pod 吗 不能 能(按 Pod 设 NSG、LB 直接转给 Pod)
消耗 VCN 的 IP 不消耗 有多少 Pod 就消耗多少

Service 的真面目:ClusterIP 没有任何网卡持有。kube-proxy 在每个 node 上写 iptables 规则:"发往 ClusterIP:80 的包 → 随机选一个 Pod,改掉目的地址(DNAT)"。所以 ClusterIP ping 不通是正常的(规则只匹配 TCP / UDP),确认 Service 要用 curl。

从浏览器到 Pod:两次独立的选择

flowchart TB
  PC["浏览器"] -->|":80"| LB["OCI LB"]
  LB -->|"① LB 选 node<br/>只认识 node 的 IP"| NA["node A<br/>:NodePort"]
  LB -.-> NB["node B<br/>:NodePort"]
  NA -->|"② node 选 Pod<br/>候选是全集群的 Pod"| PA["node A 上的 Pod"]
  NA -->|"选中别的 node 的 Pod<br/>flannel 封包"| PB["node B 上的 Pod"]
  • LB 选 node:LB 不认识 Pod 的 IP(overlay),只能把 node 的 NodePort 登记为后端
  • node 选 Pod:kube-proxy 的规则包含整个集群的 Pod,所以某台 node 上一个 Pod 都没有,请求也能成功(默认 externalTrafficPolicy: Cluster)。代价是多跳一次,Pod 看到的来源 IP 是 node 的
  • LB 的健康检查打的是 kube-proxy 的 :10256:只有 kube-proxy 知道"本 node 能不能处理这个 Service"(externalTrafficPolicy: Local 时就靠它把没有 Pod 的 node 排除掉)

L4 和 L7:LB 和 Ingress

能看到什么 能按什么分流
L4(传输层) IP、端口 "80 端口的流量交给这组服务器"
L7(应用层) HTTP 的域名、路径、header "a.example.com 给 A、/api 给 API"

Service 本身是 L4 的概念(YAML 里只能写端口)→ K8s 另外设计了 Ingress 来表达 L7。OKE 用 Service type=LoadBalancer 建的 OCI LB 产品本身支持 L7,但 Listener 是 TCP,当 L4 在用(实测确认)。

LB 是"大门",Ingress 是"进门以后的分流规则",两者一起用:

浏览器 → OCI LB(大门:公网 IP,只有 1 个)
       → Ingress Controller 的 Pod(按规则分流,本质是反向代理)
       → Service(ClusterIP)→ Pod
LB(OCI LB) Ingress
是什么 云的产品,在集群外 K8s 的对象 = 一份路由规则
谁干活 OCI Ingress Controller(另外安装的 Pod)
在哪配置 OCI(Terraform、Console) K8s 的 YAML(和应用放一起)
  • 实际项目里 HTTP(S) 应用共用 1 个入口,LB 的数量按"入口的种类"决定(公网 / 内网、非 HTTP),不是每个应用一个
  • 后继标准是 Gateway API。用 OCI 的哪种 Ingress 实现还待确认

为什么 LB 让 K8s 建,而不是 Terraform:后端(所有 node 的 NodePort)一直在变,CCM 盯着变化自动更新;应用写一个 Service 就能公开;同一份 Service 在别的云上也能用。代价:LB 不在 Terraform 的 state 里,删除时要先 kubectl delete(见 OKE 页)。一般的分工是:LB 让 K8s 建,预留 IP、DNS、证书这类"固定的东西"由 Terraform 管。

flannel 不实现 NetworkPolicy

K8s 默认所有 Pod 之间都能互相访问(跨 Namespace 也一样)。NetworkPolicy 可以限制,但 flannel 不实现它:写了会被接受,但不生效,也不报错。要用就装 Calico 等,或者用 VCN-Native + NSG。

5. 配置和存储

ConfigMap 和 Secret

把配置放到镜像外面 → 同一个镜像,开发和生产用不同的 ConfigMap。可以作为环境变量或文件注入。

改了 ConfigMap,Pod 不一定跟着变

环境变量:不会更新(进程启动时读一次)。文件:约 1 分钟内更新,但应用要自己重新读。 → 改完一般 kubectl rollout restart deployment <名字>。

Secret 的用法和 ConfigMap 几乎一样,区别是能用 RBAC 单独限制谁能读、etcd 里可以加密、挂成文件时放在内存里。

Secret 默认只是 base64,不是加密

有读取权限就能 base64 -d 看到明文。写着值的 Secret YAML 不要提交到 Git(和"机密不进 Terraform state"同样的原则)。

K8s Secret OCI Vault Secret
存在哪 etcd Vault
加密 默认没有(OKE 可以用自己的 Vault 密钥加密 etcd) 一定加密
版本管理 / 轮换 没有 有
权限 RBAC IAM Policy

实际项目常见的做法:机密在 Vault 里管理,运行时同步到 K8s(External Secrets Operator 等)。

存储:PV / PVC / StorageClass

Pod 会换 node → 数据要放在"跟着 Pod 走"的存储里。

对象 意思 和 Terraform 对比
PVC 应用的申请:"要 50GB,挂到 1 个 node 读写" .tf 里的 resource
StorageClass 怎么建。OKE 默认 oci-bv(CSI → OCI Block Volume) provider
PV 实际建出来的存储(自动创建) apply 后的实际资源

在 OKE 上要注意:

  • 最小 50GB:申请 1Gi 也建 50GB;Always Free 的 Block Volume 合计 200GB 里,2 台 node 的 boot volume 已经用了 100GB → PVC 最多 2 个
  • 不在 Terraform 的 state 里:不删 PVC 就 terraform destroy,Block Volume 会留下来占额度
  • reclaimPolicy:Delete = 删 PVC 时盘也删;Retain = 保留(忘了删就一直占额度)。oci-bv 默认是哪个待确认(讲义按文档写的是 Delete)

6. 可靠性

probe / resources 写在哪里

写在 manifest(Deployment 的 containers[])里,不是镜像里。镜像管"跑什么",manifest 管"怎么跑"。Dockerfile 的 HEALTHCHECK K8s 会忽略。

三种 probe

probe 问什么 失败了
startupProbe 启动完了吗? 完成之前不执行另外两种(防止启动慢的应用被误判)
livenessProbe 还活着吗?(要不要重启) kubelet 在同一个 Pod 里重启容器(RESTARTS 增加)
readinessProbe 现在能接请求吗?(要不要给流量) 从 Service 的转发目标里摘掉(不重启),恢复后加回来
startupProbe:
  httpGet: {path: /, port: 80}
  periodSeconds: 10
  failureThreshold: 12          # 最多等 10 秒 × 12 = 120 秒
readinessProbe:
  httpGet: {path: /, port: 80}
  periodSeconds: 5
livenessProbe:
  httpGet: {path: /, port: 80}
  periodSeconds: 10
  failureThreshold: 3           # 连续 3 次失败才重启

两个常见错误

  • liveness 检查外部依赖(例:能不能连 DB):DB 停一下 → 所有 Pod 一起反复重启,重启了也连不上。liveness 只检查自己;依赖放在 readiness
  • 启动要 60 秒的应用只写 liveness:30 秒就被判定"死了" → 重启 → 一直起不来。把开始检查的时间往后推也不够稳 → 用 startupProbe

resources:requests 和 limits

  • requests = 预约,给 scheduler 看的:"node 可分配的量 − 已有 Pod 的 requests 合计"够不够。看的不是实际用量;所有 node 都约满了 → Pending
  • limits = 上限,由 cgroups 强制:超过 CPU → 限速(不会被杀);超过内存 → OOMKilled
  • 常见做法:内存设上限,CPU 不设(本仓库的 nginx.yaml 就是这样:requests cpu 50m / memory 64Mi,limits memory 128Mi)

滚动更新:靠 readiness 保证不中断

maxSurge(最多多出几个)和 maxUnavailable(最多允许几个不可用)决定节奏,默认都是 25%。

滚动更新等新 Pod 的 readiness 成功才继续。新版本有 bug 起不来 → readiness 一直失败 → 更新卡住不往下走,旧 Pod 继续服务 → kubectl rollout undo。不写 readiness 的话,坏版本也会被当成正常的,旧 Pod 被一个个删掉。

调度:要的是「分散」,不是「指定 node」

scheduler 只在放置的那一刻决定,事后不会自动调整。Phase 6 的实验:drain 一台 → Pod 全到另一台 → uncordon 后再缩回 2 个 → 2 个都在同一台(没有冗余)。

# 把 app=web 的 Pod 按 node 分散(各 node 数量差不超过 1,做不到就不调度)
topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: kubernetes.io/hostname   # 分散的单位:node(也可以是 FD 等)
    whenUnsatisfiable: DoNotSchedule      # 做不到时:不调度(ScheduleAnyway = 尽量)
    labelSelector:
      matchLabels: {app: web}
想做的事 用什么
把 Pod 分散到不同 node / FD topologySpreadConstraints
只放到某种 node(例:arm64) nodeSelector / nodeAffinity
node 拒绝一般的 Pod(例:GPU node) node 上 taint,Pod 写 toleration
维护(drain)时保底 PodDisruptionBudget(PDB)

不要用 nodeSelector 指定某一台 node:要么全挤在那一台,要么那台被替换(升级时 node 名也变)后 Pod 无处可去。

PDB(Pod Disruption Budget,和 DB 没关系)= "维护时至少保持几个可用"。kubectl drain 时先赶一个、等新 Pod 就绪再赶下一个。但它只管 drain、升级这类自愿的中断;node 突然宕机靠的是分散配置。

可靠性的部件 防的是什么
probe 应用卡死、没准备好就接请求
resources 资源被抢光、node 被塞满
滚动更新 + readiness 换版本时中断、坏版本扩散
分散 + PDB 一台挂了全挂、维护时全停

7. 权限和运维

Namespace

集群里的分区,和 OCI 的 Compartment 很像:分组、按它写权限、设配额。

  • 不是网络隔离:不同 Namespace 的 Pod 默认能互相访问
  • 删掉 Namespace = 里面全部删掉(包括 PVC 背后的 Block Volume)

RBAC:谁能做什么

OCI 的 Policy 是一句话,K8s 拆成两个对象:Role(能做什么)+ RoleBinding(给谁)。拆开是为了同一个 Role 能绑给很多人。

范围
Role + RoleBinding 一个 Namespace 内
ClusterRole + ClusterRoleBinding 整个集群

自带的 ClusterRole:view(只读,不含 Secret)、edit、admin、cluster-admin。

复用自带的 view:RoleBinding 也能绑 ClusterRole

想让 developers 组只在 dev 里只读,不用自己写 Role:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding                 # RoleBinding → 只在这个 Namespace 有效
metadata:
  name: dev-view
  namespace: dev
subjects:
  - kind: Group
    name: developers
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole               # 绑的是集群级的 ClusterRole
  name: view
  apiGroup: rbac.authorization.k8s.io

权限的范围由 Binding 决定:ClusterRole 只是"能做什么"的定义,用 RoleBinding 绑就只在这个 Namespace 生效。

OKE 的权限是两层:① OCI IAM:能不能拿到 kubeconfig / token;② K8s RBAC:在集群里能做什么。tenancy 管理员是否自动是 cluster-admin,待确认。

ServiceAccount:Pod 自己的身份

Pod 里的程序调用 K8s API 时用的身份。每个 Namespace 自动有 default(几乎没有权限)。

  • 一般的 Web 应用不调用 K8s API → automountServiceAccountToken: false(被攻破时攻击者拿不到 token)
  • 要调用 API 的程序:单独建 ServiceAccount,只给最小权限
  • Pod 要调用 OCI API:用 node 的 Instance Principal 的话,这个 node 上所有 Pod 权限都一样(范围太大);按 ServiceAccount 写 IAM Policy 的 OKE Workload Identity 只有 Enhanced cluster(收费)能用

Job、CronJob 和 Helm

  • Job:进程正常结束 = 完成,不重启。重试或 node 故障时可能执行两次 → 要设计成幂等。CronJob 不写 timeZone 就是 UTC
  • Helm = K8s 版的 dnf:Chart(模板 + 默认参数)≒ Terraform 的 module,values ≒ terraform.tfvars,helm template ≒ plan
  • chart 里的 LoadBalancer / PVC 也会建 OCI 资源 → 先 helm uninstall,再 terraform destroy

8. 设计决策一览

决策 选了 没选 理由
运行 Pod 的方式 Deployment 直接建 Pod 删了没人补;换版本要靠 ReplicaSet
镜像 固定 tag(nginx:1.27),多架构 latest tag 不固定就不知道跑的是哪个版本;A1 要 arm64。digest 是之后的练习
resources 内存设 limits,CPU 不设 都不写 / CPU 也设上限 防止吃光 node 内存;CPU 设上限反而被不必要地限速
健康检查 liveness 只查自己,依赖放 readiness liveness 查 DB 依赖一停所有 Pod 一起重启
冗余 topologySpreadConstraints + PDB nodeSelector 指定 node node 是牲口,会被替换
权限 RoleBinding + 自带 view / edit 都给 cluster-admin 最小权限;不用重复造 Role
机密 Vault 管理,运行时同步 Secret YAML 进 Git Secret 只是 base64
DB 托管 DB(K8s 外) StatefulSet 自己跑 备份、高可用交给云
对外入口 1 个 LB(+ Ingress) 每个应用 1 个 LB 费用、管理;Always Free 只有 1 个 LB

本仓库的 06-oke/k8s/nginx.yaml 是最小示例:probe、PDB、显式分散、独立 Namespace、镜像 digest 都还没写,是后续的练习。

9. 验证(实验中观察到的)

想确认的 做了什么 看到的
自愈(reconcile) kubectl delete pod 马上补一个新 Pod,名字和 IP 都变了
组件链 node 加入前看 coredns Pod 对象存在、Pending、NODE <none>;node 加入后自动 Running
组件链 kubectl get events(Pod 自己的用 kubectl events --for pod/<name>) Deployment controller(ScalingReplicaSet)→ ReplicaSet controller(SuccessfulCreate)→ kubelet(Pulling / Started);scheduler 那一环没有单独记下来
调度只在放置时发生 drain → uncordon → 缩回 2 个 Pod 不迁回,2 个都在同一台

10. 坑

现象 原因 对策
exec format error / no matching manifest for linux/arm64 镜像没有 arm64 版(A1 是 arm64) 用多架构镜像,或 docker buildx --platform
运行中的容器里修好的 bug 又回来了 Pod 重建时可写层没了 改 Dockerfile,build 新镜像
改了 Deployment 的 selector 报 field is immutable selector 建好后不能改 delete 后重建
改了 ConfigMap,应用没变 环境变量只在启动时读 kubectl rollout restart
启动慢的应用一直 CrashLoopBackOff 只写了 liveness,启动中就被重启 加 startupProbe
写了 NetworkPolicy 但没效果 flannel 不实现 Calico 等 / VCN-Native + NSG
uncordon 后 Pod 还是挤在一台 scheduler 不会事后调整 kubectl rollout restart;平时写 topologySpreadConstraints
Service 访问没响应 selector 没匹配到 Pod 看 endpointslices
kubectl delete pod 10.6.10.x → NotFound 那是 node 名(OKE 的 node 名 = 私有 IP) Pod 名是 nginx-xxxx-xxxxx 格式

11. 自测问题

1. Deployment 的控制器建了 Pod,那 Pod 里的容器是谁启动的?

那台 node 上的 kubelet(让 CRI-O 去拉镜像、启动)。控制器只建 Pod 对象(etcd 里的记录),scheduler 只决定放哪个 node。

2. 按顺序说出 kubectl apply 一个 Deployment 之后,各组件做了什么

apiserver 存进 etcd → Deployment 控制器建 ReplicaSet → ReplicaSet 控制器建 Pod 对象 → scheduler 给 Pod 选 node → kubelet 启动容器并报告状态 → Service 的转发目标更新。各组件互不调用,都通过 apiserver。

3. 集群里还没有任何 node 时,Pod 是什么状态?卡在哪个组件?

Pending、NODE 是 <none>。Pod 对象已经由控制器建好了,卡在 scheduler(没有 node 可选),所以也没有 kubelet 来启动它。

4. 一个应用启动要 60 秒,只写了 liveness(10 秒一次、3 次失败重启),会怎样?怎么改?

30 秒就被判定为"死了"并重启,永远起不来(CrashLoopBackOff)。加 startupProbe,让 periodSeconds × failureThreshold 比启动时间长;它成功之前 liveness 不执行。

5. 滚动更新时,K8s 用哪个 probe 判断新 Pod 准备好了?liveness 失败会发生什么?

readinessProbe。liveness 失败是 kubelet 在同一个 Pod 里重启容器,不是新建 Pod,也不影响滚动更新的判断。

6. liveness 里检查「能不能连上 DB」有什么问题?

DB 一停,所有 Pod 的 liveness 都失败 → 全部反复重启,重启了也连不上,问题更大。liveness 只检查自己;依赖的检查放 readiness(连不上就先不接请求)。

7. 2 个副本的 Pod 都跑在同一台 node 上。写出让它们分到不同 node 的配置要点

topologySpreadConstraints:topologyKey: kubernetes.io/hostname(以 node 为单位)、maxSkew: 1、whenUnsatisfiable: DoNotSchedule,labelSelector 选中这组 Pod。已经偏了的话 kubectl rollout restart 重新分配。

8. 为什么不用 nodeSelector 把两个 Pod 分别指定到两台 node?

node 是会被替换的(升级时换新 node,名字也变),指定了特定 node,那台没了 Pod 就无处可去。要写的是"分散"的规则,不是"放哪台"。

9. 想让 developers 组在 dev Namespace 里只读,最省事的写法是?

在 dev 里建一个 RoleBinding,roleRef 指向自带的 ClusterRole view。不用自己写 Role;因为是 RoleBinding,权限只在 dev 里有效。

10. 自带的 view 能看 Secret 吗?Secret 是加密存的吗?

view 不含 Secret。Secret 默认只是 base64,有读取权限就能看到明文;etcd 加密要另外配置(OKE 可以用自己的 Vault 密钥)。

11. Pod 超过了 CPU 的 limits 和超过内存的 limits,结果有什么不同?

CPU:被限速,变慢但不会被杀。内存:OOMKilled,容器被重启。

12. PDB 能防止 node 突然宕机导致服务中断吗?

不能。PDB 只管 drain、node pool 升级这类自愿的中断。突然宕机要靠把 Pod 分散到不同 node / FD。

13. ClusterIP ping 不通,说明 Service 坏了吗?

不说明。ClusterIP 只是 kube-proxy 写在 node 上的 iptables 规则,只匹配 TCP / UDP 端口,不回应 ICMP。要用 curl 确认。