793 lines
25 KiB
Markdown
793 lines
25 KiB
Markdown
---
|
||
tags: [k8s, deployment, replicaset, devops]
|
||
create time: 2026-07-07 15:48
|
||
---
|
||
|
||
# Deployment 与 ReplicaSet
|
||
|
||
## 概述
|
||
|
||
Deployment 是 Kubernetes 中最常用的无状态工作负载控制器,它在 ReplicaSet 之上提供了声明式更新、滚动发布、版本回滚等高级能力。日常开发中,绝大多数无状态服务(Web API、微服务后端、定时任务 Worker)都通过 Deployment 来管理 Pod 的生命周期。理解 Deployment 与 ReplicaSet 的协作关系,是掌握 Kubernetes 应用交付模型的基础。
|
||
|
||
## ReplicaSet 详解
|
||
|
||
### 什么是 ReplicaSet
|
||
|
||
ReplicaSet(简称 RS)的核心职责只有一件事:**确保指定数量的 Pod 副本始终处于运行状态**。如果某个 Pod 意外退出或被删除,RS 会立即创建新的 Pod 来弥补差额;如果 Pod 数量超过期望值,RS 会删除多余的 Pod。
|
||
|
||
```yaml
|
||
# 一个独立的 ReplicaSet 示例(通常不直接创建,由 Deployment 自动管理)
|
||
apiVersion: apps/v1
|
||
kind: ReplicaSet
|
||
metadata:
|
||
name: nginx-rs
|
||
namespace: default
|
||
spec:
|
||
replicas: 3 # 期望副本数
|
||
selector:
|
||
matchLabels:
|
||
app: nginx # Label Selector:通过标签匹配 Pod
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: nginx # Pod 必须携带此标签才能被 RS 管理
|
||
spec:
|
||
containers:
|
||
- name: nginx
|
||
image: nginx:1.25
|
||
ports:
|
||
- containerPort: 80
|
||
```
|
||
|
||
### Label Selector 机制
|
||
|
||
RS 通过 **Label Selector** 来发现和管理 Pod,而非维护一个固定的 Pod 列表。这意味着:
|
||
|
||
- 手动创建的 Pod 只要标签匹配,也会被 RS 纳入管理(数量超出则删除,不足则新建)
|
||
- 修改某个 Pod 的标签可以将其从 RS 管理中"摘除",这在调试时非常有用
|
||
|
||
```bash
|
||
# 将一个 Pod 从 RS 中移除(不删除 Pod,仅修改标签使其脱离管理)
|
||
kubectl label pod nginx-rs-xxxx app=detached --overwrite
|
||
|
||
# 此时 RS 会发现少了一个副本,自动新建一个 Pod 来补足
|
||
```
|
||
|
||
> [!tip] 调试技巧
|
||
> 需要排查某个 Pod 问题时,先把它从 RS 中"摘出来"(修改标签),这样 RS 不会因为 Pod 异常而自动重建,你可以从容排查。
|
||
|
||
### ReplicaSet vs ReplicationController
|
||
|
||
ReplicationController(RC)是 RS 的前身,两者核心区别在于 Selector 语义:
|
||
|
||
| 对比维度 | ReplicationController | ReplicaSet |
|
||
|----------|----------------------|------------|
|
||
| Selector 类型 | 仅支持等式(`env=prod`) | 支持等式 + 集合(`env in (prod, staging)`) |
|
||
| 标签匹配灵活性 | 低 | 高 |
|
||
| 推荐程度 | 已废弃,不建议使用 | 推荐使用 |
|
||
| 滚动更新 | 需要 `kubectl rolling-update`(客户端执行) | 由 Deployment 在服务端协调 |
|
||
|
||
RC 已经是遗留概念,新资源应统一使用 ReplicaSet。不过实践中你几乎不会直接创建 RS,而是通过 Deployment 间接管理。
|
||
|
||
## Deployment 核心机制
|
||
|
||
### 声明式管理
|
||
|
||
Deployment 采用声明式模型:你只需描述"期望状态"(desired state),Kubernetes 控制面会持续将其与"实际状态"(actual state)做对比,自动执行必要的操作来弥合差距。这个循环叫做 **Reconcile Loop**(调谐循环)。
|
||
|
||
```mermaid
|
||
graph LR
|
||
A["用户提交 Deployment<br/>(desired state)"] --> B["Deployment Controller"]
|
||
B --> C{"desired == actual ?"}
|
||
C -->|No| D["创建/删除/更新<br/>ReplicaSet / Pod"]
|
||
D --> B
|
||
C -->|Yes| E["状态稳定<br/>不做任何操作"]
|
||
```
|
||
|
||
工作流程:
|
||
|
||
1. 用户通过 `kubectl apply` 提交 Deployment YAML,声明 `replicas: 3`、`image: nginx:1.25` 等期望状态
|
||
2. Deployment Controller 观察到当前没有匹配的 RS,创建一个新的 RS
|
||
3. RS Controller 观察到 Pod 数量为 0,创建 3 个 Pod
|
||
4. 持续监控:如果某个 Pod 崩溃,RS Controller 检测到 actual < desired,立即补充新 Pod
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: nginx-deploy
|
||
namespace: default
|
||
spec:
|
||
replicas: 3 # 声明:我想要 3 个副本
|
||
selector:
|
||
matchLabels:
|
||
app: nginx
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: nginx
|
||
spec:
|
||
containers:
|
||
- name: nginx
|
||
image: nginx:1.25 # 声明:我想用这个镜像
|
||
resources:
|
||
requests:
|
||
cpu: 100m
|
||
memory: 128Mi
|
||
limits:
|
||
cpu: 500m
|
||
memory: 256Mi
|
||
```
|
||
|
||
### 滚动更新(Rolling Update)
|
||
|
||
滚动更新是 Deployment 最核心的价值。当你修改了 Pod 模板(比如升级镜像版本),Deployment 不会一次性杀掉所有旧 Pod,而是**逐批替换**,在更新过程中始终保持服务可用。
|
||
|
||
#### 关键参数
|
||
|
||
| 参数 | 默认值 | 含义 |
|
||
|------|--------|------|
|
||
| `maxSurge` | 25% | 更新过程中允许超出 `replicas` 的最大 Pod 数量 |
|
||
| `maxUnavailable` | 25% | 更新过程中允许不可用的最大 Pod 数量 |
|
||
|
||
> [!info] 参数计算
|
||
> 对于 `replicas: 4`、`maxSurge: 25%`、`maxUnavailable: 25%` 的配置:
|
||
> - maxSurge = ceil(4 * 0.25) = 1,最多可以有 5 个 Pod 同时存在
|
||
> - maxUnavailable = floor(4 * 0.25) = 1,至少保证 3 个 Pod 可用
|
||
>
|
||
> 这意味着每批替换 1 个 Pod,共需 4 批完成更新。
|
||
|
||
#### 滚动更新过程
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant U as 用户
|
||
participant D as Deployment Controller
|
||
participant RS1 as ReplicaSet v1
|
||
participant RS2 as ReplicaSet v2
|
||
|
||
U->>D: kubectl apply(image: nginx:1.26)
|
||
D->>RS2: 创建新 RS(image: 1.26, replicas: 0)
|
||
D->>RS2: scale up +1(新建 1 个新 Pod)
|
||
Note over RS1,RS2: maxSurge=1, 新 Pod 启动
|
||
D->>RS1: scale down -1(销毁 1 个旧 Pod)
|
||
Note over RS1,RS2: maxUnavailable=1, 保证可用
|
||
D->>RS2: scale up +1
|
||
D->>RS1: scale down -1
|
||
Note over RS1,RS2: 重复直到 RS1=0, RS2=4
|
||
D->>RS1: 保留 RS1(revision=1, 可回滚)
|
||
```
|
||
|
||
逐批替换的过程(以 `replicas: 4` 为例):
|
||
|
||
| 阶段 | RS v1 (旧) | RS v2 (新) | 总 Pod 数 | 可用 Pod |
|
||
|------|-----------|-----------|----------|---------|
|
||
| 初始 | 4 | 0 | 4 | 4 |
|
||
| 第 1 批 | 4 → 3 | 0 → 1 | 5 | 4 |
|
||
| 第 2 批 | 3 → 2 | 1 → 2 | 4 | 4 |
|
||
| 第 3 批 | 2 → 1 | 2 → 3 | 4 | 4 |
|
||
| 第 4 批 | 1 → 0 | 3 → 4 | 4 | 4 |
|
||
|
||
#### 滚动更新配置
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: nginx-deploy
|
||
spec:
|
||
replicas: 4
|
||
strategy:
|
||
type: RollingUpdate # 更新策略
|
||
rollingUpdate:
|
||
maxSurge: 1 # 最多多 1 个 Pod
|
||
maxUnavailable: 1 # 最多少 1 个 Pod
|
||
selector:
|
||
matchLabels:
|
||
app: nginx
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: nginx
|
||
spec:
|
||
containers:
|
||
- name: nginx
|
||
image: nginx:1.25
|
||
readinessProbe: # 重要:就绪探针决定新 Pod 何时接收流量
|
||
httpGet:
|
||
path: /healthz
|
||
port: 80
|
||
initialDelaySeconds: 5
|
||
periodSeconds: 10
|
||
```
|
||
|
||
> [!warning] readinessProbe 的重要性
|
||
> 滚动更新依赖 `readinessProbe` 判断新 Pod 是否就绪。如果没有配置就绪探针,新 Pod 一创建就被视为 Ready,可能导致流量打到尚未初始化完成的 Pod 上,造成请求失败。
|
||
|
||
### 回滚策略
|
||
|
||
Kubernetes 为每次 Deployment 变更自动记录 revision(版本号),支持快速回滚到任意历史版本。
|
||
|
||
```bash
|
||
# 查看 rollout 历史
|
||
kubectl rollout history deployment nginx-deploy
|
||
|
||
# 输出示例:
|
||
# REVISION CHANGE-CAUSE
|
||
# 1 <none>
|
||
# 2 <none>
|
||
# 3 <none>
|
||
|
||
# 查看某个 revision 的详细信息
|
||
kubectl rollout history deployment nginx-deploy --revision=2
|
||
|
||
# 回滚到上一个版本
|
||
kubectl rollout undo deployment nginx-deploy
|
||
|
||
# 回滚到指定版本
|
||
kubectl rollout undo deployment nginx-deploy --to-revision=1
|
||
|
||
# 查看 rollout 状态(是否完成、耗时等)
|
||
kubectl rollout status deployment nginx-deploy
|
||
```
|
||
|
||
#### revisionHistoryLimit 配置
|
||
|
||
默认保留 10 个历史 revision。过多的历史 RS 会占用 etcd 资源,过少则可能丢失回滚能力。
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: nginx-deploy
|
||
spec:
|
||
revisionHistoryLimit: 5 # 只保留最近 5 个 revision
|
||
# 设为 0 则禁用回滚(不推荐)
|
||
replicas: 3
|
||
selector:
|
||
matchLabels:
|
||
app: nginx
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: nginx
|
||
annotations:
|
||
kubernetes.io/change-cause: "升级到 nginx 1.26" # 记录变更原因,显示在 rollout history 中
|
||
spec:
|
||
containers:
|
||
- name: nginx
|
||
image: nginx:1.26
|
||
```
|
||
|
||
> [!tip] 记录变更原因
|
||
> 在 Pod template 的 `annotations` 中添加 `kubernetes.io/change-cause`,这样 `kubectl rollout history` 会显示每次变更的原因,方便排查。
|
||
|
||
### 扩缩容
|
||
|
||
#### 手动扩缩容
|
||
|
||
```bash
|
||
# 命令行直接扩缩
|
||
kubectl scale deployment nginx-deploy --replicas=5
|
||
|
||
# 条件扩缩(只有当前副本数为 3 时才执行)
|
||
kubectl scale deployment nginx-deploy --replicas=5 --current-replicas=3
|
||
```
|
||
|
||
也可以直接修改 YAML 中的 `replicas` 字段后 `kubectl apply`。
|
||
|
||
#### HPA 自动扩缩
|
||
|
||
Horizontal Pod Autoscaler(HPA)根据 CPU、内存或自定义指标自动调整 Deployment 的副本数。
|
||
|
||
```yaml
|
||
# 前置条件:集群中需要安装 Metrics Server
|
||
apiVersion: autoscaling/v2
|
||
kind: HorizontalPodAutoscaler
|
||
metadata:
|
||
name: nginx-hpa
|
||
namespace: default
|
||
spec:
|
||
scaleTargetRef:
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
name: nginx-deploy # 目标 Deployment
|
||
minReplicas: 2 # 最小副本数
|
||
maxReplicas: 10 # 最大副本数
|
||
metrics:
|
||
- type: Resource
|
||
resource:
|
||
name: cpu
|
||
target:
|
||
type: Utilization
|
||
averageUtilization: 60 # CPU 使用率超过 60% 时扩容
|
||
- type: Resource
|
||
resource:
|
||
name: memory
|
||
target:
|
||
type: Utilization
|
||
averageUtilization: 80 # 内存使用率超过 80% 时扩容
|
||
behavior:
|
||
scaleUp:
|
||
stabilizationWindowSeconds: 60 # 扩容稳定窗口:60s 内取最大值
|
||
policies:
|
||
- type: Pods
|
||
value: 4 # 每次最多扩 4 个 Pod
|
||
periodSeconds: 60
|
||
scaleDown:
|
||
stabilizationWindowSeconds: 300 # 缩容稳定窗口:300s,防止抖动
|
||
policies:
|
||
- type: Percent
|
||
value: 10 # 每次最多缩 10%
|
||
periodSeconds: 60
|
||
```
|
||
|
||
```bash
|
||
# 创建 HPA
|
||
kubectl apply -f hpa.yaml
|
||
|
||
# 或用命令行快速创建
|
||
kubectl autoscale deployment nginx-deploy --cpu-percent=60 --min=2 --max=10
|
||
|
||
# 查看 HPA 状态
|
||
kubectl get hpa nginx-hpa
|
||
```
|
||
|
||
> [!warning] HPA 与手动 scale 冲突
|
||
> 如果同时使用 HPA 和手动 `kubectl scale`,HPA 会在下一次指标采集时覆盖手动设置。两者不应混用。
|
||
|
||
## Deployment 更新策略
|
||
|
||
Deployment 支持两种更新策略,通过 `spec.strategy.type` 指定:
|
||
|
||
| 对比维度 | RollingUpdate | Recreate |
|
||
|----------|--------------|----------|
|
||
| 更新方式 | 逐批替换 Pod | 先杀全部旧 Pod,再创建全部新 Pod |
|
||
| 更新期间可用性 | 始终可用(前提:正确配置探针) | 有短暂不可用窗口 |
|
||
| 资源占用 | 可能临时需要更多资源(maxSurge > 0 时) | 只需要新版本的资源 |
|
||
| 适用场景 | 大多数无状态服务 | 不能有两个版本同时运行的场景 |
|
||
| 典型用例 | Web 服务、API 后端 | 涉及全局独占资源(如本地文件锁、全局数据库迁移) |
|
||
|
||
```yaml
|
||
# Recreate 策略示例
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: legacy-app
|
||
spec:
|
||
replicas: 3
|
||
strategy:
|
||
type: Recreate # 全量替换,更新期间服务不可用
|
||
selector:
|
||
matchLabels:
|
||
app: legacy
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: legacy
|
||
spec:
|
||
containers:
|
||
- name: app
|
||
image: legacy-app:2.0
|
||
```
|
||
|
||
> [!info] 什么时候用 Recreate?
|
||
> 当新旧版本不能共存时使用 Recreate,比如:应用使用了本地文件锁、需要执行不可逆的数据库 Schema 迁移、或者旧版本会干扰新版本的初始化。大多数情况下 RollingUpdate 是更好的选择。
|
||
|
||
## 金丝雀发布与蓝绿部署
|
||
|
||
Deployment 本身不直接支持金丝雀发布和蓝绿部署,但可以通过组合多个 Deployment + Service 来实现。
|
||
|
||
### 金丝雀发布(Canary Release)
|
||
|
||
核心思路:同时运行两个版本的 Deployment,通过控制副本比例来控制流量分配。
|
||
|
||
```yaml
|
||
# 主 Deployment:稳定版本,占 90% 流量
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: app-stable
|
||
spec:
|
||
replicas: 9 # 9 个副本
|
||
selector:
|
||
matchLabels:
|
||
app: myapp
|
||
track: stable
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: myapp
|
||
track: stable # 核心标签 app: myapp 相同,Service 可以选中
|
||
spec:
|
||
containers:
|
||
- name: app
|
||
image: myapp:v1.0
|
||
---
|
||
# 金丝雀 Deployment:新版本,占 10% 流量
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: app-canary
|
||
spec:
|
||
replicas: 1 # 1 个副本
|
||
selector:
|
||
matchLabels:
|
||
app: myapp
|
||
track: canary
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: myapp
|
||
track: canary
|
||
spec:
|
||
containers:
|
||
- name: app
|
||
image: myapp:v2.0
|
||
```
|
||
|
||
```yaml
|
||
# Service 通过 app: myapp 同时选中两个 Deployment 的 Pod
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: myapp-svc
|
||
spec:
|
||
selector:
|
||
app: myapp # 不指定 track,两个版本都会被选中
|
||
ports:
|
||
- port: 80
|
||
targetPort: 8080
|
||
```
|
||
|
||
流量分配:9 个旧版本 Pod + 1 个新版本 Pod = 约 10% 流量到达新版本。验证无误后,逐步调整副本比例(如 `canary: 5, stable: 5`),最终全部切换到新版本。
|
||
|
||
```mermaid
|
||
graph LR
|
||
Client --> Service
|
||
Service -->|~90%| Pod1["Pod v1.0 x9"]
|
||
Service -->|~10%| Pod2["Pod v2.0 x1"]
|
||
```
|
||
|
||
### 蓝绿部署(Blue-Green Deployment)
|
||
|
||
核心思路:同时运行蓝(当前版本)和绿(新版本)两套完整的 Deployment,通过切换 Service 的 selector 来瞬间完成流量切换。
|
||
|
||
```yaml
|
||
# 蓝色 Deployment(当前生产版本)
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: app-blue
|
||
spec:
|
||
replicas: 3
|
||
selector:
|
||
matchLabels:
|
||
app: myapp
|
||
version: blue
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: myapp
|
||
version: blue
|
||
spec:
|
||
containers:
|
||
- name: app
|
||
image: myapp:v1.0
|
||
---
|
||
# 绿色 Deployment(新版本,先部署但不接收流量)
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: app-green
|
||
spec:
|
||
replicas: 3
|
||
selector:
|
||
matchLabels:
|
||
app: myapp
|
||
version: green
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: myapp
|
||
version: green
|
||
spec:
|
||
containers:
|
||
- name: app
|
||
image: myapp:v2.0
|
||
```
|
||
|
||
```yaml
|
||
# Service:切换 selector.version 即可瞬间切换流量
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: myapp-svc
|
||
spec:
|
||
selector:
|
||
app: myapp
|
||
version: blue # 当前指向蓝色,切换为 green 即完成上线
|
||
ports:
|
||
- port: 80
|
||
targetPort: 8080
|
||
```
|
||
|
||
```bash
|
||
# 切换流量到绿色版本
|
||
kubectl patch service myapp-svc -p '{"spec":{"selector":{"version":"green"}}}'
|
||
|
||
# 回滚:切换回蓝色版本
|
||
kubectl patch service myapp-svc -p '{"spec":{"selector":{"version":"blue"}}}'
|
||
```
|
||
|
||
蓝绿部署的优点是切换瞬间完成(无中间状态),缺点是需要双倍资源。适合对上线/回滚速度要求极高的场景。
|
||
|
||
## Pod 模板字段详解
|
||
|
||
`spec.template.spec` 是 Pod 模板的核心,以下是常用字段的逐一说明:
|
||
|
||
```yaml
|
||
spec:
|
||
template:
|
||
metadata:
|
||
labels: # Pod 标签,用于 Service Selector 和 ReplicaSet 匹配
|
||
app: myapp
|
||
version: v1
|
||
annotations: # 非标识性元数据,供工具或控制器使用
|
||
kubernetes.io/change-cause: "初始部署"
|
||
spec:
|
||
# ---- 容器定义 ----
|
||
containers:
|
||
- name: app # 容器名称,Pod 内唯一
|
||
image: myapp:v1.0 # 镜像地址
|
||
imagePullPolicy: IfNotPresent # Always / IfNotPresent / Never
|
||
ports:
|
||
- containerPort: 8080 # 容器监听端口(声明性质,不强制)
|
||
env: # 环境变量
|
||
- name: APP_ENV
|
||
value: "production"
|
||
- name: DB_PASSWORD
|
||
valueFrom:
|
||
secretKeyRef: # 从 Secret 读取敏感信息
|
||
name: db-secret
|
||
key: password
|
||
resources: # 资源请求与限制
|
||
requests: # 调度依据:节点至少需要这么多资源
|
||
cpu: 100m
|
||
memory: 128Mi
|
||
limits: # 上限:超出会被 OOM Kill 或 CPU 限流
|
||
cpu: 500m
|
||
memory: 256Mi
|
||
readinessProbe: # 就绪探针:决定 Pod 是否接收流量
|
||
httpGet:
|
||
path: /healthz
|
||
port: 8080
|
||
initialDelaySeconds: 5
|
||
periodSeconds: 10
|
||
livenessProbe: # 存活探针:失败则重启容器
|
||
httpGet:
|
||
path: /healthz
|
||
port: 8080
|
||
initialDelaySeconds: 15
|
||
periodSeconds: 20
|
||
startupProbe: # 启动探针:慢启动应用专用,通过前忽略 liveness
|
||
httpGet:
|
||
path: /healthz
|
||
port: 8080
|
||
failureThreshold: 30
|
||
periodSeconds: 10
|
||
|
||
# ---- 初始化容器 ----
|
||
initContainers: # 在主容器启动前顺序执行
|
||
- name: init-db-migration
|
||
image: myapp:v1.0
|
||
command: ["./migrate.sh"]
|
||
|
||
# ---- 卷挂载 ----
|
||
volumes:
|
||
- name: config-vol
|
||
configMap:
|
||
name: app-config # 挂载 ConfigMap 作为文件
|
||
- name: data-vol
|
||
emptyDir: {} # 临时卷,Pod 删除后数据丢失
|
||
|
||
# ---- 调度相关 ----
|
||
nodeSelector: # 简单节点选择
|
||
disk: ssd
|
||
affinity: # 高级亲和性规则
|
||
nodeAffinity:
|
||
requiredDuringSchedulingIgnoredDuringExecution:
|
||
nodeSelectorTerms:
|
||
- matchExpressions:
|
||
- key: kubernetes.io/os
|
||
operator: In
|
||
values: ["linux"]
|
||
tolerations: # 容忍污点
|
||
- key: "dedicated"
|
||
operator: "Equal"
|
||
value: "gpu"
|
||
effect: "NoSchedule"
|
||
|
||
# ---- 其他 ----
|
||
terminationGracePeriodSeconds: 30 # 优雅终止等待时间
|
||
serviceAccountName: myapp-sa # 使用的 ServiceAccount
|
||
```
|
||
|
||
> [!tip] imagePullPolicy 最佳实践
|
||
> - 使用 `:latest` 标签时,必须设置 `imagePullPolicy: Always`,否则可能使用本地缓存的旧镜像
|
||
> - 使用固定版本标签(如 `:v1.2.3`)时,`IfNotPresent` 更高效,避免重复拉取
|
||
> - `Never` 仅用于本地开发环境(如 minikube 中直接使用本地构建的镜像)
|
||
|
||
## 常见陷阱与最佳实践
|
||
|
||
### 更新卡住排查
|
||
|
||
滚动更新卡住是常见问题,通常由以下原因导致:
|
||
|
||
```bash
|
||
# 1. 查看 Deployment 状态
|
||
kubectl rollout status deployment nginx-deploy
|
||
# 可能输出:Waiting for rollout to finish: 2 out of 4 new replicas have been updated...
|
||
|
||
# 2. 查看 Deployment 的 Conditions
|
||
kubectl get deployment nginx-deploy -o yaml | grep -A 20 conditions:
|
||
# 常见条件:
|
||
# - Progressing: False(更新未推进)
|
||
# - Available: True/False(是否有足够可用 Pod)
|
||
|
||
# 3. 查看新 ReplicaSet 的事件
|
||
kubectl describe rs nginx-deploy-xxxxxx # 查看新 RS 的 Events
|
||
# 常见原因:
|
||
# - FailedScheduling:资源不足,没有节点能满足 requests
|
||
# - ImagePullBackOff:镜像拉取失败(镜像名错、私有仓库无凭证)
|
||
# - CrashLoopBackOff:新容器启动后立即崩溃
|
||
|
||
# 4. 查看 Pod 事件
|
||
kubectl get pods -l app=nginx --sort-by=.metadata.creationTimestamp
|
||
kubectl describe pod nginx-deploy-xxxxxx-yyyyy
|
||
```
|
||
|
||
常见原因和解决方案:
|
||
|
||
| 症状 | 原因 | 解决方案 |
|
||
|------|------|---------|
|
||
| ImagePullBackOff | 镜像不存在或无权限 | 检查镜像名称、imagePullSecrets |
|
||
| CrashLoopBackOff | 应用启动失败 | 查看 Pod 日志 `kubectl logs` |
|
||
| Pending | 资源不足或节点不满足约束 | 检查 `kubectl describe pod` 中的 Events |
|
||
| readinessProbe 一直失败 | 健康检查路径或端口错误 | 确认探针配置与应用实际端口一致 |
|
||
|
||
> [!warning] 超时回滚
|
||
> Deployment 有一个 `progressDeadlineSeconds` 参数(默认 600 秒),如果更新在该时间内未完成,Deployment 会标记为 `Progressing=False`。注意:这**不会自动回滚**,只是标记状态。需要手动执行 `kubectl rollout undo` 或编写自动化脚本。
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: nginx-deploy
|
||
spec:
|
||
progressDeadlineSeconds: 300 # 5 分钟内未完成更新则标记失败
|
||
minReadySeconds: 10 # Pod Ready 后等待 10 秒才视为 Available
|
||
replicas: 3
|
||
selector:
|
||
matchLabels:
|
||
app: nginx
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: nginx
|
||
spec:
|
||
containers:
|
||
- name: nginx
|
||
image: nginx:1.26
|
||
```
|
||
|
||
### imagePullPolicy 导致的镜像缓存问题
|
||
|
||
```bash
|
||
# 场景:修改了 YAML 中的 image 字段但没改 tag,apply 后 Pod 没有更新
|
||
# 原因:imagePullPolicy 默认为 IfNotPresent,本地已有旧镜像就不会重新拉取
|
||
|
||
# 解决方案 1:始终使用不同的 tag(推荐)
|
||
image: myapp:v1.0.1 # 而不是 myapp:latest
|
||
|
||
# 解决方案 2:强制设置 imagePullPolicy
|
||
imagePullPolicy: Always
|
||
|
||
# 解决方案 3:使用 SHA256 digest(最精确)
|
||
image: myapp@sha256:abc123...
|
||
```
|
||
|
||
### Deployment vs StatefulSet 选择
|
||
|
||
| 特性 | Deployment | StatefulSet |
|
||
|------|-----------|-------------|
|
||
| Pod 名称 | 随机(`nginx-5d6b8f7c9-abc12`) | 有序(`web-0`, `web-1`, `web-2`) |
|
||
| Pod 启动顺序 | 并行 | 有序(0 → 1 → 2) |
|
||
| Pod 终止顺序 | 并行(默认) | 逆序(2 → 1 → 0) |
|
||
| 网络标识 | 不稳定 | 稳定(`web-0.ng-svc`) |
|
||
| 持久存储 | 共享或无 | 每个 Pod 独立 PVC |
|
||
| 适用场景 | 无状态服务 | 有状态服务(数据库、MQ、ZooKeeper) |
|
||
| 滚动更新 | 替换 Pod | 逐个更新 |
|
||
|
||
> [!info] 选择原则
|
||
> 如果你的应用满足以下任意条件,应考虑 StatefulSet:
|
||
> - 需要稳定的网络标识(如 ZooKeeper 集群节点互相发现)
|
||
> - 需要持久化的、与 Pod 绑定的存储(如 MySQL 数据目录)
|
||
> - 需要有序的部署/扩缩/更新
|
||
>
|
||
> 其余场景一律使用 Deployment。
|
||
|
||
### 其他最佳实践
|
||
|
||
1. **始终配置资源请求和限制**:没有 requests 的 Pod 会被调度器视为零消耗,可能导致多个 Pod 被调度到同一节点,引发资源争抢。
|
||
|
||
2. **始终配置 readinessProbe**:没有就绪探针,新 Pod 创建后立即被标记为 Ready,流量可能打到未初始化完成的 Pod。
|
||
|
||
3. **不要直接管理 Deployment 创建的 ReplicaSet**:RS 的生命周期由 Deployment 控制器管理,手动修改 RS(如 scale)会被 Deployment 覆盖。
|
||
|
||
4. **使用 `kubectl apply` 而非 `kubectl create`**:apply 支持声明式管理,可以幂等执行,适合 GitOps 工作流。
|
||
|
||
5. **合理设置 minReadySeconds**:该参数让 Pod Ready 后再等待一段时间才被视为 Available,可以用来检测"启动后很快崩溃"的场景。
|
||
|
||
```yaml
|
||
# 推荐的最小 Deployment 模板
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: myapp
|
||
labels:
|
||
app: myapp
|
||
spec:
|
||
replicas: 3
|
||
revisionHistoryLimit: 5
|
||
progressDeadlineSeconds: 300
|
||
minReadySeconds: 10
|
||
strategy:
|
||
type: RollingUpdate
|
||
rollingUpdate:
|
||
maxSurge: 1
|
||
maxUnavailable: 0 # 保证更新期间始终有足够的可用 Pod
|
||
selector:
|
||
matchLabels:
|
||
app: myapp
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: myapp
|
||
spec:
|
||
terminationGracePeriodSeconds: 30
|
||
containers:
|
||
- name: myapp
|
||
image: myapp:v1.0.0
|
||
imagePullPolicy: IfNotPresent
|
||
ports:
|
||
- containerPort: 8080
|
||
resources:
|
||
requests:
|
||
cpu: 100m
|
||
memory: 128Mi
|
||
limits:
|
||
cpu: 500m
|
||
memory: 256Mi
|
||
readinessProbe:
|
||
httpGet:
|
||
path: /healthz
|
||
port: 8080
|
||
initialDelaySeconds: 5
|
||
periodSeconds: 10
|
||
livenessProbe:
|
||
httpGet:
|
||
path: /healthz
|
||
port: 8080
|
||
initialDelaySeconds: 15
|
||
periodSeconds: 20
|
||
```
|
||
|
||
## 延伸阅读
|
||
|
||
- [[k8s-03-pod]] — Pod 的生命周期、调度机制、资源管理
|
||
- [[k8s-05-service-and-networking]] — Service 类型、Ingress、网络策略
|
||
- [Kubernetes 官方文档 - Deployments](https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/)
|
||
- [Kubernetes 官方文档 - ReplicaSet](https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/replicaset/)
|
||
- [Kubernetes 官方文档 - HPA](https://kubernetes.io/zh-cn/docs/tasks/run-application/horizontal-pod-autoscale/)
|