8573 字
43 分钟
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:使用CronJob定时备份etcd#

9.1 准备备份镜像#

Terminal window
mkdir -p /root/k8s-demo/etcd-backup
cd /root/k8s-demo/etcd-backup

backup.sh:

cat > backup.sh << 'EOF'
#!/bin/sh
set -e
BACKUP_DIR="/backup"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/etcd-snapshot-${TIMESTAMP}.db"
echo "=== Backing up etcd at $(date) ==="
mkdir -p ${BACKUP_DIR}
ETCDCTL_API=3 etcdctl snapshot save ${BACKUP_FILE} \
--endpoints=${ETCD_ENDPOINTS} \
--cacert=${ETCD_CACERT} \
--cert=${ETCD_CERT} \
--key=${ETCD_KEY}
ETCDCTL_API=3 etcdctl snapshot status ${BACKUP_FILE} -w table
ls -lh ${BACKUP_FILE}
find ${BACKUP_DIR} -name "etcd-snapshot-*.db" -mtime +7 -delete
echo "=== Backup completed at $(date) ==="
EOF
chmod +x backup.sh

Dockerfile:

FROM alpine:3.18
RUN apk add --no-cache curl && \
ETCD_VER=v3.5.9 && \
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*
COPY backup.sh /usr/local/bin/
ENTRYPOINT ["/usr/local/bin/backup.sh"]
Terminal window
docker build -t reg.westos.org/library/etcd-backup:v1 .
docker push reg.westos.org/library/etcd-backup:v1

9.2 在master创建备份目录#

Terminal window
ssh root@192.168.100.20
mkdir -p /data/etcd-backup
chmod 777 /data/etcd-backup

9.3 创建CronJob#

etcd-backup-cronjob.yaml:

apiVersion: batch/v1
kind: CronJob
metadata:
name: etcd-backup
namespace: kube-system
spec:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
nodeSelector:
node-role.kubernetes.io/control-plane: ""
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
- name: backup-dir
hostPath:
path: /data/etcd-backup

9.4 部署和测试#

Terminal window
kubectl apply -f etcd-backup-cronjob.yaml
# 手动触发测试
kubectl create job --from=cronjob/etcd-backup etcd-backup-manual -n kube-system
kubectl logs -n kube-system -l job-name=etcd-backup-manual -f
# 验证备份文件
ssh root@192.168.100.20 "ls -lh /data/etcd-backup/"

10. 实战4:etcd备份恢复演练#

10.1 准备工作#

⚠️ 警告:仅在测试环境操作!

  1. 为所有节点创建虚拟机快照
  2. 快照名称:“Before-etcd-recovery-test”

10.2 创建初始备份#

Terminal window
kubectl create job --from=cronjob/etcd-backup etcd-backup-before-test -n kube-system
kubectl wait --for=condition=complete job/etcd-backup-before-test -n kube-system --timeout=60s

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=key1=value1 -n recovery-test
kubectl get all,cm -n recovery-test

10.4 破坏etcd#

Terminal window
ssh root@192.168.100.20
mv /etc/kubernetes/manifests/etcd.yaml /tmp/
mv /var/lib/etcd /var/lib/etcd.broken
# 集群不可用

10.5 恢复etcd#

Terminal window
# 停止其他组件
cd /etc/kubernetes/manifests
mv kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml /tmp/
# 恢复数据
BACKUP_FILE=$(ls -t /data/etcd-backup/*.db | head -1)
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
chown -R root:root /var/lib/etcd
chmod 700 /var/lib/etcd
# 恢复组件
mv /tmp/*.yaml ./
sleep 30
exit

10.6 验证恢复#

Terminal window
kubectl get nodes
kubectl get all,cm -n recovery-test
# 应该看到之前创建的资源

恢复成功!


总结#

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

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:环境变量注入

containers:
- name: app
image: nginx:1.20
env:
- name: DATABASE_HOST
valueFrom:
configMapKeyRef:
name: mysql-config
key: DB_HOST

方式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使用方式#

环境变量注入:

containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password

文件挂载:

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
发布于
2024-11-10
许可协议
CC BY-NC-SA 4.0