Repository Reading Site
K8s 架构全景 — 先看地图再上路
假设你有一个 Web 应用,跑在一台服务器上。当用户量增长,你会: 1. **加机器** — 但怎么决定哪个请求去哪台机器?(负载均衡) 2. **进程挂了怎么办** — 手动 SSH 上去重启?凌晨三点也要?(自动恢复) 3. **发版怎么不停机** — 先停旧的再起新的?用户会断线(滚动更新) 4. **配置怎么管** — 每台机器手动改配置文件?(配置
K8s 架构全景 — 先看地图再上路
为什么需要 Kubernetes?
假设你有一个 Web 应用,跑在一台服务器上。当用户量增长,你会:
- 加机器 — 但怎么决定哪个请求去哪台机器?(负载均衡)
- 进程挂了怎么办 — 手动 SSH 上去重启?凌晨三点也要?(自动恢复)
- 发版怎么不停机 — 先停旧的再起新的?用户会断线(滚动更新)
- 配置怎么管 — 每台机器手动改配置文件?(配置管理)
- 不同服务怎么互相找到对方 — 硬编码 IP?机器换了就挂(服务发现)
Kubernetes 就是解决这些问题的容器编排平台。它的核心价值是:
你告诉 K8s "我想要什么状态"(声明式),K8s 负责"怎么达到并维持这个状态"。
比如你说"我要 3 个 nginx 副本",K8s 会确保始终有 3 个在运行。挂了一个?自动补一个。
K8s 集群的组成
一个 K8s 集群由两类节点组成:
┌─────────────────────────────────────────────────────────────┐
│ Control Plane (Master) │
│ │
│ ┌──────────┐ ┌───────────┐ ┌──────────────┐ ┌────────┐ │
│ │ API Server│ │ Scheduler │ │ Controller │ │ etcd │ │
│ │ │ │ │ │ Manager │ │ │ │
│ └────┬─────┘ └─────┬─────┘ └──────┬───────┘ └───┬────┘ │
│ │ │ │ │ │
└───────┼──────────────┼───────────────┼───────────────┼───────┘
│ │ │ │
▼ ▼ ▼ ▼
所有通信的 决定Pod放 确保实际状态 存储所有
唯一入口 在哪个节点 匹配期望状态 集群数据
│
│ kubelet 定期汇报 + 接收指令
│
┌───────┼─────────────────────────────────────────────────────┐
│ ▼ Worker Node │
│ ┌──────────┐ ┌────────────┐ ┌────────────────┐ │
│ │ kubelet │ │ kube-proxy │ │ Container │ │
│ │ │ │ │ │ Runtime │ │
│ │ 节点代理 │ │ 网络代理 │ │ (containerd) │ │
│ └──────────┘ └────────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Control Plane 组件(跑在 Master 节点上)
1. API Server(kube-apiserver)— 集群的"前台"
是什么: 所有操作的唯一入口。你敲的每一条 kubectl 命令,都是发 HTTP 请求给 API Server。
为什么这样设计:
- 统一入口意味着统一鉴权:谁能做什么,在一个地方控制
- 所有组件都通过 API Server 通信,而不是互相直连,降低了耦合
- API Server 是无状态的,可以水平扩展(多个 API Server 实例 + 负载均衡)
底层细节:
- 监听端口:默认 6443(HTTPS)
- 每个请求经过:认证(Authentication)→ 授权(Authorization/RBAC)→ 准入控制(Admission Control)→ 持久化到 etcd
- 支持 Watch 机制:其他组件(如 Scheduler)通过 Watch 实时感知变化,而不是轮询
2. etcd — 集群的"数据库"
是什么: 分布式键值存储,保存集群的所有状态:节点信息、Pod 定义、ConfigMap、Secret、RBAC 规则……
为什么用 etcd 而不是 MySQL/Redis:
- 分布式一致性:基于 Raft 协议,保证多副本数据一致(即使少数节点挂掉)
- Watch 机制:API Server 可以"订阅"数据变化,实时收到通知
- 简单:K8s 只需要键值存储,不需要关系型查询
底层细节:
- 默认监听端口:2379(客户端通信)、2380(节点间通信)
- 数据存储路径:
/var/lib/etcd - 所有数据以
/registry/为前缀。例如 default 命名空间的 Pod 存在/registry/pods/default/ - 生产环境必须定期备份 etcd,因为丢了 etcd = 丢了整个集群状态
面试重点: etcd 的 Raft 协议需要超过半数节点存活才能写入。所以生产环境通常部署 3 或 5 个 etcd 节点(2/3 或 3/5 存活即可工作)。我们的实验环境只有 1 个 Master,所以 etcd 是单点——这在生产中是不可接受的。
3. Scheduler(kube-scheduler)— "调度员"
是什么: 负责决定新创建的 Pod 应该放在哪个 Node 上运行。
调度流程(两步):
- 过滤(Filtering): 排除不满足条件的节点。比如:
- 节点资源不够(CPU/内存不足)
- 节点有 Taint 而 Pod 没有对应 Toleration
- Pod 指定了 nodeSelector 但节点没有对应标签
- 打分(Scoring): 对剩余节点打分,选最高分的。打分考虑:
- 资源均衡(避免把所有 Pod 塞到一个节点)
- 亲和性偏好(preferredDuringScheduling)
- 拓扑分散(TopologySpreadConstraints)
为什么不是随机分配? 因为生产环境中要考虑:资源利用率、故障域隔离、数据本地性、合规要求等。调度器是 K8s 最复杂的组件之一。
4. Controller Manager(kube-controller-manager)— "自动驾驶系统"
是什么: 一组控制循环(Control Loop),不断检查"当前状态"是否匹配"期望状态",不匹配就采取行动。
核心控制器举例:
| 控制器 | 职责 |
|---|---|
| ReplicaSet Controller | "期望 3 个副本,目前只有 2 个" → 创建 1 个新 Pod |
| Node Controller | "节点 40 秒没心跳了" → 标记 NotReady → 5 分钟后驱逐 Pod |
| Endpoint Controller | "Service 的 selector 匹配到新 Pod" → 更新 Endpoints 列表 |
| Job Controller | "Job 完成了 3/5 个任务" → 再创建 2 个 Pod |
面试重点(声明式 vs 命令式):
命令式:kubectl scale deployment nginx --replicas=5
"帮我把副本改成5个"(一次性动作)
声明式:deployment.yaml 里写 replicas: 5,然后 kubectl apply
"我要求始终保持5个副本"(持续维持)
K8s 的哲学是声明式。你不需要说"创建第3个Pod",你只说"我要3个",Controller 会持续确保这个状态。
Worker Node 组件(跑在每个工作节点上)
1. kubelet — "节点管家"
是什么: 每个节点上都运行的代理进程,负责:
- 向 API Server 注册本节点
- 接收 API Server 下发的 Pod 定义
- 调用容器运行时(containerd)来启动/停止容器
- 定期向 API Server 汇报节点状态(心跳)和 Pod 状态
底层细节:
- 通过 CRI(Container Runtime Interface)与容器运行时通信
- 心跳间隔:默认 10 秒
- 如果 kubelet 挂了,Master 不会立刻感知,而是等心跳超时(默认 40 秒)后标记节点 NotReady
2. kube-proxy — "网络规则管理员"
是什么: 负责实现 K8s Service 的网络转发。当你创建一个 Service,kube-proxy 会在每个节点上配置网络规则,让发往 Service ClusterIP 的流量转发到后端 Pod。
实现方式(三种模式):
- iptables 模式(默认):用 Linux iptables 规则做 DNAT(目标地址转换)
- IPVS 模式:用 Linux IPVS 做负载均衡,性能更好,支持更多算法
- userspace 模式:已弃用,性能差
面试常问: "Service 的 ClusterIP 是虚拟的,没有任何网卡绑定这个 IP。流量是靠 iptables/IPVS 规则在内核层面做转发。"
3. Container Runtime — "容器引擎"
是什么: 真正负责拉取镜像、创建容器、管理容器生命周期的组件。
为什么是 containerd 而不是 Docker?
- K8s 1.24 之后移除了对 Docker 的直接支持(dockershim)
- Docker 其实是一个"全家桶":Docker CLI + Docker Daemon + containerd + runc
- K8s 只需要 containerd 这一层,Docker 的上层是多余的开销
- 你之前用
docker run的镜像,在 containerd 下照样能跑(OCI 镜像标准兼容)
之前:kubelet → dockershim → Docker Daemon → containerd → runc → 容器
现在:kubelet → CRI → containerd → runc → 容器(少了两层中间人)
我们的实验集群架构
LA Region (<2ms 内网延迟)
┌────────────────────────────────────────────────────┐
│ │
│ ┌─────────────────────┐ │
│ │ Master │ 运行: │
│ │ *.*.176.193 │ API Server, Scheduler, │
│ │ 16C/16G │ Controller Manager, etcd │
│ │ ⚠ VPN:443/40000 │ + kubelet, kube-proxy │
│ └──────────┬──────────┘ │
│ │ │
│ ┌──────────┼──────────┐ ┌──────────────────────┐ │
│ │ Worker-1 │ │ │ Worker-2 │ │
│ │ .164.118 │ 0.6ms │ │ *.*.27.60 │ │
│ │ ⚠ VPN │ │ │ 无冲突 │ │
│ └──────────┘ │ └──────────────────────┘ │
└─────────────┬──────────────────────────────────────┘
│
│ ~146ms 跨太平洋延迟
│
┌─────────────┼──────────────────────────────────────┐
│ │ HK Region (~20ms) │
│ ┌──────────┴──────────┐ ┌──────────────────────┐ │
│ │ Worker-3 │ │ Worker-4 │ │
│ │ *.*.221.17 │ │ *.*.104.66 │ │
│ │ 4C/8G (异构) │ │ 150G (存储节点) │ │
│ └─────────────────────┘ └──────────────────────┘ │
└────────────────────────────────────────────────────┘
为什么 Master 放在 LA?
etcd 使用 Raft 共识协议,对延迟敏感。API Server 和 etcd 跑在同一台机器上(kubeadm 默认部署方式),如果 Master 在 HK,LA 的 3 台 Worker 每次与 API Server 通信都要跨洋 146ms。反过来,Master 在 LA,只有 HK 的 2 台 Worker 承受延迟。
Worker 节点的 kubelet 通过公网连接 API Server。 这不是生产最佳实践(生产环境通常在内网),但我们的机器分布在不同机房,公网是唯一选择。kubeadm init 时会为 API Server 生成 TLS 证书,所有通信都是加密的。
K8s 中的核心概念预览
在开始操作之前,先建立这些概念的直觉:
Pod — 最小调度单元
Pod ≠ 容器。一个 Pod 可以包含多个容器,它们:
- 共享同一个网络命名空间(同一个 IP,互相用 localhost 访问)
- 可以共享存储卷
- 被当作一个整体调度到同一个节点
为什么不直接调度容器? 因为有些应用天然需要"伴生"容器。比如一个 Web 应用 + 一个日志收集 sidecar,它们必须在同一台机器上、共享日志目录。Pod 就是这个"共生组"的抽象。
Namespace — 虚拟集群
一个 K8s 集群可以被划分成多个 Namespace,实现:
- 资源隔离(dev 和 prod 的 Pod 不会互相干扰)
- 权限隔离(开发人员只能操作 dev namespace)
- 资源配额(dev 最多用 4 核 CPU)
Label & Selector — 万物皆可贴标签
K8s 中最强大的组织机制。给任何资源贴标签(key=value),然后用 Selector 筛选。
比如 Service 通过 selector: app=nginx 找到所有标签为 app=nginx 的 Pod 来转发流量。
Controller — 确保状态的"永动机"
Deployment、StatefulSet、DaemonSet、Job 都是 Controller。它们的共同模式是:
while true:
当前状态 = 观察集群
期望状态 = 读取用户定义
if 当前状态 ≠ 期望状态:
采取行动让当前状态趋近期望状态
sleep(一小段时间)
这就是 K8s 的灵魂 — Reconciliation Loop(调和循环)。
接下来
理解了架构后,下一步是准备操作系统环境。每个前置条件(关闭 swap、加载内核模块等)都不是"照做就行",而是有明确的技术原因。