Files
Qiniu/technical/k8s/k8s-01-overview.md
T

563 lines
21 KiB
Markdown
Raw Normal View History

2026-07-07 15:55:47 +08:00
---
tags: [k8s, devops, container-orchestration]
create time: 2026-07-07 15:48
---
# Kubernetes 概述
## 概述
Kubernetes(简称 K8s)是一个开源的**容器编排平台**,由 Google 内部的 Borg 系统演化而来,于 2014 年开源,目前由 CNCF(Cloud Native Computing Foundation)托管。它的核心使命是:**让部署、扩展和管理容器化应用变得自动化且可预测**。简单来说,Docker 解决了「如何打包一个应用」的问题,而 K8s 解决的是「如何在成百上千台机器上高效地运行和管理这些应用」的问题。
## 为什么需要 K8s
### 从 Docker 单机到集群编排
让我们先回顾一下容器技术的演进路径:
1. **裸机/虚拟机时代**:应用直接部署在物理机或 VM 上,手动管理依赖、端口和资源分配。
2. **Docker 单机时代**:容器解决了环境一致性和依赖打包的问题,但单机资源有限,且面临应用故障恢复、滚动更新等挑战。
3. **集群编排时代**:当你的服务需要跨多台机器运行时,你需要一个「大脑」来决定:把容器调度到哪台机器?挂了怎么重启?流量怎么分配?——这就是 K8s 存在的意义。
> [!tip] 一个类比
> 如果 Docker 集装箱标准化了「货物打包」,那 K8s 就是自动化码头——它决定集装箱该放哪艘船、航线怎么规划、遇到风暴如何重新调度。
### Docker Compose vs Docker Swarm vs Kubernetes
| 特性 | Docker Compose | Docker Swarm | Kubernetes |
|------|---------------|-------------|------------|
| **定位** | 单机多容器编排 | 轻量级集群编排 | 企业级容器编排平台 |
| **规模** | 单主机 | 中小规模集群 | 大规模生产环境 |
| **学习曲线** | 低 | 中 | 高 |
| **自动扩缩容** | 不支持 | 基础支持 | HPA / VPA / Cluster Autoscaler |
| **自愈能力** | 无 | 基础(重启策略) | 强(Liveness/Readiness Probe + 控制器) |
| **滚动更新** | 不支持 | 支持 | 细粒度控制(maxUnavailable / maxSurge) |
| **生态** | 仅 Docker 生态 | 仅 Docker 生态 | CNCF 生态,插件丰富 |
| **服务发现** | 容器名 DNS | 内置 DNS | CoreDNS + Service 资源 |
| **适用场景** | 本地开发、测试 | 快速上手小集群 | 生产环境首选 |
> [!info] 为什么 Swarm 没能打败 K8s?
> Docker Swarm 的设计哲学是「简单优先」,但现实世界的生产需求远比想象中复杂。K8s 的声明式 API、丰富的控制器模型、以及 CNCF 生态的飞轮效应,最终让它成为事实标准。
## 核心架构
K8s 采用经典的 **Master-Worker 架构**(官方文档中 Master 节点现在更常称为 Control Plane)。
### 架构总览
```mermaid
graph TB
subgraph ControlPlane["Control Plane(控制平面)"]
API["API Server<br/>集群的唯一入口"]
etcd["etcd<br/>分布式 KV 存储"]
sched["Scheduler<br/>Pod 调度决策"]
cm["Controller Manager<br/>维持期望状态"]
end
subgraph Worker1["Worker Node 1"]
kubelet1["kubelet<br/>管理 Pod 生命周期"]
proxy1["kube-proxy<br/>网络规则管理"]
cr1["Container Runtime<br/>containerd / CRI-O"]
P1a["Pod A"]
P1b["Pod B"]
end
subgraph Worker2["Worker Node 2"]
kubelet2["kubelet"]
proxy2["kube-proxy"]
cr2["Container Runtime"]
P2a["Pod C"]
P2b["Pod D"]
end
subgraph Client["用户 / CI/CD"]
kubectl["kubectl / SDK"]
end
kubectl -->|"REST API"| API
API <-->|"读写"| etcd
API -->|"监听事件"| sched
API -->|"监听事件"| cm
kubelet1 -->|"汇报状态"| API
kubelet2 -->|"汇报状态"| API
kubelet1 --> cr1
kubelet2 --> cr2
cr1 --> P1a & P1b
cr2 --> P2a & P2b
proxy1 -.->|"Service 流量"| P1a & P1b
proxy2 -.->|"Service 流量"| P2a & P2b
```
### Control Plane 组件详解
#### API Server(kube-apiserver)
API Server 是整个集群的**唯一入口**,所有组件之间的通信都通过它进行。它承担的核心职责:
- **RESTful API 网关**:暴露 K8s API,所有 `kubectl` 操作最终都到达这里
- **认证与授权**:RBAC、ServiceAccount、Webhook Token 等
- **准入控制(Admission Control)**:在资源持久化之前进行校验和修改(如 MutatingWebhook、ValidatingWebhook)
- **Watch 机制**:其他组件通过 Watch API Server 获取资源变更事件
> [!tip] 为什么所有组件都要通过 API Server 通信?
> 这是 K8s 的一个重要设计决策——**中心化 API 网关**保证了统一的认证、授权、审计和数据校验逻辑。如果各组件直接读写 etcd,安全策略将难以实施。
#### etcd
etcd 是一个高可用的分布式 KV 存储,是 K8s 的「唯一数据源」。所有集群状态(Pod、Service、ConfigMap 等资源对象)都持久化在 etcd 中。
- 基于 **Raft 共识算法**保证数据一致性
- 生产环境通常部署 3 或 5 个节点(奇数个,便于选举)
- **Watch 机制**:支持高效地监听 key 的变更
> [!warning] etcd 是整个集群的 Single Point of Truth
> 如果 etcd 数据丢失且没有备份,整个集群状态将不可恢复。生产环境务必做好 etcd 的定期备份。
#### Scheduler(kube-scheduler)
Scheduler 负责将新创建的 Pod **绑定到合适的 Node** 上。调度过程分为两个阶段:
1. **过滤(Filtering)**:排除不满足条件的 Node(如资源不足、Taint 不匹配、亲和性规则冲突)
2. **打分(Scoring)**:对剩余 Node 进行评分,选择得分最高的
常见调度策略:
- **资源请求与限制**:基于 Pod 的 `requests` 和 `limits`
- **亲和性与反亲和性**:`nodeAffinity`、`podAffinity`、`podAntiAffinity`
- **Taint 和 Toleration**:驱逐不兼容的 Pod
- **拓扑分布约束**:`topologySpreadConstraints` 跨可用区均匀分布
#### Controller Manager(kube-controller-manager)
Controller Manager 是一组控制器的集合,每个控制器负责一种资源类型的「调谐循环(Reconciliation Loop)」。核心思想:**持续比较「期望状态」和「实际状态」,并采取行动使实际状态趋近期望状态**。
常见控制器:
| 控制器 | 职责 |
|--------|------|
| Deployment Controller | 管理 ReplicaSet 的创建/更新 |
| ReplicaSet Controller | 确保指定数量的 Pod 副本运行 |
| Node Controller | 监控 Node 心跳,处理节点故障 |
| Job Controller | 管理一次性任务的 Pod |
| ServiceAccount Controller | 为新 Namespace 创建默认 ServiceAccount |
### Worker Node 组件详解
#### kubelet
kubelet 是运行在每个 Worker Node 上的**代理进程**,负责:
- 从 API Server 获取分配给本节点的 Pod Spec
- 调用 Container Runtime 拉取镜像、启动容器
- 执行健康检查(Liveness Probe、Readiness Probe、Startup Probe)
- 向 API Server 汇报节点和 Pod 状态
- 管理 Volume 的挂载和卸载
> [!info] kubelet 不是通过 API Server 启动容器的
> kubelet 通过 **CRI(Container Runtime Interface)** 与容器运行时交互,而非直接调用 Docker CLI。
#### kube-proxy
kube-proxy 负责在每个节点上维护**网络规则**,实现 Service 的负载均衡。它有三种工作模式:
| 模式 | 实现方式 | 性能 | 推荐度 |
|------|---------|------|--------|
| iptables | Linux iptables 规则 | 中等(规则数线性增长) | 默认模式 |
| IPVS | Linux IPVS(内核级 LB) | 高(哈希表查找) | 大规模集群推荐 |
| nftables | nftables 规则 | 高 | K8s 1.29+ 实验性 |
#### Container Runtime
Container Runtime 是真正执行容器生命周期操作的组件。K8s 通过 **CRI(Container Runtime Interface)** 标准化了与运行时的交互方式:
- **containerd**:目前最主流的选择,Docker 的核心运行时组件被独立出来
- **CRI-O**:Red Hat 主导,专为 K8s 设计的轻量级运行时
- **Docker**:自 K8s 1.24 起不再直接支持(dockershim 已移除),但 containerd 仍可使用
## 关键概念
### Pod
Pod 是 K8s 中**最小的可部署单元**,而不是容器。一个 Pod 可以包含一个或多个容器,它们:
- 共享同一个 Network Namespace(即共享 IP 和端口空间)
- 可以通过 Volume 共享存储
- 总是被调度到同一个 Node 上
> [!tip] 为什么需要 Pod 而不是直接管理容器?
> 现实中有些应用需要「伴生容器(Sidecar)」——比如日志收集器、Service Mesh 代理。Pod 将这些紧密耦合的容器打包在一起,共享网络和存储,比独立容器更优雅。
```yaml
# 一个包含 Sidecar 的 Pod 示例
apiVersion: v1
kind: Pod
metadata:
name: app-with-logging
labels:
app: my-app # 用于 Service 选择和筛选
env: production
spec:
containers:
- name: app
image: my-app:v1.2.3
ports:
- containerPort: 8080
resources:
requests:
cpu: "100m" # 0.1 核
memory: "128Mi"
limits:
cpu: "500m" # 最多 0.5 核
memory: "512Mi"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 3
- name: log-collector
image: fluentd:v1.16
volumeMounts:
- name: log-volume
mountPath: /var/log/app
volumes:
- name: log-volume
emptyDir: {} # Pod 内共享的临时目录
```
**Pod 生命周期中的几个关键阶段**:
| Phase | 含义 |
|-------|------|
| Pending | 已被 API Server 接受,但尚未调度或镜像未拉取 |
| Running | 至少一个容器正在运行 |
| Succeeded | 所有容器正常退出(退出码 0) |
| Failed | 至少一个容器异常退出 |
| Unknown | 无法获取 Pod 状态(通常是 Node 通信故障) |
### Node
Node 是 K8s 集群中的**工作机器**(物理机或虚拟机)。每个 Node 包含运行 Pod 所需的服务(kubelet、kube-proxy、Container Runtime)。
查看 Node 信息:
```bash
# 查看集群所有节点
kubectl get nodes -o wide
# 查看某个节点的详细信息(资源、条件、运行的 Pod)
kubectl describe node <node-name>
```
Node 有几个重要的**条件(Conditions)**:
| Condition | 说明 |
|-----------|------|
| Ready | 节点健康且准备好接受 Pod |
| MemoryPressure | 节点内存不足 |
| DiskPressure | 节点磁盘空间不足 |
| PIDPressure | 节点进程数过多 |
| NetworkUnavailable | 节点网络未正确配置 |
### Namespace
Namespace 是 K8s 中的**逻辑隔离机制**,用于在同一物理集群中划分多个虚拟集群。适用于:
- 多团队共享集群(`team-a`、`team-b`)
- 环境隔离(`dev`、`staging`、`production`)
- 资源配额管理(每个 Namespace 设置独立的 ResourceQuota)
```yaml
# 创建一个 Namespace
apiVersion: v1
kind: Namespace
metadata:
name: team-backend
labels:
team: backend
env: production
---
# 在该 Namespace 下设置资源配额
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-backend-quota
namespace: team-backend
spec:
hard:
requests.cpu: "10" # 总 CPU 请求上限 10 核
requests.memory: "20Gi" # 总内存请求上限 20Gi
limits.cpu: "20"
limits.memory: "40Gi"
pods: "50" # 最多 50 个 Pod
```
K8s 默认创建的 Namespace:
| Namespace | 用途 |
|-----------|------|
| `default` | 未指定 Namespace 时的默认值 |
| `kube-system` | K8s 系统组件(CoreDNS、kube-proxy 等) |
| `kube-public` | 公共资源(集群信息等) |
| `kube-node-lease` | 节点心跳 Lease 对象 |
```bash
# 在指定 Namespace 下操作
kubectl get pods -n team-backend
# 设置默认 Namespace(避免每次都输入 -n)
kubectl config set-context --current --namespace=team-backend
```
> [!warning] Namespace 不提供网络隔离
> 默认情况下,不同 Namespace 的 Pod 可以互相通信。如需网络隔离,需要配合 **NetworkPolicy** 实现。
### Label & Selector
Label 是附加在 K8s 对象上的**键值对**,是 K8s 组织和筛选资源的核心机制。你可能会问:「为什么 K8s 不用目录/文件夹的方式来组织资源?」——因为资源之间的关系是多对多的,一个 Pod 可能同时属于某个应用、某个环境、某个团队,Label 的灵活性远超树形结构。
```yaml
# Label 的常见命名约定
metadata:
labels:
app.kubernetes.io/name: my-app # 应用名称
app.kubernetes.io/version: "1.2.3" # 版本
app.kubernetes.io/component: frontend # 组件
app.kubernetes.io/part-of: platform # 所属系统
app.kubernetes.io/managed-by: helm # 管理工具
env: production # 环境
team: backend # 团队
```
**Selector 的三种用法**:
```bash
# 等值选择器
kubectl get pods -l app=my-app,env=production
# 集合选择器
kubectl get pods -l 'env in (production, staging)'
kubectl get pods -l 'env notin (dev)'
# 否定选择器
kubectl get pods -l 'app!=my-app'
```
在资源定义中使用 Selector(以 Service 为例):
```yaml
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
spec:
selector:
app: my-app # 匹配所有带 app=my-app 标签的 Pod
env: production
ports:
- port: 80
targetPort: 8080
```
### Annotation
Annotation 用于存储**非标识性的附加信息**,这些信息不会被 K8s API 用于筛选,但对工具、库和运维人员很有用。典型的 Annotation 内容:
```yaml
metadata:
annotations:
# 记录维护者信息
owner: "backend-team@company.com"
# Git 提交信息(CI/CD 注入)
git-commit: "abc123f"
git-branch: "main"
# 构建时间
build-time: "2026-07-07T15:00:00Z"
# Ingress 配置(Nginx Ingress Controller 使用)
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
# Prometheus 自动发现
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
```
> [!tip] Label vs Annotation 如何选择?
> 问自己一个问题:「我需要根据这个信息来筛选或分组资源吗?」—— 如果是,用 Label;如果只是附加的描述性信息,用 Annotation。Label 有长度和字符限制,Annotation 则更宽松。
## K8s 与 Docker 的关系
### 常见误解
很多初学者会问:「K8s 是不是要替代 Docker?」答案是:**不完全是**。准确地说:
- K8s 替代的是 Docker Swarm(编排层面的竞争)
- K8s **不再直接使用 Docker Engine** 作为容器运行时(自 1.24 起)
- 但你仍然可以**用 Docker 构建镜像**,K8s 运行的是 OCI 标准镜像,与构建工具无关
### 容器运行时的演进
```mermaid
timeline
title K8s 容器运行时演进
2014-2016 : Docker 是唯一选择
: kubelet 直接调用 Docker API
2017 : CRI 标准引入
: dockershim 作为适配层
2018 : containerd 从 Docker 拆分
: CRI-O 项目成熟
2020 : dockershim 弃用公告
2022-04 : K8s 1.24 移除 dockershim
: containerd 成为主流
2024+ : CRI-O 广泛使用
: Kata / gVisor 沙箱运行时兴起
```
**CRI(Container Runtime Interface)** 是 K8s 定义的一套 gRPC 接口规范,任何实现了 CRI 的运行时都可以被 kubelet 使用:
| 运行时 | 特点 | 适用场景 |
|--------|------|---------|
| containerd | Docker 核心组件独立版,功能全面 | 通用场景,最广泛使用 |
| CRI-O | 专为 K8s 设计,轻量级 | Red Hat / OpenShift 生态 |
| Kata Containers | 轻量级 VM,安全隔离 | 多租户、不可信工作负载 |
| gVisor (runsc) | 用户态内核沙箱 | 安全敏感场景 |
> [!info] 理解 CRI 的分层
> kubelet → CRI (gRPC) → containerd → runc → Linux Kernel(namespaces, cgroups)
>
> 每一层都有明确的职责边界。containerd 负责镜像管理和容器生命周期,runc 负责实际创建 Linux 容器。
### 为什么移除 dockershim?
移除 dockershim 的主要原因:
1. **维护成本**:dockershim 是 kubelet 内部的一层额外抽象,增加代码复杂度
2. **性能开销**:kubelet → dockershim → Docker daemon → containerd,调用链过长
3. **Docker 的「额外功能」**:Docker Engine 包含很多 K8s 不需要的组件(如 Docker Swarm、docker build 等),但作为运行时又必须加载整个 daemon
4. **CRI 标准成熟**:containerd 和 CRI-O 直接实现 CRI,不再需要中间层
## 部署方式对比
| 方案 | 类型 | 节点数 | 生产就绪 | 学习成本 | 适用场景 |
|------|------|--------|---------|---------|---------|
| **minikube** | 单节点本地集群 | 1 | 否 | 低 | 本地开发和学习,支持 Addons |
| **kind** | Docker 容器模拟 Node | 1-N | 否 | 低 | CI/CD 测试、多节点本地模拟 |
| **kubeadm** | 生产级部署工具 | N | 是 | 中高 | 自建集群、对基础设施有完全控制 |
| **EKS** | AWS 托管 | N | 是 | 中 | AWS 生态用户 |
| **AKS** | Azure 托管 | N | 是 | 中 | Azure 生态用户 |
| **GKE** | Google Cloud 托管 | N | 是 | 中 | GCP 生态用户,Autopilot 模式 |
| **ACK** | 阿里云托管 | N | 是 | 中 | 国内云生态用户 |
> [!tip] 如何选择?
> - **学习阶段**:minikube 或 kind(零成本,快速启动)
> - **CI/CD**:kind(轻量,测试完即销毁)
> - **小团队生产**:托管服务(减少运维负担)
> - **大规模/定制需求**:kubeadm 或 Cluster API
## 常见陷阱与最佳实践
### 陷阱
1. **不设置 Resource Requests/Limits**
```yaml
# 反面教材:Pod 没有资源声明
containers:
- name: app
image: my-app:v1
```
后果:调度器无法做出合理决策,某个 Pod 可能吃掉整个节点的资源,导致其他 Pod 被 OOM Kill。
2. **把所有东西塞进一个 Pod**
Pod 中的容器应该有紧密的生命周期耦合。如果两个应用可以独立部署、独立扩缩容,它们应该在不同的 Pod 中。
3. **忽略 Liveness/Readiness Probe**
没有 Probe 的 Pod,K8s 无法知道它是否真的健康。一个「Running」状态的 Pod,内部应用可能已经死锁。
4. **在生产环境使用 `latest` 标签**
```yaml
# 反面教材
image: my-app:latest
```
你无法确定每次部署用的是哪个版本,回滚更是噩梦。始终使用明确的版本标签(如 `v1.2.3` 或 Git SHA)。
5. **直接操作集群资源而不使用 GitOps**
手动 `kubectl apply` 的变更不可追溯、不可审计。使用 Helm / Kustomize + ArgoCD / Flux 进行声明式管理。
### 最佳实践
1. **声明式 > 命令式**:始终用 YAML 定义资源,而不是 `kubectl run`/`kubectl create` 的命令式操作。声明式配置可版本控制、可审计、可复现。
2. **合理使用 Namespace**:按团队或环境划分 Namespace,配合 ResourceQuota 和 LimitRange 进行资源管控。
3. **Pod 反亲和性确保高可用**:
```yaml
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: ["my-app"]
topologyKey: kubernetes.io/hostname
```
这确保同一应用的多个副本不会被调度到同一个 Node 上。
4. **使用 `imagePullPolicy` 控制镜像拉取策略**:
- `Always`:总是拉取(适用于 `latest` 标签,但不推荐用 latest)
- `IfNotPresent`:本地没有才拉取(推荐用于明确版本标签)
- `Never`:从不拉取(仅用于本地开发)
5. **优雅终止(Graceful Shutdown)**:
```yaml
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
```
`preStop` 中的 `sleep 5` 给 kube-proxy 更新规则留出时间,避免请求被发送到正在终止的 Pod。
6. **LimitRange 设置默认值**:
```yaml
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: team-backend
spec:
limits:
- default:
cpu: "500m"
memory: "256Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
type: Container
```
防止单个未配置资源限制的容器拖垮整个节点。
## 延伸阅读
- [Kubernetes 官方文档](https://kubernetes.io/zh-cn/docs/home/) — 最权威的参考
- [Kubernetes 官方教程](https://kubernetes.io/zh-cn/docs/tutorials/) — 动手入门
- [CNCF 技术雷达](https://www.cncf.io/tech-radars/) — 云原生技术选型参考
- [Kelsey Hightower - Kubernetes The Hard Way](https://github.com/kelseyhightower/kubernetes-the-hard-way) — 手动从零搭建 K8s 集群,深入理解每个组件
- 《Kubernetes in Action (第二版)》 — Marko Lukša 著,深入原理的经典教材
## 关联笔记
- [[k8s/k8s-workload-resources]] — Deployment、StatefulSet、DaemonSet 等工作负载详解
- [[k8s/k8s-networking]] — Service、Ingress、NetworkPolicy 网络模型
- [[k8s/k8s-storage]] — PV、PVC、StorageClass 存储体系