📘 Day 16:调度策略与亲和性

🎯 今日目标

  • 用 nodeSelector 做简单节点选择
  • 用 nodeAffinity 做高级节点亲和
  • 用 podAffinity 让 Pod 靠近部署
  • 用 podAntiAffinity 让 Pod 分散部署
  • 用 topologySpreadConstraints 做均匀分布

🧠 理论精讲(30 分钟)

调度策略对比

策略 作用范围 强制/偏好 典型场景
nodeSelector 节点标签 强制 简单:GPU 节点
nodeAffinity 节点标签 可偏好 复杂:优先 SSD,可退而求其次
podAffinity Pod 标签 可偏好 缓存靠近应用
podAntiAffinity Pod 标签 可偏好 高可用:同一服务分散
Taint/Toleration 节点污点 排斥/许可 专用节点
topologySpreadConstraints 拓扑域 强制 跨可用区均匀分布

nodeAffinity 字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬性要求
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
preferredDuringSchedulingIgnoredDuringExecution: # 软性偏好
- weight: 1
preference:
matchExpressions:
- key: disk
operator: In
values:
- ssd

运算符

操作符 含义
In 在列表中
NotIn 不在列表中
Exists 存在此标签
DoesNotExist 不存在此标签
Gt 大于(数值)
Lt 小于(数值)

🔧 动手实操(120 分钟)

练习 16.1:nodeSelector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 1. 给节点打标签
kubectl label node k8s-node1 disk=ssd
kubectl label node k8s-node2 disk=hdd

# 2. 创建用 nodeSelector 的 Pod
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: ssd-pod
spec:
nodeSelector:
disk: ssd
containers:
- name: app
image: nginx:alpine
EOF

# 3. 验证调度到 node1
kubectl get pod ssd-pod -o wide
# NODE: k8s-node1

# 4. 尝试调度到不存在的标签
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
nodeSelector:
gpu: nvidia
containers:
- name: app
image: nginx:alpine
EOF

kubectl get pod gpu-pod
# STATUS: Pending(因为没有节点有 gpu=nvidia)

kubectl describe pod gpu-pod | grep -A3 Events
# Warning FailedScheduling 0/3 nodes are available: ...

# 5. 清理
kubectl delete pod ssd-pod gpu-pod

练习 16.2:nodeAffinity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: affinity-pod
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: disk
operator: In
values:
- ssd
- weight: 50
preference:
matchExpressions:
- key: zone
operator: In
values:
- cn-east
containers:
- name: app
image: nginx:alpine
EOF

# 查看调度决策
kubectl get pod affinity-pod -o wide
# 优先调度到 disk=ssd 的节点

# 即使没有 disk=ssd,Pod 仍能调度(因为是 preferred)
kubectl describe pod affinity-pod | grep -A5 "Node Affinity"

kubectl delete pod affinity-pod

练习 16.3:podAffinity(靠近部署)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 场景:缓存 Pod 和 应用 Pod 部署在同一节点
# 1. 创建缓存 Pod
kubectl run cache --image=redis:7-alpine --labels=app=cache

# 2. 创建应用 Pod 靠近缓存
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: app-with-cache
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache
topologyKey: kubernetes.io/hostname # 同一节点
containers:
- name: app
image: nginx:alpine
EOF

# 3. 验证两个 Pod 在同一节点
kubectl get pod cache app-with-cache -o wide
# 两个 Pod 的 NODE 相同

# 4. 清理
kubectl delete pod cache app-with-cache

练习 16.4:podAntiAffinity(分散部署)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 场景:高可用 Web 服务分散到不同节点
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-ha
spec:
replicas: 3
selector:
matchLabels:
app: web-ha
template:
metadata:
labels:
app: web-ha
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-ha
topologyKey: kubernetes.io/hostname
containers:
- name: web
image: nginx:alpine
EOF

# 验证 Pod 分布在不同节点(3 节点集群刚好每节点 1 个)
kubectl get pod -l app=web-ha -o wide
# 3 个 Pod 应在 3 个不同节点上

# 如果再扩容会怎样?
kubectl scale deploy web-ha --replicas=4
kubectl get pod -l app=web-ha -o wide
# 第 4 个 Pod Pending(没有第 4 个节点来分散)

kubectl describe pod <pending-pod> | grep -A5 Events

# 清理
kubectl delete deploy web-ha

练习 16.5:topologySpreadConstraints

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 按可用区均匀分布
kubectl label node k8s-master topology.kubernetes.io/zone=zone-a --overwrite
kubectl label node k8s-node1 topology.kubernetes.io/zone=zone-a --overwrite
kubectl label node k8s-node2 topology.kubernetes.io/zone=zone-b --overwrite

cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: spread-demo
spec:
replicas: 6
selector:
matchLabels:
app: spread-demo
template:
metadata:
labels:
app: spread-demo
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: spread-demo
containers:
- name: app
image: nginx:alpine
EOF

# 查看分布
kubectl get pod -l app=spread-demo -o wide
# 预期 zone-a 和 zone-b 各 3 个 Pod(maxSkew=1)

# 清理
kubectl delete deploy spread-demo

🐛 排错练习(30 分钟)

场景:Pod 一直 Pending(调度失败)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 排查清单:
# 1. 查看 Pod 事件
kubectl describe pod <pod-name> | grep -A20 Events

# 常见原因:
# - "0/3 nodes are available: 3 node(s) didn't match node selector"
# → nodeSelector 太严格
# - "0/3 nodes are available: 3 node(s) had taint {xxx}, that the pod didn't tolerate"
# → 节点有污点,Pod 没容忍
# - "0/3 nodes are available: 3 Insufficient cpu/memory"
# → 资源不足

# 2. 检查节点资源
kubectl describe node <node-name> | grep -A5 "Allocated resources"

🏆 赛题模拟(40 分钟)

⚠️ 严格限时 35 分钟

题目:高级调度策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
【初始环境】3 节点集群

【操作要求】

1. 标签准备:
- k8s-node1:env=prod, tier=frontend
- k8s-node2:env=prod, tier=backend
- k8s-master:env=prod, tier=management

2. 部署 frontend Deployment(3 副本):
- podAntiAffinity:同 app=frontend 的 Pod 不在同一节点
- nodeAffinity (required):tier=frontend
- 观察 Pod 会不会 Pending(只有 1 个 frontend 节点但有 3 个副本)

3. 部署 backend Deployment(2 副本):
- nodeAffinity (required):tier=backend
- podAffinity (preferred):靠近 app=cache 的 Pod

4. 部署 cache Pod(1 个):
- nodeAffinity (required):tier=backend
- 无其他限制

5. 观察 backend 是否自动调度到与 cache 同一节点

6. 添加 topologySpreadConstraints:backend 按 zone 均匀分布

【评分标准】
- 标签设置正确(10 分)
- frontend 反亲和正确(25 分)
- backend nodeAffinity 正确(20 分)
- podAffinity 靠近 cache 正确(20 分)
- topologySpreadConstraints 正确(15 分)
- 观察分析完整(10 分)

📋 命令速查

命令 功能 注解
kubectl label node <node> key=value 给节点打标签 配合 nodeSelector 使用,Pod 通过 nodeSelector 精确匹配
kubectl label node <node> key- 删除节点标签 标签名后加 -
kubectl get nodes --show-labels 查看节点和标签 确认标签是否打对
kubectl get nodes -l key=value 按标签筛选节点 -l = --selector,快速找到匹配标签的节点
kubectl taint node <node> key=value:NoSchedule 添加污点(硬排斥) 无对应 Toleration 的 Pod 无法调度
kubectl taint node <node> key=value:PreferNoSchedule 添加软污点 尽量不调度,资源不足时仍可调度
kubectl taint node <node> key=value:NoExecute 添加驱逐级污点 已有 Pod 若未容忍也会被驱逐
kubectl taint node <node> key=value:NoSchedule- 移除污点 末尾加 - 删除对应 Taint
kubectl describe node <node> | grep Taints 查看节点污点 排错时确认节点是否有预期外的污点
kubectl cordon <node> 标记节点不可调度 等同于添加 node.kubernetes.io/unschedulable:NoSchedule
kubectl uncordon <node> 恢复节点可调度 取消 cordon 标记
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data 安全驱逐节点上所有 Pod 节点维护必需;DaemonSet Pod 需 –ignore-daemonsets 跳过
kubectl top nodes 查看节点资源用量 调度决策参考;需安装 metrics-server
kubectl get pods -o wide | grep <node> 查看某节点上的所有 Pod 替代 --field-selector=spec.nodeName=<node>

📚 参考来源

来源 链接 / 说明
Kubernetes 官方:调度与驱逐 https://kubernetes.io/docs/concepts/scheduling-eviction/
Kubernetes 官方:节点亲和性 https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity
Kubernetes 官方:污点与容忍 https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/
Kubernetes 官方:Pod 拓扑分布约束 https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/
Kubernetes 官方:安全驱逐节点 https://kubernetes.io/docs/tasks/administer-cluster/safely-drain-node/

📘 Day 17:资源限制、QoS 与 HPA

🎯 今日目标

  • 理解 requests ≠ limits 的区别与作用
  • 能判断 Pod 的 QoS 等级
  • 会用 LimitRange 限制默认资源
  • 会用 ResourceQuota 限制命名空间
  • 用 HPA 实现自动伸缩

🧠 理论精讲(30 分钟)

requests vs limits

参数 含义 调度影响
requests 保证分配的资源 调度器按此值找节点
limits 资源使用上限 超出即被 throttle/杀死
1
2
3
4
5
6
7
resources:
requests:
cpu: "200m" # 保证 0.2 核
memory: "256Mi" # 保证 256M 内存
limits:
cpu: "500m" # 最多用 0.5 核
memory: "512Mi" # 最多用 512M 内存

QoS 等级

等级 条件 驱逐优先级
Guaranteed requests == limits(两者都设且相等) 最低
Burstable requests < limits(至少一个容器设了 requests) 中等
BestEffort 未设任何 requests/limits 最高(最先被驱逐)

HPA 公式

1
期望副本数 = ceil(当前副本数 × (当前指标值 / 目标指标值))

🔧 动手实操(120 分钟)

练习 17.1:requests 与 limits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# 1. 创建不同资源规格的 Pod
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: guaranteed-pod
spec:
containers:
- name: app
image: nginx:alpine
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "100m"
memory: "128Mi"
---
apiVersion: v1
kind: Pod
metadata:
name: burstable-pod
spec:
containers:
- name: app
image: nginx:alpine
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
---
apiVersion: v1
kind: Pod
metadata:
name: beste-ffort-pod
spec:
containers:
- name: app
image: nginx:alpine
EOF

# 2. 查看 QoS 等级
kubectl get pod guaranteed-pod -o jsonpath='{.status.qosClass}'
echo
# Guaranteed

kubectl get pod burstable-pod -o jsonpath='{.status.qosClass}'
echo
# Burstable

kubectl get pod beste-ffort-pod -o jsonpath='{.status.qosClass}'
echo
# BestEffort

# 3. 模拟内存超限(OOMKilled)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: oom-pod
spec:
containers:
- name: mem-eater
image: busybox:1.36
command:
- sh
- -c
- |
# 分配超过 limit 的内存
dd if=/dev/zero of=/dev/shm/bigfile bs=100M count=10
sleep 3600
resources:
limits:
memory: "50Mi"
EOF

kubectl get pod oom-pod -w
# 观察 RESTARTS 递增,Last State: OOMKilled

kubectl describe pod oom-pod | grep -A5 "Last State"

# 4. 清理
kubectl delete pod guaranteed-pod burstable-pod beste-ffort-pod oom-pod

练习 17.2:LimitRange

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 创建命名空间
kubectl create ns resource-lab

# 创建 LimitRange
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: resource-lab
spec:
limits:
- type: Container
default:
cpu: "200m"
memory: "256Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "1"
memory: "1Gi"
min:
cpu: "50m"
memory: "64Mi"
EOF

# 创建不设 resources 的 Pod(自动应用默认值)
kubectl run auto-pod --image=nginx:alpine -n resource-lab

# 验证自动注入
kubectl get pod auto-pod -n resource-lab -o yaml | grep -A8 resources
# 应有默认的 requests 和 limits

# 尝试创建超过 max 的 Pod(应被拒绝)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: over-limit-pod
namespace: resource-lab
spec:
containers:
- name: app
image: nginx:alpine
resources:
limits:
cpu: "2" # 超过 max.cpu=1
memory: "256Mi"
EOF
# Error: [cpu: Invalid value: "2": must be less than or equal to cpu limit]

# 清理
kubectl delete pod auto-pod -n resource-lab

练习 17.3:ResourceQuota

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 创建 ResourceQuota
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-quota
namespace: resource-lab
spec:
hard:
requests.cpu: "2"
requests.memory: "2Gi"
limits.cpu: "4"
limits.memory: "4Gi"
pods: "10"
persistentvolumeclaims: "5"
services: "5"
EOF

# 查看配额
kubectl describe quota team-quota -n resource-lab

# 创建 Deployment 测试配额消耗
kubectl create deploy quota-test -n resource-lab --image=nginx:alpine --replicas=5

# 查看配额使用情况
kubectl describe quota team-quota -n resource-lab
# 可以看到 Used vs Hard

# 清理
kubectl delete deploy quota-test -n resource-lab

练习 17.4:HPA 自动伸缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 0. 确保 metrics-server 已安装
kubectl get pods -n kube-system -l k8s-app=metrics-server
# 如果没有,先安装:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# 1. 创建 Deployment(带资源请求)
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: hpa-demo
spec:
replicas: 1
selector:
matchLabels:
app: hpa-demo
template:
metadata:
labels:
app: hpa-demo
spec:
containers:
- name: web
image: nginx:alpine
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
EOF

kubectl expose deploy hpa-demo --port=80

# 2. 创建 HPA
kubectl autoscale deployment hpa-demo --cpu=50% --min=1 --max=5

# 3. 查看 HPA
kubectl get hpa
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
# hpa-demo Deployment/hpa-demo 0%/50% 1 5 1

kubectl describe hpa hpa-demo

# 4. 生成负载
kubectl run load-generator --image=busybox:1.36 --rm -it --restart=Never -- \
sh -c 'while true; do wget -q -O- http://hpa-demo; done'

# 在另一个终端观察 HPA
kubectl get hpa hpa-demo -w
# 观察 TARGETS 上升,REPLICAS 自动增加

# 5. Ctrl+C 停止负载,观察缩容
kubectl get hpa hpa-demo -w
# REPLICAS 逐渐减少回 1

# 6. 清理
kubectl delete hpa hpa-demo
kubectl delete deploy hpa-demo
kubectl delete svc hpa-demo

🐛 排错练习(30 分钟)

场景 1:HPA 不工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 排查清单
# 1. metrics-server 是否运行?
kubectl get pods -n kube-system -l k8s-app=metrics-server

# 2. 能否获取到指标?
kubectl top pods
kubectl top nodes

# 3. Deployment 是否设置了 resources.requests?
kubectl get deploy <name> -o yaml | grep -A5 resources
# HPA 基于百分比需要 requests 做基准

# 4. HPA 状态
kubectl describe hpa <name>
# 看 Events 区域的错误信息

场景 2:Pod 被驱逐(Evicted)

1
2
3
4
5
6
# 查看被驱逐的 Pod
kubectl get pods --field-selector=status.phase=Failed

# 查看驱逐原因
kubectl describe pod <evicted-pod>
# The node was low on resource: memory.

🏆 赛题模拟(40 分钟)

⚠️ 严格限时 35 分钟

题目:资源管理与自动伸缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
【操作要求】

1. 创建命名空间 resource-exam

2. 创建 LimitRange:
- 容器默认 requests:cpu 100m, memory 128Mi
- 容器默认 limits:cpu 500m, memory 256Mi
- max cpu: 1, max memory: 512Mi

3. 创建 ResourceQuota:
- 最多 10 个 Pod
- 总 requests.cpu 不超过 2 核
- 总 requests.memory 不超过 2Gi

4. 创建 Deployment web-api(2 副本,nginx:alpine):
- requests: cpu 100m, memory 128Mi
- limits: cpu 200m, memory 256Mi
- QoS 应为 Guaranteed?(检查:不是,因为 requests ≠ limits)
- 修改使其达到 Guaranteed

5. 配置 HPA:
- 基于 CPU,目标 60%
- min 2, max 8

6. 验证:
- kubectl describe quota -n resource-exam
- kubectl get hpa
- kubectl get pod 确认 QoS 等级

【评分标准】
- LimitRange 正确(15 分)
- ResourceQuota 正确(15 分)
- QoS Guaranteed 实现(20 分)
- HPA 配置正确(20 分)
- 配额验证(15 分)
- 整体正确性(15 分)

📋 命令速查

命令 功能 注解
kubectl set resources deploy/<name> -c=<container> --limits=cpu=200m,memory=256Mi --requests=cpu=100m,memory=128Mi 设置容器资源限制 触发滚动更新重建 Pod
kubectl describe pod <pod> | grep -A 5 "Requests|Limits" 查看 Pod 资源配置 确认 requests/limits 是否生效
kubectl describe node <node> | grep -A 5 "Allocated" 查看节点已分配资源 CPU/Memory 分配比例,判断节点是否过载
kubectl top pods -A --sort-by=cpu 按 CPU 排序 Pod 用量 找出 CPU 消耗最高的 Pod
kubectl top pods -A --sort-by=memory 按内存排序 Pod 用量 找出内存消耗最高的 Pod
kubectl get pod <pod> -o jsonpath='{.status.qosClass}' 查看 Pod QoS 等级 返回 Guaranteed/Burstable/BestEffort
kubectl get limitrange -A 列出所有 LimitRange 命名空间级默认资源限制
kubectl describe limitrange <name> -n <ns> 查看 LimitRange 详情 确认默认 requests/limits 和最大/最小限制
kubectl get resourcequota -A 列出所有 ResourceQuota 命名空间级资源配额
kubectl describe resourcequota <name> -n <ns> 查看 ResourceQuota 详情 已用 vs 硬限制对比
kubectl get hpa 列出水平自动扩缩器 查看当前/目标 CPU/内存使用率
kubectl describe hpa <name> HPA 详情 Metrics 段显示当前值 vs 目标值,Events 显示扩缩事件
kubectl autoscale deploy <name> --min=2 --max=10 --cpu=80% 创建 HPA 基于 CPU 使用率自动扩缩副本数
kubectl delete hpa <name> 删除 HPA 删除后副本数不再自动调整
kubectl get vpa 列出垂直自动扩缩器 需安装 VPA;自动调整 Pod requests/limits
kubectl get events --field-selector=reason=FailedScheduling 查看调度失败事件 资源不足导致 Pending 时的排错入口
kubectl describe pod <pod> | grep -A 10 Events 查看 Pod 调度事件 确认是否因资源不足 Pending

📚 参考来源

来源 链接 / 说明
Kubernetes 官方:资源管理 https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
Kubernetes 官方:QoS 等级 https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/
Kubernetes 官方:LimitRange https://kubernetes.io/docs/concepts/policy/limit-range/
Kubernetes 官方:ResourceQuota https://kubernetes.io/docs/concepts/policy/resource-quotas/
Kubernetes 官方:HPA https://kubernetes.io/docs/concepts/workloads/autoscaling/horizontal-pod-autoscale/
Kubernetes 官方:VPA https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler

📘 Day 15:存储综合实战

🎯 今日目标

  • 为有状态应用配置完整存储方案
  • ConfigMap → Secret → PVC 联动配置
  • 排查存储相关故障

🧠 理论精讲(10 分钟)

存储方案决策树

1
2
3
4
5
6
7
应用需要存储配置?
├── 非敏感配置 → ConfigMap
├── 敏感配置(密码/证书)→ Secret
└── 持久数据?
├── 临时数据(Pod 删了就丢)→ emptyDir
├── 节点级数据(日志)→ hostPath
└── 持久数据(数据库)→ PVC(+StorageClass 动态供给)

🔧 动手实操(150 分钟)

练习 15.1:WordPress 风格有状态应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
kubectl create ns wp-demo

# 1. 配置管理:ConfigMap
kubectl create configmap wp-config -n wp-demo \
--from-literal=WORDPRESS_DB_HOST=mysql-svc \
--from-literal=WORDPRESS_DB_USER=wp_user \
--from-literal=WORDPRESS_DB_NAME=wordpress \
--from-literal=WORDPRESS_DEBUG=false

# 2. 密钥管理:Secret
kubectl create secret generic wp-secrets -n wp-demo \
--from-literal=WORDPRESS_DB_PASSWORD='W0rdPr3ss!' \
--from-literal=MYSQL_ROOT_PASSWORD='R00tP@ss!'

# 3. 数据库 StatefulSet + PVC
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: mysql-svc
namespace: wp-demo
spec:
clusterIP: None
selector:
app: mysql
ports:
- port: 3306
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: wp-demo
spec:
serviceName: mysql-svc
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
envFrom:
- secretRef:
name: wp-secrets
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi
EOF

# 4. WordPress Deployment
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
namespace: wp-demo
spec:
replicas: 1
selector:
matchLabels:
app: wordpress
template:
metadata:
labels:
app: wordpress
spec:
containers:
- name: wordpress
image: wordpress:6-apache
envFrom:
- configMapRef:
name: wp-config
env:
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: wp-secrets
key: WORDPRESS_DB_PASSWORD
ports:
- containerPort: 80
volumeMounts:
- name: wp-content
mountPath: /var/www/html/wp-content
volumes:
- name: wp-content
persistentVolumeClaim:
claimName: wp-content-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wp-content-pvc
namespace: wp-demo
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
EOF

# 5. 服务暴露
kubectl expose deploy wordpress -n wp-demo --port=80
kubectl get all -n wp-demo

练习 15.2:存储故障排查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 故障 1:PVC 绑定不上
kubectl get pvc -n wp-demo
# 如果 Pending,排查:
kubectl describe pvc wp-content-pvc -n wp-demo
# 看 Events → 是否有可用的 PV 或 StorageClass?

# 故障 2:Pod 无法挂载 Volume
kubectl get pod -n wp-demo
kubectl describe pod <pod-name> -n wp-demo | grep -A5 Events
# 常见错误:mount failed、permission denied

# 故障 3:ConfigMap 环境变量未注入
kubectl exec <pod> -n wp-demo -- env | grep WORDPRESS
# 如果没有 → 检查 envFrom 配置

# 故障 4:Secret 值不正确
kubectl get secret wp-secrets -n wp-demo -o jsonpath='{.data.WORDPRESS_DB_PASSWORD}' | base64 -d
# 验证解码后的值

🏆 赛题模拟(40 分钟)

⚠️ 严格限时 40 分钟

题目:完整应用配置与存储方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
【操作要求】创建命名空间 cms-app

1. ConfigMap cms-config:
- SITE_NAME="My CMS"
- DB_HOST=cms-db-svc
- CACHE_TTL=3600

2. Secret cms-secrets:
- DB_PASS=db-secret-789
- ADMIN_PASS=admin-secret-456
- JWT_SECRET=jwt-key-012

3. StatefulSet cms-db(2 副本,mysql:8.0):
- 使用 cms-secrets 注入数据库密码
- volumeClaimTemplates 创建 2Gi PVC
- Headless Service cms-db-svc

4. Deployment cms-web(3 副本,nginx:alpine):
- 用 envFrom 注入 cms-config
- 用 valueFrom 注入 DB_PASS(命名 DATABASE_PASSWORD)
- 挂载 PVC cms-web-data(1Gi)到 /usr/share/nginx/html/uploads
- livenessProbe + readinessProbe

5. 验证:
- 所有 PVC 绑定成功
- Pod 环境变量包含 SITE_NAME、DB_HOST、DATABASE_PASSWORD
- cms-db-0 和 cms-db-1 有独立 PVC
- 向 cms-web 的 PVC 写入测试文件

【评分标准】
- ConfigMap/Secret 创建正确(15 分)
- StatefulSet + PVC 正确(30 分)
- Deployment 配置注入正确(30 分)
- PVC 独立验证(15 分)
- 验证全面(10 分)

🧹 环境清理

1
kubectl delete ns wp-demo cms-app

📋 命令速查

命令 功能 注解
kubectl get pv,pvc,sc 存储资源一览 同时查看 PV/PVC/SC 的绑定和供给关系
kubectl get pvc -A --field-selector=status.phase=Pending 查找所有未绑定的 PVC Pending 意味着没有匹配的 PV 或 SC 供给失败
kubectl get events --field-selector=reason=ProvisioningFailed 查看供给失败事件 动态供给失败时的排错入口
kubectl exec <pod> -- df -h | grep /mnt 验证存储挂载容量 确认 PV 正确挂载且容量匹配
kubectl exec <pod> -- cat /proc/mounts | grep /mnt 查看挂载详情 文件系统类型、挂载选项
kubectl exec <pod> -- touch /mnt/data/write-test 验证存储可写 ReadWriteOnce 模式下测试写权限
kubectl exec <pod> -- rm /mnt/data/write-test 验证存储可删除 ReadWriteMany 多 Pod 同时操作验证
kubectl diff -f manifest.yaml 预览修改差异 综合实战中修改 YAML 后先 diff 确认变更
kubectl apply --server-side -f manifest.yaml 服务端 Apply 处理大文件或复杂 CRD 时的备选方案

📚 参考来源

来源 链接 / 说明
Kubernetes 官方:存储最佳实践 https://kubernetes.io/docs/concepts/storage/
Kubernetes 官方:调试 PVC https://kubernetes.io/docs/tasks/debug/debug-application/
Kubernetes 官方:StatefulSet 与存储 https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#using-statefulsets
Kubernetes 存储 SIG https://github.com/kubernetes/community/tree/master/sig-storage

📘 Day 13:Volume 与 PV/PVC

🎯 今日目标

  • 会用 emptyDir 和 hostPath Volume
  • 理解 PV/PVC 的绑定流程
  • 能创建静态 PV 并供 Pod 使用
  • 掌握 AccessMode(RWO/ROX/RWX)
  • 掌握 ReclaimPolicy(Retain/Recycle/Delete)

🧠 理论精讲(30 分钟)

存储层次

1
2
3
Pod ──→ PVC(声明:我要 5Gi RWO 存储)──→ PV(物理存储:这里是 5Gi NFS)

└──→ 实际存储后端(NFS/本地盘/云盘)
概念 说明 类比
Volume Pod 级别存储定义 直接在 Pod 里写磁盘配置
PV 集群级别存储资源 管理员准备的存储池
PVC 用户存储请求 申请单

PV 关键属性

属性 可选值 说明
accessModes RWO / ROX / RWX 读写模式
ReclaimPolicy Retain / Recycle / Delete PV 释放后行为
volumeMode Filesystem / Block 文件系统还是块设备

AccessMode 速查

缩写 全称 含义
RWO ReadWriteOnce 单节点读写
ROX ReadOnlyMany 多节点只读
RWX ReadWriteMany 多节点读写

PV 生命周期

1
2
Provisioning(创建)→ Available(可用)→ Bound(已绑定)→ Released(释放)
→ Retained(保留)

🔧 动手实操(120 分钟)

练习 13.1:emptyDir Volume

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# emptyDir:Pod 生命周期内存在,Pod 删除即销毁
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: emptydir-demo
spec:
containers:
- name: writer
image: busybox:1.36
command:
- sh
- -c
- |
i=0
while true; do
echo "Data-$i" >> /data/shared.log
i=$((i+1))
sleep 2
done
volumeMounts:
- name: shared-data
mountPath: /data

- name: reader
image: busybox:1.36
command:
- sh
- -c
- tail -f /data/shared.log
volumeMounts:
- name: shared-data
mountPath: /data

volumes:
- name: shared-data
emptyDir: {}
EOF

# 验证容器间共享
kubectl logs emptydir-demo -c reader --tail=10
# 看到 writer 写入的内容

# 清理
kubectl delete pod emptydir-demo

练习 13.2:hostPath Volume

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# hostPath:挂载节点上的目录
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: hostpath-demo
spec:
containers:
- name: app
image: busybox:1.36
command:
- sh
- -c
- |
echo "Written at \$(date)" >> /host-data/hostpath-test.log
cat /host-data/hostpath-test.log
sleep 3600
volumeMounts:
- name: host-vol
mountPath: /host-data
volumes:
- name: host-vol
hostPath:
path: /tmp/k8s-hostpath
type: DirectoryOrCreate
EOF

# 获取 Pod 所在节点
NODE=$(kubectl get pod hostpath-demo -o jsonpath='{.spec.nodeName}')
echo "Pod is on: $NODE"

# SSH 到该节点验证文件
# ssh $NODE cat /tmp/k8s-hostpath/hostpath-test.log

# 删除 Pod 后,节点上的文件仍在
kubectl delete pod hostpath-demo
# ssh $NODE cat /tmp/k8s-hostpath/hostpath-test.log # 文件还在

练习 13.3:静态 PV 创建与绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# 1. 先在节点上创建实际目录
# 在所有 worker 节点上执行:
# sudo mkdir -p /data/pv-volumes/pv01

# 2. 创建 PersistentVolume
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-manual-01
spec:
capacity:
storage: 1Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
hostPath:
path: /data/pv-volumes/pv01
EOF

# 3. 查看 PV 状态
kubectl get pv pv-manual-01
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
# pv-manual-01 1Gi RWO Retain Available

# 4. 创建 PersistentVolumeClaim
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-manual-01
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Mi
EOF

# 5. 观察绑定
kubectl get pvc pvc-manual-01
# STATUS: Bound

kubectl get pv pv-manual-01
# STATUS: Bound, CLAIM: default/pvc-manual-01

# 6. 在 Pod 中使用 PVC
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: pv-pod
spec:
containers:
- name: app
image: busybox:1.36
command:
- sh
- -c
- |
echo "Persistent data!" >> /data/persistent.txt
cat /data/persistent.txt
sleep 3600
volumeMounts:
- name: persistent-storage
mountPath: /data
volumes:
- name: persistent-storage
persistentVolumeClaim:
claimName: pvc-manual-01
EOF

# 7. 验证持久性
kubectl exec pv-pod -- cat /data/persistent.txt
# 输出:Persistent data!

# 8. 删除 Pod 和 PVC
kubectl delete pod pv-pod
kubectl delete pvc pvc-manual-01

# 9. 查看 PV 状态
kubectl get pv pv-manual-01
# STATUS: Released(因为 ReclaimPolicy=Retain,不会自动删除)

# 10. 清理 PV
kubectl delete pv pv-manual-01

练习 13.4:多 PV 自动匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 创建 3 个不同大小的 PV
for i in 1 2 3; do
mkdir -p /data/pv-volumes/pv0${i}
done

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-small
spec:
capacity:
storage: 100Mi
accessModes: ["ReadWriteOnce"]
hostPath:
path: /data/pv-volumes/pv01
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-medium
spec:
capacity:
storage: 500Mi
accessModes: ["ReadWriteOnce"]
hostPath:
path: /data/pv-volumes/pv02
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-large
spec:
capacity:
storage: 2Gi
accessModes: ["ReadWriteOnce"]
hostPath:
path: /data/pv-volumes/pv03
EOF

# 创建 PVC 请求 300Mi
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-auto
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 300Mi
EOF

# 观察匹配结果(应绑定到 pv-medium,500Mi)
kubectl get pvc pvc-auto
kubectl get pv | grep pvc-auto
# 应绑定到 pv-medium(最小能匹配的 PV)

# 清理
kubectl delete pvc pvc-auto
kubectl delete pv pv-small pv-medium pv-large

🐛 排错练习(30 分钟)

场景:PVC 一直 Pending

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 排查清单
# 1. 有没有 PV?
kubectl get pv
# 如果没有 Available PV → 需要创建 PV 或配置动态供给

# 2. PV 的 accessModes 是否匹配 PVC?
kubectl get pv <pv-name> -o yaml | grep -A3 accessModes
kubectl get pvc <pvc-name> -o yaml | grep -A3 accessModes

# 3. PV 的容量是否 >= PVC?
kubectl get pv <pv-name> -o jsonpath='{.spec.capacity.storage}'
kubectl get pvc <pvc-name> -o jsonpath='{.spec.resources.requests.storage}'

# 4. PVC 的 storageClassName 是否为空字符串?
kubectl get pvc <pvc-name> -o yaml | grep storageClassName
# 如果 PVC 指定了 storageClassName,只能匹配同名的 PV

🏆 赛题模拟(40 分钟)

⚠️ 严格限时 35 分钟

题目:静态 PV 存储管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
【操作要求】

1. 创建 3 个 hostPath 类型的 PV:
- pv-1g:容量 1Gi,RWO,路径 /data/pv/vol1
- pv-5g:容量 5Gi,RWO,路径 /data/pv/vol2
- pv-10g:容量 10Gi,RWO,路径 /data/pv/vol3

2. 创建 2 个 PVC:
- pvc-db:请求 4Gi,RWO
- pvc-logs:请求 8Gi,RWO

3. 验证 pvc-db 绑定到 pv-5g,pvc-logs 绑定到 pv-10g

4. 创建一个 Pod 同时使用两个 PVC:
- db 容器挂载 pvc-db 到 /var/lib/db
- app 容器挂载 pvc-logs 到 /var/log/app
- 写入测试数据到各自目录

5. 删除 Pod 和 PVC,检查 PV 状态变化

6. 手动清理 PV,释放存储

【评分标准】
- PV 创建正确(20 分)
- PVC 绑定结果正确(25 分)
- Pod 双 Volume 挂载正确(25 分)
- 数据持久性验证(15 分)
- PV 生命周期理解(15 分)

📋 命令速查

命令 功能 注解
kubectl get pv 列出 PersistentVolume STATUS: Available (空闲)/Bound (已绑定)/Released (PVC 已删但未回收)/Failed
kubectl get pv -o wide PV + 容量/访问模式/回收策略/状态/声明 快速审计存储资源
kubectl describe pv <name> PV 详细信息 查看 Reclaim Policy、AccessModes、底层存储类型(hostPath/NFS/CSI 等)
kubectl get pvc 列出 PersistentVolumeClaim STATUS: Pending (无匹配 PV)/Bound (已绑定)
kubectl get pvc -A 所有命名空间的 PVC 跨 NS 视角排查存储资源使用
kubectl describe pvc <name> PVC 详细信息 Events 段显示绑定过程;Pending 时查看失败原因
kubectl get pv,pvc 同时查看 PV/PVC 逗号分隔查看绑定关系
kubectl patch pv <name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' 修改 PV 回收策略 防止 PVC 删除时 PV 被自动回收(Retain/Recycle/Delete)
kubectl delete pvc <name> 删除 PVC PV 的 Reclaim Policy 决定 PV 后续状态
kubectl get pods -o wide | grep <pod> 查看 Pod 所在节点 PV hostPath 必须确认 Pod 调度到了正确节点
kubectl exec <pod> -- df -h 查看 Pod 内挂载存储空间 验证 PV 是否挂载成功及容量是否正确
kubectl exec <pod> -- ls -la /mnt/data 查看挂载目录内容 验证文件是否存在和权限
kubectl exec <pod> -- touch /mnt/data/test && echo "ok" > /mnt/data/test 验证存储读写 确认 PV 不是只读挂载
kubectl get sc 列出 StorageClass 动态供给与 PV/PVC 配合使用

📚 参考来源

来源 链接 / 说明
Kubernetes 官方:PersistentVolume https://kubernetes.io/docs/concepts/storage/persistent-volumes/
Kubernetes 官方:卷 https://kubernetes.io/docs/concepts/storage/volumes/
Kubernetes 官方:配置 Pod 使用 PV https://kubernetes.io/docs/concepts/storage/persistent-volumes/
Kubernetes 官方:访问模式 https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes
Kubernetes 官方:回收策略 https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reclaim-policy

📘 Day 12:ConfigMap 与 Secret

🎯 今日目标

  • 会用 4 种方式创建 ConfigMap
  • 会用 envFrom、valueFrom、volume 三种方式消费配置
  • 能创建 Opaque、TLS、dockerconfigjson 三种 Secret
  • 理解 ConfigMap 挂载为 Volume 时的热更新
  • 掌握 immutable ConfigMap/Secret

🧠 理论精讲(30 分钟)

ConfigMap vs Secret

特性 ConfigMap Secret
用途 非敏感配置 敏感数据(密码、证书)
存储 明文(etcd 中) Base64 编码(可配合加密)
大小限制 1 MiB 1 MiB
热更新 ✅(Volume 挂载模式) ✅(Volume 挂载模式)

消费配置的三种方式

1
2
3
1. envFrom    — 整个 ConfigMap/Secret 作为环境变量
2. valueFrom — 从 ConfigMap/Secret 读取单个 key 作为环境变量
3. volume — 将 ConfigMap/Secret 挂载为文件

Secret 类型

类型 用途 数据 key 要求
Opaque 通用密钥(默认) 任意
kubernetes.io/tls TLS 证书 tls.crttls.key
kubernetes.io/dockerconfigjson 镜像拉取凭证 .dockerconfigjson
kubernetes.io/basic-auth 基础认证 usernamepassword

🔧 动手实操(120 分钟)

练习 12.1:ConfigMap 四种创建方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 方式 1:--from-literal(字面值)
kubectl create configmap app-config \
--from-literal=APP_ENV=production \
--from-literal=APP_DEBUG=false \
--from-literal=LOG_LEVEL=info

# 查看内容
kubectl get cm app-config -o yaml

# 方式 2:--from-file(从文件)
mkdir -p /tmp/cm-demo
cat > /tmp/cm-demo/nginx.conf <<EOF
server {
listen 80;
server_name localhost;
}
EOF
cat > /tmp/cm-demo/app.properties <<EOF
app.name=myapp
app.version=1.0.0
EOF

kubectl create configmap file-config --from-file=/tmp/cm-demo/

# 查看
kubectl describe cm file-config
# 两个 key:nginx.conf 和 app.properties

# 方式 3:--from-file 指定 key 名
kubectl create configmap nginx-conf --from-file=nginx.conf=/tmp/cm-demo/nginx.conf

# 方式 4:--from-env-file(从环境变量文件)
cat > /tmp/cm-demo/env.list <<EOF
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
EOF

kubectl create configmap db-env --from-env-file=/tmp/cm-demo/env.list

# 查看所有
kubectl get cm

练习 12.2:ConfigMap 三种使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# === 方式 1:envFrom(整批注入) ===
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: cm-envfrom
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "echo DB_HOST=\$DB_HOST DB_PORT=\$DB_PORT; sleep 3600"]
envFrom:
- configMapRef:
name: db-env
EOF

kubectl logs cm-envfrom
# 输出:DB_HOST=localhost DB_PORT=5432

# === 方式 2:valueFrom(单个引用) ===
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: cm-valuefrom
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "echo ENV=\$APP_ENV DEBUG=\$APP_DEBUG; sleep 3600"]
env:
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: app-config
key: APP_ENV
- name: APP_DEBUG
valueFrom:
configMapKeyRef:
name: app-config
key: APP_DEBUG
EOF

kubectl logs cm-valuefrom
# 输出:ENV=production DEBUG=false

# === 方式 3:Volume 挂载(文件方式) ===
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: cm-volume
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "cat /etc/config/nginx.conf; sleep 3600"]
volumeMounts:
- name: config
mountPath: /etc/config
volumes:
- name: config
configMap:
name: nginx-conf
EOF

kubectl logs cm-volume
# 输出 nginx.conf 内容

# 查看挂载的文件
kubectl exec cm-volume -- ls -la /etc/config/
kubectl exec cm-volume -- cat /etc/config/nginx.conf

# 清理
kubectl delete pod cm-envfrom cm-valuefrom cm-volume

练习 12.3:ConfigMap 热更新验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 1. 创建 ConfigMap 并以 Volume 方式挂载
kubectl create configmap dynamic-config --from-literal=message="version-1"

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: hot-reload
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "while true; do cat /etc/config/message; sleep 2; done"]
volumeMounts:
- name: cfg
mountPath: /etc/config
volumes:
- name: cfg
configMap:
name: dynamic-config
EOF

# 2. 观察当前值
kubectl logs hot-reload --tail=5
# 输出:version-1

# 3. 更新 ConfigMap
kubectl create configmap dynamic-config --from-literal=message="version-2" \
--dry-run=client -o yaml | kubectl apply -f -

# 4. 等待约 60-90 秒后查看(kubelet 同步周期)
sleep 60
kubectl logs hot-reload --tail=5
# 输出变为:version-2

# ⚠️ 注意:
# - Volume 挂载方式:热更新生效(有延迟,约 60-90 秒)
# - envFrom/valueFrom 方式:不会热更新!需要重建 Pod

# 5. 清理
kubectl delete pod hot-reload
kubectl delete cm dynamic-config

练习 12.4:Secret 实战

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 1. 创建 Opaque Secret
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password='P@ssw0rd!2024'

kubectl get secret db-credentials
kubectl describe secret db-credentials
# 不显示值,只显示 key 名

# 查看解码内容
kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d
echo

# 2. 在 Pod 中使用
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: secret-demo
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "echo USER=\$DB_USER PASS=\$DB_PASS; sleep 3600"]
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: db-credentials
key: password
EOF

kubectl logs secret-demo
# 输出:USER=admin PASS=P@ssw0rd!2024

# 3. 创建 TLS Secret
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /tmp/tls.key -out /tmp/tls.crt \
-subj "/CN=myapp.example.com"

kubectl create secret tls my-tls \
--key=/tmp/tls.key --cert=/tmp/tls.crt

kubectl describe secret my-tls
# Type: kubernetes.io/tls

# 4. 创建镜像拉取 Secret
kubectl create secret docker-registry my-registry \
--docker-server=registry.example.com \
--docker-username=myuser \
--docker-password=mypassword \
--docker-email=my@example.com

kubectl describe secret my-registry
# Type: kubernetes.io/dockerconfigjson

# 5. 清理
kubectl delete pod secret-demo
kubectl delete secret db-credentials my-tls my-registry
rm -f /tmp/tls.key /tmp/tls.crt

练习 12.5:Immutable ConfigMap/Secret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 不可变(immutable)的 ConfigMap 不能修改,只能删除重建
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: immutable-cm
immutable: true
data:
version: "1.0"
build-time: "$(date)"
EOF

# 尝试修改会报错
kubectl patch cm immutable-cm -p '{"data":{"version":"2.0"}}'
# Error: ConfigMap is immutable

# 清理
kubectl delete cm immutable-cm

🐛 排错练习(30 分钟)

场景 1:ConfigMap 未更新

1
2
3
4
5
6
7
8
9
10
11
12
13
# 问题:修改了 ConfigMap,但应用没有变化

# 排查:
# 1. 确认是哪种消费方式
kubectl get pod <pod> -o yaml | grep -A5 envFrom
# envFrom → 需要重建 Pod
# volume → 等待 kubelet 同步(~90s)

# 2. 如果是 envFrom,强制重建
kubectl rollout restart deploy/<name>

# 3. 如果是 volume,检查挂载点内容
kubectl exec <pod> -- cat <mount-path>/<key>

场景 2:Secret 未正确解码

1
2
3
4
5
6
# 问题:Pod 中的环境变量显示乱码或空值

# 排查:
kubectl get secret <name> -o jsonpath='{.data.<key>}' | base64 -d
# 如果解码后为空:创建 Secret 时输入有误
# 如果解码失败:不是合法的 base64(--from-literal 会自动编码)

🏆 赛题模拟(40 分钟)

⚠️ 严格限时 35 分钟

题目:应用配置管理综合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
【操作要求】

1. 创建 ConfigMap app-settings:
- APP_ENV=staging
- APP_LOG_LEVEL=debug
- APP_MAX_CONNECTIONS=100

2. 创建 Secret app-secrets:
- DB_PASSWORD=secret123
- API_KEY=sk-abcdef123456
- REDIS_PASSWORD=redispass

3. 创建 Deployment config-app(2 副本,nginx:alpine):
- 用 envFrom 注入 app-settings 的所有键
- 用 valueFrom 注入 DB_PASSWORD(命名为 DATABASE_PASSWORD)
- 将 app-settings 以 Volume 方式挂载到 /etc/app/config

4. 创建 TLS Secret app-tls(自签名证书,CN=secure.app.com)

5. 更新 app-settings 中的 APP_LOG_LEVEL=info,
验证 Volume 挂载的配置文件是否更新

6. 验证:
- kubectl exec 进入 Pod 查看环境变量
- 确认 /etc/app/config 下有配置文件的符号链接
- 确认 Secret 数据可正常读取

【评分标准】
- ConfigMap 创建正确(15 分)
- Secret 创建正确(15 分)
- Deployment 配置注入正确(35 分)
- TLS Secret 正确(10 分)
- 热更新验证(15 分)
- Immutable 属性(10 分)

📋 命令速查

命令 功能 注解
kubectl get cm 列出 ConfigMap short name: cm
kubectl get cm <name> -o yaml 查看 ConfigMap 完整内容 检查所有 key-value 数据
kubectl create cm <name> --from-file=<file> 从文件创建 ConfigMap key 为文件名,value 为文件内容
kubectl create cm <name> --from-file=key=<file> 从文件创建并指定 key key 可以不同于文件名
kubectl create cm <name> --from-literal=key=value 从字面值创建 ConfigMap 快速创建简单配置项,多个 --from-literal 可合并
kubectl create cm <name> --from-env-file=<file> 从 .env 文件创建 ConfigMap 每行 KEY=VALUE 格式
kubectl get secret 列出 Secret 默认只显示名称和类型(不显示值)
kubectl get secret <name> -o yaml 查看 Secret 完整 YAML data 字段为 base64 编码
kubectl get secret <name> -o jsonpath='{.data.<key>}' | base64 -d 解码 Secret 某个 key jsonpath 提取 + base64 解码,常用组合
kubectl get secret <name> -o jsonpath='{.data}' | jq 'map_values(@base64d)' 解码所有 Secret 字段 jq 一次性解码所有 base64 值
kubectl create secret generic <name> --from-literal=key=value 创建 Opaque Secret 值自动 base64 编码存储(注意:仅仅是编码非加密)
kubectl create secret generic <name> --from-file=<file> 从文件创建 Secret 文件内容自动 base64
kubectl create secret docker-registry <name> --docker-server=<url> --docker-username=<user> --docker-password=<pass> 创建镜像拉取密钥 私有镜像仓库认证
kubectl create secret tls <name> --cert=cert.pem --key=key.pem 创建 TLS Secret Ingress HTTPS 专用
kubectl describe cm <name> ConfigMap 概要 显示 key 列表但不显示 value
kubectl edit cm <name> 在线编辑 ConfigMap 立即生效,挂载到 Pod 中的文件会延迟更新(取决于 kubelet 同步周期)
kubectl set env deploy/<name> --from=cm/<cm-name> 将 ConfigMap 注入为环境变量 更新 Deployment 环境变量,触发滚动更新
echo -n "secret-value" | base64 base64 编码 手动编码 Secret 值,填入 YAML 中的 data 字段
kubectl exec <pod> -- cat /etc/config/<key> 验证 ConfigMap 挂载内容 确认文件内容和路径是否正确

📚 参考来源

来源 链接 / 说明
Kubernetes 官方:ConfigMap https://kubernetes.io/docs/concepts/configuration/configmap/
Kubernetes 官方:Secret https://kubernetes.io/docs/concepts/configuration/secret/
Kubernetes 官方:配置 Pod 使用 ConfigMap https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/
Kubernetes 官方:Secret 管理最佳实践 https://kubernetes.io/docs/concepts/configuration/secret/#best-practices
Kubernetes 官方:不可变 ConfigMap 与 Secret https://kubernetes.io/docs/concepts/configuration/configmap/#configmap-immutable

📘 Day 11:网络综合实战

🎯 今日目标

  • 在一个场景中综合使用 Service + Ingress + NetworkPolicy
  • 排查跨命名空间的网络故障
  • 构建生产级网络架构

🧠 理论精讲(10 分钟)

网络排错方法论

1
2
3
4
5
6
1. 确认 Pod 是否 Running?
2. 确认 Pod 是否有 IP?
3. 确认 Service 是否有 Endpoint?
4. 确认 DNS 是否解析正常?
5. 确认 NetworkPolicy 是否阻止?
6. 从近到远逐层测试

常用网络调试命令

1
2
3
4
5
6
7
8
9
10
11
# DNS 测试
nslookup <service-name>

# TCP 连通性测试
nc -zv <host> <port>

# HTTP 测试
wget -q -O- http://<host>:<port>

# 网络包抓取(需额外工具)
tcpdump -i any port <port>

🔧 动手实操(150 分钟)

练习 11.1:三层网络架构构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 创建独立命名空间
kubectl create ns net-lab

# === 第 1 层:Ingress 入口层 ===
kubectl create deploy frontend -n net-lab --image=nginx:alpine --replicas=2
kubectl expose deploy frontend -n net-lab --port=80 --name=frontend-svc

# === 第 2 层:API 中间层 ===
kubectl create deploy api -n net-lab --image=httpd:alpine --replicas=3
kubectl expose deploy api -n net-lab --port=80 --name=api-svc

# === 第 3 层:数据层 ===
kubectl run db -n net-lab --image=redis:7-alpine --port=6379
kubectl expose pod db -n net-lab --port=6379 --name=db-svc

# === Ingress 规则 ===
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: net-lab-ingress
namespace: net-lab
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: lab.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: frontend-svc
port:
number: 80
EOF

# 验证:curl -H "Host: lab.example.com" <NodeIP>:30080/

练习 11.2:跨命名空间网络通信验证

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 从 net-lab 中的 Pod 访问其他命名空间的服务
kubectl run jumpbox -n net-lab --image=busybox:1.36 -- sleep 3600

# 测试 DNS 解析
kubectl exec jumpbox -n net-lab -- nslookup db-svc.net-lab.svc.cluster.local

# 测试 db
kubectl exec jumpbox -n net-lab -- nc -zv db-svc.net-lab 6379

# 2. 从 default 命名空间访问 net-lab
kubectl run cross-test --image=busybox:1.36 --rm -it --restart=Never -- \
wget -q -O- http://api-svc.net-lab

练习 11.3:网络故障排查演练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 故障 1:Service selector 不匹配
# 创建一个选错标签的 Service
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: broken-svc
namespace: net-lab
spec:
selector:
app: non-existent # 故意选一个不存在的标签!
ports:
- port: 80
EOF

kubectl get endpoints broken-svc -n net-lab
# ENDPOINTS: <none>(问题就在这里!)

kubectl describe svc broken-svc -n net-lab

# 修复:修改 selector
kubectl patch svc broken-svc -n net-lab --type=merge -p '{"spec":{"selector":{"app":"api"}}}'
kubectl get endpoints broken-svc -n net-lab
# 现在有 Endpoint 了

# 故障 2:NetworkPolicy 阻断排查
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: isolate-db
namespace: net-lab
spec:
podSelector:
matchLabels:
run: db
policyTypes:
- Ingress
# 空 Ingress = 拒绝所有
EOF

# 测试 db 连接
kubectl exec jumpbox -n net-lab -- nc -zv db-svc 6379 -w 5
# 超时!(被 NetworkPolicy 阻断)

# 排查
kubectl get networkpolicy -n net-lab
kubectl describe networkpolicy isolate-db -n net-lab

# 修复
kubectl delete networkpolicy isolate-db -n net-lab

# 故障 3:DNS 无法解析
# 检查 CoreDNS
kubectl get pods -n kube-system -l k8s-app=kube-dns
# 如果 Pod 不 Running → 检查日志

🏆 赛题模拟(40 分钟)

⚠️ 严格限时 40 分钟

题目:生产级三层网络架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
【场景】搭建一个博客系统的网络层,包含前端、API、数据库三个层次。

【操作要求】

1. 创建命名空间 blog-system

2. 部署三层应用:
a. 前端层:Deployment blog-frontend(nginx:alpine,2 副本)
ClusterIP Service frontend-svc
b. API 层:Deployment blog-api(httpd:alpine,3 副本)
ClusterIP Service api-svc
c. 数据库层:Pod blog-db(redis:7-alpine)
ClusterIP Service db-svc

3. 配置 Ingress:
- 对外暴露 30080,域名 blog.example.com
- / → frontend-svc
- /api → api-svc
- 配置 rewrite-target: /

4. 配置 NetworkPolicy:
a. 默认拒绝所有入站流量
b. frontend-svc → api-svc:80
c. api-svc → db-svc:6379
d. Ingress Controller(ingress-nginx 命名空间)→ frontend-svc:80
e. 所有 Pod → CoreDNS UDP 53

5. 验证:
- 外部可访问 http://<NodeIP>:30080/ 和 /api
- 前端能访问 API
- API 能访问 DB
- 前端**不能**直接访问 DB
- DB **不能**访问外网

【评分标准】
- 三层应用正确部署(20 分)
- Ingress 路由正确(20 分)
- NetworkPolicy 隔离正确(40 分)
- 验证全面(20 分)

📋 命令速查

命令 功能 注解
kubectl get svc,endpoints,ingress,networkpolicy 网络资源一览 逗号分隔多资源类型,快速审计网络配置
kubectl run net-test --image=nicolaka/netshoot --rm -it -- /bin/bash 网络调试 Pod netshoot 包含 curl/wget/nslookup/dig/nc/iperf 等全套工具
kubectl exec <pod> -- curl -s -o /dev/null -w "%{http_code}" http://<svc>:<port> 测试 HTTP 状态码 仅输出 HTTP code,脚本化健康检查
kubectl exec <pod> -- dig +short <svc>.<ns>.svc.cluster.local DNS 解析(dig 方式) 比 nslookup 更详细,可分析 DNS 查询链
kubectl exec <pod> -- traceroute <target-ip> 追踪 Pod 网络路径 理解数据包从 Pod 到目标的跳数
kubectl exec <pod> -- iperf3 -c <target-ip> 网络带宽测试 性能基准,评估 CNI 插件吞吐
kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,NODE:.spec.nodeName 自定义列查看 Pod 信息 同时显示 Pod 名、IP、所在节点
kubectl label ns <ns> pod-security.kubernetes.io/enforce=restricted 设置命名空间 Pod 安全等级 综合实战中验证安全策略与网络策略协同
kubectl auth can-i create networkpolicies --as=system:serviceaccount:<ns>:<sa> 验证 SA 是否有创建 NetworkPolicy 权限 RBAC + NetworkPolicy 联合排错

📚 参考来源

来源 链接 / 说明
Kubernetes 官方:服务、负载均衡与网络 https://kubernetes.io/docs/concepts/services-networking/
Kubernetes 官方:网络策略演练 https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy/
netshoot 网络调试镜像 https://github.com/nicolaka/netshoot
Calico 网络策略示例 https://docs.tigera.io/calico/latest/network-policy/get-started/
Kubernetes 网络模型 https://kubernetes.io/docs/concepts/services-networking/#the-kubernetes-network-model

📘 Day 10:网络策略与 CNI 原理

🎯 今日目标

  • 理解 Calico CNI 的基本原理
  • 能创建 NetworkPolicy 实现 Pod 间访问控制
  • 掌握 namespaceSelector 和 podSelector
  • 能配置 ingress 和 egress 规则
  • 能通过 NetworkPolicy 实现零信任网络

🧠 理论精讲(30 分钟)

CNI 工作流程

1
2
3
4
5
1. kubelet 创建 Pod → 
2. 调用 CNI 插件 →
3. CNI 分配 IP、创建 veth pair →
4. 配置路由规则 →
5. Pod 获得网络

Calico 数据路径

1
Pod-A (veth) ←→ Host-A (cali-xxx) ←→ BGP/IPIP ←→ Host-B (cali-yyy) ←→ Pod-B (veth)

NetworkPolicy 核心概念

1
2
3
4
5
6
7
8
9
10
# 默认规则:所有流量允许(无 NetworkPolicy 时)
# 一旦创建 NetworkPolicy,默认拒绝所有未明确允许的流量

spec:
podSelector: {} # 选择受影响的 Pod
policyTypes: # 规则方向
- Ingress
- Egress
ingress: [...] # 入站规则
egress: [...] # 出站规则

选择器组合

选择器 作用域
podSelector 选 Pod
namespaceSelector 选命名空间
ipBlock 选 IP CIDR

🔧 动手实操(120 分钟)

练习 10.1:Deny-All 策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 1. 创建测试命名空间和 Pod
kubectl create ns netpol-test

kubectl run web --image=nginx:alpine -n netpol-test --port=80
kubectl expose pod web -n netpol-test --port=80

kubectl run client --image=busybox:1.36 -n netpol-test -- sleep 3600

# 2. 验证默认情况下可访问
kubectl exec client -n netpol-test -- wget -q -O- http://web.netpol-test
# 正常返回

# 3. 创建 Deny-All 策略
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
namespace: netpol-test
spec:
podSelector: {} # 选择所有 Pod
policyTypes:
- Ingress
- Egress
# 空的 ingress/egress = 不允许任何流量
EOF

# 4. 再次尝试访问
kubectl exec client -n netpol-test -- wget -q -O- http://web.netpol-test --timeout=5
# 超时!被 NetworkPolicy 阻止

# 5. 清理
kubectl delete networkpolicy deny-all -n netpol-test

练习 10.2:精确允许 Ingress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 1. 只允许特定标签的 Pod 访问 web
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-web-access
namespace: netpol-test
spec:
podSelector:
matchLabels:
run: web # 应用到 web Pod
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
role: frontend # 只允许有 role=frontend 的 Pod
ports:
- protocol: TCP
port: 80
EOF

# 2. client 没有 role=frontend 标签,应被拒绝
kubectl exec client -n netpol-test -- wget -q -O- http://web.netpol-test --timeout=5
# 超时

# 3. 给 client 打上标签
kubectl label pod client -n netpol-test role=frontend

# 4. 再次访问,应成功
kubectl exec client -n netpol-test -- wget -q -O- http://web.netpol-test
# 正常返回

# 5. 清理
kubectl delete networkpolicy allow-web-access -n netpol-test
kubectl label pod client -n netpol-test role-

练习 10.3:namespaceSelector 跨命名空间控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 1. 创建第二个命名空间
kubectl create ns netpol-external

# 2. 在 external 命名空间创建 Pod
kubectl run ext-client --image=busybox:1.36 -n netpol-external -- sleep 3600
kubectl label ns netpol-external env=trusted

# 3. 创建允许来自 trusted 命名空间的策略
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-trusted-ns
namespace: netpol-test
spec:
podSelector:
matchLabels:
run: web
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
env: trusted # 允许 env=trusted 的命名空间
ports:
- protocol: TCP
port: 80
EOF

# 4. 测试从不同命名空间访问
kubectl exec ext-client -n netpol-external -- \
wget -q -O- http://web.netpol-test.netpol-test.svc.cluster.local --timeout=5
# 正常返回

kubectl exec client -n netpol-test -- \
wget -q -O- http://web --timeout=5
# 超时(test 命名空间内默认 Pod 被拒绝)

# 5. 清理
kubectl delete networkpolicy allow-trusted-ns -n netpol-test
kubectl label ns netpol-external env-

练习 10.4:Egress 出站控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 1. 限制 Pod 只能访问特定外部地址
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: restrict-egress
namespace: netpol-test
spec:
podSelector:
matchLabels:
run: client
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
run: web # 允许访问 web
ports:
- protocol: TCP
port: 80
- to: # 还允许 DNS
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
EOF

# 2. 测试访问 web(应允许)
kubectl exec client -n netpol-test -- wget -q -O- http://web --timeout=5
# 正常返回

# 3. 测试访问外部(应被拒绝)
kubectl exec client -n netpol-test -- wget -q -O- http://www.baidu.com --timeout=5
# 超时

# 4. 清理
kubectl delete networkpolicy restrict-egress -n netpol-test

练习 10.5:ipBlock 白名单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 允许来自特定 IP 段的访问
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ipblock
namespace: netpol-test
spec:
podSelector:
matchLabels:
run: web
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 10.244.0.0/16 # Pod 网络
- ipBlock:
cidr: 10.0.0.0/8 # 节点网络
ports:
- protocol: TCP
port: 80
EOF

kubectl get networkpolicy allow-ipblock -n netpol-test -o yaml

🐛 排错练习(30 分钟)

场景:NetworkPolicy 导致服务不可用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 问题:创建 NetworkPolicy 后某些服务突然不可用

# 排查步骤:
# 1. 列出所有 NetworkPolicy
kubectl get networkpolicy --all-namespaces

# 2. 查看具体策略规则
kubectl describe networkpolicy <name> -n <ns>

# 3. 检查是否缺少必需的规则
# - CoreDNS 访问(UDP 53)
# - api-server 访问
# - 健康检查端点

# 4. 临时删除策略验证
kubectl delete networkpolicy <name> -n <ns>

# 5. 恢复后确认问题根源,补充遗漏规则

🏆 赛题模拟(40 分钟)

⚠️ 严格限时 40 分钟

题目:零信任网络策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
【初始环境】
- 命名空间 secure-app 中存在以下 Pod:
- db(标签 app=db,端口 5432)
- api(标签 app=api,端口 8080)
- web(标签 app=web,端口 80)

【操作要求】

1. 创建 Deny-All 默认策略(拒绝所有入站和出站流量)

2. 放行规则:
a. web → api:8080(前端调用 API)
b. api → db:5432(API 调用数据库)
c. 所有 Pod → CoreDNS(UDP 53,kube-system 命名空间)
d. web 允许来自 Ingress Controller 命名空间的入站(标签 app=ingress-nginx)

3. 验证:
- web 可以访问 api:8080 ✅
- api 可以访问 db:5432 ✅
- web 无法直接访问 db:5432 ❌(应被拒绝)
- api 无法访问外部网络 ❌(应被拒绝)

【评分标准】
- Deny-All 策略正确(15 分)
- web→api 规则正确(20 分)
- api→db 规则正确(20 分)
- DNS 放行规则正确(20 分)
- Ingress Controller 入站规则正确(15 分)
- 隔离验证通过(10 分)

🧹 环境清理

1
kubectl delete ns netpol-test netpol-external

📋 命令速查

命令 功能 注解
kubectl get networkpolicy 列出所有 NetworkPolicy short name: netpol
kubectl get networkpolicy -o yaml 查看 NetworkPolicy 详细规则 查看 podSelector、ingress/egress 规则、policyTypes
kubectl describe networkpolicy <name> NetworkPolicy 详情 包含规则匹配的 Pod 和流向
kubectl get pods -n kube-system -l k8s-app=calico-node 查看 Calico node Pod Calico 在每个节点运行一个 agent
kubectl -n kube-system logs -l k8s-app=calico-node --tail=50 查看 Calico 日志 NetworkPolicy 不生效时首要排查
calicoctl get ippool -o wide 查看 IP 地址池 确认 Pod CIDR 与 kubeadm init 一致
calicoctl get felixconfiguration 查看 Calico Felix 配置 Felix 是每个节点的策略执行引擎
calicoctl get networkpolicy 查看 Calico 层面的网络策略 比 kubectl get netpol 更底层
kubectl run test --image=busybox --rm -it --labels="app=test" -- wget -O- --timeout=3 http://<svc> 携带标签测试连通性 NetworkPolicy 基于标签匹配,运行测试 Pod 时需带正确的 labels
kubectl exec <pod> -- nc -zv <target-ip> <port> 测试 TCP 端口通断 比 curl/wget 更底层的连通性测试
kubectl get nodes -o wide | awk '{print $6}' 提取所有节点 CIDR Calico IPAM 基于节点 CIDR 分配 Pod IP
kubectl exec <pod> -- ip route 查看 Pod 内路由表 理解 Pod 出站流量路径

📚 参考来源

来源 链接 / 说明
Kubernetes 官方:NetworkPolicy https://kubernetes.io/docs/concepts/services-networking/network-policies/
Kubernetes 官方:声明 Network Policy https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy/
Calico 官方文档 https://docs.tigera.io/calico/latest/about/
Calico 网络策略指南 https://docs.tigera.io/calico/latest/network-policy/
CNI 规范 https://github.com/containernetworking/cni/blob/master/SPEC.md
Calico IPAM 原理 https://docs.tigera.io/calico/latest/networking/ipam/

📘 Day 09:Ingress 与外部流量接入

🎯 今日目标

  • 理解 Ingress 不是 Service 的替代,而是 L7 路由层
  • 安装并配置 NGINX Ingress Controller
  • 创建基于路径的路由规则
  • 创建基于域名的路由规则
  • 配置 TLS 证书实现 HTTPS

🧠 理论精讲(30 分钟)

Ingress 是什么

1
2
3
4
5
6
7
8
9
                ┌──────────────┐
│ Ingress │ ← L7 路由规则
│ Controller │
└──────┬───────┘

┌─────────────────┼─────────────────┐
│ │ │
/api ──→ api-svc /web ──→ web-svc / ──→ frontend-svc
(ClusterIP) (ClusterIP) (ClusterIP)

Ingress 提供 HTTP/HTTPS 路由SSL 终止基于名称的虚拟托管

Ingress vs Service

特性 Service (NodePort/LB) Ingress
工作层级 L4 (TCP/UDP) L7 (HTTP/HTTPS)
路由能力 端口映射 路径/域名路由
SSL 需手动配置 内置 TLS 终止
外部入口 每 Service 一个 统一入口

Ingress Controller 选择

Controller 特点
NGINX Ingress 最常用,功能全面
Traefik 自动发现,适合微服务
Istio Gateway 服务网格场景
Contour/Envoy 高性能

🔧 动手实操(120 分钟)

练习 9.1:安装 NGINX Ingress Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 方式 1:使用 Helm(推荐)
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.service.type=NodePort \
--set controller.service.nodePorts.http=30080 \
--set controller.service.nodePorts.https=30443

# 方式 2:使用 kubectl apply(如果没装 Helm)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.0/deploy/static/provider/cloud/deploy.yaml

# 验证安装
kubectl get pods -n ingress-nginx
# ingress-nginx-controller-xxx Running

kubectl get svc -n ingress-nginx
# ingress-nginx-controller NodePort/LoadBalancer

# 验证 Controller 可访问
# 获取 NodePort 端口
kubectl get svc -n ingress-nginx ingress-nginx-controller

# 测试 404 页面(还没有 Ingress 规则)
curl http://<节点IP>:30080
# 返回 404(正常,还没有配置后端)

练习 9.2:基于路径的路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 1. 创建两个后端应用
kubectl create deploy app-v1 --image=nginx:alpine
kubectl expose deploy app-v1 --port=80

kubectl create deploy app-v2 --image=httpd:alpine
kubectl expose deploy app-v2 --port=80

# 2. 创建 Ingress 规则
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: path-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: app-v1
port:
number: 80
- path: /v2
pathType: Prefix
backend:
service:
name: app-v2
port:
number: 80
EOF

# 3. 查看 Ingress
kubectl get ingress path-ingress
# NAME CLASS HOSTS ADDRESS PORTS AGE
# path-ingress nginx * <IP> 80 10s

kubectl describe ingress path-ingress

# 4. 测试路由
curl http://<节点IP>:30080/v1
# 返回 nginx 默认页面

curl http://<节点IP>:30080/v2
# 返回 httpd 的 "It works!" 页面

# 5. 清理
kubectl delete ingress path-ingress
kubectl delete deploy app-v1 app-v2
kubectl delete svc app-v1 app-v2

练习 9.3:基于域名的路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 1. 创建两个后端服务
kubectl create deploy web-a --image=nginx:alpine
kubectl expose deploy web-a --port=80
kubectl create deploy web-b --image=httpd:alpine
kubectl expose deploy web-b --port=80

# 2. 创建域名 Ingress
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: host-ingress
spec:
ingressClassName: nginx
rules:
- host: app-a.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-a
port:
number: 80
- host: app-b.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-b
port:
number: 80
EOF

# 3. 测试域名路由(通过 Host 头)
curl -H "Host: app-a.example.com" http://<节点IP>:30080/
# 返回 nginx 页面

curl -H "Host: app-b.example.com" http://<节点IP>:30080/
# 返回 httpd 页面

# 4. 清理
kubectl delete ingress host-ingress
kubectl delete deploy web-a web-b
kubectl delete svc web-a web-b

练习 9.4:Ingress TLS 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 1. 生成自签名证书
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key -out tls.crt \
-subj "/CN=tls-demo.example.com/O=tls-demo"

# 2. 创建 TLS Secret
kubectl create secret tls demo-tls --key=tls.key --cert=tls.crt

# 3. 创建后端服务
kubectl create deploy secure-app --image=nginx:alpine
kubectl expose deploy secure-app --port=80

# 4. 创建 TLS Ingress
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
spec:
ingressClassName: nginx
tls:
- hosts:
- tls-demo.example.com
secretName: demo-tls
rules:
- host: tls-demo.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: secure-app
port:
number: 80
EOF

# 5. 测试 HTTPS 访问(-k 跳过证书验证)
# 先获取 HTTPS NodePort
kubectl get svc -n ingress-nginx ingress-nginx-controller

curl -k -H "Host: tls-demo.example.com" https://<节点IP>:30443/
# 返回 nginx 页面

# 6. 查看证书信息
echo | openssl s_client -connect <节点IP>:30443 -servername tls-demo.example.com 2>/dev/null | openssl x509 -noout -subject -dates

# 7. 清理
rm -f tls.key tls.crt
kubectl delete ingress tls-ingress
kubectl delete secret demo-tls
kubectl delete deploy secure-app
kubectl delete svc secure-app

🐛 排错练习(30 分钟)

场景 1:Ingress 返回 404

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 排查步骤
# 1. 确认 Ingress Controller 是否运行
kubectl get pods -n ingress-nginx

# 2. 确认 Ingress 资源创建成功
kubectl get ingress

# 3. 检查 Ingress 详情
kubectl describe ingress <name>
# 看 Rules 和 Backend 是否正确

# 4. 确认后端 Service 存在且有 Endpoint
kubectl get svc <svc-name>
kubectl get endpoints <svc-name>

# 5. 检查 ingressClassName
kubectl get ingress <name> -o yaml | grep ingressClassName
# 应为 nginx(或与 Controller 类型一致)

# 6. 查看 Ingress Controller 日志
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=50

场景 2:TLS 证书不生效

1
2
3
4
5
6
7
8
9
10
11
# 排查步骤
# 1. 检查 Secret 是否存在且类型正确
kubectl get secret <secret-name>
kubectl describe secret <secret-name>
# 应有 tls.crt 和 tls.key

# 2. 检查证书内容
kubectl get secret <secret-name> -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout | head -20

# 3. 检查 Ingress tls 段
kubectl get ingress <name> -o yaml | grep -A5 tls

🏆 赛题模拟(40 分钟)

⚠️ 严格限时 40 分钟

题目:统一网关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
【环境要求】
- NGINX Ingress Controller 已安装(NodePort 30080/30443)

【操作要求】

1. 创建 3 个后端应用:
- api(nginx:alpine,2 副本,ClusterIP Service api-svc)
- web(httpd:alpine,2 副本,ClusterIP Service web-svc)
- admin(nginx:alpine,1 副本,ClusterIP Service admin-svc)

2. 创建 Ingress 规则:
a. 路径路由:
- /api → api-svc
- / → web-svc
b. 域名路由(新增第二个 Ingress 或合并):
- admin.example.com → admin-svc

3. 配置注解:
- nginx.ingress.kubernetes.io/rewrite-target: /
- 为 api 路径配置 CORS 允许所有来源

4. 为 admin.example.com 配置自签名 TLS

5. 验证:
- curl <NodeIP>:30080/api → 返回 nginx
- curl <NodeIP>:30080/ → 返回 httpd
- curl -H "Host: admin.example.com" <NodeIP>:30080/ → 返回 nginx
- curl -k -H "Host: admin.example.com" https://<NodeIP>:30443/ → 返回 nginx

【评分标准】
- 后端服务创建(20 分)
- 路径路由正确(25 分)
- 域名路由正确(20 分)
- TLS 配置正确(25 分)
- 验证全部通过(10 分)

📋 命令速查

命令 功能 注解
kubectl get ingress 列出 Ingress ADDRESS 为空说明 Ingress Controller 未就绪
kubectl get ingress -o wide Ingress + 主机/地址 查看 Host 规则和分配的 ADDRESS
kubectl describe ingress <name> Ingress 详细规则 查看每条 Path 规则和后端 Service
kubectl get ingressclass 列出 IngressClass 多个 Ingress Controller 时用于区分
kubectl -n ingress-nginx get pods 查看 Ingress Controller Pod nginx-ingress 默认部署在 ingress-nginx 命名空间
kubectl -n ingress-nginx logs -l app.kubernetes.io/name=ingress-nginx 查看 Ingress Controller 日志 Ingress 路由异常时的首要排查
kubectl -n ingress-nginx get svc 查看 Ingress Controller Service 确认是否分配了 EXTERNAL-IP
curl -H "Host: <hostname>" http://<node-ip>:<nodeport> 测试 Ingress 路由 绕过 DNS 直接用 Header 指定 Host
curl -k https://<hostname> 测试 HTTPS Ingress -k 跳过自签名证书验证
kubectl create ingress <name> --rule="host/path=svc:port" --dry-run=client -o yaml 生成 Ingress YAML 快速生成基本的 Ingress 规则模板
kubectl get svc -A | grep LoadBalancer 查找所有 LB 类型 Service 云环境中 LB 会分配公网 IP
kubectl patch ingress <name> -p '{"metadata":{"annotations":{"nginx.ingress.kubernetes.io/rewrite-target":"/"}}}}' 添加 Ingress 注解 nginx-ingress rewrite 注解实现 URL 重写
kubectl create secret tls <name> --cert=cert.pem --key=key.pem 创建 TLS Secret Ingress HTTPS 必须用此类型 Secret

📚 参考来源

来源 链接 / 说明
Kubernetes 官方:Ingress https://kubernetes.io/docs/concepts/services-networking/ingress/
Kubernetes 官方:Ingress Controller https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/
NGINX Ingress Controller 文档 https://kubernetes.github.io/ingress-nginx/
NGINX Ingress 注解参考 https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/
Kubernetes 官方:TLS/SSL https://kubernetes.io/docs/concepts/services-networking/ingress/#tls

📘 Day 07:Pod 资源综合实战

🎯 今日目标

  • 在一个命名空间内从零搭建多类型工作负载
  • 通过故障注入验证 K8s 自愈能力
  • 完成完整的版本发布与回滚流程
  • 能根据业务需求选择正确的工作负载类型

🧠 理论精讲(10 分钟)

工作负载选型决策树

1
2
3
4
5
6
7
需要运行容器?
├── 需要每节点一个?──→ DaemonSet
├── 需要稳定网络标识 + 持久存储?──→ StatefulSet
├── 一次性任务?──→ Job
├── 定时任务?──→ CronJob
├── 无状态、需要滚动更新?──→ Deployment
└── 单个 Pod,不需要自愈?──→ Pod(restartPolicy: Never)

🔧 动手实操(150 分钟)

练习 7.1:从零构建微服务应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# 创建独立命名空间
kubectl create ns microshop

# === 1. 数据库层(StatefulSet) ===
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: db-svc
namespace: microshop
spec:
clusterIP: None
selector:
app: shop-db
ports:
- port: 6379
targetPort: 6379
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: shop-db
namespace: microshop
spec:
serviceName: db-svc
replicas: 1
selector:
matchLabels:
app: shop-db
template:
metadata:
labels:
app: shop-db
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
volumeMounts:
- name: db-data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: db-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Mi
EOF

# === 2. 后端服务(Deployment) ===
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: microshop
spec:
replicas: 2
selector:
matchLabels:
app: shop-backend
template:
metadata:
labels:
app: shop-backend
spec:
containers:
- name: api
image: nginx:alpine
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 3
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
EOF

kubectl expose deploy backend -n microshop --name=backend-svc --port=80

# === 3. 前端服务(Deployment) ===
kubectl create deploy frontend -n microshop --image=nginx:alpine --replicas=3
kubectl expose deploy frontend -n microshop --name=frontend-svc --port=80

# === 4. 日志采集(DaemonSet) ===
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: log-agent
namespace: microshop
spec:
selector:
matchLabels:
app: log-agent
template:
metadata:
labels:
app: log-agent
spec:
containers:
- name: logger
image: busybox:1.36
command:
- sh
- -c
- |
while true; do
echo "[$(date)] Node: $(hostname) - collecting logs"
sleep 10
done
EOF

# === 5. 定时清理(CronJob) ===
cat <<'EOF' | kubectl apply -f -
apiVersion: batch/v1
kind: CronJob
metadata:
name: log-cleanup
namespace: microshop
spec:
schedule: "0 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: cleanup
image: busybox:1.36
command:
- sh
- -c
- echo "Cleaning old logs at $(date)"
restartPolicy: Never
EOF

# 6. 验证全部资源
kubectl get all -n microshop
# 预期:
# - Pod: frontend×3, backend×2, shop-db-0×1, log-agent×3
# - Service: db-svc, backend-svc, frontend-svc
# - DaemonSet: log-agent
# - StatefulSet: shop-db
# - CronJob: log-cleanup

练习 7.2:故障演练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 故障 1:删除 Pod(验证自愈)
kubectl get pod -n microshop -l app=shop-backend
POD_NAME=$(kubectl get pod -n microshop -l app=shop-backend -o name | head -1)
kubectl delete $POD_NAME -n microshop
kubectl get pod -n microshop -l app=shop-backend -w
# 观察:旧 Pod Terminating,新 Pod 自动创建并 Running

# 故障 2:杀掉容器进程(验证 livenessProbe)
BACKEND_POD=$(kubectl get pod -n microshop -l app=shop-backend -o name | head -1)
kubectl exec $BACKEND_POD -n microshop -- nginx -s stop
# 预期:livenessProbe 失败 → 容器重启
kubectl get $BACKEND_POD -n microshop -w
# RESTARTS 列递增

# 故障 3:缩容 frontend(验证可用性)
kubectl scale deploy/frontend -n microshop --replicas=1
kubectl get pod -n microshop -l app=shop-frontend
# 只保留 1 个

# 故障 4:模拟节点不可用(cordon)
kubectl cordon k8s-node1
# 注意:已有 Pod 不受影响,新 Pod 不调度到 node1

# 恢复
kubectl uncordon k8s-node1
kubectl scale deploy/frontend -n microshop --replicas=3

练习 7.3:版本演练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. 记录当前版本
kubectl get deploy -n microshop backend -o jsonpath='{.spec.template.spec.containers[0].image}'
# nginx:alpine

# 2. 更新 backend
kubectl set image deploy/backend -n microshop api=nginx:alpine-slim
kubectl annotate deploy/backend -n microshop kubernetes.io/change-cause="switch to slim image"
kubectl rollout status deploy/backend -n microshop

# 3. 查看历史
kubectl rollout history deploy/backend -n microshop

# 4. 制造一个失败更新
kubectl set image deploy/backend -n microshop api=nginx:9.9.9-invalid
kubectl annotate deploy/backend -n microshop kubernetes.io/change-cause="invalid update"

# 5. 发现失败,立即回滚
kubectl rollout undo deploy/backend -n microshop
kubectl rollout status deploy/backend -n microshop

# 6. 最终验证
kubectl get deploy -n microshop backend
kubectl rollout history deploy/backend -n microshop

🏆 赛题模拟(40 分钟)

⚠️ 严格限时 40 分钟

题目:微服务应用全流程部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
【场景】
为一个电商系统部署基础设施,包含缓存层、API 层、Web 层和监控组件。

【环境要求】
- 新建命名空间:ecommerce

【操作要求】
1. 缓存层:StatefulSet redis-cache(2 副本)
- 镜像 redis:7-alpine
- Headless Service redis-cache-svc
- 每个 Pod 独立 PVC(50Mi)

2. API 层:Deployment api-server(3 副本)
- 镜像 nginx:alpine
- 配置 livenessProbe + readinessProbe
- 资源限制:cpu 100m, memory 128Mi
- 滚动策略:maxSurge=1, maxUnavailable=0
- ClusterIP Service api-svc

3. Web 层:Deployment web-front(3 副本)
- 镜像 httpd:alpine
- ClusterIP Service web-svc

4. 监控层:DaemonSet node-exporter
- 镜像 busybox:1.36
- 每 5 秒输出主机名和时间

5. 备份任务:CronJob data-backup
- 镜像 busybox:1.36
- 每 15 分钟执行备份命令

6. 故障测试:
- 删除一个 api-server Pod → 验证自愈
- 缩容 redis-cache 到 1 → 观察顺序缩容
- 模拟 API 版本更新并回滚

【评分标准】
- 所有资源正确创建(40 分)
- Service 正确关联(15 分)
- 探针配置正确(15 分)
- 故障测试通过(20 分)
- 版本回滚成功(10 分)

🧹 环境清理

1
2
3
# 练习结束后清理
kubectl delete ns microshop
kubectl delete ns ecommerce

📋 命令速查

命令 功能 注解
kubectl apply -f <manifest>.yaml 声明式部署资源 综合实战中一次性部署多种资源
kubectl get all 查看所有资源 Pod/Service/Deploy/RS/StatefulSet/DaemonSet/Job 一览
kubectl get pods --show-labels Pod + 标签 验证标签是否与 Service Selector 匹配
kubectl get pods -L app,version 按指定标签列显示 Pod -L 将标签值作为独立列展示
kubectl describe svc <name> Service 详情 查看 Endpoints 是否绑定到正确的 Pod
kubectl get endpoints <svc> 查看 Service 后端 Endpoints 为空说明 Selector 不匹配或 Pod 未就绪
kubectl run tmp --image=busybox --rm -it -- wget -O- http://<svc>:<port> 临时 Pod 测试服务连通性 --rm 退出即删,测试网络首选
kubectl exec <pod> -- env 查看容器环境变量 验证 ConfigMap/Secret 注入是否正确
kubectl exec <pod> -- cat /etc/config/key 查看挂载的配置文件 验证 ConfigMap/Secret 挂载内容
kubectl exec <pod> -- nslookup <svc-name> 验证 DNS 解析 确认 CoreDNS 正常工作
kubectl -n <ns> logs -l app=<name> --tail=20 按标签批量查看日志 -l 按标签选择器筛选 Pod
kubectl top pods --sort-by=cpu 按 CPU 排序 Pod 用量 找出资源消耗最大的 Pod
kubectl get events --sort-by=.metadata.creationTimestamp 按时间排序事件 按时间线排查问题

📚 参考来源

来源 链接 / 说明
Kubernetes 官方:工作负载 https://kubernetes.io/docs/concepts/workloads/
Kubernetes 官方:标签与选择器 https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
Kubernetes 官方:Service https://kubernetes.io/docs/concepts/services-networking/service/
Kubernetes 官方:应用排错 https://kubernetes.io/docs/tasks/debug/debug-application/

📘 Day 08:Service 与集群内服务发现

🎯 今日目标

  • 理解 kube-proxy 如何实现 Service 流量转发
  • 创建四种类型 Service 并验证
  • 理解 Endpoint 与 Service 的关系
  • 掌握 CoreDNS 域名格式和 DNS 策略
  • 能通过 Pod DNS 名称实现服务间通信

🧠 理论精讲(30 分钟)

Service 解决了什么问题

Pod 是短暂的(IP 会变),Service 提供稳定的访问入口

1
2
3
Pod-A (IP: 10.244.1.5) ─┐
Pod-B (IP: 10.244.2.3) ─┤──→ Service (ClusterIP: 10.96.0.1) ──→ 客户端
Pod-C (IP: 10.244.1.9) ─┘

无论后端 Pod 如何变化,Service 的 ClusterIP 不变。

kube-proxy 工作模式

模式 原理 性能
iptables 通过 iptables 规则随机转发 规则数多时性能下降
IPVS 内核级负载均衡 高性能,支持多种调度算法

四种 Service 类型

类型 访问范围 典型场景
ClusterIP 集群内部 内部服务间通信
NodePort 节点 IP + 端口 开发调试、简单外部访问
LoadBalancer 外部 LB 分发 生产环境外部入口
ExternalName DNS CNAME 外部服务映射

CoreDNS 域名格式

1
<service-name>.<namespace>.svc.cluster.local
1
2
3
# 示例
api-server.default.svc.cluster.local
redis-cache.production.svc.cluster.local

🔧 动手实操(120 分钟)

练习 8.1:ClusterIP Service 基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1. 创建后端 Deployment
kubectl create deploy backend --image=nginx:alpine --replicas=3

# 2. 暴露为 ClusterIP Service
kubectl expose deploy backend --port=80 --target-port=80

# 3. 查看 Service
kubectl get svc backend
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# backend ClusterIP 10.96.xxx.xxx <none> 80/TCP 10s

# 4. 查看 Endpoint
kubectl get endpoints backend
# NAME ENDPOINTS AGE
# backend 10.244.1.2:80,10.244.2.3:80,10.244.1.4:80 10s

# 5. 用临时 Pod 测试访问
kubectl run test-client --image=busybox:1.36 --rm -it --restart=Never -- \
wget -q -O- http://backend
# 输出:nginx 默认页面

# 6. 测试完整的 FQDN
kubectl run test-dns --image=busybox:1.36 --rm -it --restart=Never -- \
wget -q -O- http://backend.default.svc.cluster.local

练习 8.2:NodePort Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1. 创建 NodePort Service
kubectl expose deploy backend --type=NodePort --port=80 --name=backend-np

# 2. 查看分配的端口
kubectl get svc backend-np
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# backend-np NodePort 10.96.xxx.xxx <none> 80:30080/TCP 5s

# NodePort 范围:30000-32767

# 3. 通过节点 IP 访问
# 获取任一节点 IP
kubectl get nodes -o wide
# 在集群外浏览器或 curl 访问:
curl http://<任意节点IP>:30080

# 4. 验证 NodePort 也在 ClusterIP 上可用
kubectl run test-np --image=busybox:1.36 --rm -it --restart=Never -- \
wget -q -O- http://backend-np

# 5. 清理
kubectl delete svc backend-np

练习 8.3:ExternalName Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 将外部域名映射为集群内部 Service
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
type: ExternalName
externalName: database.example.com
EOF

# 验证
kubectl get svc external-db
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# external-db ExternalName <none> database.example.com <none> 5s

# 集群内 Pod 访问 external-db 会解析到 database.example.com
kubectl run dns-check --image=busybox:1.36 --rm -it --restart=Never -- \
nslookup external-db

# 清理
kubectl delete svc external-db

练习 8.4:Headless Service + 直接 Pod DNS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1. 创建 Headless Service
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: headless-svc
spec:
clusterIP: None
selector:
app: backend
ports:
- port: 80
EOF

# 2. 验证 ClusterIP 为空
kubectl get svc headless-svc
# CLUSTER-IP: None

# 3. DNS 查询 Headless Service
kubectl run dns-hl --image=busybox:1.36 --rm -it --restart=Never -- \
nslookup headless-svc
# 返回所有后端 Pod 的 IP(而不是一个 ClusterIP)

# 4. 清理
kubectl delete svc headless-svc

练习 8.5:Service 负载均衡验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 1. 创建一个能区分 Pod 的后端
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: lb-test
spec:
replicas: 3
selector:
matchLabels:
app: lb-test
template:
metadata:
labels:
app: lb-test
spec:
containers:
- name: http
image: busybox:1.36
command:
- sh
- -c
- |
echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello from $(hostname)" > /tmp/response
while true; do nc -l -p 8080 < /tmp/response; done
ports:
- containerPort: 8080
EOF

kubectl expose deploy lb-test --port=8080 --target-port=8080

# 2. 多次请求验证负载均衡效果
kubectl run lb-client --image=busybox:1.36 --rm -it --restart=Never -- \
sh -c 'for i in $(seq 1 10); do wget -q -O- http://lb-test:8080; echo; done'
# 输出应分布在不同 Pod

# 3. 检查 kube-proxy 模式
kubectl logs -n kube-system -l k8s-app=kube-proxy | grep "Using"

# 4. 清理
kubectl delete deploy backend lb-test
kubectl delete svc backend lb-test

🐛 排错练习(30 分钟)

场景 1:Service 无法访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 排查清单
# 1. Service 的 selector 是否匹配 Pod 的 labels?
kubectl get svc <svc-name> -o jsonpath='{.spec.selector}'
kubectl get pod -l <key>=<value>

# 2. Endpoint 是否为空?
kubectl get endpoints <svc-name>
# 如果 ENDPOINTS 为空,说明 selector 不匹配

# 3. targetPort 是否正确?
kubectl get svc <svc-name> -o jsonpath='{.spec.ports[*].targetPort}'
kubectl get pod -l app=<name> -o jsonpath='{.spec.containers[*].ports}'

# 4. 网络策略是否阻止?
kubectl get networkpolicies

场景 2:CoreDNS 解析失败

1
2
3
4
5
6
7
8
9
10
11
# 测试 DNS 解析
kubectl run dns-debug --image=busybox:1.36 --rm -it --restart=Never -- nslookup kubernetes.default

# 检查 CoreDNS Pod
kubectl get pods -n kube-system -l k8s-app=kube-dns

# 检查 CoreDNS 日志
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=20

# 重启 CoreDNS
kubectl rollout restart deploy/coredns -n kube-system

🏆 赛题模拟(40 分钟)

⚠️ 严格限时 35 分钟

题目:多层服务暴露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
【操作要求】

1. 创建 Deployment app-v1(3 副本,nginx:alpine),暴露为 ClusterIP Service app-svc
2. 创建 Deployment app-v2(2 副本,httpd:alpine),暴露为 ClusterIP Service app-v2-svc
3. 在 app-v1 的 nginx 中配置反向代理到 app-v2-svc(通过 Service 名称)
4. 创建 NodePort Service app-gateway,暴露 app-svc 到节点 31080 端口
5. 验证:
- 集群内 curl http://app-svc → 返回 nginx 页面
- 集群内 curl http://app-v2-svc → 返回 httpd 页面
- 节点 IP:31080 → 返回 nginx 页面
- CoreDNS 解析 app-svc 和 app-v2-svc 均正常
6. 输出所有 Service 的 Endpoint 信息

【评分标准】
- Service 创建正确(30 分)
- 集群内通信正常(25 分)
- NodePort 访问正常(20 分)
- DNS 解析正常(15 分)
- Endpoint 正确(10 分)

📋 命令速查

命令 功能 注解
kubectl get svc 列出所有 Service TYPE 列显示 ClusterIP/NodePort/LoadBalancer/ExternalName
kubectl get svc -o wide Service + 选择器/端口 确认 Selector 和暴露的端口
kubectl get endpoints 查看 Service 后端端点 为空说明 Selector 没匹配到 Running Pod
kubectl describe svc <name> Service 详细信息 查看 SessionAffinity、Endpoints、Events
kubectl expose deploy <name> --port=80 --target-port=8080 快速创建 Service 暴露 Deployment 默认创建 ClusterIP 类型
kubectl expose deploy <name> --type=NodePort --port=80 --target-port=8080 创建 NodePort Service 节点 IP:30000-32767 可外部访问
kubectl expose deploy <name> --type=LoadBalancer --port=80 创建 LoadBalancer Service 云厂商分配外部 LB IP(学习环境会一直 Pending)
kubectl run test --image=busybox --rm -it -- wget -O- http://<svc-name> 临时 Pod 测试 Service 可达性 --rm 退出即删除,网络调试首选
kubectl exec <pod> -- nslookup <svc-name> 验证 CoreDNS 解析 解析失败说明 CoreDNS 故障或 SVC 不存在
kubectl exec <pod> -- nslookup <svc-name>.<ns>.svc.cluster.local 验证 FQDN 解析 全限定域名格式:<svc>.<namespace>.svc.cluster.local
kubectl exec <pod> -- curl -s <svc-name>.<ns>.svc.cluster.local:<port> 通过 FQDN 访问服务 跨命名空间通信必须用 FQDN
kubectl get pods -l app=<label> 按标签查 Pod 验证 Service Selector 是否匹配到正确的 Pod
kubectl patch svc <name> -p '{"spec":{"sessionAffinity":"ClientIP"}}' 修改会话亲和性 同一客户端 IP 始终路由到同一 Pod
kubectl create svc clusterip <name> --tcp=80:8080 --dry-run=client -o yaml 生成 ClusterIP Service YAML --tcp 指定 <port>:<targetPort>
kubectl create svc nodeport <name> --tcp=80:8080 --node-port=30080 --dry-run=client -o yaml 生成 NodePort Service YAML 指定固定 NodePort 而非随机端口
kubectl -n kube-system logs -l k8s-app=kube-dns 查看 CoreDNS 日志 DNS 解析故障时首要排查

📚 参考来源

来源 链接 / 说明
Kubernetes 官方:Service https://kubernetes.io/docs/concepts/services-networking/service/
Kubernetes 官方:DNS 与服务发现 https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/
Kubernetes 官方:Service 调试 https://kubernetes.io/docs/tasks/debug/debug-application/debug-service/
Kubernetes 官方:EndpointSlice https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/
CoreDNS 官方文档 https://coredns.io/manual/toc/
0%