前置回顾
阶段一最关键的认知基础:
容器 = Namespace + Cgroups + OverlayFS 隔离的进程(第 02/03/04 篇)
docker CLI → dockerd → containerd → runc → kernel(第 05 篇)
Compose 管理单机多容器,但无法跨机器调度(第 07 篇)
CRI/CNI/CSI 三接口让容器运行时,网络,存储可插拔(第 09 篇)
K8s 就是站在这些基础之上,解决多台机器上大规模容器的自动调度与管理 问题.
从 Compose 到 K8s:为什么需要编排系统
回顾第 07 篇 Compose 的生产边界表: Compose 不能跨机器调度 .你的应用从 1 台机器扩展到 10 台时,会出现一连串 Compose 解决不了的问题
场景
Compose 的局限
K8s 的答案
容器该跑在哪台机器上?
你手动选
Scheduler 自动根据资源,亲和性等策略分配
机器挂了怎么办?
手动恢复
Controller 检测到 Pod 消失,自动在新机器上重建
流量怎么分散到多台机器?
需要外部负载均衡器 + 手动配
Service + kube-proxy 自动负载均衡
怎么滚动更新不中断服务?
无滚动更新,手动 up --no-deps
Deployment Controller 逐批替换,保证可用副本数
怎么让 API 和 DB 部署在一起?
单机天然在一起
Pod 概念:共享 Network/Mount NS 的一组容器
10 台机器的日志怎么看?
每台登上去看
统一的日志/监控体系(阶段四/五的主题)
Compose = 公寓管家,K8s = 城市管理者.
管家只管一栋楼(一台机器)里的水电维修,城市管理者要管几百栋楼的供水供电,交通调度,紧急疏散.
K8s 面对的是一整个数据中心,每台机器都可能挂,每个容器都可能被迁移--它必须把这些不确定性交给自动化系统来处理.
Pod 是什么:最小调度单元,不是容器
k8s 以 Pod 为最小调度单元,Pod 是一组容器的集合,它们共享部分 Namespace,必须一起调度到同一台机器上.
Docker 的原子单元是容器, K8s 的原子单元是 Pod.
Docker 世界: K8s 世界: ┌──────────┐ ┌──── Pod ────────────────┐ │ Container│ ← 最小单元 │ ┌──────┐ ┌──────┐ │ │ │ │ │ C1 │ │ C2 │ │ ← 一组容器 │ nginx │ │ │nginx │ │fluentd│ │ │ │ │ └──────┘ └──────┘ │ └──────────┘ │ 共享 NS + Volume │ └────────────────────────┘ ↑ 最小调度单元
为什么要在一组容器外面再包一层?
因为存在紧密耦合的容器组 --比如 nginx 写 access.log, 然后 fluentd 读取并转发日志.它们必须:
共享文件系统(看到同一份日志文件)
部署在同一台机器上(否则文件访问会有网络延迟)
Pod 就是为这类场景设计的抽象.
Pod = 豆荚,容器 = 豆子.
豆荚是一个整体(调度单位),里面的豆子紧密挨在一起,共享同一片"荚壁"(Network Namespace).
不过大多数场景下,一个 Pod 里可以只跑一个容器.多容器 Pod(Sidecar 模式)是进阶用法,后面篇章再展开.现在只需要记住:Pod 是 K8s 调度的最小单位, K8s 不会只调度 Pod 里的某一个容器.
K8s YAML 字段速览
在动手之前,先认识 K8s 资源的四个必填字段.所有 K8s 资源(Pod,Deployment,Service...)的 YAML 都遵循同一个骨架:
apiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx:1.25
字段
含义
apiVersion
资源所属的 API 组和版本. - 核心资源(Pod,Service)用 v1 - apps 组资源(Deployment,StatefulSet)用 apps/v1 - batch 组(Job,CronJob)用 batch/v1
kind
资源类型:Pod,Deployment,Service,ConfigMap 等
metadata
资源的标识信息.至少包含 name;labels 用于被 selector 选中;namespace 不写则用当前上下文默认值
spec
资源的"期望状态"--你告诉 K8s 这个东西应该长什么样.每种 kind 的 spec 结构不同,这是最核心也最复杂的部分
各字段在不同资源中的取值:
资源
apiVersion
spec 里有什么(概要)
Pod
v1
containers(容器列表),volumes,restartPolicy...
Deployment
apps/v1
replicas + selector(选 Pod 的规则)+ template(Pod 模板,就是一个 Pod spec)
Service
v1
selector(选哪些 Pod 作为后端)+ ports + type
ConfigMap
v1
data(键值对)
Job
batch/v1
completions + parallelism + Pod template
后面的内容会反复出现这些字段
03 篇 Deployment 比 Pod 多了 selector 和 template 两个字段--selector 定义"哪些 Pod 归我管",template 就是 Pod 的 spec.
04 篇 Service 用 selector 定义"流量发给哪些 Pod".
本质都是同一个骨架,只是 spec 里的内容随资源类型变化.
动手:启动第一个 Pod
准备集群环境
你需要一个本地 K8s 集群.以下三种方式任选其一:
方式
适合场景
安装方式
Docker Desktop 内置 K8s
macOS/Windows 已装 Docker Desktop
Settings → Kubernetes → Enable
k3d
轻量,多节点模拟
brew install k3d && k3d cluster create mycluster
kind
CI 友好,用 Docker 容器模拟节点
brew install kind && kind create cluster
确认集群就绪:
$ kubectl get nodes NAME STATUS ROLES AGE VERSION docker-desktop Ready control-plane 10m v1.28.2
写一个最简 Pod YAML
创建一个文件 nginx-pod.yaml:
apiVersion: v1 kind: Pod metadata: name: my-nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80
就这么短--声明"我要一个 Pod,里面跑一个 nginx 容器,监听 80 端口".
启动 Pod
$ kubectl apply -f nginx-pod.yaml pod/my-nginx created $ kubectl get pods NAME READY STATUS RESTARTS AGE my-nginx 1/1 Running 0 12s
查看详情
$ kubectl describe pod my-nginx Name: my-nginx Namespace: default Node: docker-desktop/192.168.65.4 ← 被调度到了哪台机器 IP: 10.1.0.15 ← Pod 的集群内 IP Status: Running ... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 30s default-scheduler Successfully assigned default/my-nginx to docker-desktop Normal Pulling 29s kubelet Pulling image "nginx:1.25" Normal Pulled 20s kubelet Successfully pulled image Normal Created 20s kubelet Created container nginx Normal Started 20s kubelet Started container nginx
访问与调试
$ kubectl logs my-nginx $ kubectl exec -it my-nginx -- /bin/bash root@my-nginx:/# curl localhost:80 <!DOCTYPE html> <html> <head ><title>Welcome to nginx!</title></head> ... $ kubectl port-forward pod/my-nginx 8080:80 Forwarding from 127.0.0.1:8080 -> 80 $ curl localhost:8080
删除 Pod
$ kubectl delete pod my-nginx pod "my-nginx" deleted $ kubectl get pods No resources found in default namespace.
体验"自愈":用 Deployment 管理 Pod
裸 Pod 有一个致命问题:删了就没了,没有人帮你重建. 这和 docker run 一样--进程挂了就挂了.
K8s 的核心价值在于 Deployment:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80
$ kubectl apply -f nginx-deployment.yaml deployment.apps/nginx-deploy created $ kubectl get pods NAME READY STATUS RESTARTS AGE nginx-deploy-7fb96c846b-2xk4n 1/1 Running 0 5s nginx-deploy-7fb96c846b-9m2dv 1/1 Running 0 5s nginx-deploy-7fb96c846b-kx7pq 1/1 Running 0 5s $ kubectl delete pod nginx-deploy-7fb96c846b-2xk4n pod "nginx-deploy-7fb96c846b-2xk4n" deleted $ kubectl get pods NAME READY STATUS RESTARTS AGE nginx-deploy-7fb96c846b-9m2dv 1/1 Running 0 30s nginx-deploy-7fb96c846b-kx7pq 1/1 Running 0 30s nginx-deploy-7fb96c846b-r8wtz 1/1 Running 0 3s ← 自动重建了! $ kubectl delete deployment nginx-deploy
现在你已经亲手:创建了一个 Pod,看到了它的生命周期事件,体验了 Deployment 的自愈能力.接下来我们深入底层--这些操作背后,K8s 的各个组件是怎么协作完成的?
集群架构全景
K8s 集群的所有组件分成两大类:控制平面 管决策,数据平面 管执行:
┌────────────────────────────────────────┐ │ Control Plane(控制平面) │ ├────────────────────────────────────────┤ │ • API Server(唯一入口) │ │ • Scheduler(调度决策) │ │ • Controller Manager(维持期望状态) │ │ • etcd(分布式KV存储) │ └────────────────────────────────────────┘ ┌────────────────────────┐ ┌────────────────────────┐ │ Worker Node 1 │ │ Worker Node 2 │ ├────────────────────────┤ ├────────────────────────┤ │ • kubelet │ │ • kubelet │ │ • kube-proxy │ │ • kube-proxy │ │ • containerd │ │ • containerd │ │ • Pod A / Pod B │ │ • Pod C / Pod D │ └────────────────────────┘ └────────────────────────┘
回忆你刚才 kubectl apply 的体验:提交 YAML → Pod 出现在某个节点上 → 容器开始运行.
这背后涉及控制平面的调度决策和数据平面的实际执行.
控制平面(Control Plane)
API Server - 集群的唯一入口
API Server 是 K8s 集群的中央枢纽 .任何对集群的操作--不管是 kubectl apply,Scheduler 做调度决策,还是 kubelet 汇报 Pod 状态--全部通过 API Server.没有任何组件之间直接通信,全部走 API Server 中转.
请求进入 API Server 后的处理链: Authentication(你是谁?) │ ▼ Authorization(你能做什么?RBAC) │ ▼ Admission(请求合规吗?校验+变更) │ ▼ Validation(数据格式对吗?) │ ▼ 写入 etcd
关键设计:所有组件通过"watch"机制工作,而非轮询. Scheduler 不是在问 "有什么新 Pod 吗?"--它打开一个到 API Server 的长连接,API Server 有新 Pod 分配请求时主动推送给它.这意味着 Scheduler 几乎实时响应,且不会浪费 CPU 做空轮询.
# 各组件的典型 watch 行为: Scheduler: watch Pods (spec.nodeName == "") → 我来调度! Controller Manager: watch Deployments → 实际副本够不够? watch Pods → 有 Pod 挂了?重建! kubelet: watch Pods (spec.nodeName == "node-1") → 这是我的活! kube-proxy: watch Services + Endpoints → iptables 要更新了
etcd - 集群的"唯一真相来源"
etcd(读作 et-see-dee)是一个分布式键值存储 .你可以把它理解为一个支持高可用的 Redis--但设计目标比 Redis 更极端:强调一致性 (任何时刻所有节点读到同样的值)和持久性 (写入的数据绝不丢失).
K8s 集群中 etcd 存着什么? /registry/ ├── pods/default/nginx-abc123 ← 每个 Pod 的定义和状态 ├── deployments/default/nginx ← Deployment 的期望副本数 ├── services/default/nginx-svc ← Service 的 ClusterIP ├── configmaps/default/app-config ← ConfigMap 内容 ├── secrets/default/db-password ← Secret(base64 编码) ├── nodes/node-1 ← Node 的状态(可调度?CPU/内存剩余?) ├── namespaces/default ← Namespace 定义 └── ... # 集群里的一切(除了容器运行时状态和网络规则)都在 etcd 里 # etcd 挂 = 集群失忆 = 虽然已有容器还能跑,但任何新操作都做不了
etcd 和 Redis 的关键区别
Redis 是缓存(丢数据可以接受),etcd 是唯一真相来源(Source of Truth) --丢了就丢失集群的完整状态.
所以 etcd 默认用 Raft 共识算法保证多节点数据一致,写操作必须被超过半数节点确认才算成功.生产环境 etcd 至少 3 节点且奇数个(防脑裂).
Scheduler - 决定 Pod 去哪台机器
Scheduler 的职责只有一句话:给每个新创建的 Pod 找一个最合适的 Node .它不做别的--不监控已运行的 Pod,不重启崩溃的容器,只负责"选节点"这一个决策.
Scheduler 的决策流程: 1. watch API Server → 发现一个新 Pod,spec.nodeName 为空 ("这个 Pod 还没分配节点,该我了") 2. 过滤(Filtering)--排除不合格的节点: ❌ 节点不可调度(cordoned) ❌ 节点资源不够(剩余 CPU < Pod 请求的 CPU) ❌ 节点不满足亲和性/反亲和性规则 ❌ 节点不满足污点/容忍规则 → 得到"候选节点列表" 3. 打分(Scoring)--对候选节点排名: ✓ 剩余资源最多的加分 ✓ Pod 间亲和性匹配的加分 ✓ 避免把同一 Deployment 的 Pod 全放一个节点 → 选出最优节点 4. 更新 API Server → 设置 Pod.spec.nodeName = "node-3" ("这个 Pod 我给安排到 node-3 了") → Scheduler 的任务到此结束.后续是 node-3 上 kubelet 的事了.
Controller Manager - 维持集群的"期望状态"
Controller Manager 是 K8s 的"自动驾驶仪"--它持续对比"用户想要的"和"实际存在的",发现差异就自动修正.
它是多个独立 Controller 的集合体,每个 Controller 管一种资源:
Controller
管什么
做什么
Deployment Controller
Deployment 资源
期望 3 个副本,实际只有 2 个 → 创建一个新 Pod
ReplicaSet Controller
ReplicaSet 资源
保证 Pod 数量 = replicas 字段值
Node Controller
Node 资源
某节点 5 分钟没心跳 → 标记为 Unknown,把上面的 Pod 驱逐走,在其他节点重建
Service Controller
Service + Endpoints
Service 选中的 Pod 变了 → 更新 Endpoints 列表
Job Controller
Job 资源
创建一个 Pod 执行任务,失败了重试,成功了记录完成
K8s 最核心的设计模式:声明式 + 控制循环
每个 Controller 都运行同一个逻辑:观察(observe)→ 比对(diff)→ 修正(reconcile) .
用户声明"我要 3 个副本"(写 YAML),Controller 持续保证"真的有 3 个副本在跑".
挂了一个--重建; 多了一个--删除.这不是一次性的命令("创建 3 个"然后就不管了),而是持续的保证("始终有 3 个").这是 K8s 和传统运维脚本最根本的区别.
回忆你刚才的实验:删掉一个 Pod 后 Deployment 立刻补回来--这就是 Controller Manager 在起作用.
数据平面(Data Plane)
kubelet - 节点上的"车间主任"
每个 Worker Node 上都跑着一个 kubelet.它是该节点上 K8s 的唯一代理人--控制平面只和 kubelet 说话,kubelet 负责让本机上的 Pod 按期望运行.
kubelet 的工作循环: 1. 向 API Server 注册自己:"我是 node-3,我有 4 核 CPU,8G 内存" 持续汇报心跳 + 节点资源使用情况 2. watch API Server → 获知有哪些 Pod 被分配到自己 (Scheduler 已经给 Pod 写上了 nodeName=node-3) 3. 同步 Pod 状态: Pod 应该在跑但没跑?→ 调用 CRI 创建容器 Pod 不应该跑了? → 调用 CRI 停止容器 Pod 配置更新了? → 重新创建容器 4. 持续监控: 容器退出了? → 重启(根据 restartPolicy) 探针失败了? → 标记 Pod 不健康,根据需要重启或标记 NotReady 5. 把 Pod 的状态回报给 API Server # kubelet 不负责的事情: # • 不决定 Pod 去哪台机器(那是 Scheduler 的事) # • 不处理 Service 的网络转发(那是 kube-proxy 的事) # • 不跨节点调度(只管自己这台机器)
kube-proxy - 把 Service 翻译成网络规则
回顾第 01 篇的 iptables 和第 07 篇 Compose 的 DNS 服务发现.kube-proxy 做的是类似的事--但规模大得多:
apiVersion: v1 kind: Service metadata: name: nginx-svc spec: selector: app: nginx ports: - port: 80 targetPort: 80
容器运行时
这就回到第 09 篇的内容了.K8s 不自己创建容器--它通过 CRI(Container Runtime Interface) 调用 containerd 或 CRI-O,然后 containerd 再调用 runc/crun/kata:
kubelet │ CRI (gRPC) ▼ containerd / CRI-O ← 高级运行时(管理镜像 + 容器生命周期) │ OCI Runtime Spec (config.json) ▼ runc / crun / kata ← 低级运行时(调系统调用,创建 Namespace/Cgroup/rootfs) │ clone/unshare/mount ▼ Linux Kernel # 这条链和 docker run 的链几乎一样--只是把 dockerd 换成了 kubelet # docker: docker CLI → dockerd → containerd → runc → kernel # K8s: kubectl → API Server → kubelet → containerd → runc → kernel # ↑ ↑ # 中间多了两层调度(因为有集群维度)
Pod 创建全流程:从 kubectl apply 到容器运行
回忆你之前执行 kubectl apply -f nginx-pod.yaml 时,kubectl describe 显示了一串 Events.现在来还原完整链路--把前面所有组件串起来走一遍:
$ kubectl apply -f nginx-pod.yaml apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - name: nginx image: nginx:1.25 下面是一次完整的 Pod 创建过程(7 步): ┌─ 第 1 步:认证 & 授权 ──────────────────────────────────────┐ │ kubectl → API Server (HTTPS) │ │ │ │ API Server 收到 POST /api/v1/namespaces/default/pods │ │ → Authentication:检查你的 kubeconfig 里的证书/Token │ │ → Authorization (RBAC):你有创建 Pod 的权限吗? │ │ → Admission:Webhook 校验(合规检查,注入 Sidecar 等) │ │ → 通过 ✓ │ └──────────────────────────────────────────────────────────────┘ ┌─ 第 2 步:写入 etcd ────────────────────────────────────────┐ │ API Server → etcd │ │ │ │ 将 Pod 对象序列化为 JSON → 写入 /registry/pods/default/nginx │ │ → etcd 通过 Raft 协议复制到其他 etcd 节点 │ │ → 写入确认,API Server 返回 kubectl "created" │ │ │ │ 此时 Pod 在 etcd 里了,但 spec.nodeName 是空的 │ │ 没有容器在运行,也没有节点被分配 │ └──────────────────────────────────────────────────────────────┘ ┌─ 第 3 步:Scheduler 发现未分配的 Pod ───────────────────────┐ │ Scheduler 通过 watch 机制发现: │ │ "有个新 Pod nginx,spec.nodeName 为空!" │ │ │ │ → 过滤(Filtering):3 个节点中哪些满足条件? │ │ node-1: 剩余 CPU 够 ✓ 可调度 ✓ → 候选 │ │ node-2: 剩余 CPU 够 ✓ 可调度 ✓ → 候选 │ │ node-3: 剩余 CPU 不够 ✗ → 淘汰 │ │ │ │ → 打分(Scoring):node-1 vs node-2 │ │ node-1: 剩余资源多,分数高 → 胜出! │ │ │ │ → Scheduler 更新 API Server: │ │ Pod.spec.nodeName = "node-1" │ │ │ │ Scheduler 的工作到此结束 │ └──────────────────────────────────────────────────────────────┘ ┌─ 第 4 步:kubelet 发现分配给自己的 Pod ──────────────────────┐ │ node-1 上的 kubelet watch API Server 发现: │ │ "有个 Pod nginx,nodeName=node-1,这是我的活!" │ │ │ │ → kubelet 调用 CRI (gRPC): │ │ containerd,拉镜像 nginx:1.25 │ │ (如果本地没有 → 去 Registry pull,按层下载) │ │ │ │ → 镜像拉取完成 │ └──────────────────────────────────────────────────────────────┘ ┌─ 第 5 步:kubelet 创建 Pod 的沙箱(Sandbox)─────────────────┐ │ kubelet → CRI → containerd → runc │ │ │ │ 1. containerd 生成 OCI Runtime Spec 的 config.json │ │ 2. containerd 调用 runc: │ │ → 创建 Network Namespace(Pod 内所有容器共享此 NS) │ │ → 创建 PID Namespace(可选,通过 shareProcessNamespace 控制)│ │ → 创建 Mount Namespace │ │ → 创建 UTS/IPC Namespace │ │ │ │ 3. runc 启动 Pause 容器(Pod 的"占位符") │ │ → Pause 容器是一个极小的进程(sleep 永久),作用是: │ │ • 持有 Pod 的 Network NS(其他容器共享它的 NS) │ │ • 作为 PID 1 收割僵尸进程 │ │ → Pause 容器在宿主机上是一个极小的进程,永远不退出 │ │ │ │ 4. CNI 插件此时介入: │ │ → 给 Pod 分配一个 IP(如 10.244.1.5) │ │ → 创建 veth pair,一端插入 Pod 的 Network NS │ │ → 另一端接到宿主机的 CNI 网桥上 │ │ (这和你在第 01 篇手动搭的 veth + bridge 原理一样) │ └──────────────────────────────────────────────────────────────┘ ┌─ 第 6 步:kubelet 启动用户容器 ──────────────────────────────┐ │ kubelet → CRI → containerd → runc │ │ │ │ 1. containerd 生成第二个 OCI Runtime Spec: │ │ → 加入 Pause 容器的 Network NS(共享网络!) │ │ → rootfs = nginx 镜像的 OverlayFS merged 目录 │ │ → 限制 CPU/内存根据 Pod spec 配置 │ │ │ │ 2. runc 创建 nginx 容器进程: │ │ → clone/unshare(加入已有的 Network NS) │ │ → mount OverlayFS rootfs │ │ → pivot_root │ │ → exec nginx │ │ │ │ 3. nginx 进程开始运行 │ │ → 它看到 eth0: 10.244.1.5(和 Pause 容器共享的网络) │ │ → 它的 PID:在 Pod NS 内是某个数字,宿主机上是真实的 PID │ └──────────────────────────────────────────────────────────────┘ ┌─ 第 7 步:kubelet 回报状态 ─────────────────────────────────┐ │ kubelet → API Server: │ │ Pod nginx 的状态更新为 Running │ │ Pod IP = 10.244.1.5 │ │ │ │ kubectl get pods 现在显示 Running │ │ │ │ Controller Manager 中的各种 Controller 持续 watch: │ │ → 这个 Pod 是健康的吗? → 不影响,继续监控 │ │ → 这个 Pod 的副本数对吗? → 不影响,继续监控 │ │ │ │ 全流程结束 ✓ │ └──────────────────────────────────────────────────────────────┘
高可用部署
生产环境的 K8s 集群不会只跑一个控制平面--会出现单点故障.标准的高可用拓扑:
┌────────── High Availability K8s ──────────────────────────────┐ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Load Balancer │ │ │ │ (HAProxy / Nginx / 云 LB) │ │ │ └────┬────────────────────┬────────────────────┬──────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─ API Server 1 ─┐ ┌─ API Server 2 ─┐ ┌─ API Server 3 ─┐ │ │ │ │ │ │ │ │ │ │ │ Scheduler │ │ Scheduler │ │ Scheduler │ │ │ │ Ctrl Manager │ │ Ctrl Manager │ │ Ctrl Manager │ │ │ └───────┬────────┘ └───────┬────────┘ └───────┬────────┘ │ │ │ │ │ │ │ └──────────────────┼──────────────────┘ │ │ │ │ │ ┌────────┴────────┐ │ │ │ etcd Cluster │ │ │ │ (3 或 5 节点) │ │ │ │ Raft 共识协议 │ │ │ └─────────────────┘ │ │ │ │ API Server:无状态,可横向扩展,前端放 LB │ │ Scheduler + Controller Manager:通过 Leader Election 选主 │ │ 同时只有一个在工作(避免冲突),其他的 standby │ │ etcd:奇数节点,Raft 协议保证一致性,需 ≥半数的节点存活 │ │ │ │ Worker Nodes:每个 Worker 跑 kubelet + kube-proxy + CRI │ │ 一个 Worker 挂 → 上面的 Pod 被调度到其他 Worker 重建 │ │ 只要还有足够的 Worker,应用就不中断 │ └────────────────────────────────────────────────────────────────┘
小结
本篇要点回顾
要点
一句话概括
Pod
K8s 最小调度单元,不是"一个容器"而是"一组共享 NS 的容器集合"
YAML 骨架
apiVersion + kind + metadata + spec;所有 K8s 资源都遵循这四个必填字段
kubectl 基本操作
apply(创建/更新) / get(查看) / describe(详情) / logs(日志) / exec(进入) / delete(删除)
Deployment
管理 Pod 副本;你声明"要 3 个",Controller 持续保证"真的有 3 个在跑"
控制平面 vs 数据平面
控制平面管决策(API Server/etcd/Scheduler/Controller Manager),数据平面管执行(kubelet/kube-proxy/CRI)
API Server
集群唯一入口,所有组件通过它通信;watch 机制实现实时推送而非轮询
etcd
分布式 KV 存储,集群的"唯一真相来源",存所有资源对象;用 Raft 保证一致性
Scheduler
只做一件事:给未分配 Pod 选最合适的 Node.过滤→打分→写入 nodeName
Controller Manager
多个 Controller 的集合:观察→比对→修正,持续把实际状态推向期望状态
kubelet
节点代理人:watch 分配给自己的 Pod → 调 CRI 拉镜像/创建容器 → 监控 → 回报状态
kube-proxy
watch Service/Endpoints → 写 iptables/IPVS 规则 → 让 ClusterIP 的流量分发到后端 Pod
Pod 创建全流程
kubectl → API Server → etcd → Scheduler → kubelet → CRI → CNI → runc → Pause → 用户容器 → 回报
高可用
API Server 多副本+LB,Scheduler/CM 选主,etcd 奇数节点 Raft
动手练习
用 kubectl run hello --image=nginx:1.25 --port=80 快速创建一个 Pod(不写 YAML 也能跑),然后用 kubectl get pod hello -o yaml 查看 K8s 自动生成的完整 YAML--观察哪些字段是你没写但被补上的
写一个 Deployment(replicas=2),启动后执行 kubectl delete pod <其中一个>,观察新 Pod 的自动重建;再试 kubectl scale deployment <name> --replicas=5,观察扩容
执行 kubectl get pods -n kube-system 查看控制平面组件(它们在 kube-system 命名空间里以静态 Pod 方式运行)
画出 K8s 集群架构图(不参照任何资料,凭记忆画),标注每个组件的职责和它与其他组件的通信关系
默写 Pod 创建全流程的 7 个步骤,特别标注出哪几步涉及阶段一学过的 Namespace/Cgroup/OverlayFS
思考题:为什么所有组件都必须通过 API Server 通信,而不是直接互相调用?如果 Scheduler 直接告诉 kubelet "你去跑这个 Pod",会有什么问题?