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

563 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 存储体系