Kubernetes相关
Kubernetes 容器编排 完整系统学习大纲
本文面向测试工程师,按”概念 → 动手 → 核心对象 → 网络/配置/存储 → 调度运维 → 测试实战”的顺序组织,由浅入深。 学 Docker 解决的是”一个容器怎么跑”,学 Kubernetes(简称 K8s)解决的是”成百上千个容器怎么协同、自愈、伸缩、发布”。测试同学掌握它,就能自己拉起一整套测试环境、验证弹性伸缩与高可用、做故障注入与混沌测试。
一、Kubernetes 概述
1.1 什么是 Kubernetes?
- 定义:Kubernetes 是 Google 开源的容器编排系统,用于自动化部署、扩缩容和管理容器化应用。
- 词源:希腊语”舵手/领航员”,简称 K8s(K 和 s 之间有 8 个字母)。
- 起源:源自 Google 内部使用了十几年的 Borg 系统,2014 年开源,现由 CNCF 托管。
- 地位:容器编排领域的事实标准,云厂商(阿里云 ACK、腾讯云 TKE、AWS EKS、GKE)全部托管支持。
1.2 容器编排要解决什么问题?
Docker 单机很方便,但到了生产/测试集群就会遇到:
| 问题 | Docker 单机 | Kubernetes 的解法 |
|---|---|---|
| 容器挂了怎么办 | 人工重启 | 自愈:自动重建 |
| 流量大了怎么办 | 手动多起几个 | 自动伸缩(HPA) |
| 容器放哪台机器 | 自己挑 | 自动调度(按资源打分) |
| 服务之间怎么访问 | 手动配 IP/端口 | Service + DNS 服务发现 |
| 怎么发布新版本 | 停掉再起 | 滚动更新,不中断 |
| 配置/密码怎么管 | 打进镜像 | ConfigMap / Secret 解耦 |
| 数据怎么存 | 挂本地目录 | PV / PVC 持久化 |
1.3 编排工具对比
| 工具 | 定位 | 现状 |
|---|---|---|
| Kubernetes | 完整编排平台 | ✅ 行业标准,生态最全 |
| Docker Swarm | Docker 官方编排 | ❌ 已基本停止发展 |
| Docker Compose | 单机多容器 | ✅ 本地开发/小规模仍好用 |
| Mesos + Marathon | 通用集群调度 | ❌ 已退场 |
💡 测试建议:本地联调、单人开发用 Docker Compose 就够了;一旦涉及多套环境、弹性伸缩、高可用验证,就必须上 K8s。
1.4 核心概念(务必先记牢)
| 概念 | 一句话解释 | 类比 |
|---|---|---|
| Cluster(集群) | 一组机器(节点)组成的整体 | 一个机房 |
| Node(节点) | 集群中的一台机器(物理机/虚拟机) | 一台服务器 |
| Pod | 最小调度单元,含 1~N 个容器 | 一个”逻辑主机” |
| Container | 真正跑应用的容器 | 一个进程 |
| Deployment | 管理 Pod 副本数、滚动更新 | 应用发布器 |
| ReplicaSet | 保证 Pod 副本数量 | 副本控制器 |
| Service | 为一组 Pod 提供稳定访问入口 | 负载均衡 + 内网域名 |
| Ingress | 七层(HTTP)路由入口 | Nginx 虚拟主机 |
| Namespace | 逻辑隔离的”分组” | 环境隔离(dev/test/prod) |
| ConfigMap | 配置(明文) | 配置文件 |
| Secret | 敏感配置(密码/密钥) | 加密配置 |
| Volume / PVC | 存储 | 硬盘 |
| Label / Selector | 标签与选择器,对象间关联的纽带 | 打标签筛选 |
1.5 整体架构
┌───────────────────────── Control Plane(控制平面,Master)────────────────────────┐
│ │
│ kube-apiserver 集群唯一入口,所有操作都经过它(REST 接口) │
│ etcd 集群的"数据库",保存所有状态(KV 存储) │
│ kube-scheduler 调度器:决定 Pod 放哪个 Node │
│ kube-controller-manager 控制器:副本数、节点、服务账号等循环控制 │
│ cloud-controller-manager 云厂商对接(负载均衡、磁盘等) │
│ │
└───────────────────────────────────────────────────────────────────────────────────┘
│ 通过 kube-apiserver 通信
┌───────────────┴───────────────────── Worker Node(工作节点)──────────────────────┐
│ │
│ kubelet 节点上的"管家",负责启停容器、上报状态 │
│ kube-proxy 维护网络规则,实现 Service 的负载均衡 │
│ 容器运行时 真正跑容器的程序(containerd / CRI-O;K8s 1.24+ 已移除 dockershim) │
│ Pod ┌──────────┐ ┌──────────┐ │
│ │ Container│ │ Container│ │
│ └──────────┘ └──────────┘ │
└───────────────────────────────────────────────────────────────────────────────────┘
一句话理解:你向 apiserver 提交”期望状态”(我要 3 个副本),控制器不断对比”实际状态”,不一致就调整——这就是声明式 API。
1.6 典型应用场景
- 微服务部署与治理
- 多套测试环境隔离(用 Namespace 给每个测试人员一套环境)
- 弹性伸缩应对流量高峰
- CI/CD 流水线(构建 → 推镜像 → 部署到 K8s)
- AI/大数据任务调度
二、环境搭建与快速上手
2.1 几种本地环境对比
| 方案 | 特点 | 适合 |
|---|---|---|
| minikube | 单节点集群,自带 Dashboard,最成熟 | ✅ 学习首选 |
| kind | 用 Docker 容器模拟节点,启动快 | ✅ CI 环境首选 |
| k3s / k3d | 轻量级 K8s,资源占用小 | 资源紧张的机器 |
| Docker Desktop | 自带 K8s,勾选即用 | Windows/macOS 图省事 |
| kubeadm | 手动搭建生产级集群 | 学习集群组建原理 |
2.2 minikube 快速上手(推荐)
# 1. 安装(需先有 Docker 或其他虚拟化环境)
# Windows (需管理员权限打开 PowerShell)
choco install minikube
# 或用 winget
winget install Kubernetes.minikube
# macOS
brew install minikube
# 2. 启动集群(国内建议加镜像源)
minikube start --image-mirror-country=cn --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers
# 3. 验证
kubectl cluster-info
kubectl get nodes
# 4. 打开可视化面板(另开终端保持运行)
minikube dashboard
# 5. 停止 / 删除
minikube stop
minikube delete
2.3 kind(CI 环境首选)
# 安装 kind
choco install kind # Windows
brew install kind # macOS
# 创建集群(比 minikube 快很多)
kind create cluster --name test
# 多节点集群配置 kind-config.yaml
cat > kind-config.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
EOF
kind create cluster --name multi --config kind-config.yaml
# 删除
kind delete cluster --name test
2.4 kubectl 安装与配置
# kubectl 是与集群交互的唯一命令行工具
kubectl version --client
# 配置自动补全(强烈建议,命令太长)
# Bash
echo 'source <(kubectl completion bash)' >> ~/.bashrc
# Zsh
echo 'source <(kubectl completion zsh)' >> ~/.zshrc
# 设置别名(后面文章都用 k 替代 kubectl)
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -F __start_kubectl k' >> ~/.bashrc
kubectl 命令通式:
kubectl [command] [TYPE] [NAME] [flags]
command: get / describe / create / apply / delete / logs / exec / edit / scale
TYPE: pods(po) / services(svc) / deployments(deploy) / nodes(no) / namespaces(ns)
NAME: 资源名,省略表示全部
flags: -n 命名空间 -o 输出格式 --all-namespaces(-A)
常用缩写(记住能省很多时间):
| 全称 | 缩写 | 全称 | 缩写 |
|---|---|---|---|
| pods | po | services | svc |
| deployments | deploy | namespaces | ns |
| nodes | no | replicasets | rs |
| configmaps | cm | persistentvolumeclaims | pvc |
| ingresses | ing | statefulsets | sts |
2.5 第一个应用:跑一个 Nginx
# 1. 创建一个 Deployment
kubectl create deployment my-nginx --image=nginx:1.25
# 2. 查看
kubectl get deployments
kubectl get pods -o wide
# 3. 暴露为 Service(NodePort 类型)
kubectl expose deployment my-nginx --port=80 --type=NodePort
# 4. 查看访问地址
kubectl get svc my-nginx
minikube service my-nginx --url # minikube 专用,直接给 URL
# 5. 本地端口转发(测试调试最常用)
kubectl port-forward svc/my-nginx 8080:80
# 然后浏览器访问 http://localhost:8080
# 6. 清理
kubectl delete deployment my-nginx
kubectl delete service my-nginx
💡 测试提示:
kubectl port-forward是测试同学最常用的命令——不需要配 Ingress、不需要 LoadBalancer,直接把集群内的服务映射到本地端口调试。
三、核心对象:Pod
3.1 什么是 Pod?
- Pod 是 K8s 的最小调度单元,不是容器。
- 一个 Pod 里可以跑 1 个或多个紧密协作的容器。
- 同一 Pod 内的容器:共享网络(同一个 IP、同一个 localhost)、共享存储卷。
- Pod 是短暂的(ephemeral):随时可能被销毁重建,IP 会变。
3.2 为什么需要 Pod 这一层?
场景:主容器 + 日志收集容器(sidecar)
Pod (IP: 10.244.1.5)
├── 容器 A:业务应用(写日志到 /var/log/app.log)
└── 容器 B:日志采集器(读 /var/log/app.log 发到 ES)
两个容器共享同一个 Volume,B 能读到 A 写的日志;
共享网络,B 可以用 localhost 访问 A 的接口。
3.3 Pod 生命周期与状态
Pod 阶段(Phase):
| 状态 | 含义 | 测试排查方向 |
|---|---|---|
| Pending | 已创建但还没调度成功或镜像在下载 | 资源不足?镜像拉不下来? |
| Running | 已调度,至少一个容器在运行 | 正常,但仍需看就绪探针 |
| Succeeded | 所有容器成功结束(Job 类) | 一次性任务完成 |
| Failed | 至少一个容器异常退出 | 看日志和退出码 |
| Unknown | 节点失联,状态未知 | 节点网络/心跳问题 |
容器状态(比 Pod 状态更常用):
| 状态 | 含义 |
|---|---|
| Waiting | 等待中(拉取镜像、等待调度) |
| Running | 正常运行 |
| Terminated | 已结束(有退出码) |
3.4 用 YAML 创建 Pod(声明式,重点)
命令行适合临时测试,正式用法一律写 YAML。
# pod-demo.yaml
apiVersion: v1 # API 版本
kind: Pod # 资源类型
metadata: # 元数据
name: nginx-pod
namespace: default
labels: # 标签(Service 靠它筛选 Pod)
app: nginx
env: test
spec: # 期望状态
containers:
- name: nginx
image: nginx:1.25 # 务必写具体版本,别用 latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
resources: # 资源限制
requests: # 调度时的最小保证
cpu: "100m" # 100m = 0.1 核
memory: "128Mi"
limits: # 上限,超了会被 OOMKill
cpu: "500m"
memory: "256Mi"
env: # 环境变量
- name: ENV_NAME
value: "test"
kubectl apply -f pod-demo.yaml # 创建/更新
kubectl get pod nginx-pod
kubectl describe pod nginx-pod # 看详情和事件(排查首选)
kubectl logs nginx-pod # 看日志
kubectl logs -f nginx-pod # 实时跟踪
kubectl exec -it nginx-pod -- /bin/sh # 进容器
kubectl delete -f pod-demo.yaml # 删除
kubectl edit pod nginx-pod # 在线编辑
YAML 四大段(所有 K8s 资源通用):
| 字段 | 作用 |
|---|---|
apiVersion |
用哪个 API 组/版本(v1、apps/v1、batch/v1…) |
kind |
资源类型(Pod/Deployment/Service…) |
metadata |
名字、命名空间、标签、注解 |
spec |
期望状态(你要什么) |
status |
实际状态(系统填,不用写) |
💡 记不住字段怎么写?用
kubectl explain pod.spec.containers查文档,比翻官网快。
3.5 探针(Probe)—— 保证应用”真活着”
K8s 默认的健康检查只能看容器进程在不在,进程在但应用假死它发现不了。探针解决这个。
| 探针 | 作用 | 失败后果 |
|---|---|---|
| livenessProbe 存活探针 | 容器是否活着 | 失败 → 重启容器 |
| readinessProbe 就绪探针 | 是否能接流量 | 失败 → 从 Service 摘掉(不重启) |
| startupProbe 启动探针 | 是否启动完成 | 成功前,另两个探针不生效(保护慢启动应用) |
spec:
containers:
- name: app
image: myapp:1.0
# 存活探针:HTTP 检查
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15 # 启动后等 15 秒再开始探测
periodSeconds: 10 # 每 10 秒探一次
timeoutSeconds: 2
failureThreshold: 3 # 连续失败 3 次才判定失败
# 就绪探针:TCP 检查
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
# 启动探针:保护慢启动(如 Java 应用)
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30 # 最多等 30*5=150 秒
periodSeconds: 5
三种探测方式:
# 1. HTTP GET(最常用,返回 2xx/3xx 视为成功)
httpGet:
path: /health
port: 8080
httpHeaders:
- name: Custom-Header
value: test
# 2. TCP 端口(能连上就算成功)
tcpSocket:
port: 8080
# 3. 执行命令(退出码为 0 视为成功)
exec:
command: ["cat", "/tmp/healthy"]
⚠️ 常见坑:
initialDelaySeconds设太小,应用还没启动就被判定失败 → 无限重启(CrashLoopBackOff)。Java 类应用务必配startupProbe或把延迟调大。
3.6 资源限制:requests 与 limits
| 字段 | 作用 | 超出后果 |
|---|---|---|
requests |
调度依据,节点必须有这么多空闲资源才调度 | 不超(保底) |
limits |
硬上限 | CPU 超限被限流(变慢);内存超限被 OOMKill(重启) |
resources:
requests:
cpu: "250m" # 0.25 核
memory: "256Mi"
limits:
cpu: "1" # 1 核
memory: "512Mi"
CPU 单位:1 = 1 核,100m = 0.1 核(100 毫核)
内存单位:128Mi(MiB)、1Gi(GiB)、1G(十进制 GB)
💡 测试价值:故意把
limits.memory设小,就能稳定复现 OOMKilled,验证应用内存泄漏或异常时的表现。这是 Compose 很难做的测试。
三种 QoS 等级(了解即可)
K8s 根据 requests/limits 的组合给 Pod 划分服务质量等级,节点资源不足时按等级决定先杀谁:
| 等级 | 条件 | 被驱逐优先级 |
|---|---|---|
| Guaranteed | requests == limits(cpu 和 memory 都满足) | 最低(最后才被杀) |
| Burstable | 至少设了 requests,但不满足上面 | 中等 |
| BestEffort | 完全没设 requests/limits | 最高(最先被杀) |
生产环境的关键服务建议设成 Guaranteed。
四、工作负载:Deployment 等控制器
4.1 为什么不用裸 Pod?
裸 Pod(直接 kind: Pod)的问题:
- 挂了没人管,不会自动重建
- 没有副本数概念,无法扩缩容
- 更新要手动删了重建,会中断服务
结论:生产/测试环境一律用工作负载控制器管理 Pod。
4.2 工作负载类型对比
| 类型 | 用途 | 特点 |
|---|---|---|
| Deployment | 无状态应用(Web/API)★ | 最常用,支持滚动更新、回滚 |
| ReplicaSet | 副本控制器 | 一般不直接用,Deployment 自动管理 |
| StatefulSet | 有状态应用(MySQL/Redis/Kafka) | 固定名称、固定存储、有序启停 |
| DaemonSet | 每节点跑一个(日志采集/监控 agent) | 新节点加入会自动部署 |
| Job | 一次性任务 | 跑完就结束 |
| CronJob | 定时任务 | 类似 Linux crontab |
4.3 Deployment 实战(重点)
# deployment-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
labels:
app: web
spec:
replicas: 3 # 副本数
selector: # 选择器:管理哪些 Pod(必须匹配 template 的 labels)
matchLabels:
app: web
strategy: # 更新策略
type: RollingUpdate # RollingUpdate(默认)| Recreate
rollingUpdate:
maxSurge: 1 # 更新期间最多比期望多 1 个 Pod
maxUnavailable: 0 # 更新期间最多允许 0 个不可用(零中断)
template: # Pod 模板(就是前面讲的 Pod spec)
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests: {cpu: "100m", memory: "128Mi"}
limits: {cpu: "500m", memory: "256Mi"}
readinessProbe:
httpGet: {path: /, port: 80}
initialDelaySeconds: 5
kubectl apply -f deployment-demo.yaml
# 查看
kubectl get deploy
kubectl get rs # 自动创建的 ReplicaSet
kubectl get pods -l app=web # 按标签筛选
# 扩缩容
kubectl scale deploy web-app --replicas=5
# 更新镜像(触发滚动更新)
kubectl set image deploy web-app web=nginx:1.26
# 查看更新状态
kubectl rollout status deploy web-app
# 查看历史版本
kubectl rollout history deploy web-app
# 回滚
kubectl rollout undo deploy web-app # 回上一版
kubectl rollout undo deploy web-app --to-revision=2 # 回指定版本
4.4 滚动更新原理
初始:v1 v1 v1 (3 副本)
滚动更新过程(maxSurge=1, maxUnavailable=0):
v1 v1 v1 + v2 → 新建 1 个 v2,就绪后
v1 v1 v2 → 删掉 1 个 v1,再建 1 个 v2
v1 v2 v2 → ...
v2 v2 v2 → 完成,全程始终有 3 个可用
关键点:readinessProbe 决定了"新 Pod 什么时候算就绪"
探针没配好 → 新版本不可用就被切流量 → 线上事故
两种策略:
| 策略 | 行为 | 适用 |
|---|---|---|
| RollingUpdate | 逐个替换,不中断(默认) | 绝大多数场景 |
| Recreate | 先全删再全建,会中断 | 不支持多版本共存的应用(如有锁/共享存储) |
4.5 StatefulSet(有状态应用)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql # 必须有一个 Headless Service
replicas: 3
selector:
matchLabels: {app: mysql}
template:
metadata:
labels: {app: mysql}
spec:
containers:
- name: mysql
image: mysql:8.0
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates: # 每个副本自动创建独立 PVC
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
Deployment vs StatefulSet:
| 维度 | Deployment | StatefulSet |
|---|---|---|
| Pod 名称 | 随机后缀 web-5f7d9c-x2k9p |
固定有序 mysql-0/1/2 |
| 存储 | 共享(通常) | 每个 Pod 独立 PVC |
| 启停顺序 | 无序 | 有序(0→1→2 启动,2→1→0 停止) |
| 网络标识 | 不稳定 | 稳定 DNS(mysql-0.mysql.default.svc) |
| 适用 | 无状态 Web/API | MySQL、Redis、Kafka、ZooKeeper |
4.6 DaemonSet 与 Job
# DaemonSet:每个节点跑一个日志采集器
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: log-collector
spec:
selector:
matchLabels: {app: log-collector}
template:
metadata:
labels: {app: log-collector}
spec:
containers:
- name: collector
image: fluentd:latest
# Job:一次性任务(数据初始化、跑批)
apiVersion: batch/v1
kind: Job
metadata:
name: data-init
spec:
backoffLimit: 3 # 失败重试次数
template:
spec:
restartPolicy: Never # Job 必须设为 Never 或 OnFailure
containers:
- name: init
image: myapp:1.0
command: ["python", "init_data.py"]
---
# CronJob:定时任务
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-clean
spec:
schedule: "0 2 * * *" # 每天凌晨 2 点
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: clean
image: myapp:1.0
command: ["python", "cleanup.py"]
五、服务与网络:Service / Ingress
5.1 为什么需要 Service?
问题:Pod 随时会被销毁重建,IP 每次都变;而且一个应用有多个副本,客户端该访问哪个?
解决:Service 提供稳定的访问入口(固定 IP + DNS 名),并自动把流量负载均衡到后端健康的 Pod。
客户端 → Service (固定: 10.96.x.x / web-app.default.svc)
↓ 自动负载均衡
┌────┼────┐
Pod1 Pod2 Pod3 (IP 会变,由 Service 动态追踪)
Service 靠 Label Selector 找 Pod:
spec:
selector:
app: web # 选中所有带 app=web 标签的 Pod
只有 readinessProbe 通过的 Pod 才会被加入 Service 的后端。
5.2 Service 四种类型
| 类型 | 作用 | 访问方式 | 适用 |
|---|---|---|---|
| ClusterIP | 集群内部访问(默认) | 服务名.命名空间.svc:端口 |
微服务间调用 |
| NodePort | 每个节点开一个端口(30000-32767) | 节点IP:30080 |
测试环境/外部临时访问 |
| LoadBalancer | 云厂商负载均衡器 | 公网 IP | 公有云生产环境 |
| ExternalName | DNS 别名映射到外部域名 | CNAME |
访问集群外服务 |
apiVersion: v1
kind: Service
metadata:
name: web-svc
spec:
type: ClusterIP # 默认可省略
selector:
app: web
ports:
- protocol: TCP
port: 80 # Service 暴露的端口
targetPort: 8080 # Pod 内容器的端口(不写则等于 port)
kubectl get svc
kubectl describe svc web-svc # 看 Endpoints(后端 Pod 列表)
kubectl get endpoints web-svc # 快速看有没有选中 Pod
⚠️ 排查口诀:Service 访问不通,先看
kubectl get endpoints。Endpoints 为空说明 selector 没匹配上 Pod,或 Pod 未就绪——这是最高频的问题。
5.3 服务发现(DNS)
集群内的每个 Service 都会自动注册一条 DNS 记录:
格式:<service名>.<namespace>.svc.cluster.local
同命名空间可简写:
curl http://web-svc:80 # 同 namespace
curl http://web-svc.test:80 # 跨 namespace(test 命名空间)
环境变量方式(Pod 创建时注入,不推荐,Service 后建会有问题):
WEB_SVC_SERVICE_HOST / WEB_SVC_SERVICE_PORT
# 在集群内临时起一个调试 Pod 验证连通性(测试常用)
kubectl run curl-test --image=curlimages/curl -it --rm -- sh
# 进去后
curl http://web-svc
curl http://web-svc.test.svc.cluster.local
nslookup web-svc
5.4 Ingress(七层路由)
Service 是四层(TCP/UDP),Ingress 是七层(HTTP):按域名、路径路由到不同 Service,相当于集群入口的 Nginx。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
spec:
ingressClassName: nginx # 需先安装 Ingress Controller
rules:
- host: test.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-svc
port: {number: 8080}
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port: {number: 80}
# minikube 启用 Ingress 插件
minikube addons enable ingress
kubectl get ingress
Service vs Ingress:
| 维度 | Service | Ingress |
|---|---|---|
| 层级 | 四层(TCP/UDP) | 七层(HTTP/HTTPS) |
| 能力 | 负载均衡 | 域名/路径路由、HTTPS 终结、重写 |
| 依赖 | 无 | 需安装 Ingress Controller(如 ingress-nginx) |
5.5 网络模型要点
- 每个 Pod 一个独立 IP,Pod 之间可直接互通(无需 NAT)——由 CNI 插件实现(Calico、Flannel、Cilium)。
- Pod 内容器共享网络,用
localhost互通。 - Service 的 ClusterIP 是虚拟 IP,由 kube-proxy 维护的 iptables/IPVS 规则转发。
- K8s 1.24+ 使用
containerd作为运行时,不再依赖 Docker daemon。
六、配置管理:ConfigMap / Secret
6.1 为什么要把配置拿出来?
配置写死在镜像里的问题是:改个数据库地址就要重新构建镜像。K8s 把配置独立成对象,实现”一份镜像,多环境部署“。
6.2 ConfigMap(明文配置)
# 命令行创建
kubectl create configmap app-config --from-literal=DB_HOST=mysql --from-literal=LOG_LEVEL=debug
# 从文件创建(整个配置文件)
kubectl create configmap nginx-conf --from-file=nginx.conf
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DB_HOST: "mysql"
DB_PORT: "3306"
LOG_LEVEL: "debug"
app.properties: | # 多行配置(整个文件)
server.port=8080
spring.profiles.active=test
6.3 Secret(敏感配置)
# 创建
kubectl create secret generic db-secret \
--from-literal=username=root \
--from-literal=password=123456
# 从文件(如 TLS 证书)
kubectl create secret tls my-tls --cert=tls.crt --key=tls.key
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque # Opaque(默认) / kubernetes.io/tls / docker-registry
data:
username: cm9vdA== # 注意:value 必须是 base64 编码
password: MTIzNDU2
# 也可以用 stringData 明文写,K8s 会自动编码(更方便)
stringData:
password: "123456"
# base64 编解码
echo -n "123456" | base64 # MTIzNDU2
echo "MTIzNDU2" | base64 -d # 123456
| Secret 类型 | 用途 |
|---|---|
Opaque |
通用(默认),存用户名密码 |
kubernetes.io/tls |
TLS 证书(配合 Ingress 用) |
kubernetes.io/dockerconfigjson |
私有镜像仓库登录凭证 |
6.4 三种注入方式
spec:
containers:
- name: app
image: myapp:1.0
# 方式一:单个环境变量
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: DB_HOST
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
# 方式二:整体注入(推荐,配置项多时省事)
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: db-secret
# 方式三:挂载为文件(配置文件场景)
volumeMounts:
- name: config-volume
mountPath: /etc/app
volumes:
- name: config-volume
configMap:
name: app-config
⚠️ 重要坑:
- 环境变量方式不会热更新 —— 改了 ConfigMap,Pod 里的环境变量不变,必须重启 Pod。
- 挂载为文件方式会自动更新(约 1 分钟延迟),但应用要支持热加载配置才生效。
- Secret 的 base64 不是加密,只是编码,任何能
kubectl get secret -o yaml的人都能解码。生产要开启 etcd 静态加密或用 Vault。
七、存储:Volume / PV / PVC
7.1 为什么需要存储?
Pod 是短暂的,容器重启后文件系统会被重置。数据库、日志、上传文件必须存在 Pod 之外。
7.2 存储层次关系
Pod → volume → PVC(申请)→ PV(实际存储)→ StorageClass(自动创建 PV)
类比:
Pod = 人
PVC = 提需求:"我要 10G 硬盘"
PV = 实际硬盘
StorageClass = 硬盘供应商(按需自动供货)
7.3 常用卷类型
spec:
containers:
- name: app
volumeMounts:
- name: cache
mountPath: /tmp/cache
- name: data
mountPath: /data
volumes:
# 1. emptyDir:临时卷,Pod 删除就没了(同 Pod 多容器共享)
- name: cache
emptyDir: {}
# 2. hostPath:挂载宿主机目录(测试环境方便,生产不安全)
- name: data
hostPath:
path: /data/app
type: DirectoryOrCreate
# 3. PVC:持久化卷(生产/测试推荐)
- name: data
persistentVolumeClaim:
claimName: app-pvc
7.4 PV / PVC / StorageClass
# PVC:用户申请存储
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-pvc
spec:
accessModes:
- ReadWriteOnce # RWO(单节点读写) / ROX(多节点只读) / RWX(多节点读写)
storageClassName: standard
resources:
requests:
storage: 5Gi
# StorageClass:动态供给,自动创建 PV
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
provisioner: k8s.io/minikube-hostpath # 不同环境不一样
reclaimPolicy: Delete # Delete / Retain
volumeBindingMode: Immediate
kubectl get pv, pvc
kubectl describe pvc app-pvc # 卡在 Pending 就看这里的原因
| 访问模式 | 缩写 | 含义 |
|---|---|---|
| ReadWriteOnce | RWO | 单节点可读写(最常用) |
| ReadOnlyMany | ROX | 多节点只读 |
| ReadWriteMany | RWX | 多节点读写(需 NFS/分布式存储) |
八、命名空间与资源管理
8.1 Namespace(逻辑隔离)
kubectl get ns # 默认有 default / kube-system / kube-public / kube-node-lease
kubectl create ns test
kubectl apply -f app.yaml -n test
kubectl get pods -n test
kubectl config set-context --current --namespace=test # 切换默认命名空间
💡 测试价值:给每个测试人员/每个需求分支开一个 Namespace,就能在同一套集群上跑多套互不干扰的测试环境,资源利用率远高于”一人一台虚拟机”。
8.2 资源配额
# 限制整个 Namespace 的资源总量
apiVersion: v1
kind: ResourceQuota
metadata:
name: test-quota
namespace: test
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
pods: "20"
services: "10"
# 给未指定资源的容器设默认值
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: test
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
8.3 Label / Selector
# 打标签
kubectl label pod web-xxx env=test version=v2
kubectl label pod web-xxx env- # 删除标签(键后加减号)
# 筛选
kubectl get pods -l app=web
kubectl get pods -l 'env in (test,dev),version!=v1'
Label vs Annotation:
| 用途 | 能否被 Selector 筛选 | |
|---|---|---|
| Label | 标识身份,用于关联选择 | ✅ 能 |
| Annotation | 存附加信息(构建号、负责人、监控配置) | ❌ 不能 |
九、调度与运维
9.1 调度流程
1. 过滤(Filtering):排除不满足条件的节点
资源够吗?端口冲突吗?有污点吗?匹配 nodeSelector 吗?
2. 打分(Scoring):对剩余节点打分
资源越空闲分越高、镜像已存在分越高…
3. 选中最高分节点,绑定 Pod
9.2 控制 Pod 落在哪
spec:
# 1. 简单:按节点标签选
nodeSelector:
disktype: ssd
# 2. 亲和性(更灵活,支持软硬策略)
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬条件,必须满足
nodeSelectorTerms:
- matchExpressions:
- key: env
operator: In
values: [test, dev]
preferredDuringSchedulingIgnoredDuringExecution: # 软条件,尽量满足
- weight: 1
preference:
matchExpressions:
- key: zone
operator: In
values: [zone-a]
# Pod 反亲和:让副本分散到不同节点(高可用必备)
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values: [web]
topologyKey: kubernetes.io/hostname
9.3 污点与容忍
Taint(污点) 打在节点上,表示”拒绝”;Toleration(容忍) 写在 Pod 上,表示”我能接受”。
# 给节点打污点
kubectl taint nodes node1 dedicated=test:NoSchedule
# 查看
kubectl describe node node1 | grep Taint
# 删除污点
kubectl taint nodes node1 dedicated=test:NoSchedule-
| 效果 | 行为 |
|---|---|
NoSchedule |
新 Pod 不调度上来(已有 Pod 不受影响) |
PreferNoSchedule |
尽量不调度(软策略) |
NoExecute |
不调度 + 驱逐已有 Pod |
# Pod 声明容忍
spec:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "test"
effect: "NoSchedule"
💡 Master 节点默认有
NoSchedule污点,所以普通 Pod 不会调度上去——这就是”为什么我的 Pod 都不在 master 上”的原因。
9.4 驱逐与优雅停机
spec:
terminationGracePeriodSeconds: 30 # 优雅停机宽限期(默认 30 秒)
containers:
- name: app
lifecycle:
preStop: # 停止前执行(如先摘流量、等待处理完)
exec:
command: ["sh", "-c", "sleep 10"]
停机流程:收到 SIGTERM → 执行 preStop → 从 Service 摘除 → 等待处理完 → 宽限期到发 SIGKILL。
十、Helm:K8s 的包管理器
10.1 为什么需要 Helm?
一个应用往往有十几个 YAML(Deployment、Service、ConfigMap、Ingress、PVC…),改个镜像版本要改多处。Helm 把这些打包成一个 Chart,用变量统一管理。
| 概念 | 含义 |
|---|---|
| Chart | 一个应用的包(模板集合),相当于”安装包” |
| Repository | Chart 仓库,相当于”应用商店” |
| Release | Chart 的一次部署实例(同一 Chart 可部署多次) |
10.2 安装与常用命令
# 安装 Helm
choco install kubernetes-helm # Windows
brew install helm # macOS
# 添加仓库(国内可用镜像源)
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
# 搜索
helm search repo mysql
# 安装(一个 Chart 就拉起整套)
helm install my-mysql bitnami/mysql
# 查看已部署
helm list
# 升级(改配置后)
helm upgrade my-mysql bitnami/mysql --set auth.rootPassword=newpass
# 回滚
helm rollback my-mysql 1
# 卸载
helm uninstall my-mysql
10.3 测试环境一键搭建示例
# 一键起 Redis(测试依赖)
helm install redis bitnami/redis --set architecture=standalone
# 获取密码
kubectl get secret redis -o jsonpath="{.data.redis-password}" | base64 -d
# 一键起 MySQL
helm install mysql bitnami/mysql \
--set auth.rootPassword=test123 \
--set primary.persistence.size=5Gi
💡 相比 Docker Compose,Helm 的优势是社区维护了上千个成熟 Chart,MySQL 主从、Redis 哨兵、Kafka、Prometheus 都有现成的,几分钟就能拉起一套接近生产的测试环境。
十一、可观测性与故障排查
11.1 排查三板斧
# 第一斧:看状态
kubectl get pods -A -o wide
# 第二斧:看事件(90% 的问题在这里有答案)
kubectl describe pod <pod名>
# 重点看底部的 Events 区域
# 第三斧:看日志
kubectl logs <pod名> [-c 容器名] [--tail=100] [-f]
kubectl logs <pod名> --previous # 看上一次崩溃前的日志(CrashLoopBackOff 必用)
11.2 常见故障速查表
| 状态 / 事件 | 原因 | 排查命令 |
|---|---|---|
| ImagePullBackOff | 镜像拉不到 | kubectl describe pod 看事件;检查镜像名/tag、仓库权限、网络 |
| ErrImagePull | 镜像不存在或没权限 | 确认镜像地址,私有仓库需配 imagePullSecrets |
| CrashLoopBackOff | 容器启动后立刻退出 | kubectl logs <pod> --previous;检查配置、依赖、启动命令 |
| Pending | 无法调度 | kubectl describe pod 看事件:资源不足 / 无匹配节点 / PVC 未绑定 |
| OOMKilled | 内存超 limits | 调大 limits.memory 或排查内存泄漏;kubectl describe 看 Last State |
| CreateContainerConfigError | 引用的 ConfigMap/Secret 不存在 | 检查 kubectl get cm,secret |
| ContainerCreating 卡住 | 存储/网络插件问题 | 看节点事件、CNI 插件状态 |
| Evicted | 节点资源不足被驱逐 | kubectl describe node 看磁盘/内存压力 |
| Terminating 卡住 | 优雅停机超时或 finalizer | kubectl delete pod <pod> --force --grace-period=0 |
| Service 访问不通 | Endpoints 为空 | kubectl get endpoints <svc>;检查 selector 和就绪探针 |
| PVC 一直 Pending | 无可用 PV / StorageClass | kubectl describe pvc;检查是否有默认 SC |
11.3 监控与日志方案
| 组件 | 作用 |
|---|---|
| Metrics Server | 提供 kubectl top 资源指标(HPA 依赖它) |
| Prometheus | 时序监控,采集集群和应用指标 |
| Grafana | 可视化大盘 |
| EFK / Loki | 日志收集(Elasticsearch+Fluentd+Kibana / Grafana Loki) |
| kube-state-metrics | 暴露 K8s 对象状态指标 |
# 启用 metrics-server(minikube)
minikube addons enable metrics-server
kubectl top nodes
kubectl top pods -A
十二、测试视角:K8s 怎么测(重点)
12.1 K8s 给测试带来的能力
| 能力 | 测试价值 |
|---|---|
| 环境快速复制 | 一套 YAML 拉起完整环境,几分钟一套 |
| Namespace 隔离 | 多人并行测试互不干扰 |
| 自愈能力 | 可验证”服务挂了能不能自己恢复” |
| 弹性伸缩 | 可验证 HPA 扩容是否生效 |
| 滚动更新 | 可验证”发布期间业务是否中断” |
| 资源限制 | 可构造 OOM、CPU 限流等异常场景 |
| 故障注入 | 删 Pod、断网、杀节点,做混沌测试 |
12.2 测试点清单
- 部署正确性:副本数、镜像版本、资源限制、环境变量是否正确
- 服务可用性:Service/Ingress 能访问,返回正常
- 就绪探针:未就绪的 Pod 不会被接入流量
- 自愈能力:删掉 Pod 后能否自动重建
- 扩缩容:手动 scale 与 HPA 自动扩容是否生效
- 滚动更新:更新过程中请求不中断、无 5xx
- 回滚:更新失败后能否快速回滚
- 配置生效:改 ConfigMap/Secret 后应用行为是否变化
- 资源限制:超限时是否被限流/OOMKill,表现是否符合预期
- 持久化:Pod 重建后数据是否保留
- 节点故障:节点宕机后 Pod 是否漂移到其他节点
12.3 自愈能力测试
# 1. 查看当前 Pod
kubectl get pods -l app=web -o wide
# 2. 强制删除一个 Pod
kubectl delete pod web-app-xxxxx --force --grace-period=0
# 3. 立刻观察:Deployment 会自动创建新 Pod
kubectl get pods -l app=web -w # -w 实时观察
# 断言点:
# □ 新 Pod 在 10 秒内被创建
# □ 副本数最终恢复到 3
# □ 新 Pod 就绪后能正常提供服务
12.4 滚动更新零中断测试
# 终端 1:持续请求,统计失败数
for i in $(seq 1 10000); do
code=$(curl -s -o /dev/null -w "%{http_code}" http://web-svc/health)
if [ "$code" != "200" ]; then echo "FAIL: $code at $i"; fi
sleep 0.1
done
# 终端 2:执行更新
kubectl set image deploy web-app web=nginx:1.26
# 断言点:
# □ 全程无 5xx
# □ 无连接超时
# □ 更新完成后所有 Pod 都是新版本
kubectl rollout status deploy web-app
kubectl get pods -o jsonpath='{.items[*].spec.containers[*].image}'
💡 如果滚动更新期间出现 5xx,常见原因:① 没配
readinessProbe,新 Pod 还没起来就接了流量;②maxUnavailable设太大;③ 应用没有优雅停机(preStop)。
12.5 弹性伸缩(HPA)测试
# 1. 给 Deployment 设资源 requests(HPA 依赖它)
# 2. 创建 HPA:CPU 超过 50% 就扩容,2~10 个副本
kubectl autoscale deploy web-app --cpu-percent=50 --min=2 --max=10
kubectl get hpa
# 3. 加压(在集群内)
kubectl run -i --tty load-gen --rm --image=busybox --restart=Never -- \
/bin/sh -c "while sleep 0.01; do wget -q -O- http://web-svc; done"
# 4. 观察扩容过程
kubectl get hpa -w
kubectl get pods -w
# 断言点:
# □ CPU 超阈值后副本数增加
# □ 最多扩到 max 个
# □ 停止加压后,5 分钟(默认缩容稳定窗口)后缩回
12.6 异常场景测试
# 场景 1:内存溢出(构造 OOMKilled)
kubectl set resources deploy web-app --limits=memory=50Mi
kubectl get pods -w # 观察是否出现 OOMKilled、重启次数增加
# 场景 2:配置错误(验证应用容错)
kubectl edit cm app-config # 把 DB_HOST 改成一个不存在的地址
kubectl rollout restart deploy web-app
kubectl logs -l app=web # 看是否有清晰的错误日志和重试机制
# 场景 3:依赖服务不可用
kubectl scale deploy mysql --replicas=0 # 停掉依赖
# 观察被测应用是否降级/熔断/友好提示,而不是崩溃
# 场景 4:节点故障模拟
kubectl drain <node名> --ignore-daemonsets --delete-emptydir-data
# 观察 Pod 是否漂移到其他节点
kubectl uncordon <node名> # 恢复
12.7 Python 封装示例(可复用)
# k8s_helper.py
import subprocess
import json
def kubectl(args: list, check: bool = True) -> str:
"""执行 kubectl 命令并返回 stdout"""
result = subprocess.run(
["kubectl", *args], capture_output=True, text=True, encoding="utf-8"
)
if check and result.returncode != 0:
raise RuntimeError(f"kubectl {' '.join(args)} 失败:{result.stderr}")
return result.stdout.strip()
def get_pods(namespace: str = "default", label: str = None) -> list:
"""获取 Pod 列表"""
args = ["get", "pods", "-n", namespace, "-o", "json"]
if label:
args += ["-l", label]
data = json.loads(kubectl(args))
return [
{
"name": item["metadata"]["name"],
"phase": item["status"]["phase"],
"ready": all(
c.get("ready", False) for c in item["status"].get("containerStatuses", [])
),
"restarts": sum(
c.get("restartCount", 0) for c in item["status"].get("containerStatuses", [])
),
}
for item in data["items"]
]
def wait_ready(deploy: str, namespace: str = "default", timeout: int = 300) -> bool:
"""等待 Deployment 就绪"""
kubectl([
"rollout", "status", f"deploy/{deploy}",
"-n", namespace, f"--timeout={timeout}s",
])
return True
# ---------------- 测试示例 ----------------
def test_deployment_ready():
"""验证部署就绪且副本数正确"""
wait_ready("web-app", "test")
pods = get_pods("test", "app=web")
assert len(pods) == 3, f"期望 3 个副本,实际 {len(pods)}"
assert all(p["ready"] for p in pods), f"存在未就绪 Pod:{pods}"
print("部署验证通过:", pods)
def test_self_healing():
"""验证自愈:删掉 Pod 后能自动恢复"""
before = get_pods("test", "app=web")
victim = before[0]["name"]
kubectl(["delete", "pod", victim, "-n", "test", "--wait=false"])
wait_ready("web-app", "test")
after = get_pods("test", "app=web")
assert len(after) == len(before), "副本数未恢复"
assert all(p["ready"] for p in after), "恢复后仍有未就绪 Pod"
print(f"自愈验证通过:{victim} 已被重建")
def test_no_crashloop():
"""验证没有崩溃循环"""
pods = get_pods("test", "app=web")
for p in pods:
assert p["restarts"] < 3, f"{p['name']} 重启次数异常:{p['restarts']}"
print("无 CrashLoopBackOff")
# test_k8s_deploy.py(配合 pytest)
import pytest
from k8s_helper import test_deployment_ready, test_self_healing, test_no_crashloop
NS = "test"
def test_deploy():
test_deployment_ready()
def test_healing():
test_self_healing()
def test_stability():
test_no_crashloop()
12.8 测试 K8s 应用的常见坑
| 坑 | 说明 |
|---|---|
| 探针没配或配错 | 导致更新期间 502、或假死容器不重启 |
用 latest 标签 |
无法判断跑的是哪个版本,回滚也困难 |
没设 resources |
一个 Pod 吃光节点资源,引发连锁驱逐 |
| ConfigMap 改了以为生效 | 环境变量方式必须重启 Pod |
kubectl port-forward 断了 |
它是前台进程,终端关掉就没了,别拿它做长期访问 |
| 在 Pod 里改文件验证 | Pod 重建后全部丢失,必须改镜像或 ConfigMap |
| 忽略 Events | 90% 的问题答案都在 kubectl describe 的 Events 里 |
十三、kubectl 命令速查
# ---------- 查看 ----------
kubectl get pods|svc|deploy|nodes|ns [-A] [-o wide|yaml|json]
kubectl describe <type> <name> # 详情 + 事件(排查首选)
kubectl explain <type>.<field> # 查字段含义
kubectl api-resources # 所有资源类型
# ---------- 创建/更新 ----------
kubectl apply -f xxx.yaml # 声明式创建/更新(推荐)
kubectl create -f xxx.yaml # 命令式(已存在会报错)
kubectl create deploy <name> --image=<img> # 快速创建
kubectl edit <type> <name> # 在线编辑
kubectl scale deploy <name> --replicas=N # 扩缩容
kubectl set image deploy <name> c=img:v2 # 更新镜像
# ---------- 删除 ----------
kubectl delete -f xxx.yaml
kubectl delete pod <name> [--force --grace-period=0]
kubectl delete pods -l app=web # 按标签批量删
# ---------- 调试 ----------
kubectl logs <pod> [-c <container>] [-f] [--tail=N] [--previous]
kubectl exec -it <pod> [-- <command>] # -- /bin/sh
kubectl port-forward <pod|svc/name> 8080:80
kubectl cp <pod>:/path ./local # 拷文件
kubectl run debug -it --rm --image=busybox -- sh # 临时调试 Pod
# ---------- 发布 ----------
kubectl rollout status|history|undo deploy/<name>
kubectl rollout restart deploy/<name> # 重启(改配置后常用)
# ---------- 集群 ----------
kubectl top nodes|pods # 资源占用
kubectl get events --sort-by=.lastTimestamp # 集群事件(排查利器)
kubectl config get-contexts # 多集群切换
kubectl config use-context <name>
十四、学习资源
| 资源 | 说明 |
|---|---|
| Kubernetes 官方文档 | 权威,有中文版 |
| kubectl 命令备忘单 | 速查必备 |
| Minikube 文档 | 本地环境搭建 |
| Kind 文档 | CI 环境搭建 |
| Helm 官方文档 | 包管理 |
| Play with Kubernetes | 在线免费实验环境 |
| Katacoda / Killercoda | 交互式 K8s 场景练习 |
| 《Kubernetes in Action》 | 系统学习书籍 |
| Artifact Hub | Helm Chart 仓库搜索 |