在 Docker 出现之后,我们解决了应用运行环境不一致的问题。
开发人员可以把应用、依赖、运行时环境一起打包成镜像,然后通过容器启动:
docker run -d --name nginx -p 80:80 nginx
对于一台服务器来说,这已经足够简单。
但如果服务器从 1 台变成 10 台、100 台甚至更多,问题就开始出现了:
- 容器应该运行在哪台机器?
- 某个容器挂了,谁负责重新启动?
- 服务需要扩容,如何快速启动几十个实例?
- 某台服务器宕机了,运行在上面的服务怎么办?
- 如何让多个容器之间进行稳定通信?
- 应用升级时,如何做到不停机?
- 配置、密码、证书应该放在哪里?
- 如何知道整个集群现在到底是什么状态?
Docker 解决的是容器运行问题,而 Kubernetes 解决的是更上层的容器编排问题。
Kubernetes,通常简称 K8s,可以理解成一个管理容器化应用的分布式系统。
它最核心的思想其实并不复杂:
你告诉 Kubernetes“我希望系统最终是什么样子”,Kubernetes 不断检查当前状态,并通过各种控制器把实际状态调整到期望状态。
这也是理解 Kubernetes 最重要的一句话。
一、先理解 Kubernetes 到底解决了什么问题
假设我们有一个 Node.js 服务:
user-service
最开始只有一个 Docker 容器:
Server
└── user-service
随着用户增加,我们需要运行 3 个实例:
Server
├── user-service
├── user-service
└── user-service
如果继续增加到 10 个实例,单台机器可能已经无法承载,于是我们增加服务器:
Load Balancer
│
┌───────────┼───────────┐
▼ ▼ ▼
Server1 Server2 Server3
│ │ │
Container Container Container
到了这里,我们实际上已经遇到了一个典型的分布式系统问题。
如果完全手工管理,就需要自己处理:
容器启动
容器停止
容器故障恢复
服务发现
负载均衡
扩容缩容
机器故障
版本升级
配置管理
存储
网络
权限
Kubernetes 就是把这些事情统一抽象成了一套 API 和控制系统。
因此,不要简单地把 Kubernetes 理解成:
“一个可以运行 Docker 的工具。”
更准确地说:
Kubernetes 是一个通过声明式 API 管理分布式应用运行状态的平台。
二、Kubernetes 的基本架构
一个 Kubernetes 集群通常由两类节点组成:
Kubernetes Cluster
Control Plane
┌───────────────┐
│ API Server │
│ Scheduler │
│ Controller │
│ etcd │
└───────┬───────┘
│
┌────────┼────────┐
▼ ▼ ▼
Worker Worker Worker
Node Node Node
│ │ │
Pod Pod Pod
Control Plane 负责管理整个集群。
Worker Node 负责真正运行应用。
现在 Kubernetes 官方通常使用 control plane / worker node 这样的叫法。早期资料中经常会看到 master / node 的称呼。
API Server
API Server 是 Kubernetes 集群最重要的入口。
你执行:
kubectl get pods
实际上不是直接去访问某台服务器上的容器,而是:
kubectl
│
▼
API Server
│
├── 查询集群状态
│
└── 返回结果
所有 Kubernetes API 操作最终都会经过 API Server。
例如:
kubectl create
kubectl get
kubectl delete
kubectl apply
本质上都是在和 Kubernetes API 打交道。
etcd
etcd 是 Kubernetes 的核心数据存储。
可以简单理解为:
Kubernetes 集群的“数据库”。
例如我们创建了一个 Deployment:
replicas: 3
Kubernetes 需要记录:
这个 Deployment 存在
期望副本数 = 3
镜像是什么
配置是什么
标签是什么
这些集群状态最终会持久化到 etcd。
所以生产环境中 etcd 是非常重要的组件。
如果 etcd 中的数据出现严重问题,整个 Kubernetes 集群的控制面都会受到影响。
Scheduler
Scheduler 负责决定:
一个新的 Pod 应该运行在哪个 Node 上?
例如现在有三个节点:
node-1
CPU: 2
Memory: 4GB
node-2
CPU: 8
Memory: 16GB
node-3
CPU: 4
Memory: 8GB
新 Pod 提交后,Scheduler 会综合考虑:
CPU / Memory
节点资源
亲和性
反亲和性
污点
容忍
拓扑
节点选择条件
然后选择合适的节点。
注意:
Scheduler 负责“决定放在哪里”,并不负责真正启动容器。
Controller Manager
Controller 是 Kubernetes 最有意思的设计之一。
假设你告诉 Kubernetes:
replicas: 3
代表:
我希望最终存在 3 个 Pod。
现在实际情况可能是:
期望状态:3
实际状态:2
Controller 发现两者不一致,就会尝试创建一个新的 Pod。
于是:
Desired State
│
▼
Controller
│
▼
Actual State
这就是 Kubernetes 的控制循环。
如果一个 Pod 被删除:
期望:3
实际:2
Controller 会创建新的 Pod。
如果某个 Pod 所在节点故障,Kubernetes 也可以根据资源控制器重新创建实例。
所以 Kubernetes 并不是简单地“执行一次命令”。
它实际上一直在:
观察 → 对比 → 调整 → 再观察
这就是 Kubernetes 的核心思想之一。
三、Pod:Kubernetes 中最基本的运行单位
刚接触 Kubernetes 时,最容易产生一个疑问:
Docker 不是直接运行 Container 吗?为什么 Kubernetes 又搞了一个 Pod?
因为 Kubernetes 并没有直接把 Container 作为自己的最小调度单位,而是引入了 Pod。
最简单的 Pod 可以理解为:
Pod
└── Container
例如:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
创建:
kubectl apply -f nginx.yaml
查看:
kubectl get pods
可能得到:
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 10s
这里的:
1/1
表示 Pod 中有 1 个容器,当前 1 个容器都是 Ready 状态。
为什么不直接调度 Container?
因为 Kubernetes 有时候需要让多个容器共享一些运行环境。
例如:
Pod
├── application
└── sidecar
两个 Container:
- 共享网络命名空间
- 可以通过 localhost 通信
- 可以共享 Volume
例如:
application
│
│ localhost
▼
sidecar
这类设计在日志收集、代理、Service Mesh 等场景中比较常见。
不过对于普通业务应用来说:
一个 Pod 里面通常一个主要业务容器就够了。
不要为了“看起来高级”而随便把多个业务服务塞进一个 Pod。
四、Deployment:不要直接管理 Pod
虽然可以直接创建 Pod,但生产环境一般不会这么做。
因为 Pod 本身并不负责:
副本管理
滚动更新
版本发布
故障恢复策略
因此通常会使用 Deployment。
例如:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
执行:
kubectl apply -f deployment.yaml
然后:
kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE
nginx 3/3 3 3
此时 Kubernetes 会创建:
Deployment
│
▼
ReplicaSet
│
├── Pod
├── Pod
└── Pod
所以实际关系是:
Deployment
↓
ReplicaSet
↓
Pod
↓
Container
Deployment 负责声明:
我希望 nginx 有 3 个实例
ReplicaSet 负责保证:
实际存在 3 个 Pod
Pod 最终运行 Container。
五、Service:Pod 为什么不能直接访问?
这是 Kubernetes 网络中非常重要的一点。
Pod 是具有生命周期的。
例如:
Pod A
IP: 10.244.1.10
Pod 重建以后:
Pod B
IP: 10.244.2.15
IP 可能发生变化。
如果前端直接访问:
10.244.1.10
那么 Pod 重建之后连接就失效了。
因此 Kubernetes 提供了 Service。
Service
10.96.10.20
│
┌─────────┼─────────┐
▼ ▼ ▼
Pod A Pod B Pod C
Service 提供一个稳定的访问入口。
例如:
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
type: ClusterIP
创建后:
kubectl get svc
可以看到:
NAME TYPE CLUSTER-IP
nginx ClusterIP 10.96.10.20
集群内部的其他服务就可以通过:
nginx:80
访问它。
这里 Service 最重要的机制就是:
Service
│
│ selector
▼
Pod
例如:
selector:
app: nginx
它会选择拥有:
app=nginx
这个 Label 的 Pod。
所以 Kubernetes 中:
Label + Selector 是非常核心的一套机制。
六、Ingress:让外部用户访问 Kubernetes 服务
Service 的 ClusterIP 通常只用于集群内部。
如果用户通过浏览器访问:
https://example.com
我们需要把外部请求引入 Kubernetes。
常见结构是:
Internet
│
▼
LoadBalancer
│
▼
Ingress Controller
│
▼
Service
│
▼
Pod
Ingress 可以理解成:
HTTP/HTTPS 层面的路由规则。
例如:
api.example.com
│
▼
api-service
www.example.com
│
▼
web-service
Ingress 规则可能类似:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
于是:
https://api.example.com
就可以路由到:
api-service
需要注意,Ingress 本身只是 API 资源。
真正处理 HTTP 请求的通常是 Ingress Controller,例如 NGINX Ingress Controller、Traefik 等。
七、ConfigMap 和 Secret:配置不要直接写死在镜像里
假设 Node.js 应用需要:
DATABASE_HOST
DATABASE_PORT
DATABASE_USER
如果直接写到 Docker 镜像里面:
ENV DATABASE_HOST=xxx
那么不同环境就需要构建不同镜像。
这并不理想。
Kubernetes 提供了 ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DATABASE_HOST: postgres
DATABASE_PORT: "5432"
Pod 可以通过环境变量读取。
env:
- name: DATABASE_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: DATABASE_HOST
对于密码、Token 等敏感信息,则可以使用 Secret:
apiVersion: v1
kind: Secret
metadata:
name: app-secret
type: Opaque
stringData:
DATABASE_PASSWORD: "password"
然后:
env:
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: app-secret
key: DATABASE_PASSWORD
不过需要注意:
Kubernetes Secret 并不等于天然安全的密码保险箱。
Secret 默认主要是 Base64 编码,而不是加密意义上的“不可读取”。
生产环境通常还会结合:
RBAC
etcd encryption
External Secrets
Vault
云厂商 Secret Manager
进一步加强敏感信息管理。
八、资源限制:为什么 Kubernetes 有时候会杀掉你的 Pod?
在 Kubernetes 中,可以给 Container 设置资源请求和限制。
例如:
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
这里:
500m CPU = 0.5 CPU
requests 表示:
调度 Pod 时,希望至少能够提供这些资源。
limits 表示:
Container 使用资源时允许达到的上限。
例如:
requests:
cpu: 500m
limits:
cpu: 1
Scheduler 会根据 requests 判断节点是否有足够资源。
这也是生产环境非常重要的一点。
如果完全不设置资源限制,一个服务出现内存泄漏:
Node
├── service-a
├── service-b
├── service-c
└── service-d
↑
内存不断增长
最终可能影响整个节点。
如果超过 Container 的 memory limit,Linux cgroup 会触发 OOM,Pod 可能出现:
OOMKilled
所以排查 Kubernetes 中的 Pod 异常时:
kubectl describe pod xxx
以及:
kubectl get pod xxx -o yaml
是非常常用的命令。
九、健康检查:不要只看 Pod 是不是 Running
很多刚开始使用 Kubernetes 的人会认为:
STATUS = Running
就代表服务正常。
实际上并不是。
一个 Node.js 应用可能:
进程还活着
但是:
数据库连接已经断开
Kafka 消费已经停止
Redis 不可用
应用已经无法正常处理请求
因此 Kubernetes 提供了探针。
Liveness Probe
用于判断:
这个容器是不是已经死掉了?
例如:
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
如果连续检查失败,Kubernetes 可以重启 Container。
Readiness Probe
用于判断:
这个 Pod 当前能不能接收流量?
例如应用启动过程中:
Pod
│
├── Container Running
│
├── 初始化数据库
│
├── 初始化 Redis
│
├── 加载配置
│
└── Ready
在 Ready 之前,Service 不应该把正常业务流量发送给它。
这就是 Readiness Probe 的作用。
生产环境中:
Liveness 和 Readiness 不应该简单地使用同一个接口、同一种判断逻辑。
十、滚动更新:Kubernetes 最实用的能力之一
假设线上运行:
nginx:1.27
现在需要升级:
nginx:1.28
如果直接停止旧版本,再启动新版本:
旧版本停止
↓
服务不可用
↓
新版本启动
就会产生明显的停机时间。
Deployment 默认支持 Rolling Update。
例如:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
原来:
v1 v1 v1
升级过程中可能变成:
v1 v1 v1 v2
然后:
v1 v1 v2
再变成:
v1 v2 v2
最后:
v2 v2 v2
整个过程由 Deployment 控制。
查看发布状态:
kubectl rollout status deployment/nginx
查看历史:
kubectl rollout history deployment/nginx
如果新版本出现问题,可以回滚:
kubectl rollout undo deployment/nginx
这也是 Kubernetes 在实际生产环境中非常有价值的能力。
十一、StatefulSet:有状态服务怎么办?
Deployment 更适合:
Web
API
Worker
Gateway
这类无状态应用。
但是数据库、Kafka、ZooKeeper 等服务具有明显的状态特征。
例如:
mysql-0
mysql-1
mysql-2
它们不是简单的三个完全相同的 Pod。
每个实例可能拥有:
固定身份
固定网络名称
独立存储
这时候 Kubernetes 提供 StatefulSet。
例如:
StatefulSet
│
├── mysql-0
├── mysql-1
└── mysql-2
与 Deployment 最大的区别之一,就是 StatefulSet 会维护 Pod 的稳定身份。
例如:
mysql-0
即使 Pod 重建,它仍然可以继续叫:
mysql-0
同时可以配合 PersistentVolume 保存数据。
十二、Kubernetes 存储
容器有一个天然的问题:
Container 删除以后,里面的数据怎么办?
如果数据库直接把数据放在 Container 文件系统里:
Container
└── /var/lib/mysql
Container 删除:
数据也可能一起消失
因此 Kubernetes 引入了 Volume、PersistentVolume 和 PersistentVolumeClaim。
关系可以简单理解为:
Pod
│
▼
PVC
│
▼
PV
│
▼
Storage
PVC 表示:
我需要一块存储。
例如:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
Pod 使用:
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-data
这样数据库数据就可以脱离 Pod 生命周期。
在云环境中,PV 最终可能对应:
云硬盘
NAS
Ceph
云厂商 CSI
NFS
十三、调度:Pod 到底应该运行在哪里?
当 Kubernetes 集群只有几个节点时,你可能感觉不到调度的重要性。
但生产环境可能有:
100+ Nodes
1000+ Pods
这时候就不能简单地:
哪里有资源就扔哪里
Kubernetes 提供了很多调度机制。
最基础的是:
nodeSelector:
kubernetes.io/hostname: node-01
表示:
这个 Pod 必须运行在 node-01。
更灵活的方式是 Node Affinity。
例如:
SSD 节点
GPU 节点
业务节点
计算节点
还可以通过:
Taint
Toleration
Affinity
Anti-Affinity
Topology Spread Constraints
控制 Pod 的分布。
例如一个高可用 Web 服务:
Node 1
└── web-1
Node 2
└── web-2
Node 3
└── web-3
而不是:
Node 1
├── web-1
├── web-2
└── web-3
因为如果 Node 1 故障,后一种部署方式会让三个实例同时消失。
十四、Namespace:把一个集群分成多个逻辑环境
一个 Kubernetes 集群通常不会只运行一个项目。
例如:
production
staging
development
monitoring
可以使用 Namespace:
kubectl create namespace production
然后:
production
├── api
├── web
├── worker
└── redis
staging
├── api
├── web
└── worker
查看 Pod:
kubectl get pods -n production
Namespace 可以用于:
资源隔离
权限控制
资源配额
环境划分
但是要注意:
Namespace 主要是逻辑隔离,不等于完整的安全隔离。
十五、RBAC:谁可以操作 Kubernetes?
Kubernetes 本身就是一个非常强大的控制系统。
如果一个用户拥有:
cluster-admin
那么基本可以控制整个集群。
因此生产环境必须考虑权限。
Kubernetes 的 RBAC 核心对象包括:
Role
ClusterRole
RoleBinding
ClusterRoleBinding
例如:
开发人员
│
▼
只能访问 staging namespace
│
├── get pods
├── logs pods
└── restart deployment
而不是直接给:
cluster-admin
这也是生产 Kubernetes 必须重视的问题。
十六、Kubernetes 真正运行起来之后,应该怎么排查问题?
实际工作中,Kubernetes 最常见的并不是“怎么创建 Pod”,而是:
Pod 为什么没起来?
可以形成一套比较固定的排查路径。
首先:
kubectl get pods -n xxx
观察:
Pending
Running
CrashLoopBackOff
ImagePullBackOff
ErrImagePull
OOMKilled
如果是:
Pending
可以查看:
kubectl describe pod xxx -n xxx
重点看:
Events
可能发现:
Insufficient cpu
Insufficient memory
node affinity conflict
unbound PersistentVolumeClaim
如果:
CrashLoopBackOff
重点看日志:
kubectl logs xxx -n xxx
如果容器已经重启过,可以:
kubectl logs xxx -n xxx --previous
如果 Pod 看起来正常,但是服务访问不了,可以继续检查:
kubectl get svc
kubectl get endpoints
kubectl get ingress
再检查:
Service selector
Pod labels
Readiness Probe
NetworkPolicy
Ingress
DNS
这套思路比“看到 Pod 不正常就重启”重要得多。
十七、从 Docker 到 Kubernetes,可以这样理解
如果把整个 Kubernetes 的概念压缩成一张图:
Kubernetes
│
┌───────────┴───────────┐
│ │
Control Plane Worker Node
│ │
┌─────┼─────┐ │
│ │ │ │
API Server etcd Scheduler │
│ │
└──────── Controller │
│
Pod
│
┌────┴────┐
│ │
Container Container
应用层面:
Deployment
│
▼
ReplicaSet
│
▼
Pod
│
▼
Container
网络层面:
Internet
│
▼
Ingress
│
▼
Service
│
▼
Pod
配置层面:
ConfigMap
Secret
│
▼
Pod
存储层面:
Pod
│
▼
PVC
│
▼
PV
│
▼
Storage
权限层面:
User
│
▼
RBAC
│
▼
Kubernetes API
十八、生产环境中的 Kubernetes
真正进入生产以后,Kubernetes 本身只是基础设施的一部分。
一个比较完整的生产环境通常还会围绕 Kubernetes 建立:
Git
│
▼
CI Pipeline
│
▼
Docker Image
│
▼
Image Registry
│
▼
Kubernetes
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Monitoring Logging Tracing
Prometheus Loki/ES Jaeger
│ │ │
└──────────────┼──────────────┘
▼
Grafana
部署方式也逐渐从:
kubectl apply -f deployment.yaml
发展到:
Git
↓
CI
↓
Image
↓
Helm / Kustomize
↓
Argo CD / Flux
↓
Kubernetes
这时候 Kubernetes 就不再只是“运行 Docker 容器”,而成为整个 DevOps、微服务和云原生体系中的基础平台。
十九、最后重新理解 Kubernetes
学习 Kubernetes 的时候,很容易陷入命令记忆:
kubectl get pods
kubectl get svc
kubectl describe pod
kubectl logs
kubectl exec
这些命令当然需要掌握,但真正重要的是理解 Kubernetes 背后的思想。
Kubernetes 并不是:
我执行一条命令,它帮我启动一个容器。
而更接近:
我声明系统应该是什么状态,Kubernetes 持续让真实系统向这个状态靠近。
例如:
replicas: 3
你表达的是:
我希望有 3 个实例
而不是:
请现在帮我启动 3 个容器
如果 Pod 从:
3 → 2
Controller 会发现:
Desired = 3
Actual = 2
于是自动修复。
如果节点发生故障:
Node
×
Kubernetes 又会重新调度工作负载。
如果发布新版本:
v1 → v2
Deployment 可以通过滚动更新逐步替换实例。
所以从本质上来说,Kubernetes 最值得学习的不是某一个 YAML 字段,而是下面这条完整的思维链:
声明期望状态
↓
API Server
↓
etcd 保存状态
↓
Controller 观察状态
↓
Scheduler 决定位置
↓
Kubelet 执行
↓
Container Runtime
↓
应用运行
↓
持续监控实际状态
↓
发现偏差
↓
再次调整
这就是 Kubernetes 的核心控制循环。
当真正理解了这套模型之后,再去学习:
Deployment
Service
Ingress
ConfigMap
Secret
StatefulSet
DaemonSet
Job
CronJob
PV/PVC
RBAC
HPA
NetworkPolicy
Helm
Operator
就不会再觉得它们是一堆互相独立的 YAML 配置。
它们实际上都是 Kubernetes 围绕“声明状态、观察状态、不断调谐状态”这个核心思想构建出来的不同抽象。
而这也是 Kubernetes 能够从一个容器编排工具,逐渐发展成云原生基础平台的关键所在。
