12476 字
62 分钟
05.Kubernetes 学习笔记:特殊工作负载与配置管理

七、DaemonSet 和 Job:特殊的工作负载#

1. DaemonSet:每个节点都需要的”守护神”#

1.1 DaemonSet是什么#

DaemonSet 是Kubernetes的一种工作负载控制器,它确保在集群的每个(或特定)节点上都运行一个Pod副本。

用生活化的比喻来理解:

想象一个小区的物业管理:
节点(Node) = 每栋楼
DaemonSet Pod = 每栋楼的保安(每栋楼必须有且只有一个保安)
小区新建一栋楼 → 自动分配一个保安
拆除一栋楼 → 这栋楼的保安也会撤离

核心特点:

  1. 节点覆盖

    • 每个节点运行一个Pod副本(且只有一个)
    • 新节点加入集群,自动创建Pod
    • 节点离开集群,自动删除Pod
  2. 不受调度器控制

    • DaemonSet Pod绕过Scheduler直接调度
    • 即使节点有污点(Taint),也能运行(如果配置了容忍)
  3. 自动修复

    • Pod被删除后,会立即在同一节点上重建

1.2 为什么需要DaemonSet#

在前面的章节中,我们学习了Deployment,它管理的Pod可能分散在不同节点上。但有些服务有特殊需求:

必须在每个节点上运行一个实例

想象这些场景:

场景1:日志收集

没有DaemonSet:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Node1 │ │ Node2 │ │ Node3 │
│ │ │ │ │ │
│ 3个Pod │ │ 5个Pod │ │ 2个Pod │
└─────────┘ └─────────┘ └─────────┘
? ? ?
怎么收集日志?要在哪里部署日志收集器?
有DaemonSet:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Node1 │ │ Node2 │ │ Node3 │
│ Fluentd │ │ Fluentd │ │ Fluentd │ <- 每个节点都有日志收集器
│ 3个Pod │ │ 5个Pod │ │ 2个Pod │ 自动收集本节点日志
└─────────┘ └─────────┘ └─────────┘

场景2:节点监控

需求:监控每个节点的CPU、内存、磁盘
解决:在每个节点上运行监控Agent(如node-exporter)

场景3:网络插件

需求:每个节点都需要网络组件(如Calico、Flannel)
解决:用DaemonSet部署网络插件

1.3 DaemonSet的典型应用场景#

场景示例说明
日志收集Fluentd、Filebeat、Logstash收集节点上所有容器的日志
监控AgentPrometheus Node Exporter、Datadog Agent监控节点资源和容器指标
网络插件Calico、Flannel、Weave为Pod提供网络功能
存储插件Ceph、GlusterFS客户端提供分布式存储支持
安全扫描Falco、Sysdig监控节点安全事件
性能分析cAdvisor收集容器性能数据

DaemonSet vs Deployment对比:

Deployment:我需要3个副本,调度器帮我分配到合适的节点
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Node1 │ │ Node2 │ │ Node3 │
│ Pod1 │ │ Pod2 │ │ │
│ │ │ Pod3 │ │ │
└─────────┘ └─────────┘ └─────────┘
DaemonSet:每个节点必须运行一个副本
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Node1 │ │ Node2 │ │ Node3 │
│ Pod1 │ │ Pod2 │ │ Pod3 │ <- 每个节点都有
└─────────┘ └─────────┘ └─────────┘

2. DaemonSet的实现逻辑和工作原理#

2.1 DaemonSet Controller的工作机制#

DaemonSet由DaemonSet Controller管理,它的工作原理很像一个”节点巡查员”:

工作流程:

flowchart TD A["1. 监听节点变化<br/>Watch Node资源"] --> B B["2. 遍历所有节点<br/>检查每个节点是否应该运行DaemonSet Pod"] --> C C{"3. 判断节点<br/>是否匹配条件?"} --> |"匹配"| D C --> |"不匹配"| E D{"4. 节点上是否<br/>已有Pod?"} --> |"没有"| F D --> |"有"| G F["5. 创建Pod<br/>在该节点上"] --> G E{"6. 节点上是否<br/>有Pod?"} --> |"有"| H E --> |"没有"| G H["7. 删除Pod<br/>不应该运行"] --> G G["8. 继续监听<br/>持续调谐"]

详细步骤解析:

  1. 持续监听

    • DaemonSet Controller watch API Server的Node和Pod资源
    • 一旦有变化(新增节点、节点标签改变、Pod删除),立即响应
  2. 节点匹配

    • 检查节点是否满足nodeSelector、affinity等条件
    • 检查节点是否有Taint,Pod是否有对应的Toleration
  3. Pod创建

    • 如果节点需要Pod但没有,创建一个
    • 直接指定Pod运行的节点(不通过Scheduler)
    • 设置Pod的ownerReference指向DaemonSet
  4. Pod删除

    • 如果节点不应该运行Pod但有Pod,删除它
    • 节点被删除时,自动清理Pod

2.2 DaemonSet调度机制#

与Deployment的重要区别:

Deployment Pod创建流程:
用户 → API Server → Deployment Controller创建ReplicaSet
→ ReplicaSet Controller创建Pod(状态:Pending,未分配节点)
→ Scheduler选择节点并绑定
→ Kubelet创建容器
DaemonSet Pod创建流程:
用户 → API Server → DaemonSet Controller创建Pod
→ 直接指定nodeName(绕过Scheduler)
→ Kubelet创建容器

为什么要绕过Scheduler?

  1. 明确的调度需求

    • DaemonSet知道Pod必须运行在哪个节点
    • 不需要Scheduler的复杂调度算法
  2. 忽略资源限制

    • 某些系统组件(如网络插件)必须运行,即使节点资源不足
    • Scheduler可能因资源不足拒绝调度,但DaemonSet强制创建
  3. 忽略污点

    • DaemonSet可以配置Toleration容忍所有污点
    • 在master节点上运行也没问题

2.3 DaemonSet的自我修复#

场景1:Pod被意外删除

Terminal window
# 删除DaemonSet的一个Pod
kubectl delete pod <daemonset-pod> -n kube-system
# 立即(几秒内)重建
kubectl get pods -n kube-system -w
# 你会看到Pod立即进入Pending → Running

场景2:节点宕机后恢复

节点宕机 → Pod状态变为Unknown
→ 节点恢复 → DaemonSet Controller检测到
→ 重建Pod

场景3:新节点加入集群

Terminal window
# 添加新节点
kubeadm join ...
# DaemonSet自动在新节点上创建Pod
kubectl get pods -o wide
# 你会看到新节点上已经有DaemonSet的Pod

3. DaemonSet配置详解#

3.1 基本YAML结构#

最简单的DaemonSet:

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: example-daemonset
namespace: default
spec:
selector:
matchLabels:
app: example
template:
metadata:
labels:
app: example
spec:
containers:
- name: example-container
image: busybox
command: ['sh', '-c', 'echo Hello from $(hostname) && sleep 3600']

核心字段说明:

字段说明必需
apiVersionapps/v1
kindDaemonSet
metadata.nameDaemonSet名称
spec.selector标签选择器,匹配Pod
spec.templatePod模板(与Pod的spec一样)
spec.updateStrategy更新策略(RollingUpdate/OnDelete)
spec.minReadySecondsPod就绪后多久才认为可用(秒)

3.2 节点选择:控制在哪些节点上运行#

方式1:nodeSelector(简单标签匹配)

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx-daemonset
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
nodeSelector:
disktype: ssd # 只在有disktype=ssd标签的节点上运行
containers:
- name: nginx
image: nginx:1.20

使用示例:

Terminal window
# 给节点打标签
kubectl label nodes k8s-node1 disktype=ssd
kubectl label nodes k8s-node2 disktype=ssd
# DaemonSet只会在node1和node2上创建Pod
kubectl get pods -o wide

方式2:nodeAffinity(灵活的节点亲和性)

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: monitoring-daemonset
spec:
selector:
matchLabels:
app: monitoring
template:
metadata:
labels:
app: monitoring
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node1
- k8s-node2
containers:
- name: node-exporter
image: prom/node-exporter:latest

操作符说明:

操作符说明示例
In标签值在列表中key: In values: [v1, v2]
NotIn标签值不在列表中key: NotIn values: [v1]
Exists标签存在(不管值是什么)key: Exists
DoesNotExist标签不存在key: DoesNotExist
Gt标签值大于指定值(数值比较)key: Gt value: "100"
Lt标签值小于指定值key: Lt value: "50"

3.3 污点和容忍:在特殊节点上运行#

什么是Taint(污点)和Toleration(容忍)?

污点(Taint)= 节点的"拒客牌"
容忍(Toleration)= Pod的"通行证"
想象一个VIP餐厅:
- 餐厅门口挂牌:"只接待VIP客户"(这是污点)
- 你有VIP卡(这是容忍),就能进去

污点的三种效果:

效果说明
NoSchedule不允许新Pod调度到该节点(已有Pod不受影响)
PreferNoSchedule尽量不调度新Pod到该节点(不是硬性要求)
NoExecute不允许Pod运行,已有Pod如果不容忍污点会被驱逐

DaemonSet容忍所有污点的示例:

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: kube-system
spec:
selector:
matchLabels:
app: fluent-bit
template:
metadata:
labels:
app: fluent-bit
spec:
tolerations:
# 容忍master节点污点(允许在master上运行)
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
# 容忍节点未就绪
- key: node.kubernetes.io/not-ready
operator: Exists
effect: NoExecute
# 容忍节点不可达
- key: node.kubernetes.io/unreachable
operator: Exists
effect: NoExecute
containers:
- name: fluent-bit
image: fluent/fluent-bit:latest

容忍的配置项:

字段说明
key污点的键(留空表示匹配所有键)
operator匹配操作符(Exists=存在即可,Equal=值必须相等)
value污点的值(operator为Equal时必须指定)
effect污点效果(NoSchedule/PreferNoSchedule/NoExecute,留空表示匹配所有效果)

4. DaemonSet更新策略#

4.1 更新策略类型#

DaemonSet支持两种更新策略:

策略说明使用场景
RollingUpdate滚动更新(默认)自动化更新,推荐
OnDelete手动删除Pod后才更新需要精细控制更新时机

4.2 滚动更新(RollingUpdate)#

工作原理:

滚动更新流程:
1. 更新DaemonSet的Pod模板
2. DaemonSet Controller逐个节点更新Pod
- 删除旧Pod
- 等待旧Pod终止
- 创建新Pod
- 等待新Pod就绪
- 继续下一个节点

配置示例:

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # 同时更新的最大节点数(默认1)
selector:
matchLabels:
app: fluentd
template:
metadata:
labels:
app: fluentd
spec:
containers:
- name: fluentd
image: fluent/fluentd:v1.14 # 镜像版本

maxUnavailable参数:

maxUnavailable: 1 (保守更新)
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Node1 │ │ Node2 │ │ Node3 │
│ v1.14 ✓ │ │ v1.13 │ │ v1.13 │ <- 一个一个更新
└─────────┘ └─────────┘ └─────────┘
↓ 等待 等待
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Node1 │ │ Node2 │ │ Node3 │
│ v1.14 ✓ │ │ v1.14 ✓ │ │ v1.13 │
└─────────┘ └─────────┘ └─────────┘
maxUnavailable: 2 (激进更新)
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Node1 │ │ Node2 │ │ Node3 │
│ v1.14 ✓ │ │ v1.14 ✓ │ │ v1.13 │ <- 两个同时更新
└─────────┘ └─────────┘ └─────────┘

执行更新:

Terminal window
# 方式1:修改镜像
kubectl set image daemonset/fluentd fluentd=fluent/fluentd:v1.15 -n kube-system
# 方式2:修改YAML后apply
kubectl apply -f fluentd-daemonset.yaml
# 查看更新状态
kubectl rollout status daemonset/fluentd -n kube-system
# 查看更新历史
kubectl rollout history daemonset/fluentd -n kube-system

监控更新过程:

Terminal window
# 实时监控Pod变化
kubectl get pods -n kube-system -l app=fluentd -w
# 查看DaemonSet状态
kubectl get daemonset fluentd -n kube-system
# 输出:
# NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE
# fluentd 3 3 2 1 2
#
# DESIRED: 期望的Pod数量(节点数)
# CURRENT: 当前运行的Pod数量
# READY: 就绪的Pod数量
# UP-TO-DATE: 已更新到最新版本的Pod数量
# AVAILABLE: 可用的Pod数量

4.3 手动更新(OnDelete)#

使用场景:

  • 需要在特定时间窗口更新
  • 需要逐个验证更新效果
  • 关键服务不能自动更新

配置示例:

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: critical-monitoring
spec:
updateStrategy:
type: OnDelete # 手动更新模式
selector:
matchLabels:
app: monitoring
template:
metadata:
labels:
app: monitoring
spec:
containers:
- name: monitor
image: monitoring:v2.0

更新流程:

Terminal window
# 1. 修改DaemonSet(只更新模板,不影响现有Pod)
kubectl apply -f daemonset.yaml
# 2. 手动删除节点1的Pod(触发更新)
kubectl delete pod critical-monitoring-xxxxx -n default
# 3. 新Pod自动创建,使用新模板
# 4. 验证新Pod正常后,继续删除下一个
kubectl delete pod critical-monitoring-yyyyy -n default

4.4 回滚DaemonSet#

查看历史版本:

Terminal window
# 查看DaemonSet的历史版本
kubectl rollout history daemonset/fluentd -n kube-system
# 输出:
# REVISION CHANGE-CAUSE
# 1 <none>
# 2 kubectl apply --filename=fluentd-v1.14.yaml
# 3 kubectl set image daemonset/fluentd fluentd=fluent/fluentd:v1.15

回滚到上一个版本:

Terminal window
# 回滚到上一版本
kubectl rollout undo daemonset/fluentd -n kube-system
# 查看回滚状态
kubectl rollout status daemonset/fluentd -n kube-system

回滚到指定版本:

Terminal window
# 回滚到revision 2
kubectl rollout undo daemonset/fluentd --to-revision=2 -n kube-system

暂停和恢复更新:

Terminal window
# 暂停更新(已更新的不会回滚,未更新的停止更新)
kubectl rollout pause daemonset/fluentd -n kube-system
# 恢复更新
kubectl rollout resume daemonset/fluentd -n kube-system

5. Job:一次性任务执行器#

5.1 Job是什么#

Job 是Kubernetes中用于运行一次性任务的工作负载控制器。

与Deployment/DaemonSet的区别:

Deployment: 我要这个服务一直运行着(长期运行服务)
- Web服务器
- API服务
- 数据库
DaemonSet: 每个节点都要运行一个服务(节点级长期服务)
- 日志收集
- 监控Agent
Job: 执行一个任务,完成就结束(一次性任务)
- 数据库备份
- 批量数据处理
- 定时清理任务

生活化比喻:

Deployment = 餐厅的厨师(一直工作)
DaemonSet = 每层楼的保洁(每层都有,一直工作)
Job = 搬家工人(搬完就走)

5.2 Job的核心特点#

  1. 任务完成即终止

    • Pod运行完成(退出码0)后,Job标记为Complete
    • Pod不会重启(除非失败重试)
  2. 失败重试

    • 支持配置重试次数
    • 失败的Pod会被删除并重建
  3. 并行执行

    • 支持同时运行多个Pod
    • 支持顺序执行或并行执行
  4. 保留历史

    • 完成的Pod默认保留(可以查看日志)
    • 可配置自动清理

5.3 Job的工作模式#

模式1:单次执行(最常见)

apiVersion: batch/v1
kind: Job
metadata:
name: backup-job
spec:
template:
spec:
containers:
- name: backup
image: mysql:8.0
command: ['sh', '-c', 'mysqldump -h mysql -u root -pPassword123 mydb > /backup/mydb.sql']
restartPolicy: Never # 重要:Job必须设置为Never或OnFailure

执行流程:

创建Job → 创建Pod → Pod运行 → 任务完成(退出码0) → Pod状态Completed → Job状态Complete

模式2:固定完成次数(completions)

apiVersion: batch/v1
kind: Job
metadata:
name: data-process-job
spec:
completions: 5 # 需要成功完成5次
template:
spec:
containers:
- name: processor
image: data-processor:latest
restartPolicy: Never

执行流程:

创建5个Pod(可能顺序执行)→ 每个Pod完成一个任务 → 5个全部成功 → Job完成

模式3:并行执行(parallelism)

apiVersion: batch/v1
kind: Job
metadata:
name: parallel-job
spec:
completions: 10 # 总共需要完成10个任务
parallelism: 3 # 同时运行3个Pod
template:
spec:
containers:
- name: worker
image: worker:latest
restartPolicy: Never

执行流程:

第一批:创建3个Pod同时运行
→ Pod1完成 → 创建Pod4
→ Pod2完成 → 创建Pod5
→ Pod3完成 → 创建Pod6
...
→ 10个全部完成 → Job完成

对比图:

顺序执行(parallelism=1, completions=3):
时间 →
Pod1 [████████]
Pod2 [████████]
Pod3 [████████]
并行执行(parallelism=3, completions=3):
时间 →
Pod1 [████████]
Pod2 [████████]
Pod3 [████████]
混合模式(parallelism=2, completions=5):
时间 →
Pod1 [████████]
Pod2 [████████]
Pod3 [████████]
Pod4 [████████]
Pod5 [████████]

5.4 Job配置详解#

完整的Job YAML结构:

apiVersion: batch/v1
kind: Job
metadata:
name: example-job
namespace: default
spec:
# 并行控制
completions: 3 # 需要成功完成的Pod数量(默认1)
parallelism: 2 # 同时运行的最大Pod数量(默认1)
# 失败控制
backoffLimit: 6 # 最大失败重试次数(默认6)
# 超时控制
activeDeadlineSeconds: 600 # Job运行的最大时间(秒),超时则终止
# 清理控制
ttlSecondsAfterFinished: 100 # Job完成后多久自动删除(秒)
# Pod模板
template:
spec:
restartPolicy: Never # 必须是Never或OnFailure
containers:
- name: worker
image: busybox
command: ['sh', '-c', 'echo Processing... && sleep 10 && echo Done!']

关键字段详解:

字段说明默认值
completions需要成功完成的Pod总数1
parallelism同时运行的最大Pod数1
backoffLimit失败重试次数限制6
activeDeadlineSecondsJob运行最大时长(秒),超时终止所有Pod
ttlSecondsAfterFinishedJob完成后自动清理时间(秒)
restartPolicy必须是Never或OnFailure(不能是Always)Never

restartPolicy的区别:

# Never: Pod失败后不重启,Job Controller创建新Pod
restartPolicy: Never
# 行为:Pod1失败 → 创建Pod2 → Pod2失败 → 创建Pod3 ...
# OnFailure: Pod失败后在原地重启容器
restartPolicy: OnFailure
# 行为:Pod1失败 → 容器重启 → 失败 → 容器再重启 ...

使用建议:

  • Never:适合有状态任务或需要重新初始化环境的任务
  • OnFailure:适合无状态任务,节省Pod创建开销

5.5 Job失败处理#

场景1:backoffLimit限制重试次数

apiVersion: batch/v1
kind: Job
metadata:
name: retry-job
spec:
backoffLimit: 3 # 最多失败3次
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: busybox
command: ['sh', '-c', 'exit 1'] # 故意失败

执行结果:

创建Pod1 → 失败(退出码1)
→ 创建Pod2 → 失败
→ 创建Pod3 → 失败
→ 创建Pod4 → 失败
→ 达到backoffLimit限制,Job标记为Failed

场景2:超时控制

apiVersion: batch/v1
kind: Job
metadata:
name: timeout-job
spec:
activeDeadlineSeconds: 30 # 30秒超时
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: busybox
command: ['sh', '-c', 'sleep 60'] # 睡眠60秒

执行结果:

创建Pod → 运行30秒 → 超时 → Job终止Pod → Job状态Failed

查看Job状态:

Terminal window
# 查看Job
kubectl get jobs
# 输出示例:
# NAME COMPLETIONS DURATION AGE
# backup-job 1/1 15s 1m <- 成功
# retry-job 0/1 2m 2m <- 失败
# 查看详细状态
kubectl describe job retry-job
# 查看失败的Pod日志
kubectl logs <pod-name>

5.6 Job清理#

问题:Job完成后Pod会一直存在

Terminal window
kubectl get jobs
# NAME COMPLETIONS DURATION AGE
# backup-job 1/1 15s 7d <- 7天前完成的Job还在
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# backup-job-xxxxx 0/1 Completed 0 7d <- Pod也还在

解决方案1:自动清理(推荐)

apiVersion: batch/v1
kind: Job
metadata:
name: auto-cleanup-job
spec:
ttlSecondsAfterFinished: 100 # 完成后100秒自动删除
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: busybox
command: ['sh', '-c', 'echo Done && sleep 10']

解决方案2:手动清理

Terminal window
# 删除Job(会级联删除Pod)
kubectl delete job backup-job
# 批量删除已完成的Job
kubectl delete jobs --field-selector status.successful=1
# 批量删除失败的Job
kubectl delete jobs --field-selector status.failed=1

6. CronJob:定时任务调度器#

6.1 CronJob是什么#

CronJob 是Kubernetes中用于定时执行任务的控制器,就像Linux的crontab。

生活化比喻:

Job = 临时工(干完活就走)
CronJob = 定时闹钟(每天早上7点叫你起床)
示例:
- 每天凌晨2点备份数据库
- 每小时清理临时文件
- 每周一生成报表

核心特点:

  1. 定时执行

    • 按照Cron表达式定时创建Job
    • Job执行完成后自动清理
  2. 历史管理

    • 保留最近几次成功/失败的Job
    • 自动清理旧Job
  3. 并发控制

    • 控制同时运行的Job数量
    • 处理上次Job未完成的情况

6.2 Cron表达式详解#

格式:

分钟 小时 日期 月份 星期
* * * * *

字段说明:

字段取值范围特殊字符
分钟0-59* , - /
小时0-23* , - /
日期1-31* , - / ?
月份1-12* , - /
星期0-7 (0和7都是周日)* , - / ?

特殊字符含义:

字符含义示例
*****任意值* * * * * = 每分钟
,列举多个值0 8,12,18 * * * = 8点、12点、18点
-范围0 9-17 * * * = 9点到17点每小时
/步长/间隔*/5 * * * * = 每5分钟
?不指定(日期和星期互斥时使用)0 0 1 * ? = 每月1号

常用示例:

Cron表达式说明
*/5 * * * *每5分钟
0 * * * *每小时整点
0 0 * * *每天凌晨0点
0 2 * * *每天凌晨2点
0 0 * * 0每周日凌晨0点
0 0 1 * *每月1号凌晨0点
0 9-17 * * 1-5周一到周五,9点到17点每小时
*/10 9-17 * * *9点到17点,每10分钟
0 0 1 1 *每年1月1号凌晨0点

在线测试工具: https://crontab.guru/

6.3 CronJob基本配置#

最简单的CronJob:

apiVersion: batch/v1
kind: CronJob
metadata:
name: hello-cronjob
spec:
schedule: "*/1 * * * *" # 每分钟执行一次
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: hello
image: busybox
command: ['sh', '-c', 'date; echo Hello from CronJob']

完整配置:

apiVersion: batch/v1
kind: CronJob
metadata:
name: backup-cronjob
namespace: default
spec:
# Cron表达式
schedule: "0 2 * * *" # 每天凌晨2点
# 时区(Kubernetes 1.25+)
timeZone: "Asia/Shanghai" # 使用中国时区
# 并发策略
concurrencyPolicy: Forbid # Allow/Forbid/Replace
# Job历史保留
successfulJobsHistoryLimit: 3 # 保留3个成功的Job
failedJobsHistoryLimit: 1 # 保留1个失败的Job
# 启动延迟
startingDeadlineSeconds: 100 # 如果错过调度时间,100秒内还可以启动
# 暂停调度
suspend: false # true表示暂停,不创建新Job
# Job模板(与Job的spec相同)
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: backup
image: mysql:8.0
command: ['sh', '-c', 'mysqldump -h mysql -u root -p$MYSQL_PWD mydb > /backup/backup-$(date +%Y%m%d-%H%M%S).sql']
env:
- name: MYSQL_PWD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password

关键字段详解:

字段说明默认值
scheduleCron表达式必填
timeZone时区(K8s 1.25+)UTC
concurrencyPolicy并发策略Allow
successfulJobsHistoryLimit保留成功Job数量3
failedJobsHistoryLimit保留失败Job数量1
startingDeadlineSeconds错过调度后的启动宽限期(秒)无限制
suspend是否暂停false

6.4 并发策略详解#

concurrencyPolicy决定如何处理Job重叠的情况:

策略1:Allow(允许并发,默认)

spec:
schedule: "*/1 * * * *"
concurrencyPolicy: Allow # 允许多个Job同时运行

场景:

时间线:
0:00 → Job1创建,运行中
0:01 → Job2创建,运行中(Job1还没完成)
0:02 → Job3创建,运行中(Job1、Job2都还没完成)
结果:3个Job同时运行

适用场景: 任务之间互不影响,可以并行执行

策略2:Forbid(禁止并发)

spec:
schedule: "*/1 * * * *"
concurrencyPolicy: Forbid # 如果上一个Job还在运行,跳过本次调度

场景:

时间线:
0:00 → Job1创建,运行中
0:01 → 检查:Job1还在运行 → 跳过,不创建Job2
0:02 → 检查:Job1还在运行 → 跳过,不创建Job3
0:03 → Job1完成
0:04 → Job2创建,运行中
结果:同一时间只有1个Job运行

适用场景: 任务不能并发(如数据库备份、资源清理)

策略3:Replace(替换)

spec:
schedule: "*/1 * * * *"
concurrencyPolicy: Replace # 终止旧Job,创建新Job

场景:

时间线:
0:00 → Job1创建,运行中
0:01 → 检查:Job1还在运行 → 终止Job1 → 创建Job2
0:02 → 检查:Job2还在运行 → 终止Job2 → 创建Job3
结果:旧Job被新Job替换

适用场景: 只关心最新的任务结果(如监控数据采集)

6.5 CronJob管理命令#

创建和查看:

Terminal window
# 创建CronJob
kubectl apply -f cronjob.yaml
# 查看CronJob
kubectl get cronjobs
# 输出:
# NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
# backup-cronjob 0 2 * * * False 0 15h 3d
# 查看详细信息
kubectl describe cronjob backup-cronjob
# 查看CronJob创建的Job
kubectl get jobs
# NAME COMPLETIONS DURATION AGE
# backup-cronjob-28441920 1/1 15s 15h
# backup-cronjob-28443360 1/1 16s 39m

手动触发:

Terminal window
# 手动创建一个Job(不等调度时间)
kubectl create job --from=cronjob/backup-cronjob manual-backup-job
# 查看手动创建的Job
kubectl get jobs manual-backup-job

暂停和恢复:

Terminal window
# 暂停CronJob(不创建新Job,已有Job继续运行)
kubectl patch cronjob backup-cronjob -p '{"spec":{"suspend":true}}'
# 恢复CronJob
kubectl patch cronjob backup-cronjob -p '{"spec":{"suspend":false}}'

删除:

Terminal window
# 删除CronJob(不删除已创建的Job)
kubectl delete cronjob backup-cronjob
# 删除CronJob及其所有Job
kubectl delete cronjob backup-cronjob --cascade=foreground

7. 实战1:部署Fluentd DaemonSet收集日志#

7.1 实战目标#

部署Fluentd日志收集器到集群的每个节点,收集所有容器的日志并输出到stdout(生产环境会输出到Elasticsearch等)。

架构图:

graph TB subgraph "K8s Cluster" subgraph "Node1" P1["应用Pod1<br/>写日志到/var/log/containers/"] F1["Fluentd DaemonSet Pod<br/>收集/var/log/containers/"] P1 -.日志文件.-> F1 end subgraph "Node2" P2["应用Pod2<br/>写日志到/var/log/containers/"] F2["Fluentd DaemonSet Pod<br/>收集/var/log/containers/"] P2 -.日志文件.-> F2 end subgraph "Node3" P3["应用Pod3<br/>写日志到/var/log/containers/"] F3["Fluentd DaemonSet Pod<br/>收集/var/log/containers/"] P3 -.日志文件.-> F3 end end F1 --> Output["输出到stdout<br/>(或Elasticsearch)"] F2 --> Output F3 --> Output

7.2 理解Fluentd工作原理#

Fluentd是什么?

Fluentd是一个开源的日志收集器,专门设计用于统一日志收集和分发。

在K8s中的工作方式:

  1. 容器日志位置

    容器日志 → Docker/containerd写入 → /var/log/containers/
    日志文件命名格式:
    <pod-name>_<namespace>_<container-name>-<container-id>.log
    示例:
    nginx-deployment-5d59d67564-abcde_default_nginx-a1b2c3d4.log
  2. Fluentd收集策略

    DaemonSet部署到每个节点
    → 挂载宿主机的/var/log/containers目录
    → 读取所有日志文件
    → 解析、过滤、格式化
    → 发送到目标(Elasticsearch、S3、Kafka等)

7.3 准备ConfigMap配置#

Fluentd需要配置文件告诉它如何处理日志。

创建fluentd-config.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-config
namespace: kube-system
data:
fluent.conf: |
# 输入源:读取容器日志
<source>
@type tail # 使用tail插件(类似tail -f命令)
path /var/log/containers/*.log # 监控所有容器日志
pos_file /var/log/fluentd-containers.log.pos # 记录读取位置(避免重复)
tag kubernetes.* # 日志标签
read_from_head true # 从头开始读取
<parse>
@type json # 解析JSON格式日志
time_key time
time_format %Y-%m-%dT%H:%M:%S.%NZ
keep_time_key true
</parse>
</source>
# 过滤器:添加Kubernetes元数据
<filter kubernetes.**>
@type kubernetes_metadata # 添加Pod名称、命名空间等信息
</filter>
# 输出:打印到stdout(测试用)
<match **>
@type stdout # 输出到标准输出
</match>

配置解释:

配置项说明
@type tail监控文件变化(类似tail -f
path日志文件路径(支持通配符)
pos_file记录读取位置的文件(重启后从上次位置继续)
tag日志标签,用于路由
@type json解析JSON格式的日志
@type kubernetes_metadata自动添加Pod名称、命名空间、标签等Kubernetes信息
@type stdout输出到标准输出(生产环境改为elasticsearch等)

创建ConfigMap:

Terminal window
kubectl apply -f fluentd-config.yaml

7.4 创建Fluentd DaemonSet#

创建fluentd-daemonset.yaml:

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
namespace: kube-system
labels:
app: fluentd
spec:
selector:
matchLabels:
app: fluentd
template:
metadata:
labels:
app: fluentd
spec:
# 服务账号(需要权限读取Pod元数据)
serviceAccountName: fluentd
# 容忍master节点污点(在所有节点运行)
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: fluentd
image: fluent/fluentd-kubernetes-daemonset:v1-debian-elasticsearch
env:
# 禁用Elasticsearch输出(我们只用stdout测试)
- name: FLUENT_ELASTICSEARCH_HOST
value: "localhost"
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
# 挂载配置文件
- name: config
mountPath: /fluentd/etc/fluent.conf
subPath: fluent.conf
# 挂载容器日志目录
- name: varlog
mountPath: /var/log
readOnly: true
# 挂载容器运行时目录
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
volumes:
- name: config
configMap:
name: fluentd-config
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers

关键配置说明:

  1. ServiceAccount

    Fluentd需要调用K8s API获取Pod信息
    → 需要创建ServiceAccount和对应的RBAC权限
  2. tolerations

    允许在master节点运行
    → 收集master节点上的系统组件日志
  3. volumeMounts

    • /var/log: 宿主机日志目录
    • /var/lib/docker/containers: 容器日志文件的软链接源

7.5 创建RBAC权限#

创建fluentd-rbac.yaml:

---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fluentd
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: fluentd
rules:
- apiGroups: [""]
resources:
- pods
- namespaces
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: fluentd
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: fluentd
subjects:
- kind: ServiceAccount
name: fluentd
namespace: kube-system

权限说明:

  • get/list/watch pods: 获取Pod信息(名称、标签、命名空间)
  • get/list/watch namespaces: 获取命名空间信息

7.6 部署和验证#

1. 部署Fluentd

Terminal window
# 创建RBAC权限
kubectl apply -f fluentd-rbac.yaml
# 创建ConfigMap
kubectl apply -f fluentd-config.yaml
# 创建DaemonSet
kubectl apply -f fluentd-daemonset.yaml

2. 验证部署

Terminal window
# 查看DaemonSet状态
kubectl get daemonset -n kube-system fluentd
# 输出:
# NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
# fluentd 3 3 3 3 3 <none> 1m
# 查看Pod分布
kubectl get pods -n kube-system -l app=fluentd -o wide
# 输出:
# NAME READY STATUS RESTARTS AGE IP NODE
# fluentd-abc12 1/1 Running 0 1m 10.244.0.5 k8s-master
# fluentd-def34 1/1 Running 0 1m 10.244.1.5 k8s-node1
# fluentd-ghi56 1/1 Running 0 1m 10.244.2.5 k8s-node2

3. 创建测试应用生成日志

Terminal window
# 创建一个简单的Pod生成日志
kubectl run log-generator --image=busybox --restart=Never -- sh -c 'while true; do echo "Log message at $(date)"; sleep 5; done'

4. 查看Fluentd收集的日志

Terminal window
# 查看Fluentd日志(会看到收集到的应用日志)
kubectl logs -n kube-system -l app=fluentd --tail=50
# 输出示例:
# 2024-01-15 10:30:15 +0000 kubernetes.var.log.containers.log-generator_default_log-generator-xxx.log:
# {
# "log":"Log message at Mon Jan 15 10:30:15 UTC 2024\n",
# "stream":"stdout",
# "time":"2024-01-15T10:30:15.123456789Z",
# "kubernetes":{
# "pod_name":"log-generator",
# "namespace_name":"default",
# "pod_id":"abc123",
# "container_name":"log-generator",
# "host":"k8s-node1"
# }
# }

5. 测试更新DaemonSet

Terminal window
# 修改Fluentd镜像版本(触发滚动更新)
kubectl set image daemonset/fluentd fluentd=fluent/fluentd-kubernetes-daemonset:v1.15-debian-elasticsearch-1 -n kube-system
# 查看更新状态
kubectl rollout status daemonset/fluentd -n kube-system
# 监控更新过程
kubectl get pods -n kube-system -l app=fluentd -w

8. 实战2:使用Job执行一次性数据处理任务#

8.1 实战目标#

创建Job执行批量数据处理任务,演示并行执行、失败重试机制。

场景: 批量处理图片缩略图(模拟)

8.2 准备测试镜像#

在harbor机器上创建处理镜像:

Terminal window
mkdir -p /root/k8s-demo/image-processor
cd /root/k8s-demo/image-processor

创建process.sh:

cat > process.sh << 'EOF'
#!/bin/sh
TASK_ID=${TASK_ID:-"unknown"}
echo "=== Task $TASK_ID Started at $(date) ==="
SLEEP_TIME=$((5 + RANDOM % 10))
echo "Processing for $SLEEP_TIME seconds..."
sleep $SLEEP_TIME
echo "=== Task $TASK_ID Completed at $(date) ==="
exit 0
EOF
chmod +x process.sh

Dockerfile和构建:

FROM alpine:3.18
COPY process.sh /usr/local/bin/
CMD ["/usr/local/bin/process.sh"]
Terminal window
docker build -t reg.westos.org/library/image-processor:v1 .
docker push reg.westos.org/library/image-processor:v1

8.3 创建并行Job#

parallel-job.yaml:

apiVersion: batch/v1
kind: Job
metadata:
name: parallel-image-job
spec:
completions: 10
parallelism: 3
backoffLimit: 20
ttlSecondsAfterFinished: 300
template:
spec:
restartPolicy: Never
containers:
- name: processor
image: reg.westos.org/library/image-processor:v1
env:
- name: TASK_ID
valueFrom:
fieldRef:
fieldPath: metadata.name

执行:

Terminal window
kubectl apply -f parallel-job.yaml
watch kubectl get jobs parallel-image-job
kubectl get pods -l job-name=parallel-image-job -w

9. 实战3:etcd备份完全指南#

9.1 为什么必须备份etcd?#

9.1.1 etcd在K8s中的地位#
┌─────────────────────────────────────────────────────────────────────────┐
│ K8s集群的"大脑"与"记忆" │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ etcd存储了K8s集群的【一切状态信息】: │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 所有资源对象 │ │
│ │ ├── Namespace、Pod、Deployment、Service... │ │
│ │ ├── ConfigMap、Secret(包含敏感数据!) │ │
│ │ ├── PV、PVC、StorageClass │ │
│ │ ├── RBAC规则(Role、ClusterRole、Binding) │ │
│ │ └── 所有自定义资源(CRD) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 集群配置 │
│ ├── 节点信息、调度数据 │
│ ├── 服务发现信息(Endpoints) │
│ └── 租约(Lease)信息 │
│ │
│ ⚠️ 一旦etcd数据丢失 = 整个集群配置全部丢失 = 集群报废! │
│ │
└─────────────────────────────────────────────────────────────────────────┘
9.1.2 什么情况需要从备份恢复?#
灾难场景后果需要备份恢复?
Master节点硬盘损坏etcd数据全丢✅ 必须
误删除关键资源(如所有Deployment)业务全挂✅ 必须
etcd集群脑裂数据不一致✅ 可能需要
K8s升级失败集群无法启动✅ 必须
勒索软件/恶意攻击数据被加密或删除✅ 必须

🧠 思维模型:把etcd想象成银行的账本数据库。所有客户的存款记录都在里面。如果没有备份,银行失火后客户的钱就”消失”了——虽然钱(容器镜像)还在,但谁有多少钱(Pod配置)的记录没了。

9.2 etcd备份原理#

9.2.1 备份的本质是什么?#
┌─────────────────────────────────────────────────────────────────────────┐
│ etcd备份机制 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ etcd数据存储在:/var/lib/etcd/member/ │
│ ├── snap/ ← 快照文件(数据的压缩存档) │
│ └── wal/ ← 预写日志(Write-Ahead Log,记录所有变更) │
│ │
│ ❌ 直接复制这些文件? │
│ └── 不行!文件可能正在写入,复制出来的是不一致的"半成品" │
│ │
│ ✅ 正确做法:使用etcdctl snapshot save │
│ └── etcd会: │
│ ├── 1. 暂停接受新写入(或使用MVCC快照) │
│ ├── 2. 将当前状态导出为一个一致的快照文件 │
│ └── 3. 快照包含某一时刻的【完整数据镜像】 │
│ │
│ 快照文件(.db): │
│ ├── 二进制格式,包含所有键值对 │
│ ├── 通常几MB到几百MB(取决于集群资源数量) │
│ └── 可以用来恢复到快照时刻的状态 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
9.2.2 etcd的安全认证#

etcd使用**mTLS(双向TLS)**认证,备份时必须提供证书:

┌─────────────────────────────────────────────────────────────────────────┐
│ etcd证书认证流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ etcd服务端 备份客户端(etcdctl) │
│ ┌────────────┐ ┌────────────┐ │
│ │ │ ←─── 1. 建立TLS连接 ───→ │ │ │
│ │ 验证 │ │ 提供 │ │
│ │ 客户端证书 │ ←─── 2. 客户端证书 ───── │ 客户端证书│ │
│ │ │ │ │ │
│ │ 允许 │ ───→ 3. 数据访问 ──────→ │ 执行备份 │ │
│ │ 访问 │ │ │ │
│ └────────────┘ └────────────┘ │
│ │
│ 需要的证书文件(kubeadm集群位于 /etc/kubernetes/pki/etcd/): │
│ ├── ca.crt ← CA证书,用于验证服务端身份 │
│ ├── server.crt ← 客户端证书,证明"我有权访问" │
│ └── server.key ← 客户端私钥,用于签名 │
│ │
└─────────────────────────────────────────────────────────────────────────┘

9.3 手动备份etcd(先学会手动,再自动化)#

9.3.1 第一步:确认etcd信息#
Terminal window
# 登录到master节点
ssh root@192.168.100.20
# 1. 确认etcd正在运行
kubectl get pods -n kube-system | grep etcd
# 输出示例:etcd-k8s-master 1/1 Running 0 10d
# 2. 查看etcd的配置(从静态Pod定义中获取)
cat /etc/kubernetes/manifests/etcd.yaml | grep -E "listen-client|cert-file|key-file|trusted-ca"
# 你会看到:
# --listen-client-urls=https://127.0.0.1:2379,https://192.168.100.20:2379
# --cert-file=/etc/kubernetes/pki/etcd/server.crt
# --key-file=/etc/kubernetes/pki/etcd/server.key
# --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
# 3. 确认证书文件存在
ls -la /etc/kubernetes/pki/etcd/
# 应该看到:ca.crt, server.crt, server.key 等文件
9.3.2 第二步:设置环境变量(简化命令)#
Terminal window
# 为了避免每次都输入长长的参数,先设置环境变量
# etcd API版本(必须是3,v2 API已废弃)
export ETCDCTL_API=3
# etcd服务地址(HTTPS协议)
export ETCD_ENDPOINTS="https://192.168.100.20:2379"
# 证书路径
export ETCD_CACERT="/etc/kubernetes/pki/etcd/ca.crt"
export ETCD_CERT="/etc/kubernetes/pki/etcd/server.crt"
export ETCD_KEY="/etc/kubernetes/pki/etcd/server.key"
# 验证变量是否设置成功
echo $ETCDCTL_API $ETCD_ENDPOINTS
9.3.3 第三步:拷贝etcdctl并测试etcd连接#
Terminal window
#拷贝etcdctl
etcd_id=crictl ps | grep etcd | awk '{print $1}'
PID=$(crictl inspect $etcd_id | grep pid | head -2 | tail -1 | awk '{print $2}' | tr -d ',')
# 直接去该进程的文件系统里拷
cp /proc/$PID/root/usr/local/bin/etcdctl /usr/local/bin/
chmod +x /usr/local/bin/etcdctl
# 先测试能否连接到etcd(这一步很重要!)
etcdctl --endpoints=$ETCD_ENDPOINTS \
--cacert=$ETCD_CACERT \
--cert=$ETCD_CERT \
--key=$ETCD_KEY \
endpoint health
# 预期输出:
# https://192.168.100.20:2379 is healthy: successfully committed proposal: took = 1.234ms
# 如果报错,常见原因:
# - "connection refused":etcd没运行或端口不对
# - "certificate"相关:证书路径错误或证书不匹配
# - "context deadline exceeded":网络不通或防火墙阻止
9.3.4 第四步:执行备份#
Terminal window
# 创建备份目录
mkdir -p /data/etcd-backup
# 生成带时间戳的备份文件名
BACKUP_FILE="/data/etcd-backup/etcd-snapshot-$(date +%Y%m%d-%H%M%S).db"
echo "备份文件将保存到: $BACKUP_FILE"
# 执行备份!
etcdctl --endpoints=$ETCD_ENDPOINTS \
--cacert=$ETCD_CACERT \
--cert=$ETCD_CERT \
--key=$ETCD_KEY \
snapshot save $BACKUP_FILE
# 预期输出:
# {"level":"info","ts":"2024-01-15T10:30:00.123Z","msg":"created temporary snapshot file","path":"/data/etcd-backup/etcd-snapshot-20240115-103000.db.part"}
# {"level":"info","ts":"2024-01-15T10:30:00.456Z","msg":"saved snapshot","path":"/data/etcd-backup/etcd-snapshot-20240115-103000.db"}
# Snapshot saved at /data/etcd-backup/etcd-snapshot-20240115-103000.db

命令参数解释:

参数含义为什么需要
--endpointsetcd服务地址告诉etcdctl去哪里连接etcd
--cacertCA证书验证etcd服务端身份(防止连到假的etcd)
--cert客户端证书向etcd证明”我有权限访问”
--key客户端私钥配合证书使用,用于TLS握手
snapshot save保存快照etcdctl的备份子命令
9.3.5 第五步:验证备份文件#
Terminal window
# 查看备份文件大小
ls -lh /data/etcd-backup/
# 示例输出:
# -rw------- 1 root root 5.2M Jan 15 10:30 etcd-snapshot-20240115-103000.db
# 验证备份文件完整性(非常重要!)
etcdctl --write-out=table snapshot status $BACKUP_FILE
# 输出示例(表格形式):
# +----------+----------+------------+------------+
# | HASH | REVISION | TOTAL KEYS | TOTAL SIZE |
# +----------+----------+------------+------------+
# | 3c5a6d2e | 12345 | 1024 | 5.2 MB |
# +----------+----------+------------+------------+
# 各字段含义:
# HASH - 快照的哈希值,用于校验完整性
# REVISION - etcd的修订版本号(每次写操作+1)
# TOTAL KEYS - 快照中包含的键值对数量
# TOTAL SIZE - 快照文件大小

⚠️ 重要:如果snapshot status报错,说明备份文件损坏,不要使用!

9.3.6 手动备份完整脚本(一键执行版)#
manual-etcd-backup.sh
#!/bin/bash
# 用途:手动备份etcd,可直接在master节点执行
set -e # 任何命令失败立即退出
# ============== 配置区 ==============
ETCDCTL_API=3
ETCD_ENDPOINTS="https://192.168.100.20:2379"
ETCD_CACERT="/etc/kubernetes/pki/etcd/ca.crt"
ETCD_CERT="/etc/kubernetes/pki/etcd/server.crt"
ETCD_KEY="/etc/kubernetes/pki/etcd/server.key"
BACKUP_DIR="/data/etcd-backup"
RETENTION_DAYS=7 # 保留最近7天的备份
# ====================================
# 生成备份文件名
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/etcd-snapshot-${TIMESTAMP}.db"
echo "========================================="
echo " etcd备份脚本"
echo " 时间: $(date)"
echo " 目标: $BACKUP_FILE"
echo "========================================="
# 创建备份目录
mkdir -p ${BACKUP_DIR}
# 测试连接
echo "[1/4] 测试etcd连接..."
etcdctl --endpoints=$ETCD_ENDPOINTS \
--cacert=$ETCD_CACERT \
--cert=$ETCD_CERT \
--key=$ETCD_KEY \
endpoint health
# 执行备份
echo "[2/4] 执行备份..."
etcdctl --endpoints=$ETCD_ENDPOINTS \
--cacert=$ETCD_CACERT \
--cert=$ETCD_CERT \
--key=$ETCD_KEY \
snapshot save $BACKUP_FILE
# 验证备份
echo "[3/4] 验证备份完整性..."
etcdctl snapshot status $BACKUP_FILE --write-out=table
# 清理旧备份
echo "[4/4] 清理${RETENTION_DAYS}天前的旧备份..."
find ${BACKUP_DIR} -name "etcd-snapshot-*.db" -mtime +${RETENTION_DAYS} -delete -print
echo "========================================="
echo " 备份完成!"
echo " 文件: $BACKUP_FILE"
echo " 大小: $(ls -lh $BACKUP_FILE | awk '{print $5}')"
echo "========================================="
Terminal window
# 保存脚本并执行
chmod +x manual-etcd-backup.sh
./manual-etcd-backup.sh

9.4 使用CronJob自动定时备份#

9.4.1 为什么不直接用Linux的crontab?#
方式优点缺点
Linux crontab简单直接只在一台机器上运行,机器挂了就不备份了;日志管理麻烦
K8s CronJob高可用(可调度到任意节点);统一管理;日志集中需要容器化;依赖K8s本身运行

💡 最佳实践:两种方式都配置,CronJob作为主备份,crontab作为兜底。

9.4.2 构建备份镜像#

为什么需要专门的镜像?

CronJob运行在Pod中,Pod是隔离的容器环境。我们需要:

  1. 容器里有etcdctl工具
  2. 容器能访问etcd的证书
  3. 容器能把备份文件写到宿主机

Step 1:创建备份脚本

Terminal window
mkdir -p /root/k8s-demo/etcd-backup
cd /root/k8s-demo/etcd-backup
# 创建备份脚本(容器内执行版)
cat > backup.sh << 'EOF'
#!/bin/sh
# etcd备份脚本 - 容器版
# 环境变量由CronJob的Pod定义传入
set -e # 任何错误立即退出
echo "========================================="
echo " etcd自动备份"
echo " 时间: $(date)"
echo "========================================="
# 配置
BACKUP_DIR="/backup"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/etcd-snapshot-${TIMESTAMP}.db"
# 创建目录
mkdir -p ${BACKUP_DIR}
# 执行备份
echo "[1/3] 执行备份..."
ETCDCTL_API=3 etcdctl snapshot save ${BACKUP_FILE} \
--endpoints=${ETCD_ENDPOINTS} \
--cacert=${ETCD_CACERT} \
--cert=${ETCD_CERT} \
--key=${ETCD_KEY}
# 验证备份
echo "[2/3] 验证备份..."
ETCDCTL_API=3 etcdctl snapshot status ${BACKUP_FILE} -w table
# 显示文件信息
ls -lh ${BACKUP_FILE}
# 清理7天前的旧备份
echo "[3/3] 清理旧备份..."
find ${BACKUP_DIR} -name "etcd-snapshot-*.db" -mtime +7 -delete -print 2>/dev/null || true
echo "========================================="
echo " 备份完成: ${BACKUP_FILE}"
echo "========================================="
EOF
chmod +x backup.sh

Step 2:创建Dockerfile

Terminal window
cat > Dockerfile << 'EOF'
# 基础镜像:Alpine(小巧、安全)
FROM alpine:3.18
# 安装etcdctl工具
# 为什么不用etcd镜像?因为我们只需要etcdctl客户端,完整etcd太大了
RUN apk add --no-cache curl && \
ETCD_VER=v3.5.9 && \
echo "下载etcd ${ETCD_VER}..." && \
curl -L https://github.com/etcd-io/etcd/releases/download/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd.tar.gz && \
tar xzvf /tmp/etcd.tar.gz -C /tmp && \
mv /tmp/etcd-${ETCD_VER}-linux-amd64/etcdctl /usr/local/bin/ && \
rm -rf /tmp/etcd* && \
apk del curl && \
etcdctl version
# 复制备份脚本
COPY backup.sh /usr/local/bin/backup.sh
# 设置入口点
ENTRYPOINT ["/usr/local/bin/backup.sh"]
EOF

Step 3:构建并推送镜像

Terminal window
# 构建镜像
docker build -t reg.westos.org/library/etcd-backup:v1 .
# 验证镜像
docker run --rm reg.westos.org/library/etcd-backup:v1 etcdctl version
# 推送到私有仓库
docker push reg.westos.org/library/etcd-backup:v1
9.4.3 在Master节点创建备份目录#
Terminal window
# CronJob会把备份文件写到这个目录
ssh root@192.168.100.20 "mkdir -p /data/etcd-backup && chmod 755 /data/etcd-backup"
9.4.4 创建CronJob资源#
etcd-backup-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: etcd-backup
namespace: kube-system # 放在系统命名空间,与etcd在一起
spec:
# ============== 调度配置 ==============
schedule: "0 2 * * *" # Cron表达式:每天凌晨2点执行
# │ │ │ │ │
# │ │ │ │ └── 星期几 (0-7, 0和7都是周日)
# │ │ │ └──── 月份 (1-12)
# │ │ └────── 日期 (1-31)
# │ └──────── 小时 (0-23)
# └────────── 分钟 (0-59)
concurrencyPolicy: Forbid # 禁止并发:上一个没跑完,不启动新的
successfulJobsHistoryLimit: 3 # 保留最近3个成功Job的记录
failedJobsHistoryLimit: 1 # 保留最近1个失败Job的记录
startingDeadlineSeconds: 200 # 如果错过调度时间超过200秒,跳过本次
# ============== Job模板 ==============
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure # 失败时重试
# 调度到Master节点(因为etcd证书在Master上)
nodeSelector:
node-role.kubernetes.io/control-plane: ""
# 容忍Master节点的污点
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: etcd-backup
image: reg.westos.org/library/etcd-backup:v1
# 通过环境变量传递配置(脚本中使用)
env:
- name: ETCD_ENDPOINTS
value: "https://192.168.100.20:2379"
- name: ETCD_CACERT
value: "/etc/kubernetes/pki/etcd/ca.crt"
- name: ETCD_CERT
value: "/etc/kubernetes/pki/etcd/server.crt"
- name: ETCD_KEY
value: "/etc/kubernetes/pki/etcd/server.key"
# 挂载卷
volumeMounts:
- name: etcd-certs
mountPath: /etc/kubernetes/pki/etcd # 挂载证书目录
readOnly: true # 只读,安全
- name: backup-dir
mountPath: /backup # 备份文件写入位置
# 卷定义
volumes:
- name: etcd-certs
hostPath:
path: /etc/kubernetes/pki/etcd # Master上的证书目录
type: Directory
- name: backup-dir
hostPath:
path: /data/etcd-backup # Master上的备份目录
type: DirectoryOrCreate # 不存在则创建

关键配置解释:

┌─────────────────────────────────────────────────────────────────────────┐
│ CronJob配置详解 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 为什么要用nodeSelector调度到Master? │
│ └── etcd证书只在Master节点的/etc/kubernetes/pki/etcd目录下 │
│ 如果调度到Worker节点,证书目录是空的,备份必然失败 │
│ │
│ 为什么要配置tolerations容忍污点? │
│ └── Master节点默认有污点,普通Pod无法调度上去 │
│ node-role.kubernetes.io/control-plane:NoSchedule │
│ 必须容忍这个污点,Pod才能调度到Master │
│ │
│ 为什么用hostPath挂载? │
│ └── 证书和备份文件都在宿主机上,必须用hostPath访问 │
│ etcd-certs: 读取证书用于认证 │
│ backup-dir: 写入备份文件到宿主机 │
│ │
│ 为什么concurrencyPolicy设为Forbid? │
│ └── 备份是独占操作,同时运行多个备份没意义,还可能冲突 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
9.4.5 部署CronJob#
Terminal window
# 应用CronJob
kubectl apply -f etcd-backup-cronjob.yaml
# 查看CronJob状态
kubectl get cronjob -n kube-system etcd-backup
# NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
# etcd-backup 0 2 * * * False 0 <none> 5s
9.4.6 手动触发测试(不等到凌晨2点)#
Terminal window
# 从CronJob模板创建一个立即执行的Job
kubectl create job etcd-backup-manual --from=cronjob/etcd-backup -n kube-system
# 查看Job状态
kubectl get jobs -n kube-system | grep etcd-backup
# etcd-backup-manual 1/1 12s 20s
# 实时查看备份日志
kubectl logs -n kube-system -l job-name=etcd-backup-manual -f
# 预期看到:
# =========================================
# etcd自动备份
# 时间: Mon Jan 15 10:30:00 UTC 2024
# =========================================
# [1/3] 执行备份...
# Snapshot saved at /backup/etcd-snapshot-20240115-103000.db
# [2/3] 验证备份...
# +----------+----------+------------+------------+
# | HASH | REVISION | TOTAL KEYS | TOTAL SIZE |
# +----------+----------+------------+------------+
# | 3c5a6d2e | 12345 | 1024 | 5.2 MB |
# +----------+----------+------------+------------+
# [3/3] 清理旧备份...
# =========================================
# 备份完成: /backup/etcd-snapshot-20240115-103000.db
# =========================================
9.4.7 验证备份文件#
Terminal window
# 登录Master查看备份文件
ssh root@192.168.100.20 "ls -lh /data/etcd-backup/"
# -rw------- 1 root root 5.2M Jan 15 10:30 etcd-snapshot-20240115-103000.db
# 验证备份文件完整性
ssh root@192.168.100.20 "ETCDCTL_API=3 etcdctl snapshot status /data/etcd-backup/etcd-snapshot-*.db --write-out=table"

9.5 备份策略建议#

┌─────────────────────────────────────────────────────────────────────────┐
│ 生产环境备份策略 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 备份频率: │
│ ├── 生产环境:每2-4小时一次 │
│ ├── 测试环境:每天一次 │
│ └── 重大变更前:手动备份一次 │
│ │
│ 备份保留: │
│ ├── 本地保留:7天 │
│ └── 远程备份:至少30天(异地存储!) │
│ │
│ 备份存储: │
│ ├── 本地:/data/etcd-backup(快速恢复) │
│ ├── NFS:挂载网络存储(防单点故障) │
│ └── 对象存储:S3/OSS/MinIO(异地容灾) │
│ │
│ ⚠️ 关键原则:备份文件绝不能只存在etcd所在的同一台机器上! │
│ 机器挂了,etcd和备份一起丢失 = 没有备份 │
│ │
└─────────────────────────────────────────────────────────────────────────┘

10. 实战4:etcd恢复演练(灾难恢复)#

10.1 恢复原理#

┌─────────────────────────────────────────────────────────────────────────┐
│ etcd恢复原理 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ etcdctl snapshot restore 做了什么? │
│ │
│ 1. 读取快照文件 (.db) │
│ └── 解析其中的所有键值对 │
│ │
│ 2. 创建新的数据目录 │
│ └── --data-dir=/var/lib/etcd (必须是空目录或不存在) │
│ │
│ 3. 初始化etcd成员信息 │
│ ├── --name:当前节点名称 │
│ ├── --initial-cluster:集群成员列表 │
│ └── --initial-advertise-peer-urls:节点间通信地址 │
│ │
│ 4. 写入数据 │
│ └── 将快照数据写入新数据目录 │
│ │
│ ⚠️ 注意:restore后的etcd是"新集群",节点ID会变化 │
│ 所以必须重新配置集群成员信息 │
│ │
└─────────────────────────────────────────────────────────────────────────┘

10.2 准备工作#

⚠️ 警告:以下操作会破坏集群,仅在测试环境执行!

Terminal window
# 第一步:创建虚拟机快照(必做!)
# 在VMware/KVM中为所有节点创建快照
# 快照名称建议:"Before-etcd-recovery-test-$(date +%Y%m%d)"
# 第二步:确保有可用的备份
kubectl create job etcd-backup-before-test --from=cronjob/etcd-backup -n kube-system
kubectl wait --for=condition=complete job/etcd-backup-before-test -n kube-system --timeout=60s
# 验证备份存在
ssh root@192.168.100.20 "ls -lh /data/etcd-backup/"

10.3 模拟创建业务资源#

Terminal window
# 创建一些测试资源,恢复后验证是否存在
kubectl create namespace recovery-test
kubectl create deployment nginx --image=nginx:1.20 --replicas=3 -n recovery-test
kubectl expose deployment nginx --port=80 -n recovery-test
kubectl create configmap test-config --from-literal=env=production --from-literal=version=1.0 -n recovery-test
kubectl create secret generic test-secret --from-literal=password=abc123 -n recovery-test
# 记录当前资源(恢复后对比)
echo "=== 当前资源 ==="
kubectl get all,cm,secret -n recovery-test

10.4 模拟etcd灾难#

Terminal window
# 登录Master节点
ssh root@192.168.100.20
# 1. 停止etcd(移走静态Pod配置)
mv /etc/kubernetes/manifests/etcd.yaml /tmp/etcd.yaml.bak
# 等待etcd Pod终止
sleep 10
crictl ps | grep etcd # 应该看不到etcd容器了
# 2. 破坏etcd数据(模拟磁盘损坏)
mv /var/lib/etcd /var/lib/etcd.broken
# 此时集群完全不可用
kubectl get nodes
# Error: dial tcp: lookup ... connect: connection refused

10.5 执行恢复#

Terminal window
# 以下所有操作在Master节点(192.168.100.20)上执行
# ============== 第一步:停止所有控制平面组件 ==============
cd /etc/kubernetes/manifests
mv kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml /tmp/
# 等待组件停止
sleep 15
# ============== 第二步:恢复etcd数据 ==============
# 找到最新的备份文件
BACKUP_FILE=$(ls -t /data/etcd-backup/*.db | head -1)
echo "使用备份文件: $BACKUP_FILE"
# 执行恢复(关键命令!)
ETCDCTL_API=3 etcdctl snapshot restore $BACKUP_FILE \
--data-dir=/var/lib/etcd \
--name=k8s-master \
--initial-cluster=k8s-master=https://192.168.100.20:2380 \
--initial-advertise-peer-urls=https://192.168.100.20:2380
# 参数解释:
# --data-dir : 恢复到的数据目录(会自动创建)
# --name : 当前etcd节点名称(必须与原来一致)
# --initial-cluster : 集群成员列表(格式:name=url)
# --initial-advertise-peer-urls : 节点间通信地址
# ============== 第三步:设置正确的权限 ==============
chown -R root:root /var/lib/etcd
chmod 700 /var/lib/etcd
# ============== 第四步:恢复控制平面组件 ==============
mv /tmp/etcd.yaml.bak /etc/kubernetes/manifests/etcd.yaml
mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/
mv /tmp/kube-controller-manager.yaml /etc/kubernetes/manifests/
mv /tmp/kube-scheduler.yaml /etc/kubernetes/manifests/
# 等待组件启动
echo "等待控制平面启动..."
sleep 30
# 退出Master
exit

10.6 验证恢复结果#

Terminal window
# 回到管理机验证
# 1. 检查节点状态
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# k8s-master Ready control-plane 10d v1.28.0
# k8s-node1 Ready <none> 10d v1.28.0
# k8s-node2 Ready <none> 10d v1.28.0
# 2. 检查系统组件
kubectl get pods -n kube-system
# 所有组件应该都是Running
# 3. 检查测试资源是否恢复
kubectl get all,cm,secret -n recovery-test
# 应该看到之前创建的nginx Deployment、Service、ConfigMap、Secret
# 4. 验证应用可访问
kubectl exec -it $(kubectl get pod -n recovery-test -o name | head -1) -n recovery-test -- curl localhost
# 应该看到nginx欢迎页

10.7 恢复后清理#

Terminal window
# 删除测试资源
kubectl delete namespace recovery-test
# 删除测试Job
kubectl delete job etcd-backup-before-test -n kube-system
# 清理Master上的临时文件
ssh root@192.168.100.20 "rm -rf /var/lib/etcd.broken"

10.8 恢复流程总结#

┌─────────────────────────────────────────────────────────────────────────┐
│ etcd恢复完整流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 第一步:停止所有控制平面组件 │
│ └── 移走 /etc/kubernetes/manifests/ 下的yaml文件 │
│ │
│ 第二步:清理旧数据(如果存在损坏的数据) │
│ └── rm -rf /var/lib/etcd 或 mv /var/lib/etcd /var/lib/etcd.old │
│ │
│ 第三步:使用etcdctl snapshot restore恢复数据 │
│ └── 指定正确的--name、--initial-cluster等参数 │
│ │
│ 第四步:设置权限 │
│ └── chown -R root:root /var/lib/etcd && chmod 700 /var/lib/etcd │
│ │
│ 第五步:恢复控制平面组件 │
│ └── 移回yaml文件到 /etc/kubernetes/manifests/ │
│ │
│ 第六步:验证 │
│ └── kubectl get nodes/pods,确认集群正常 │
│ │
└─────────────────────────────────────────────────────────────────────────┘

总结#

本章学习了三种特殊的工作负载:

DaemonSet:

  • 每个节点运行一个Pod
  • 用于日志收集、监控、网络插件
  • 支持滚动更新和回滚

Job:

  • 一次性任务执行
  • 支持并行和失败重试
  • 自动清理机制

CronJob:

  • 定时任务调度
  • Cron表达式控制执行时间
  • 并发策略控制

实战收获:

  • 部署Fluentd DaemonSet收集日志
  • 使用Job并行处理任务
  • CronJob定时备份etcd
  • etcd备份恢复演练

生产建议:

  • DaemonSet用于节点级服务
  • Job用于批处理任务
  • CronJob必须备份关键数据
  • etcd备份是集群安全的最后防线

八、配置管理:ConfigMap和Secret#

1. 为什么需要配置管理#

在前面章节中,我们把配置信息直接写在YAML文件里。这样做有严重问题:

问题:

  • 密码明文存储
  • 无法复用配置
  • 环境差异难以处理
  • 版本管理混乱

Kubernetes解决方案:

  • ConfigMap:存储非敏感配置(数据库地址、端口)
  • Secret:存储敏感信息(密码、密钥)

2. ConfigMap:配置数据的”字典”#

2.1 ConfigMap是什么#

ConfigMap 存储非敏感配置数据,以键值对形式。

创建方式:

Terminal window
# 方式1:从字面量
kubectl create configmap app-config \
--from-literal=DB_HOST=mysql.example.com \
--from-literal=DB_PORT=3306
# 方式2:从文件
kubectl create configmap app-config --from-file=app.properties
# 方式3:从YAML
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
DB_HOST: "mysql-service"
DB_PORT: "3306"
my.cnf: |
[mysqld]
max_connections=200

2.2 ConfigMap使用方式#

方式1:环境变量注入

#方式1:单独导入
containers:
- name: app
image: nginx:1.20
env:
- name: DATABASE_HOST
valueFrom:
configMapKeyRef:
name: mysql-config
key: DB_HOST
#方式2: 批量导入
containers:
- name: app
image: nginx:1.20
envFrom:
- configMapRef:
name: mysql-config

方式2:文件挂载

containers:
- name: app
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: mysql-config

2.3 ConfigMap热更新#

⚠️ 重要特性:

  • Volume方式:支持热更新(30-60秒)
  • 环境变量:需要重启Pod

3. Secret:敏感数据的”保险箱”#

3.1 Secret是什么#

Secret 存储敏感信息,数据Base64编码(注意:不是加密!)

Secret类型:

类型说明用途
Opaque通用类型密码、密钥
kubernetes.io/dockerconfigjsonDocker凭证拉取私有镜像
kubernetes.io/tlsTLS证书HTTPS

3.2 Secret创建方式#

Terminal window
# 创建通用Secret
kubectl create secret generic mysql-secret \
--from-literal=username=root \
--from-literal=password='MyP@ssw0rd123'
# 创建Docker镜像仓库Secret
kubectl create secret docker-registry harbor-secret \
--docker-server=reg.westos.org \
--docker-username=admin \
--docker-password=Harbor12345

YAML方式(stringData自动编码):

apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: Opaque
stringData:
username: root
password: MyP@ssw0rd123

3.3 Secret使用方式#

环境变量注入:

#方式1:单个读取
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
#方式2: 批量导入
containers:
- name: mysql
image: mysql:8.0
envFrom:
- secretRef:
name: mysql-secret

文件挂载:

containers:
- name: app
volumeMounts:
- name: secret-volume
mountPath: /etc/secret
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: mysql-secret

⚠️ 安全注意:

  • Base64不是加密
  • 不要提交到Git
  • 使用RBAC限制访问
  • 避免在日志中打印

4. 实战:ConfigMap和Secret综合应用#

4.1 实战目标#

创建一个完整的Web应用,展示配置管理的最佳实践。

架构:

ConfigMap (app-config) → 环境变量
Secret (db-secret) → 数据库密码
Nginx Pod → 显示配置信息

4.2 创建ConfigMap#

app-configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_NAME: "ConfigDemo"
APP_ENV: "production"
DB_HOST: "mysql-service"
DB_PORT: "3306"
index.html: |
<!DOCTYPE html>
<html>
<head>
<title>Config Demo</title>
<style>
body { font-family: Arial; max-width: 800px; margin: 50px auto; padding: 20px; }
.box { background: white; padding: 20px; border-radius: 8px; margin: 10px 0; }
h1 { color: #333; }
</style>
</head>
<body>
<h1>ConfigMap和Secret演示</h1>
<div class="box">
<h2>配置信息:</h2>
<p>应用名称:<span id="app-name"></span></p>
<p>运行环境:<span id="app-env"></span></p>
<p>数据库地址:<span id="db-host"></span></p>
</div>
<div class="box">
<h2>敏感信息(从Secret读取):</h2>
<p>数据库用户:<span id="db-user"></span></p>
<p>数据库密码:<span id="db-pass"></span></p>
</div>
</body>
</html>
Terminal window
kubectl apply -f app-configmap.yaml

4.3 创建Secret#

db-secret.yaml:

apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
stringData:
DB_USER: "root"
DB_PASSWORD: "MySecretPass123"
Terminal window
kubectl apply -f db-secret.yaml

4.4 创建应用#

app-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
name: config-demo
spec:
replicas: 1
selector:
matchLabels:
app: config-demo
template:
metadata:
labels:
app: config-demo
spec:
containers:
- name: nginx
image: nginx:1.20
ports:
- containerPort: 80
# ConfigMap环境变量
env:
- name: APP_NAME
valueFrom:
configMapKeyRef:
name: app-config
key: APP_NAME
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: app-config
key: APP_ENV
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: DB_HOST
# Secret环境变量
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-secret
key: DB_USER
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: DB_PASSWORD
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
# 启动脚本:生成页面
command: ["/bin/sh", "-c"]
args:
- |
cat > /usr/share/nginx/html/index.html << EOF
<!DOCTYPE html>
<html>
<head><title>Config Demo</title>
<style>
body { font-family: Arial; max-width: 800px; margin: 50px auto; padding: 20px; background: #f0f0f0; }
.box { background: white; padding: 20px; border-radius: 8px; margin: 10px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
h1 { color: #333; }
.label { font-weight: bold; }
.value { color: #0066cc; }
</style>
</head>
<body>
<h1>🎯 ConfigMap和Secret演示</h1>
<div class="box">
<h2>ConfigMap配置:</h2>
<p><span class="label">应用名称:</span><span class="value">$APP_NAME</span></p>
<p><span class="label">运行环境:</span><span class="value">$APP_ENV</span></p>
<p><span class="label">数据库地址:</span><span class="value">$DB_HOST</span></p>
</div>
<div class="box">
<h2>Secret敏感信息:</h2>
<p><span class="label">数据库用户:</span><span class="value">$DB_USER</span></p>
<p><span class="label">数据库密码:</span><span class="value">$DB_PASSWORD</span></p>
</div>
<div class="box">
<p>💡 提示:此页面展示了ConfigMap和Secret的使用</p>
</div>
</body>
</html>
EOF
nginx -g 'daemon off;'
volumes:
- name: html
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: config-demo
spec:
selector:
app: config-demo
ports:
- port: 80
targetPort: 80
nodePort: 30088
type: NodePort
Terminal window
kubectl apply -f app-deployment.yaml

4.5 访问和测试#

Terminal window
# 查看Pod
kubectl get pods -l app=config-demo
# 获取Service地址
kubectl get svc config-demo
# 访问应用(浏览器打开)
http://192.168.100.21:30088

测试配置更新:

Terminal window
# 1. 更新ConfigMap
kubectl patch configmap app-config -p '{"data":{"APP_ENV":"development"}}'
# 2. 重启Pod(环境变量需要重启)
kubectl rollout restart deployment config-demo
# 3. 再次访问,看到环境变量已更新

测试Secret更新:

Terminal window
# 更新Secret
kubectl patch secret db-secret -p '{"stringData":{"DB_PASSWORD":"NewPassword456"}}'
# 重启Pod
kubectl rollout restart deployment config-demo
# 验证密码已更新

总结#

ConfigMap:

  • 存储非敏感配置
  • 支持文件热更新
  • 多种创建和使用方式

Secret:

  • 存储敏感信息
  • Base64编码(不是加密)
  • RBAC权限控制

最佳实践:

  • 配置与代码分离
  • 不要把Secret提交到Git
  • 环境变量用于简单配置
  • 文件挂载用于复杂配置
  • 生产环境启用etcd加密
05.Kubernetes 学习笔记:特殊工作负载与配置管理
https://dev-null-sec.github.io/posts/05-k8s学习笔记-特殊工作负载与配置管理/
作者
DevNull
发布于
2025-05-10
许可协议
CC BY-NC-SA 4.0