一、Kubernetes 是什么?——名字都念不对,怎么学?
1.1 先把名字念对
Kubernetes(读作 /kuːbərˈnɛtɪs/),源自希腊语,意为"舵手"或"领航员"。
简称 K8s——因为 K 和 s 之间有 8 个字母。程序员的偷懒精神,连名字都不放过。
也有人叫它 Kube,但请不要叫它 "Ku-ber-NET-eez",会被鄙视的。
1.2 K8s 的前世今生
timeline
title 容器编排的进化史
2003-2004 : Google 内部开发 Borg
: 管理数十亿容器
2013 : Docker 发布
: 容器技术走向大众
2014 : Google 开源 Kubernetes
: 基于 Borg 15年经验
2015 : CNCF 成立
: K8s 成为首个项目
2017 : Docker Swarm 认输
: K8s 赢得编排大战
2018 : K8s 统治市场
: 各大云厂商全面支持
2020+ : 云原生成为标配
: 不会 K8s 等于不会呼吸
1.3 为什么需要 K8s?——Docker Compose 不够吗?
graph TB
subgraph "Docker Compose 能做的 ✅"
DC1["单机部署多个容器"]
DC2["容器间网络通信"]
DC3["定义依赖关系"]
DC4["一键启动/停止"]
end
subgraph "Docker Compose 做不到的 ❌"
K1["跨多台机器部署"]
K2["自动扩缩容"]
K3["容器挂了自动重启"]
K4["滚动更新/回滚"]
K5["服务发现与负载均衡"]
K6["Secret/Config 管理"]
K7["存储编排"]
K8["资源调度优化"]
end
DC1 -.->|"K8s 全都能做
而且做得更好"| K1 style K1 fill:#c8e6c9 style K2 fill:#c8e6c9 style K3 fill:#c8e6c9 style K4 fill:#c8e6c9 style K5 fill:#c8e6c9 style K6 fill:#c8e6c9 style K7 fill:#c8e6c9 style K8 fill:#c8e6c9
而且做得更好"| K1 style K1 fill:#c8e6c9 style K2 fill:#c8e6c9 style K3 fill:#c8e6c9 style K4 fill:#c8e6c9 style K5 fill:#c8e6c9 style K6 fill:#c8e6c9 style K7 fill:#c8e6c9 style K8 fill:#c8e6c9
用人话说:Docker Compose 是单机版"开心消消乐",K8s 是分布式"星际争霸"。
二、K8s 架构——看完就想放弃的那种
2.1 整体架构
graph TB
USER["👤 用户 / CI/CD"] -->|kubectl| API["API Server
☸️ 集群大脑的前台"] subgraph CP["Control Plane(控制平面)🧠"] API --> ETCD["etcd
📦 分布式键值存储
集群的唯一数据源"] API --> SCHED["Scheduler
📋 调度器
决定 Pod 跑在哪个节点"] API --> CM["Controller Manager
🔄 控制器管理器
维护集群期望状态"] API --> CCM["Cloud Controller
☁️ 云控制器
对接云厂商 API"] end subgraph N1["Worker Node 1 🖥️"] KL1["Kubelet
🤖 节点代理
管理本机 Pod"] KP1["Kube-proxy
🔀 网络代理
维护网络规则"] CR1["Container Runtime
🐳 containerd"] subgraph PODS1["Pods"] P1["Pod A"] P2["Pod B"] end KL1 --> CR1 --> PODS1 end subgraph N2["Worker Node 2 🖥️"] KL2["Kubelet"] KP2["Kube-proxy"] CR2["Container Runtime"] subgraph PODS2["Pods"] P3["Pod C"] P4["Pod D"] end KL2 --> CR2 --> PODS2 end subgraph N3["Worker Node 3 🖥️"] KL3["Kubelet"] KP3["Kube-proxy"] CR3["Container Runtime"] subgraph PODS3["Pods"] P5["Pod E"] end KL3 --> CR3 --> PODS3 end API --> KL1 API --> KL2 API --> KL3
☸️ 集群大脑的前台"] subgraph CP["Control Plane(控制平面)🧠"] API --> ETCD["etcd
📦 分布式键值存储
集群的唯一数据源"] API --> SCHED["Scheduler
📋 调度器
决定 Pod 跑在哪个节点"] API --> CM["Controller Manager
🔄 控制器管理器
维护集群期望状态"] API --> CCM["Cloud Controller
☁️ 云控制器
对接云厂商 API"] end subgraph N1["Worker Node 1 🖥️"] KL1["Kubelet
🤖 节点代理
管理本机 Pod"] KP1["Kube-proxy
🔀 网络代理
维护网络规则"] CR1["Container Runtime
🐳 containerd"] subgraph PODS1["Pods"] P1["Pod A"] P2["Pod B"] end KL1 --> CR1 --> PODS1 end subgraph N2["Worker Node 2 🖥️"] KL2["Kubelet"] KP2["Kube-proxy"] CR2["Container Runtime"] subgraph PODS2["Pods"] P3["Pod C"] P4["Pod D"] end KL2 --> CR2 --> PODS2 end subgraph N3["Worker Node 3 🖥️"] KL3["Kubelet"] KP3["Kube-proxy"] CR3["Container Runtime"] subgraph PODS3["Pods"] P5["Pod E"] end KL3 --> CR3 --> PODS3 end API --> KL1 API --> KL2 API --> KL3
🧠 记忆法:Control Plane 是公司总部(CEO、财务、HR),Worker Node 是各地分公司(干活的)。API Server 是前台,所有命令都要通过它。etcd 是公司保险柜,存着所有重要数据。
2.2 组件职责一张图
graph LR
subgraph "Control Plane 组件"
A["API Server
🚪 唯一入口
所有通信的中枢"] B["etcd
💾 数据库
集群状态持久化"] C["Scheduler
🎯 调度器
为 Pod 选择节点"] D["Controller Manager
♻️ 控制循环
确保实际=期望"] end subgraph "Node 组件" E["Kubelet
👷 工头
执行 Pod 生命周期"] F["Kube-proxy
🔌 网络管家
Service → Pod 路由"] G["Container Runtime
🏭 容器工厂
创建/销毁容器"] end A <-->|"读写状态"| B A -->|"Pod 调度决策"| C A -->|"状态协调"| D A -->|"Pod 操作指令"| E E -->|"管理容器"| G
🚪 唯一入口
所有通信的中枢"] B["etcd
💾 数据库
集群状态持久化"] C["Scheduler
🎯 调度器
为 Pod 选择节点"] D["Controller Manager
♻️ 控制循环
确保实际=期望"] end subgraph "Node 组件" E["Kubelet
👷 工头
执行 Pod 生命周期"] F["Kube-proxy
🔌 网络管家
Service → Pod 路由"] G["Container Runtime
🏭 容器工厂
创建/销毁容器"] end A <-->|"读写状态"| B A -->|"Pod 调度决策"| C A -->|"状态协调"| D A -->|"Pod 操作指令"| E E -->|"管理容器"| G
2.3 K8s 的"声明式"哲学
sequenceDiagram
participant U as 你 (kubectl)
participant A as API Server
participant E as etcd
participant C as Controller
participant S as Scheduler
participant K as Kubelet
U->>A: 我要 3 个 nginx Pod
A->>E: 记下来:期望状态 = 3 pods
A->>C: 嘿,检查一下状态
C->>A: 当前 0 个,需要创建 3 个
A->>S: 这 3 个 Pod 放哪?
S->>A: Node1 放 1 个,Node2 放 2 个
A->>K: Node1: 创建 1 个 Pod
A->>K: Node2: 创建 2 个 Pod
K->>A: 报告!Pod 已运行
A->>E: 更新:当前状态 = 3 pods ✅
Note over C: 之后持续监控...
如果有 Pod 挂了
自动拉起新的
如果有 Pod 挂了
自动拉起新的
🔑 核心理念:你不是告诉 K8s "怎么做",而是告诉它 "我要什么样的结果"。它会自己想办法达到那个状态,并且持续维护。
就像你对一个靠谱的管家说"家里永远要有 3 盒牛奶",而不是"去超市买 3 盒牛奶"。牛奶喝完了?管家自己去买。过期了?管家自己换。
三、K8s 核心概念——名词轰炸开始
3.1 概念关系全景图
graph TB
subgraph "工作负载(Workloads)"
POD["Pod
🫛 最小调度单元
一个或多个容器"] RS["ReplicaSet
🔢 副本控制器
维护 Pod 数量"] DEPLOY["Deployment
🚀 部署控制器
滚动更新/回滚"] STS["StatefulSet
📊 有状态部署
稳定网络标识+存储"] DS["DaemonSet
👹 守护进程集
每个节点跑一个"] JOB["Job / CronJob
⏰ 任务/定时任务
跑完就结束"] DEPLOY --> RS --> POD end subgraph "服务发现与负载均衡" SVC["Service
🔀 服务
稳定的访问入口"] ING["Ingress
🌐 入口网关
HTTP/HTTPS 路由"] EP["Endpoints
📍 端点
Service 背后的 Pod IP"] ING --> SVC --> EP --> POD end subgraph "配置与存储" CM["ConfigMap
📝 配置映射"] SEC["Secret
🔐 敏感信息"] PV["PersistentVolume
💾 持久化卷"] PVC["PersistentVolumeClaim
📋 存储申请单"] SC["StorageClass
🏭 存储类"] PVC -->|"绑定"| PV SC -->|"动态创建"| PV end subgraph "命名空间与权限" NS["Namespace
📂 命名空间
逻辑隔离"] SA["ServiceAccount
🪪 服务账号"] RBAC["RBAC
🔑 角色权限控制"] end
🫛 最小调度单元
一个或多个容器"] RS["ReplicaSet
🔢 副本控制器
维护 Pod 数量"] DEPLOY["Deployment
🚀 部署控制器
滚动更新/回滚"] STS["StatefulSet
📊 有状态部署
稳定网络标识+存储"] DS["DaemonSet
👹 守护进程集
每个节点跑一个"] JOB["Job / CronJob
⏰ 任务/定时任务
跑完就结束"] DEPLOY --> RS --> POD end subgraph "服务发现与负载均衡" SVC["Service
🔀 服务
稳定的访问入口"] ING["Ingress
🌐 入口网关
HTTP/HTTPS 路由"] EP["Endpoints
📍 端点
Service 背后的 Pod IP"] ING --> SVC --> EP --> POD end subgraph "配置与存储" CM["ConfigMap
📝 配置映射"] SEC["Secret
🔐 敏感信息"] PV["PersistentVolume
💾 持久化卷"] PVC["PersistentVolumeClaim
📋 存储申请单"] SC["StorageClass
🏭 存储类"] PVC -->|"绑定"| PV SC -->|"动态创建"| PV end subgraph "命名空间与权限" NS["Namespace
📂 命名空间
逻辑隔离"] SA["ServiceAccount
🪪 服务账号"] RBAC["RBAC
🔑 角色权限控制"] end
3.2 用一家餐厅来理解 K8s
graph TB
subgraph "K8s = 一家连锁餐厅 🍜"
CLUSTER["Cluster 集群
= 整个连锁品牌"] subgraph "Control Plane = 总部" CEO["API Server = 前台经理"] DB["etcd = 账本"] HR["Scheduler = HR
(安排员工去哪个分店)"] QA["Controller = 品控
(确保每家店品质一致)"] end subgraph "Node = 分店" CHEF["Kubelet = 店长"] WAITER["Kube-proxy = 服务员
(把客人引到对的桌子)"] end subgraph "核心概念对照" P["Pod = 一道菜
(可能有主菜+配菜)"] D["Deployment = 菜谱
(规定怎么做这道菜)"] S["Service = 菜单上的菜名
(客人不关心谁做的)"] I["Ingress = 餐厅大门
(外部客人从这进来)"] NS2["Namespace = 楼层
(中餐在2楼,西餐在3楼)"] CM2["ConfigMap = 调料架"] SEC2["Secret = 秘方
(锁在保险柜里)"] PV2["PV = 冰箱"] PVC2["PVC = 食材申请单"] end end
= 整个连锁品牌"] subgraph "Control Plane = 总部" CEO["API Server = 前台经理"] DB["etcd = 账本"] HR["Scheduler = HR
(安排员工去哪个分店)"] QA["Controller = 品控
(确保每家店品质一致)"] end subgraph "Node = 分店" CHEF["Kubelet = 店长"] WAITER["Kube-proxy = 服务员
(把客人引到对的桌子)"] end subgraph "核心概念对照" P["Pod = 一道菜
(可能有主菜+配菜)"] D["Deployment = 菜谱
(规定怎么做这道菜)"] S["Service = 菜单上的菜名
(客人不关心谁做的)"] I["Ingress = 餐厅大门
(外部客人从这进来)"] NS2["Namespace = 楼层
(中餐在2楼,西餐在3楼)"] CM2["ConfigMap = 调料架"] SEC2["Secret = 秘方
(锁在保险柜里)"] PV2["PV = 冰箱"] PVC2["PVC = 食材申请单"] end end
客人(请求)从大门(Ingress)进来,看菜单(Service)点菜,服务员(Kube-proxy)把订单送到后厨,店长(Kubelet)安排厨师用菜谱(Deployment)做菜(Pod)。如果某道菜做砸了,品控(Controller)会让重新做一道。
四、安装 K8s——第一个 Boss 关
4.1 安装方式选择
graph TD
START["我要安装 K8s!"] --> Q1{"你是来学习的
还是上生产的?"} Q1 -->|"学习"| Q2{"你的电脑
内存够吗?"} Q2 -->|"≥ 8GB"| MINIKUBE["Minikube
✅ 最推荐新手"] Q2 -->|"≥ 4GB"| K3S["K3s
✅ 轻量级 K8s"] Q2 -->|"< 4GB"| KIND["Kind
(K8s in Docker)
⚠️ 但你确定要套娃?"] Q1 -->|"上生产"| Q3{"你有云账号吗?"} Q3 -->|"有钱"| MANAGED["托管服务
EKS / AKS / GKE
💰 花钱买省心"] Q3 -->|"没钱"| Q4{"你有几台机器?"} Q4 -->|"≥ 3台"| KUBEADM["kubeadm
🔧 官方安装工具
⚠️ 需要一定经验"] Q4 -->|"1台"| K3S_PROD["K3s
单节点也能跑"] MINIKUBE --> NOTE1["⚠️ 仅限学习
不要上生产!"] MANAGED --> NOTE2["💡 强烈推荐
除非你喜欢加班"] KUBEADM --> NOTE3["🎓 适合想深入理解的人
也适合想掉头发的人"] style MANAGED fill:#c8e6c9 style MINIKUBE fill:#bbdefb style NOTE3 fill:#ffcdd2
还是上生产的?"} Q1 -->|"学习"| Q2{"你的电脑
内存够吗?"} Q2 -->|"≥ 8GB"| MINIKUBE["Minikube
✅ 最推荐新手"] Q2 -->|"≥ 4GB"| K3S["K3s
✅ 轻量级 K8s"] Q2 -->|"< 4GB"| KIND["Kind
(K8s in Docker)
⚠️ 但你确定要套娃?"] Q1 -->|"上生产"| Q3{"你有云账号吗?"} Q3 -->|"有钱"| MANAGED["托管服务
EKS / AKS / GKE
💰 花钱买省心"] Q3 -->|"没钱"| Q4{"你有几台机器?"} Q4 -->|"≥ 3台"| KUBEADM["kubeadm
🔧 官方安装工具
⚠️ 需要一定经验"] Q4 -->|"1台"| K3S_PROD["K3s
单节点也能跑"] MINIKUBE --> NOTE1["⚠️ 仅限学习
不要上生产!"] MANAGED --> NOTE2["💡 强烈推荐
除非你喜欢加班"] KUBEADM --> NOTE3["🎓 适合想深入理解的人
也适合想掉头发的人"] style MANAGED fill:#c8e6c9 style MINIKUBE fill:#bbdefb style NOTE3 fill:#ffcdd2
4.2 Minikube 快速开始(学习推荐)
# ===== macOS =====
brew install minikube
minikube start --driver=docker --memory=4096 --cpus=2
# ===== Linux =====
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
minikube start --driver=docker --memory=4096 --cpus=2
# 验证安装
kubectl get nodes
# 输出:
# NAME STATUS ROLES AGE VERSION
# minikube Ready control-plane 30s v1.28.3
# 打开 Dashboard(可视化界面)
minikube dashboard
# 🎉 浏览器自动打开,看到界面那一刻你会觉得 K8s 也没那么难
# 别高兴太早4.3 kubeadm 安装(生产级,给勇者)
# ===== 所有节点执行 =====
# 1. 关闭 swap(K8s 的强制要求,别问为什么)
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
# 2. 加载内核模块
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# 3. 内核参数
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
# 4. 安装 containerd
sudo apt-get update && sudo apt-get install -y containerd.io
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
# 修改 SystemdCgroup = true
sudo systemctl restart containerd
# 5. 安装 kubeadm, kubelet, kubectl
sudo apt-get install -y apt-transport-https ca-certificates curl
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | \
sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] \
https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /' | \
sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
# ===== 仅 Master 节点执行 =====
# 6. 初始化集群
sudo kubeadm init \
--pod-network-cidr=10.244.0.0/16 \
--apiserver-advertise-address=<MASTER_IP>
# 7. 配置 kubectl
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# 8. 安装网络插件(Calico)
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml
# ===== 仅 Worker 节点执行 =====
# 9. 加入集群(使用 kubeadm init 输出的命令)
sudo kubeadm join <MASTER_IP>:6443 \
--token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>😅 如果你顺利走完了以上步骤没出错,请立刻去买彩票。
五、kubectl——你的新命令行伙伴
5.1 kubectl 命令结构
graph LR
CMD["kubectl"] --> VERB["动词
get / create / apply
delete / describe"] VERB --> RESOURCE["资源类型
pod / svc / deploy
node / ns / ing"] RESOURCE --> NAME["资源名称
my-app / nginx"] NAME --> FLAGS["选项
-n namespace
-o yaml/json
--watch"] style CMD fill:#e1f5fe style VERB fill:#fff9c4 style RESOURCE fill:#c8e6c9 style FLAGS fill:#f3e5f5
get / create / apply
delete / describe"] VERB --> RESOURCE["资源类型
pod / svc / deploy
node / ns / ing"] RESOURCE --> NAME["资源名称
my-app / nginx"] NAME --> FLAGS["选项
-n namespace
-o yaml/json
--watch"] style CMD fill:#e1f5fe style VERB fill:#fff9c4 style RESOURCE fill:#c8e6c9 style FLAGS fill:#f3e5f5
5.2 必背 kubectl 命令速查表
# ============ 查看类 ============
# 查看所有 Pod
kubectl get pods # 当前 namespace
kubectl get pods -A # 所有 namespace
kubectl get pods -o wide # 显示更多信息(IP、节点等)
kubectl get pods --watch # 实时监控变化
kubectl get pods -l app=nginx # 按标签筛选
# 查看所有资源
kubectl get all # Pod, Service, Deployment...
kubectl get nodes # 节点状态
kubectl get svc # Service 列表
kubectl get deploy # Deployment 列表
kubectl get ns # Namespace 列表
# 详细信息(排错必用)
kubectl describe pod <pod-name>
kubectl describe node <node-name>
kubectl describe svc <svc-name>
# 查看日志
kubectl logs <pod-name> # 当前日志
kubectl logs -f <pod-name> # 实时跟踪
kubectl logs <pod-name> -c <container> # 多容器 Pod 指定容器
kubectl logs --previous <pod-name> # 上一次崩溃的日志(救命)
# ============ 操作类 ============
# 应用 YAML(声明式,推荐)
kubectl apply -f deployment.yaml
kubectl apply -f ./k8s/ # 应用整个目录
kubectl apply -f https://xxx/xxx.yaml # 直接从 URL 应用
# 删除资源
kubectl delete -f deployment.yaml
kubectl delete pod <pod-name>
kubectl delete pod <pod-name> --force --grace-period=0 # 强制删除(卡住时用)
# 进入容器(debug 用)
kubectl exec -it <pod-name> -- /bin/sh
kubectl exec -it <pod-name> -c <container> -- /bin/bash
# 端口转发(本地调试用)
kubectl port-forward pod/<pod-name> 8080:80
kubectl port-forward svc/<svc-name> 8080:80
# 扩缩容
kubectl scale deployment <name> --replicas=5
# 查看资源使用
kubectl top nodes
kubectl top pods
# ============ 高级操作 ============
# 滚动更新镜像
kubectl set image deployment/<name> <container>=<image>:<tag>
# 查看滚动更新状态
kubectl rollout status deployment/<name>
# 回滚到上一个版本
kubectl rollout undo deployment/<name>
# 回滚到指定版本
kubectl rollout history deployment/<name>
kubectl rollout undo deployment/<name> --to-revision=2
# 临时运行一个 Pod(debug 神器)
kubectl run debug --rm -it --image=busybox -- /bin/sh
kubectl run debug --rm -it --image=nicolaka/netshoot -- /bin/bash # 网络调试5.3 kubectl 效率提升
# 配置别名(加到 ~/.bashrc 或 ~/.zshrc)
alias k='kubectl'
alias kgp='kubectl get pods'
alias kgs='kubectl get svc'
alias kgd='kubectl get deploy'
alias kgn='kubectl get nodes'
alias kdp='kubectl describe pod'
alias kaf='kubectl apply -f'
alias kdf='kubectl delete -f'
alias kl='kubectl logs -f'
alias kei='kubectl exec -it'
# 自动补全
source <(kubectl completion bash) # bash
source <(kubectl completion zsh) # zsh
# 快速切换 namespace
kubectl config set-context --current --namespace=production
# 或者安装 kubectx / kubens(强烈推荐)
# kubens production ← 切换 namespace
# kubectx staging ← 切换集群六、用 YAML 定义一切——K8s 的灵魂
6.1 YAML 恐惧症
graph LR
A["第 1 天
YAML 挺简洁的"] --> B["第 7 天
缩进错了一格
整个集群炸了"] B --> C["第 30 天
我的 YAML 文件
比代码还多"] C --> D["第 90 天
我开始用 Helm
生成 YAML"] D --> E["第 180 天
Helm template
比原始 YAML 还复杂"] E --> F["第 365 天
用 AI 写 YAML 😌"] style B fill:#ffcdd2 style E fill:#ffcdd2 style F fill:#c8e6c9
YAML 挺简洁的"] --> B["第 7 天
缩进错了一格
整个集群炸了"] B --> C["第 30 天
我的 YAML 文件
比代码还多"] C --> D["第 90 天
我开始用 Helm
生成 YAML"] D --> E["第 180 天
Helm template
比原始 YAML 还复杂"] E --> F["第 365 天
用 AI 写 YAML 😌"] style B fill:#ffcdd2 style E fill:#ffcdd2 style F fill:#c8e6c9
6.2 最完整的 Deployment YAML(带注释)
apiVersion: apps/v1 # API 版本
kind: Deployment # 资源类型
metadata:
name: payment-service # Deployment 名字
namespace: production # 命名空间
labels:
app: payment # 标签(用于选择器匹配)
team: deposit # Joey 的 Deposit Team 😎
version: v2.1.0
annotations:
description: "NPSS Aani payment processing service"
spec:
replicas: 3 # 副本数
revisionHistoryLimit: 5 # 保留 5 个历史版本(用于回滚)
strategy: # 更新策略
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 更新时最多多出 1 个 Pod
maxUnavailable: 0 # 更新时不允许有 Pod 不可用
selector: # 选择器:匹配哪些 Pod
matchLabels:
app: payment
template: # Pod 模板
metadata:
labels:
app: payment
version: v2.1.0
spec:
# ===== 优雅终止 =====
terminationGracePeriodSeconds: 60
# ===== 安全上下文 =====
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
# ===== 反亲和性(分散到不同节点)=====
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- payment
topologyKey: kubernetes.io/hostname
# ===== Init 容器 =====
initContainers:
- name: wait-for-mysql
image: busybox:1.36
command: ['sh', '-c', 'until nc -z mysql-svc 3306; do echo waiting for mysql; sleep 2; done']
# ===== 主容器 =====
containers:
- name: payment-app
image: registry.astratech.ae/payment-service:v2.1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
protocol: TCP
- name: actuator
containerPort: 8081
protocol: TCP
# ===== 环境变量 =====
env:
- name: SPRING_PROFILES_ACTIVE
value: "production"
- name: TZ
value: "Asia/Dubai"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: payment-secrets
key: db-password
- name: JAVA_OPTS
value: "-XX:MaxRAMPercentage=75.0 -XX:+UseG1GC"
envFrom:
- configMapRef:
name: payment-config
# ===== 资源限制 =====
resources:
requests:
cpu: "250m" # 0.25 核
memory: "512Mi"
limits:
cpu: "1000m" # 1 核
memory: "1Gi"
# ===== 探针 =====
startupProbe: # 启动探针(Java 应用启动慢,先用这个)
httpGet:
path: /actuator/health
port: actuator
failureThreshold: 30
periodSeconds: 10
livenessProbe: # 存活探针(挂了就重启)
httpGet:
path: /actuator/health/liveness
port: actuator
initialDelaySeconds: 0
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe: # 就绪探针(没准备好就不接流量)
httpGet:
path: /actuator/health/readiness
port: actuator
initialDelaySeconds: 0
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# ===== 挂载 =====
volumeMounts:
- name: config-vol
mountPath: /app/config
readOnly: true
- name: logs-vol
mountPath: /app/logs
# ===== 卷定义 =====
volumes:
- name: config-vol
configMap:
name: payment-config-files
- name: logs-vol
emptyDir:
sizeLimit: 500Mi
# ===== 镜像拉取凭证 =====
imagePullSecrets:
- name: registry-credentials😱 一个 Deployment 就写了 130+ 行 YAML。而这只是一个服务。一个完整的微服务体系可能有几十个这样的文件。现在理解为什么有人发明了 Helm 了吧?
6.3 三大探针对比
sequenceDiagram
participant K as Kubelet
participant P as Pod
Note over K,P: 🟡 阶段1:启动探针 (startupProbe)
loop 每10s检查一次,最多30次
K->>P: GET /health
P-->>K: 503 还在启动...
end
K->>P: GET /health
P-->>K: 200 OK 启动完成!
Note over K,P: 🟢 阶段2:就绪探针 (readinessProbe)
loop 每10s
K->>P: GET /health/readiness
alt 返回 200
P-->>K: ✅ Ready
Note over P: 接收流量
else 返回非200
P-->>K: ❌ Not Ready
Note over P: 从 Service 摘除
不再接收新请求 end end Note over K,P: 🔴 阶段3:存活探针 (livenessProbe) loop 每15s K->>P: GET /health/liveness alt 返回 200 P-->>K: ✅ Alive else 连续3次失败 P-->>K: ❌ Dead Note over K: 重启 Pod! end end
不再接收新请求 end end Note over K,P: 🔴 阶段3:存活探针 (livenessProbe) loop 每15s K->>P: GET /health/liveness alt 返回 200 P-->>K: ✅ Alive else 连续3次失败 P-->>K: ❌ Dead Note over K: 重启 Pod! end end
| 探针 | 作用 | 失败后果 | 适用场景 |
|---|---|---|---|
| startupProbe | 判断应用是否启动完成 | 继续等待 | Java/Spring Boot 等启动慢的应用 |
| readinessProbe | 判断是否准备好接收流量 | 从 Service 摘除 | 依赖外部服务、缓存预热 |
| livenessProbe | 判断应用是否还活着 | 重启 Pod | 死锁检测、OOM 恢复 |
⚠️ 血泪教训:不要把 livenessProbe 指向会因为外部依赖(如数据库)失败的接口。否则数据库一挂,所有 Pod 连环重启,雪崩式灾难。
七、Service 与 Ingress——流量怎么进来的
7.1 Service 四种类型
graph TB
subgraph "ClusterIP(默认)"
direction TB
CIP["Service
ClusterIP: 10.96.0.1
只在集群内部可访问"] CIP --> CP1["Pod 1"] CIP --> CP2["Pod 2"] CIP --> CP3["Pod 3"] end subgraph "NodePort" direction TB NP["Service
NodePort: 30080
通过节点IP:端口访问"] EXT1["外部请求
NodeIP:30080"] --> NP NP --> NP1["Pod 1"] NP --> NP2["Pod 2"] end subgraph "LoadBalancer" direction TB LB["云厂商负载均衡器
公网 IP"] LBSVC["Service
LoadBalancer"] LB --> LBSVC LBSVC --> LP1["Pod 1"] LBSVC --> LP2["Pod 2"] end subgraph "ExternalName" direction TB EN["Service
ExternalName"] EDNS["external-api.example.com
DNS CNAME 映射"] EN --> EDNS end style CIP fill:#bbdefb style NP fill:#fff9c4 style LB fill:#c8e6c9 style EN fill:#f3e5f5
ClusterIP: 10.96.0.1
只在集群内部可访问"] CIP --> CP1["Pod 1"] CIP --> CP2["Pod 2"] CIP --> CP3["Pod 3"] end subgraph "NodePort" direction TB NP["Service
NodePort: 30080
通过节点IP:端口访问"] EXT1["外部请求
NodeIP:30080"] --> NP NP --> NP1["Pod 1"] NP --> NP2["Pod 2"] end subgraph "LoadBalancer" direction TB LB["云厂商负载均衡器
公网 IP"] LBSVC["Service
LoadBalancer"] LB --> LBSVC LBSVC --> LP1["Pod 1"] LBSVC --> LP2["Pod 2"] end subgraph "ExternalName" direction TB EN["Service
ExternalName"] EDNS["external-api.example.com
DNS CNAME 映射"] EN --> EDNS end style CIP fill:#bbdefb style NP fill:#fff9c4 style LB fill:#c8e6c9 style EN fill:#f3e5f5
# ClusterIP(集群内部通信)
apiVersion: v1
kind: Service
metadata:
name: payment-svc
spec:
type: ClusterIP
selector:
app: payment
ports:
- port: 80 # Service 端口
targetPort: 8080 # Pod 端口
protocol: TCP
---
# NodePort(开发/测试用)
apiVersion: v1
kind: Service
metadata:
name: payment-svc-nodeport
spec:
type: NodePort
selector:
app: payment
ports:
- port: 80
targetPort: 8080
nodePort: 30080 # 范围:30000-327677.2 Ingress 路由规则
graph LR
INTERNET["🌐 Internet"] --> ING["Ingress Controller
(Nginx / Traefik)"] ING -->|"api.astratech.ae/payment/*"| SVC1["payment-svc
:80"] ING -->|"api.astratech.ae/deposit/*"| SVC2["deposit-svc
:80"] ING -->|"api.astratech.ae/exchange/*"| SVC3["exchange-svc
:80"] ING -->|"dashboard.astratech.ae"| SVC4["dashboard-svc
:80"] SVC1 --> P1["Payment Pods"] SVC2 --> P2["Deposit Pods"] SVC3 --> P3["Exchange Pods"] SVC4 --> P4["Dashboard Pods"]
(Nginx / Traefik)"] ING -->|"api.astratech.ae/payment/*"| SVC1["payment-svc
:80"] ING -->|"api.astratech.ae/deposit/*"| SVC2["deposit-svc
:80"] ING -->|"api.astratech.ae/exchange/*"| SVC3["exchange-svc
:80"] ING -->|"dashboard.astratech.ae"| SVC4["dashboard-svc
:80"] SVC1 --> P1["Payment Pods"] SVC2 --> P2["Deposit Pods"] SVC3 --> P3["Exchange Pods"] SVC4 --> P4["Dashboard Pods"]
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: astratech-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/rate-limit: "100"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.astratech.ae
secretName: api-tls-cert
rules:
- host: api.astratech.ae
http:
paths:
- path: /payment
pathType: Prefix
backend:
service:
name: payment-svc
port:
number: 80
- path: /deposit
pathType: Prefix
backend:
service:
name: deposit-svc
port:
number: 80
- path: /exchange
pathType: Prefix
backend:
service:
name: exchange-svc
port:
number: 80八、ConfigMap 与 Secret——配置管理
8.1 配置和密钥的正确姿势
graph TB
subgraph "❌ 错误做法"
BAD1["把密码写在镜像里"]
BAD2["环境变量硬编码在 YAML"]
BAD3["配置文件 bake 进镜像"]
end
subgraph "✅ 正确做法"
CM["ConfigMap
📝 非敏感配置"] SEC["Secret
🔐 敏感信息"] CM -->|"挂载为文件"| POD1["Pod"] CM -->|"注入为环境变量"| POD1 SEC -->|"挂载为文件"| POD2["Pod"] SEC -->|"注入为环境变量"| POD2 end BAD1 -.->|"别这样"| SEC BAD2 -.->|"别这样"| CM BAD3 -.->|"别这样"| CM
📝 非敏感配置"] SEC["Secret
🔐 敏感信息"] CM -->|"挂载为文件"| POD1["Pod"] CM -->|"注入为环境变量"| POD1 SEC -->|"挂载为文件"| POD2["Pod"] SEC -->|"注入为环境变量"| POD2 end BAD1 -.->|"别这样"| SEC BAD2 -.->|"别这样"| CM BAD3 -.->|"别这样"| CM
# ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: payment-config
data:
# 键值对形式
DB_HOST: "mysql-svc"
DB_PORT: "3306"
REDIS_HOST: "redis-svc"
LOG_LEVEL: "INFO"
# 整个文件形式
application.yml: |
spring:
application:
name: payment-service
datasource:
url: jdbc:mysql://${DB_HOST}:${DB_PORT}/payment_db
hikari:
maximum-pool-size: 20
---
# Secret(base64 编码,注意:不是加密!)
apiVersion: v1
kind: Secret
metadata:
name: payment-secrets
type: Opaque
data:
db-password: cGFzc3dvcmQxMjM= # echo -n "password123" | base64
redis-password: cmVkaXNQQHNz # echo -n "redisP@ss" | base64
api-key: c2VjcmV0LWFwaS1rZXktMTIz # echo -n "secret-api-key-123" | base64
# ⚠️ 严肃提醒:K8s Secret 默认只是 base64 编码,不是加密!
# 生产环境请配合:
# - Sealed Secrets
# - HashiCorp Vault
# - AWS Secrets Manager / Azure Key Vault
# - SOPS (Mozilla)🔥 Secret 的安全真相:K8s 的 Secret 默认存在 etcd 里,而且只是 base64 编码。echo "cGFzc3dvcmQxMjM=" | base64 -d 就能解码。所以生产环境必须启用 etcd 加密或用外部密钥管理服务。九、持久化存储——有状态服务的噩梦
9.1 存储架构
graph TB
subgraph "存储体系"
POD["Pod"] -->|"volumeMounts"| PVC["PVC
PersistentVolumeClaim
📋 我要 10Gi 的 SSD"] PVC -->|"绑定"| PV["PV
PersistentVolume
💾 这里有 10Gi 的 SSD"] SC["StorageClass
🏭 存储工厂"] -->|"动态创建"| PV PV --> BACKEND{"实际存储后端"} BACKEND --> LOCAL["本地磁盘
hostPath"] BACKEND --> NFS["NFS
网络文件系统"] BACKEND --> CLOUD["云存储
EBS / Azure Disk
GCE PD"] BACKEND --> CEPH["Ceph / Rook"] end
PersistentVolumeClaim
📋 我要 10Gi 的 SSD"] PVC -->|"绑定"| PV["PV
PersistentVolume
💾 这里有 10Gi 的 SSD"] SC["StorageClass
🏭 存储工厂"] -->|"动态创建"| PV PV --> BACKEND{"实际存储后端"} BACKEND --> LOCAL["本地磁盘
hostPath"] BACKEND --> NFS["NFS
网络文件系统"] BACKEND --> CLOUD["云存储
EBS / Azure Disk
GCE PD"] BACKEND --> CEPH["Ceph / Rook"] end
# StorageClass(动态存储,推荐)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs # 或其他存储提供商
parameters:
type: gp3
iopsPerGB: "50"
reclaimPolicy: Retain # 删除 PVC 后保留数据
allowVolumeExpansion: true # 允许扩容
---
# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 50Gi9.2 StatefulSet vs Deployment
graph TB
subgraph "Deployment(无状态)"
D_RS["ReplicaSet"]
D_P1["pod-随机hash-abc"]
D_P2["pod-随机hash-def"]
D_P3["pod-随机hash-ghi"]
D_RS --> D_P1
D_RS --> D_P2
D_RS --> D_P3
D_NOTE["Pod 名称随机
可以随意替换
共享存储或无存储
无序启动/终止"] end subgraph "StatefulSet(有状态)" S_SS["StatefulSet"] S_P0["mysql-0 👑
(master)"] S_P1["mysql-1
(slave)"] S_P2["mysql-2
(slave)"] S_PV0["PVC-0
50Gi"] S_PV1["PVC-1
50Gi"] S_PV2["PVC-2
50Gi"] S_SS --> S_P0 --> S_PV0 S_SS --> S_P1 --> S_PV1 S_SS --> S_P2 --> S_PV2 S_NOTE["Pod 名称有序
固定网络标识
各自独立 PVC
有序启动: 0→1→2
有序终止: 2→1→0"] end style D_NOTE fill:#fff9c4 style S_NOTE fill:#bbdefb
可以随意替换
共享存储或无存储
无序启动/终止"] end subgraph "StatefulSet(有状态)" S_SS["StatefulSet"] S_P0["mysql-0 👑
(master)"] S_P1["mysql-1
(slave)"] S_P2["mysql-2
(slave)"] S_PV0["PVC-0
50Gi"] S_PV1["PVC-1
50Gi"] S_PV2["PVC-2
50Gi"] S_SS --> S_P0 --> S_PV0 S_SS --> S_P1 --> S_PV1 S_SS --> S_P2 --> S_PV2 S_NOTE["Pod 名称有序
固定网络标识
各自独立 PVC
有序启动: 0→1→2
有序终止: 2→1→0"] end style D_NOTE fill:#fff9c4 style S_NOTE fill:#bbdefb
🎯 选择指南:无状态服务(Web API, 微服务)→ Deployment。有状态服务(数据库, 消息队列, Zookeeper)→ StatefulSet。
十、Helm——K8s 的包管理器
10.1 为什么需要 Helm
graph TD
subgraph "没有 Helm 😰"
Y1["deployment.yaml"]
Y2["service.yaml"]
Y3["configmap.yaml"]
Y4["secret.yaml"]
Y5["ingress.yaml"]
Y6["pvc.yaml"]
Y7["hpa.yaml"]
Y8["networkpolicy.yaml"]
Y1 --> REPEAT["× 3 个环境
(dev, staging, prod)
= 24 个 YAML 文件
内容几乎一样 😱"] end subgraph "有了 Helm 😌" CHART["📦 Helm Chart"] TMPL["模板 (templates/)"] VAL["values.yaml"] VAL_DEV["values-dev.yaml"] VAL_STG["values-staging.yaml"] VAL_PRD["values-prod.yaml"] CHART --> TMPL CHART --> VAL VAL_DEV -->|"helm install -f"| DEPLOY_D["部署到 Dev"] VAL_STG -->|"helm install -f"| DEPLOY_S["部署到 Staging"] VAL_PRD -->|"helm install -f"| DEPLOY_P["部署到 Prod"] end
(dev, staging, prod)
= 24 个 YAML 文件
内容几乎一样 😱"] end subgraph "有了 Helm 😌" CHART["📦 Helm Chart"] TMPL["模板 (templates/)"] VAL["values.yaml"] VAL_DEV["values-dev.yaml"] VAL_STG["values-staging.yaml"] VAL_PRD["values-prod.yaml"] CHART --> TMPL CHART --> VAL VAL_DEV -->|"helm install -f"| DEPLOY_D["部署到 Dev"] VAL_STG -->|"helm install -f"| DEPLOY_S["部署到 Staging"] VAL_PRD -->|"helm install -f"| DEPLOY_P["部署到 Prod"] end
10.2 Helm 常用命令
# 添加仓库
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
# 搜索 Chart
helm search repo nginx
helm search hub prometheus # 搜索 Artifact Hub
# 安装应用
helm install my-redis bitnami/redis \
--namespace redis \
--create-namespace \
--set auth.password=myRedisP@ss \
--set replica.replicaCount=3
# 用自定义 values 文件安装
helm install my-app ./my-chart -f values-prod.yaml
# 查看已安装的 Release
helm list -A
# 升级
helm upgrade my-app ./my-chart -f values-prod.yaml
# 回滚
helm rollback my-app 1 # 回滚到版本 1
# 卸载
helm uninstall my-app
# 查看渲染后的 YAML(debug 用)
helm template my-app ./my-chart -f values-prod.yaml10.3 Chart 目录结构
my-chart/
├── Chart.yaml # Chart 元信息(名称、版本)
├── values.yaml # 默认配置值
├── values-dev.yaml # 开发环境覆盖值
├── values-prod.yaml # 生产环境覆盖值
├── templates/ # K8s 资源模板
│ ├── _helpers.tpl # 模板函数
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ ├── ingress.yaml
│ ├── hpa.yaml
│ └── NOTES.txt # 安装后提示信息
└── charts/ # 子 Chart 依赖十一、监控与可观测性——出了问题才知道重要
11.1 可观测性三大支柱
graph TB
subgraph "可观测性 Observability"
METRICS["📊 Metrics 指标
Prometheus + Grafana
———
CPU/内存使用率
请求延迟 P99
错误率"] LOGS["📝 Logs 日志
EFK / Loki
———
应用日志
错误堆栈
审计日志"] TRACES["🔍 Traces 链路追踪
Jaeger / Tempo
———
请求在微服务间
的完整调用链路"] end METRICS --- LOGS --- TRACES subgraph "K8s 监控栈推荐" PM["Prometheus
指标收集"] GF["Grafana
可视化"] LK["Loki
日志聚合"] TM["Tempo
链路追踪"] AM["AlertManager
告警通知"] PM --> GF LK --> GF TM --> GF PM --> AM end
Prometheus + Grafana
———
CPU/内存使用率
请求延迟 P99
错误率"] LOGS["📝 Logs 日志
EFK / Loki
———
应用日志
错误堆栈
审计日志"] TRACES["🔍 Traces 链路追踪
Jaeger / Tempo
———
请求在微服务间
的完整调用链路"] end METRICS --- LOGS --- TRACES subgraph "K8s 监控栈推荐" PM["Prometheus
指标收集"] GF["Grafana
可视化"] LK["Loki
日志聚合"] TM["Tempo
链路追踪"] AM["AlertManager
告警通知"] PM --> GF LK --> GF TM --> GF PM --> AM end
11.2 Prometheus + Grafana 一键部署
# 使用 kube-prometheus-stack(Helm Chart,一键搞定)
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install monitoring prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set grafana.adminPassword=admin \
--set prometheus.prometheusSpec.retention=30d \
--set alertmanager.config.global.slack_api_url="https://hooks.slack.com/services/xxx"
# 访问 Grafana Dashboard
kubectl port-forward -n monitoring svc/monitoring-grafana 3000:80
# 打开 http://localhost:3000
# 用户名:admin,密码:admin
# 🎉 内置了 N 个 Dashboard,开箱即用十二、HPA 自动扩缩容——让 K8s 替你加班
12.1 HPA 工作原理
sequenceDiagram
participant HPA as HPA Controller
participant MS as Metrics Server
participant D as Deployment
participant P as Pods (3个)
loop 每 15 秒
HPA->>MS: 查询 Pod CPU/Memory 指标
MS-->>HPA: 当前平均 CPU: 80%
alt CPU > 目标值 (70%)
HPA->>D: 扩容! replicas: 3 → 5
D->>P: 创建 2 个新 Pod
Note over P: 🚀 5 个 Pod
else CPU < 目标值 (70%) 且持续 5 分钟
HPA->>D: 缩容! replicas: 5 → 3
D->>P: 终止 2 个 Pod
Note over P: 📉 3 个 Pod
end
end
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: payment-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: payment-service
minReplicas: 2 # 最少 2 个(高可用)
maxReplicas: 20 # 最多 20 个(防止失控)
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # CPU 超过 70% 就扩
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80 # 内存超过 80% 就扩
behavior:
scaleUp:
stabilizationWindowSeconds: 60 # 扩容等 60 秒再决定
policies:
- type: Percent
value: 100 # 每次最多翻倍
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300 # 缩容等 5 分钟再决定(防抖)
policies:
- type: Percent
value: 25 # 每次最多缩 25%
periodSeconds: 60💡 缩容要比扩容保守:扩容要快(用户在等),缩容要慢(防止波动)。就像招人很急,裁人要慎重。
十三、K8s 排错指南——放弃之前的最后一搏
13.1 Pod 状态诊断
graph TD
START["Pod 出问题了!"] --> STATUS{"kubectl get pod
Pod 状态是什么?"} STATUS -->|"Pending"| PEND{"一直 Pending?"} PEND -->|"资源不足"| PEND_A["节点 CPU/内存不够
kubectl describe pod 看 Events
→ 增加节点或降低 requests"] PEND -->|"调度失败"| PEND_B["nodeSelector/affinity 不匹配
→ 检查标签和污点"] PEND -->|"PVC 未绑定"| PEND_C["StorageClass 不存在
或没有可用 PV
→ 检查存储配置"] STATUS -->|"CrashLoopBackOff"| CRASH["容器启动后立即崩溃"] CRASH --> CRASH_A["1. kubectl logs pod-name --previous
2. 检查启动命令 CMD
3. 检查配置文件
4. 检查依赖服务是否就绪"] STATUS -->|"ImagePullBackOff"| IMG["镜像拉取失败"] IMG --> IMG_A["1. 镜像名/Tag 拼写错误?
2. 私有仓库需要 imagePullSecret?
3. 网络能访问仓库吗?"] STATUS -->|"OOMKilled"| OOM["内存溢出被杀"] OOM --> OOM_A["1. 增加 resources.limits.memory
2. 检查应用是否有内存泄漏
3. JVM: -XX:MaxRAMPercentage"] STATUS -->|"Running
但不正常"| RUN{"Service 能访问吗?"} RUN -->|"不能"| RUN_A["1. kubectl get endpoints
2. Pod labels 和 Service selector 匹配吗?
3. 端口号对吗?"] RUN -->|"能但响应异常"| RUN_B["1. kubectl logs -f pod-name
2. kubectl exec 进去看看
3. 检查 ConfigMap/Secret"]
Pod 状态是什么?"} STATUS -->|"Pending"| PEND{"一直 Pending?"} PEND -->|"资源不足"| PEND_A["节点 CPU/内存不够
kubectl describe pod 看 Events
→ 增加节点或降低 requests"] PEND -->|"调度失败"| PEND_B["nodeSelector/affinity 不匹配
→ 检查标签和污点"] PEND -->|"PVC 未绑定"| PEND_C["StorageClass 不存在
或没有可用 PV
→ 检查存储配置"] STATUS -->|"CrashLoopBackOff"| CRASH["容器启动后立即崩溃"] CRASH --> CRASH_A["1. kubectl logs pod-name --previous
2. 检查启动命令 CMD
3. 检查配置文件
4. 检查依赖服务是否就绪"] STATUS -->|"ImagePullBackOff"| IMG["镜像拉取失败"] IMG --> IMG_A["1. 镜像名/Tag 拼写错误?
2. 私有仓库需要 imagePullSecret?
3. 网络能访问仓库吗?"] STATUS -->|"OOMKilled"| OOM["内存溢出被杀"] OOM --> OOM_A["1. 增加 resources.limits.memory
2. 检查应用是否有内存泄漏
3. JVM: -XX:MaxRAMPercentage"] STATUS -->|"Running
但不正常"| RUN{"Service 能访问吗?"} RUN -->|"不能"| RUN_A["1. kubectl get endpoints
2. Pod labels 和 Service selector 匹配吗?
3. 端口号对吗?"] RUN -->|"能但响应异常"| RUN_B["1. kubectl logs -f pod-name
2. kubectl exec 进去看看
3. 检查 ConfigMap/Secret"]
13.2 排错命令清单
# ===== 第一步:看状态 =====
kubectl get pods -o wide
kubectl get events --sort-by='.lastTimestamp' # 按时间排序的事件
# ===== 第二步:看详情 =====
kubectl describe pod <pod-name> # 重点看 Events 部分!
# ===== 第三步:看日志 =====
kubectl logs <pod-name> # 当前日志
kubectl logs <pod-name> --previous # 崩溃前的日志
kubectl logs <pod-name> --all-containers # 所有容器的日志
# ===== 第四步:进去看 =====
kubectl exec -it <pod-name> -- /bin/sh
# ===== 第五步:网络排查 =====
# 临时启动一个网络调试 Pod
kubectl run netdebug --rm -it --image=nicolaka/netshoot -- /bin/bash
# 在调试 Pod 内:
nslookup payment-svc # DNS 解析
curl -v http://payment-svc:80/health # 服务连通性
traceroute payment-svc # 路由跟踪
# ===== 第六步:资源排查 =====
kubectl top nodes # 节点资源
kubectl top pods # Pod 资源
kubectl describe node <node-name> | grep -A 10 "Allocated" # 已分配资源
# ===== 终极手段 =====
kubectl delete pod <pod-name> # 删了重建
kubectl rollout restart deployment <deploy-name> # 滚动重启13.3 常见错误速查
| 症状 | 常见原因 | 解决方案 |
|---|---|---|
| Pod 一直 Pending | 资源不足 / 调度约束 | kubectl describe pod 看 Events |
| CrashLoopBackOff | 启动命令错误 / 配置缺失 | kubectl logs --previous |
| ImagePullBackOff | 镜像不存在 / 没有拉取权限 | 检查镜像名和 imagePullSecret |
| OOMKilled | 内存不够 | 增加 limits.memory |
| Evicted | 节点资源不足被驱逐 | 检查节点资源 / 设置 PDB |
| Service 不通 | label 不匹配 / 端口错误 | kubectl get endpoints |
| Ingress 404 | path 不匹配 / backend 配错 | 检查 Ingress 规则 |
| PVC Pending | StorageClass 不存在 | kubectl get sc |
十四、生产环境最佳实践——防止半夜被叫醒
14.1 安全清单
graph TD
SEC["🔒 K8s 安全加固"] --> S1["RBAC 权限最小化
别给 cluster-admin"] SEC --> S2["Pod Security Standards
禁止特权容器"] SEC --> S3["Network Policies
微分段,零信任"] SEC --> S4["Secret 加密存储
etcd encryption"] SEC --> S5["镜像签名 & 扫描
只允许可信镜像"] SEC --> S6["Resource Quotas
限制 namespace 资源"] SEC --> S7["API Server 审计
记录所有操作"] SEC --> S8["定期更新 K8s 版本
CVE 修复"]
别给 cluster-admin"] SEC --> S2["Pod Security Standards
禁止特权容器"] SEC --> S3["Network Policies
微分段,零信任"] SEC --> S4["Secret 加密存储
etcd encryption"] SEC --> S5["镜像签名 & 扫描
只允许可信镜像"] SEC --> S6["Resource Quotas
限制 namespace 资源"] SEC --> S7["API Server 审计
记录所有操作"] SEC --> S8["定期更新 K8s 版本
CVE 修复"]
14.2 资源管理黄金法则
# 每个容器都必须设置 resources!
resources:
requests: # 调度依据(保证能拿到这么多)
cpu: "250m" # 1/4 核
memory: "256Mi"
limits: # 上限(超过会被 throttle/OOMKill)
cpu: "1000m" # 1 核
memory: "512Mi"
# 💡 经验值:
# - requests ≈ 实际平均使用量
# - limits = requests × 2~4
# - 不设 limits → 一个 Pod 吃光整个节点
# - requests = limits → 保证 QoS 为 Guaranteed(最不容易被驱逐)14.3 高可用部署 Checklist
graph TD
HA["🏆 高可用部署"] --> H1["✅ replicas ≥ 2
至少两个副本"] HA --> H2["✅ Pod 反亲和
分散到不同节点"] HA --> H3["✅ PodDisruptionBudget
限制同时不可用数"] HA --> H4["✅ 滚动更新策略
maxUnavailable: 0"] HA --> H5["✅ 健康检查三件套
startup + readiness + liveness"] HA --> H6["✅ Resource requests/limits
防止资源饥饿"] HA --> H7["✅ 优雅终止
preStop + SIGTERM 处理"] HA --> H8["✅ HPA 自动扩缩容
应对流量波动"]
至少两个副本"] HA --> H2["✅ Pod 反亲和
分散到不同节点"] HA --> H3["✅ PodDisruptionBudget
限制同时不可用数"] HA --> H4["✅ 滚动更新策略
maxUnavailable: 0"] HA --> H5["✅ 健康检查三件套
startup + readiness + liveness"] HA --> H6["✅ Resource requests/limits
防止资源饥饿"] HA --> H7["✅ 优雅终止
preStop + SIGTERM 处理"] HA --> H8["✅ HPA 自动扩缩容
应对流量波动"]
# PodDisruptionBudget - 限制同时中断数
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: payment-pdb
spec:
minAvailable: 2 # 至少保持 2 个可用
# 或者 maxUnavailable: 1 # 最多 1 个不可用
selector:
matchLabels:
app: payment十五、K8s 生态全景图
mindmap
root((Kubernetes 生态))
包管理
Helm
Kustomize
Jsonnet
CI/CD
ArgoCD
FluxCD
Tekton
Jenkins X
监控
Prometheus
Grafana
Datadog
New Relic
日志
EFK Stack
Loki
Fluentbit
网络
Calico
Cilium
Istio Service Mesh
Linkerd
存储
Rook/Ceph
Longhorn
OpenEBS
安全
Falco
OPA/Gatekeeper
Trivy
Cert-Manager
开发体验
Telepresence
Skaffold
Tilt
DevSpace
多集群
Rancher
KubeFed
Crossplane
😵 看到这张图,你可能已经理解了为什么这个系列叫「从入门到放弃」了。
十六、K8s 学习路线图——如果你还没放弃
graph TB
L1["🌱 入门阶段
2-4 周"] L1 --> L1_1["理解核心概念
Pod, Deployment, Service"] L1 --> L1_2["Minikube 上手"] L1 --> L1_3["kubectl 基本操作"] L1 --> L1_4["写简单的 YAML"] L2["🌿 进阶阶段
1-2 个月"] L1 --> L2 L2 --> L2_1["ConfigMap, Secret
存储管理"] L2 --> L2_2["Ingress, NetworkPolicy"] L2 --> L2_3["Helm 包管理"] L2 --> L2_4["健康检查, 滚动更新"] L3["🌳 高级阶段
2-3 个月"] L2 --> L3 L3 --> L3_1["HPA, VPA 自动扩缩容"] L3 --> L3_2["RBAC 权限管理"] L3 --> L3_3["Prometheus 监控体系"] L3 --> L3_4["CI/CD 集成 (ArgoCD)"] L4["🏔️ 专家阶段
3-6 个月"] L3 --> L4 L4 --> L4_1["Service Mesh (Istio)"] L4 --> L4_2["Operator 开发"] L4 --> L4_3["多集群管理"] L4 --> L4_4["K8s 源码阅读"] L5["🌌 放弃阶段"] L4 --> L5 L5 --> L5_1["老板说要学 Serverless 了..."] style L1 fill:#e8f5e9 style L2 fill:#c8e6c9 style L3 fill:#a5d6a7 style L4 fill:#81c784 style L5 fill:#ffcdd2
2-4 周"] L1 --> L1_1["理解核心概念
Pod, Deployment, Service"] L1 --> L1_2["Minikube 上手"] L1 --> L1_3["kubectl 基本操作"] L1 --> L1_4["写简单的 YAML"] L2["🌿 进阶阶段
1-2 个月"] L1 --> L2 L2 --> L2_1["ConfigMap, Secret
存储管理"] L2 --> L2_2["Ingress, NetworkPolicy"] L2 --> L2_3["Helm 包管理"] L2 --> L2_4["健康检查, 滚动更新"] L3["🌳 高级阶段
2-3 个月"] L2 --> L3 L3 --> L3_1["HPA, VPA 自动扩缩容"] L3 --> L3_2["RBAC 权限管理"] L3 --> L3_3["Prometheus 监控体系"] L3 --> L3_4["CI/CD 集成 (ArgoCD)"] L4["🏔️ 专家阶段
3-6 个月"] L3 --> L4 L4 --> L4_1["Service Mesh (Istio)"] L4 --> L4_2["Operator 开发"] L4 --> L4_3["多集群管理"] L4 --> L4_4["K8s 源码阅读"] L5["🌌 放弃阶段"] L4 --> L5 L5 --> L5_1["老板说要学 Serverless 了..."] style L1 fill:#e8f5e9 style L2 fill:#c8e6c9 style L3 fill:#a5d6a7 style L4 fill:#81c784 style L5 fill:#ffcdd2
结语:从入门到放弃,再到不得不捡起来
K8s 的学习曲线长这样:
graph LR
A["🤩 K8s 好酷!
kubectl get pods"] --> B["📚 概念好多..."] B --> C["📝 YAML 好长..."] C --> D["🔧 为什么 Pod 是 Pending?"] D --> E["😱 CrashLoopBackOff!!!"] E --> F["💀 网络不通 + 存储丢了"] F --> G["🪦 想放弃了..."] G --> H["😤 不行,简历上已经写了"] H --> I["🧠 突然顿悟声明式的美"] I --> J["🏆 真正掌握 K8s"] J --> K["😭 老板说要学 Service Mesh..."] K --> L["🌀 Istio 从入门到放弃(预告)"]
kubectl get pods"] --> B["📚 概念好多..."] B --> C["📝 YAML 好长..."] C --> D["🔧 为什么 Pod 是 Pending?"] D --> E["😱 CrashLoopBackOff!!!"] E --> F["💀 网络不通 + 存储丢了"] F --> G["🪦 想放弃了..."] G --> H["😤 不行,简历上已经写了"] H --> I["🧠 突然顿悟声明式的美"] I --> J["🏆 真正掌握 K8s"] J --> K["😭 老板说要学 Service Mesh..."] K --> L["🌀 Istio 从入门到放弃(预告)"]
记住:没有人真正"精通" Kubernetes,我们都只是在不同阶段的放弃与坚持之间反复横跳。
而最终你会发现:放弃 K8s 最大的障碍,是你的简历上已经写了"熟练掌握 Kubernetes"。
📝 关于本文
这是「从入门到放弃」系列的又一力作。如果你从 Docker 篇一路读到这里还没放弃,说明你是一个内心极其强大的人。
更多技术分享请访问 underestimated.cn
系列回顾: Docker 从入门到放弃 → Kubernetes 从入门到放弃(本篇) → Istio 从入门到放弃(敬请期待)
最后的最后,保命命令升级版:
# Docker 版
alias yolo-docker='docker system prune -a --volumes -f'
# K8s 版(请勿在生产环境执行)
alias yolo-k8s='kubectl delete all --all -A && echo "☸️ Cluster is clean. Your career might not be."'
# 终极版
alias yolo='yolo-docker && yolo-k8s && echo "🔥 You are now free. And unemployed."'用之前请三思。用之后请更新简历。