Kubernetes 常用运维指令手册
Kubernetes 常用运维指令手册
适用版本:Kubernetes 1.35 / 1.36 / 1.37(当前支持窗口,N-2) 配套:containerd 2.x、kubeadm v1beta4、CNI(Calico/Cilium/Flannel)、CSI、k3s 约定:
#root 或 sudo,$普通用户,<xxx>为占位符,<ns>为 namespace。 差异标注:正文用> **版本差异**标注跨版本不一致之处。 更新时间:2026-11(版本事实核对至 2026-11-05)
目录
- 版本与生命周期
- 架构与组件
- 集群部署
- 节点管理
- kubectl 全解
- 工作负载
- 调度
- 存储
- 网络
- 配置与密钥
- RBAC 与认证
- 集群安全
- 集群运维
- 故障排查
- 监控与日志
- 备份与迁移
- 卸载与重建
- 高频故障速查
- 附录
1. 版本与生命周期
1.1 发布模型
Kubernetes 每年 3 个 minor 版本,约 15 周(4 个月) 一发,每个版本维护约 14 个月,只支持最近 3 个 minor(N-2)。
| 版本 | GA 时间 | 支持截止 | 最新补丁 | 状态 |
|---|---|---|---|---|
| 1.37 | 2026-08-26 | 2027-10-28 | 1.37.2 | 当前稳定,首选 |
| 1.36 | 2026-04-22 | 2027-06-28 | 1.36.6 | 支持中 |
| 1.35 | 2025-12-17 | 2027-02-28 | 1.35.10 | 支持中,剩余时间最短 |
| 1.34 | 2025-08-27 | 2026-10-27 | 1.34.13 | 已 EOL,禁止新部署 |
| 1.33 及更早 | — | 已 EOL | — | 安全风险 |
下一版本 1.38 预计 2026-12-16(code freeze 2026-11-17)。
1.2 三个必须知道的时间点
- cgroup v1 已不可用:1.35 起 kubelet 在 cgroup v1 上默认直接失败。CentOS 7 等老系统不能作为新集群节点。
- kube-proxy 的 ipvs 模式已弃用(1.35 起):新集群用
iptables或nftables模式。 - Ingress NGINX 已退役(2026-03 归档):新项目直接上 Gateway API,存量集群要规划迁移。
1.3 1.33 → 1.37 重要变更(升级影响)
| 版本 | 变更 | 影响 |
|---|---|---|
| 1.33 | 原生 sidecar 容器 GA(initContainers + restartPolicy: Always) |
可替代注入式 sidecar |
| 1.33 | Ingress NGINX 退役公告 | 规划迁 Gateway API |
| 1.34 | Dynamic Resource Allocation GA | GPU/特殊设备分配 |
| 1.34 | kubectl get -o kyaml(1.37 GA) |
更易读的输出格式 |
| 1.35 | cgroup v1 默认失败;ipvs 弃用;In-place Pod resize GA | 老节点必须升级系统 |
| 1.36 | containerd 1.x 不再支持;MutatingAdmissionPolicy GA | 运行时必须升 2.x |
| 1.37 | KYAML GA;HPA scale-to-zero(beta);静态 Pod 不能引用 Secret/ConfigMap | 静态 Pod 需改造 |
版本差异:
kubeadm.k8s.io/v1beta3在 1.31 弃用,1.32+ 使用 v1beta4。extraArgs从 map 改为[{name, value}]列表,dryRun/timeout移出配置文件。抄 1.2x 的旧 yaml 会直接解析失败。
1.4 版本识别
kubectl version -o yaml
kubectl version --short 2>/dev/null || kubectl version
kubectl get nodes -o wide
kubeadm version -o short
kubelet --version
crictl version
# 服务端各组件的版本
kubectl get --raw /version | jq
kubectl -n kube-system get pod -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].image}{"\n"}{end}'
1.5 API 废弃检查(升级前必做)
# 1) 官方工具(推荐):pluto
pluto detect-all-in-cluster
pluto detect-files -d ./manifests
# 2) kubectl 插件
kubectl krew install deprecated-api
kubectl deprecated-api
# 3) 内置:查看 API 资源版本
kubectl api-resources --verbs=list --namespaced -o name | \
xargs -n1 kubectl get --show-kind --ignore-not-found -A 2>/dev/null | head
# 4) 审计日志找废弃 API 调用
# apiserver 开 audit 后:
grep -o '"resource":"[^"]*"' /var/log/kubernetes/audit.log | sort | uniq -c | sort -rn | head
常见已移除 API(1.29 之后):
| 移除的 API | 替代 |
|---|---|
autoscaling/v2beta2 |
autoscaling/v2 |
policy/v1beta1 PodDisruptionBudget |
policy/v1 |
batch/v1beta1 CronJob |
batch/v1 |
node.k8s.io/v1beta1 RuntimeClass |
node.k8s.io/v1 |
flowcontrol.apiserver.k8s.io/v1beta2 |
.../v1 |
extensions/v1beta1 Ingress |
networking.k8s.io/v1 |
2. 架构与组件
2.1 组件关系
┌────────────────────────────────────────┐
│ Control Plane │
│ ┌───────────┐ ┌──────────────────┐ │
kubectl ────────▶│ │ apiserver │◀──│ etcd │ │
│ └─────┬─────┘ └──────────────────┘ │
│ │ │
│ ┌─────▼──────┐ ┌──────────────────┐ │
│ │ controller │ │ scheduler │ │
│ │ manager │ │ │ │
│ └────────────┘ └──────────────────┘ │
└────────────┬───────────────────────────┘
│ (watch/list)
┌────────────────────────┼────────────────────────┐
│ │ │
┌────▼─────┐ ┌─────▼────┐ ┌─────▼────┐
│ kubelet │ │ kubelet │ │ kubelet │
│ + │ │ + │ │ + │
│containerd│ │containerd│ │containerd│
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
┌────▼─────┐ ┌────▼─────┐ ┌────▼─────┐
│ Pods │ │ Pods │ │ Pods │
└──────────┘ └──────────┘ └──────────┘
└─────────── CNI(跨节点网络)────────────────┘
| 组件 | 职责 | 挂了会怎样 |
|---|---|---|
kube-apiserver |
唯一入口,认证/授权/准入/持久化 | kubectl 全部失效;已运行 pod 不受影响 |
etcd |
集群唯一状态存储 | apiserver 无法读写,集群"假死" |
kube-controller-manager |
各种控制器(Deploy/Node/Job…) | 期望状态不再收敛(不重建 pod、不扩缩) |
kube-scheduler |
决定 pod 落在哪个节点 | 新 pod 一直 Pending |
kubelet |
节点代理,管 pod 生命周期、探针、上报状态 | 该节点 NotReady |
kube-proxy |
Service 的 iptables/nftables 规则 | Service 访问异常(用 Cilium 可替代) |
| CNI 插件 | pod 网络、IP 分配 | pod 无法通信、节点 NotReady |
| CoreDNS | 集群内 DNS | 服务发现失败 |
2.2 控制面高可用基础
- apiserver 无状态 → 多副本 + LB/VIP
- scheduler / controller-manager → 多副本但只有 leader 工作(选举)
- etcd → 需要 奇数个(3/5),基于 Raft,多数派可用
| 节点数 | 容忍故障 |
|---|---|
| 3 | 1 |
| 5 | 2 |
| 7 | 3 |
3. 集群部署
3.1 规划
最小生产:3 control-plane + 3 worker,etcd 与 control-plane 混部(≤5 节点可接受)。
| 角色 | 数量 | 规格(参考) |
|---|---|---|
| control-plane | 3 | 4C8G,SSD 100G |
| worker | ≥3 | 8C16G 起,数据盘按业务 |
| etcd(独立部署时) | 3/5 | 4C8G,SSD 必需,延迟 < 10ms |
网段规划(必须提前定,后期改代价大):
| 网段 | 示例 | 说明 |
|---|---|---|
| 节点网段 | 10.0.0.0/24 | 物理网络 |
| Service CIDR | 10.96.0.0/12 | 不与节点/办公网重叠 |
| Pod CIDR | 10.244.0.0/16 | 不与节点/办公网重叠 |
| VIP(apiserver) | 10.0.0.100 | HA 用 |
3.2 所有节点:系统准备
# 主机名
hostnamectl set-hostname k8s-master01
# hosts
cat >>/etc/hosts <<'EOF'
10.0.0.11 k8s-master01
10.0.0.12 k8s-master02
10.0.0.13 k8s-master03
10.0.0.21 k8s-node01
10.0.0.22 k8s-node02
10.0.0.23 k8s-node03
10.0.0.100 api.k8s.local
EOF
# 时间同步(证书与 etcd 强依赖)
dnf install -y chrony && systemctl enable --now chronyd
chronyc sources -v
# 关闭 swap
swapoff -a
sed -ri 's/^([^#].*\sswap\s.*)$/#\1/' /etc/fstab
free -h
# 内核模块与参数
cat >/etc/modules-load.d/k8s.conf <<'EOF'
overlay
br_netfilter
EOF
modprobe overlay && modprobe br_netfilter
cat >/etc/sysctl.d/99-k8s.conf <<'EOF'
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
# 高并发场景(Service 多时防 conntrack 满)
net.netfilter.nf_conntrack_max = 1048576
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 1024
EOF
sysctl --system
# 确认 cgroup v2(1.35+ 必须)
stat -fc %T /sys/fs/cgroup/ # 必须输出 cgroup2fs
# SELinux(官方要求 permissive 或 disabled)
setenforce 0
sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
# 防火墙放行
for p in 6443 2379 2380 10250 10257 10259 8472; do
firewall-cmd --permanent --add-port=${p}/tcp
done
firewall-cmd --permanent --add-port=8472/udp
firewall-cmd --reload
# 装 iproute-tc(kubeadm 需要)
dnf install -y iproute-tc
坑点:
fs.inotify.max_user_watches太小会导致大量 pod 场景下 kubelet 报 "too many open files"、日志采集异常。生产默认调到 524288。
3.3 所有节点:容器运行时
# 装 containerd(详见 containerd 分册)
dnf install -y containerd.io
containerd config default > /etc/containerd/config.toml
# 两处必改
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# sandbox_image 与 k8s 版本匹配(1.37 用 pause:3.10)
sed -i 's|sandbox_image = ".*"|sandbox_image = "registry.k8s.io/pause:3.10"|' /etc/containerd/config.toml
systemctl enable --now containerd
systemctl status containerd | head -5
3.4 所有节点:kube 组件
cat >/etc/yum.repos.d/kubernetes.repo <<'EOF'
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.37/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.37/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
dnf install -y kubelet kubeadm kubectl cri-tools --disableexcludes=kubernetes
systemctl enable kubelet # 先不 start,等 init 后自动拉起
kubeadm version -o short
kubelet --version
Debian 系:
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.37/deb/Release.key \
| 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.37/deb/ /' \
> /etc/apt/sources.list.d/kubernetes.list
apt update && apt install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl
3.5 镜像准备(国内必需)
kubeadm config images list --kubernetes-version v1.37.2
# 方式一:init 时指定仓库
# --image-repository registry.aliyuncs.com/google_containers
# 方式二:containerd mirror(推荐,全局生效)
mkdir -p /etc/containerd/certs.d/registry.k8s.io
cat >/etc/containerd/certs.d/registry.k8s.io/hosts.toml <<'EOF'
server = "https://registry.k8s.io"
[host."https://registry.aliyuncs.com/google_containers"]
capabilities = ["pull", "resolve"]
EOF
systemctl restart containerd
# 预拉取
kubeadm config images pull --kubernetes-version v1.37.2
crictl images | grep -E 'kube|pause|etcd'
3.6 单 master 初始化
cat >/tmp/kubeadm-config.yaml <<'EOF'
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 10.0.0.11
bindPort: 6443
nodeRegistration:
criSocket: unix:///run/containerd/containerd.sock
imagePullPolicy: IfNotPresent
taints: []
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.37.2
clusterName: prod
imageRepository: registry.aliyuncs.com/google_containers
controlPlaneEndpoint: "10.0.0.11:6443"
networking:
serviceSubnet: 10.96.0.0/12
podSubnet: 10.244.0.0/16
dnsDomain: cluster.local
apiServer:
certSANs:
- 10.0.0.11
- api.k8s.local
timeoutForControlPlane: 4m0s
extraArgs:
- name: audit-log-path
value: /var/log/kubernetes/audit.log
- name: audit-log-maxage
value: "30"
- name: audit-log-maxbackup
value: "10"
- name: audit-log-maxsize
value: "100"
- name: audit-policy-file
value: /etc/kubernetes/audit-policy.yaml
extraVolumes:
- name: audit-policy
hostPath: /etc/kubernetes/audit-policy.yaml
mountPath: /etc/kubernetes/audit-policy.yaml
readOnly: true
- name: audit-log
hostPath: /var/log/kubernetes
mountPath: /var/log/kubernetes
pathType: DirectoryOrCreate
controllerManager:
extraArgs:
- name: bind-address
value: 0.0.0.0
scheduler:
extraArgs:
- name: bind-address
value: 0.0.0.0
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
failSwapOn: false
maxPods: 250
serializeImagePulls: false
evictionHard:
nodefs.available: "10%"
imagefs.available: "15%"
memory.available: "500Mi"
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: nftables
EOF
# 预检(会检查端口、swap、cgroup、运行时)
kubeadm init --config /tmp/kubeadm-config.yaml --dry-run
# 正式初始化
kubeadm init --config /tmp/kubeadm-config.yaml --upload-certs
初始化成功后:
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
export KUBECONFIG=/etc/kubernetes/admin.conf
kubectl get nodes # 此时 NotReady,等 CNI
kubectl get pod -n kube-system
3.7 安装 CNI
# Flannel(简单,内网够用,不支持 NetworkPolicy)
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
# Calico(支持 NetworkPolicy,生产常用)
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.30/manifests/calico.yaml
# 或 Tigera operator
# kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.30/manifests/tigera-operator.yaml
# kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.30/manifests/custom-resources.yaml
# Cilium(eBPF,可替代 kube-proxy,性能好)
helm repo add cilium https://helm.cilium.io/
helm install cilium cilium/cilium --namespace kube-system \
--set kubeProxyReplacement=true \
--set k8sServiceHost=10.0.0.100 \
--set k8sServicePort=6443
# 验证
kubectl get pod -n kube-system -o wide | grep -E 'flannel|calico|cilium'
kubectl get nodes
坑点:CNI 的 podCIDR 必须与 kubeadm 的
podSubnet一致。Flannel 默认10.244.0.0/16,Calico 默认192.168.0.0/16——用 Calico 时要改CALICO_IPV4POOL_CIDR或 kubeadm 配192.168.0.0/16。
3.8 加入 worker
# master 上生成
kubeadm token create --print-join-command
# kubeadm join 10.0.0.11:6443 --token xxx --discovery-token-ca-cert-hash sha256:xxx
# worker 执行上述命令
# 验证
kubectl get nodes -o wide
3.9 加入 control-plane(HA)
# 在 master01 生成带证书的 join 命令
kubeadm init phase upload-certs --upload-certs
# 输出 certificate key
kubeadm token create --print-join-command --certificate-key <key>
# 在 master02/03 执行
kubeadm join 10.0.0.100:6443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--control-plane \
--certificate-key <key>
3.10 完整 HA(VIP + keepalived)
# 三台 master 上装
dnf install -y keepalived
cat >/etc/keepalived/keepalived.conf <<'EOF'
global_defs {
router_id k8s-ha
script_user root
enable_script_security
}
vrrp_script check_apiserver {
script "/etc/keepalived/check_apiserver.sh"
interval 3
weight -20
fall 3
rise 2
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
advert_int 1
nopreempt
authentication {
auth_type PASS
auth_pass K8sHa123
}
virtual_ipaddress {
10.0.0.100/24
}
track_script {
check_apiserver
}
}
EOF
# 健康检查脚本
cat >/etc/keepalived/check_apiserver.sh <<'EOF'
#!/bin/bash
err=0
for k in $(seq 1 5); do
curl -sf --max-time 2 https://127.0.0.1:6443/healthz >/dev/null || err=1
[ $err -eq 0 ] && break
sleep 2
done
exit $err
EOF
chmod +x /etc/keepalived/check_apiserver.sh
systemctl enable --now keepalived
ip a | grep 10.0.0.100
# kubeadm 用 VIP 作 controlPlaneEndpoint
# controlPlaneEndpoint: "10.0.0.100:6443"
3.11 k3s(单机/边缘)
# server
curl -sfL https://get.k3s.io | INSTALL_K3S_MIRROR=cn \
sh -s - server \
--cluster-init \
--tls-san 10.0.0.11 \
--disable traefik \
--write-kubeconfig-mode 644
cat /var/lib/rancher/k3s/server/node-token
# agent
curl -sfL https://get.k3s.io | INSTALL_K3S_MIRROR=cn \
K3S_URL=https://10.0.0.11:6443 K3S_TOKEN=<token> sh -
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
k3s kubectl get nodes
# 配置化
cat >/etc/rancher/k3s/config.yaml <<'EOF'
write-kubeconfig-mode: "0644"
tls-san: ["10.0.0.11", "k3s.local"]
disable: ["traefik"]
EOF
systemctl restart k3s
3.12 部署后验收
# 1) 节点
kubectl get nodes -o wide
# 2) 系统组件
kubectl get pod -n kube-system
kubectl get pod -n kube-system -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.phase}{"\t"}{.status.containerStatuses[0].restartCount}{"\n"}{end}'
# 3) DNS
kubectl run -it --rm dnstest --image=busybox:1.36 --restart=Never -- nslookup kubernetes.default
kubectl get svc -n kube-system kube-dns
# 4) 跨节点网络
kubectl create deploy nginx --image=nginx:1.25 --replicas=3
kubectl get pod -o wide
# 进一个 pod ping 另一个 pod IP
# 5) Service
kubectl expose deploy nginx --port=80 --target-port=80
kubectl run -it --rm curltest --image=curlimages/curl --restart=Never -- curl -s nginx
# 6) 证书
kubeadm certs check-expiration
# 7) etcd
kubectl -n kube-system exec etcd-k8s-master01 -- etcdctl \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
endpoint health
# 8) 清理测试
kubectl delete deploy nginx && kubectl delete svc nginx
4. 节点管理
4.1 查看
kubectl get nodes -o wide
kubectl get nodes --show-labels
kubectl describe node <node>
kubectl top node
kubectl get node <node> -o yaml
# 资源分配情况
kubectl describe node <node> | grep -A8 'Allocated resources'
# 节点上运行的 pod
kubectl get pod -A --field-selector spec.nodeName=<node> -o wide
# 按状态筛选
kubectl get nodes --no-headers | awk '$2!="Ready"{print}'
自定义列:
kubectl get nodes -o custom-columns=\
'NAME:.metadata.name,STATUS:.status.conditions[-1].type,ROLES:.metadata.labels.node-role\.kubernetes\.io/control-plane,VER:.status.nodeInfo.kubeletVersion,IP:.status.addresses[?(@.type=="InternalIP")].address,OS:.status.nodeInfo.osImage'
4.2 标记与调度控制
# cordon:不可调度(新 pod 不进来,已有 pod 不动)
kubectl cordon <node>
kubectl uncordon <node>
# drain:驱逐 pod 并 cordon
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data --force --timeout=300s
# --force:驱逐非控制器管理的 pod(裸 pod)
# --delete-emptydir-data:删除 emptyDir 数据(会丢临时数据)
# --dry-run=server:先看会做什么
# 恢复
kubectl uncordon <node>
4.3 污点(Taint)
# 语法:key=value:effect
# effect: NoSchedule / PreferNoSchedule / NoExecute
kubectl taint node <node> dedicated=gpu:NoSchedule
kubectl taint node <node> node.kubernetes.io/unschedulable:NoSchedule
kubectl taint node <node> dedicated=gpu:NoSchedule- # 删除(末尾加 -)
kubectl taint node <node> dedicated- # 删该 key 全部
kubectl describe node <node> | grep Taints
# 内置污点
# node.kubernetes.io/not-ready:NoExecute
# node.kubernetes.io/unreachable:NoExecute
# node.kubernetes.io/disk-pressure:NoSchedule
# node.kubernetes.io/memory-pressure:NoSchedule
# node.kubernetes.io/pid-pressure:NoSchedule
# node.kubernetes.io/unschedulable:NoSchedule
4.4 标签(Label)
kubectl label node <node> disktype=ssd
kubectl label node <node> topology.kubernetes.io/zone=az1
kubectl label node <node> node-role.kubernetes.io/worker=""
kubectl label node <node> disktype- # 删除
kubectl label node <node> disktype=hdd --overwrite
# 常用标准标签
# topology.kubernetes.io/region
# topology.kubernetes.io/zone
# node.kubernetes.io/instance-type
# kubernetes.io/arch / kubernetes.io/os
4.5 节点扩容与替换
# 扩容:新机器按 3.2-3.4 准备,然后 join
kubeadm join <endpoint> --token <t> --discovery-token-ca-cert-hash sha256:<h>
# 替换:先加新节点,再排空旧节点,最后删除
kubectl drain <old-node> --ignore-daemonsets --delete-emptydir-data
kubectl delete node <old-node>
# 旧机器上执行
kubeadm reset -f
4.6 节点状态判读
| STATUS | 含义 | 排查 |
|---|---|---|
Ready |
正常 | — |
NotReady |
kubelet 未上报 Ready | 见 14.2 |
Ready,SchedulingDisabled |
已 cordon | 正常维护状态 |
Unknown |
kubelet 失联(心跳超时) | 网络/kubelet 进程 |
带 MemoryPressure |
内存不足 | free -h、驱逐阈值 |
带 DiskPressure |
磁盘不足 | df -h、镜像回收 |
带 PIDPressure |
进程数过多 | --pods-per-core、pids limit |
kubectl describe node <node> | sed -n '/Conditions:/,/Addresses:/p'
5. kubectl 全解
5.1 kubeconfig 与多集群
kubectl config view
kubectl config view --minify
kubectl config get-contexts
kubectl config current-context
kubectl config use-context prod
kubectl config set-context --current --namespace=web
kubectl config rename-context old prod
# 合并多个 kubeconfig
KUBECONFIG=~/.kube/a:~/.kube/b kubectl config view --flatten > ~/.kube/merged
export KUBECONFIG=~/.kube/merged
# 提取当前 context 的独立 kubeconfig(给他人用)
kubectl config view --minify --flatten --context=prod > /tmp/prod.kubeconfig
# 用 token 认证
kubectl --server=https://10.0.0.100:6443 --token=<token> --insecure-skip-tls-verify get nodes
5.2 输出格式
# wide
kubectl get pod -o wide
# yaml / json
kubectl get pod web -o yaml
kubectl get pod web -o json | jq '.status'
# name
kubectl get pod -o name
# custom-columns
kubectl get pod -o custom-columns='NAME:.metadata.name,NODE:.spec.nodeName,IP:.status.podIP'
# jsonpath
kubectl get pod -o jsonpath='{.items[*].metadata.name}'
kubectl get pod -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.podIP}{"\n"}{end}'
kubectl get node -o jsonpath='{.items[0].status.nodeInfo.kubeletVersion}'
# go-template
kubectl get pod -o go-template='{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}'
# kyaml(1.37 GA,更易读)
kubectl get pod web -o kyaml
# sort
kubectl get pod --sort-by=.metadata.creationTimestamp
kubectl get pod --sort-by='.status.containerStatuses[0].restartCount'
# 只取某些字段
kubectl get svc -A -o wide
kubectl get events --sort-by=.lastTimestamp
5.3 创建与变更
# 声明式(推荐)
kubectl apply -f app.yaml
kubectl apply -f ./manifests/ # 整个目录
kubectl apply -f https://example.com/app.yaml
kubectl apply -f app.yaml --dry-run=server
kubectl apply -f app.yaml --prune -l app=web # 删除清单中不存在的资源
# 命令式(快速验证)
kubectl create deploy web --image=nginx:1.25 --replicas=3
kubectl create ns prod
kubectl create configmap cm --from-file=./conf
kubectl create secret generic s --from-literal=k=v
kubectl create job test --image=busybox -- echo hi
kubectl create cronjob backup --image=busybox --schedule="0 2 * * *" -- echo backup
kubectl expose deploy web --port=80 --target-port=80 --type=ClusterIP
kubectl run tmp --image=busybox:1.36 --rm -it --restart=Never -- sh
# 生成 yaml 不落地
kubectl create deploy web --image=nginx --dry-run=client -o yaml > web.yaml
kubectl run tmp --image=busybox --dry-run=client -o yaml > pod.yaml
# 编辑与打补丁
kubectl edit deploy web
kubectl patch deploy web -p '{"spec":{"replicas":3}}'
kubectl patch deploy web --type=json -p '[{"op":"replace","path":"/spec/replicas","value":3}]'
kubectl set image deploy/web web=nginx:1.26
kubectl set env deploy/web TZ=Asia/Shanghai
kubectl set resources deploy/web --limits=cpu=2,memory=2Gi
kubectl label/annotate ...
5.4 删除
kubectl delete pod web
kubectl delete pod web --grace-period=0 --force # 卡 Terminating 时
kubectl delete -f app.yaml
kubectl delete deploy web
kubectl delete pod -l app=web
kubectl delete pod --field-selector status.phase=Failed
kubectl delete ns prod # ★ 删 ns 会删其中所有资源
kubectl delete all --all -n test # ★ 危险
kubectl delete pvc --all -n test # ★ 可能连带删底层存储
# 删除卡住
kubectl patch pod web -p '{"metadata":{"finalizers":null}}' --type=merge
kubectl api-resources --verbs=list --namespaced -o name | \
xargs -n1 kubectl get -n <ns> --show-kind --ignore-not-found
5.5 等待与监视
kubectl wait --for=condition=Ready pod/web --timeout=120s
kubectl wait --for=condition=Available deploy/web --timeout=300s
kubectl wait --for=delete pod/web --timeout=60s
kubectl get pod -w
kubectl get pod -w -o wide
watch -n2 'kubectl get pod -o wide'
kubectl rollout status deploy/web -w
kubectl logs -f <pod>
5.6 调试与转发
kubectl logs <pod>
kubectl logs <pod> -c <container>
kubectl logs <pod> --previous
kubectl logs <pod> --tail=200 --timestamps
kubectl logs -l app=web --max-log-requests=10
kubectl logs <pod> --since=10m
kubectl logs <pod> --since-time="2026-11-05T08:00:00Z"
kubectl exec -it <pod> -- sh
kubectl exec -it <pod> -c <container> -- bash
kubectl exec <pod> -- env
kubectl exec <pod> -- cat /etc/resolv.conf
kubectl port-forward pod/web 8080:80
kubectl port-forward svc/web 8080:80
kubectl port-forward deploy/web 8080:80
kubectl port-forward svc/web 8080:80 --address 0.0.0.0
kubectl cp <pod>:/app/logs ./logs
kubectl cp ./config.yaml <pod>:/app/config.yaml -c app
kubectl cp <ns>/<pod>:/path ./local
kubectl attach -it <pod>
kubectl debug -it <pod> --image=busybox:1.36 --target=app
kubectl debug <pod> --copy-to=web-debug --image=ubuntu:24.04 --share-processes -it
kubectl debug node/<node> -it --image=ubuntu:24.04
5.7 集群与 API
kubectl cluster-info
kubectl cluster-info dump > /tmp/dump.txt
kubectl get --raw /healthz
kubectl get --raw '/readyz?verbose'
kubectl get --raw /livez
kubectl get --raw /metrics | head -50
kubectl get --raw /api/v1/namespaces
kubectl api-resources
kubectl api-resources --namespaced=false
kubectl api-versions
kubectl explain pod.spec.containers
kubectl explain deploy.spec.strategy --recursive
kubectl auth can-i --list
kubectl auth can-i create pods --as=system:serviceaccount:prod:ci
5.8 效率配置
# 别名
cat >>~/.bashrc <<'EOF'
alias k=kubectl
alias kgp='kubectl get pods -o wide'
alias kgn='kubectl get nodes -o wide'
alias kga='kubectl get all'
alias kd='kubectl describe'
alias kl='kubectl logs -f --tail=200'
alias ke='kubectl exec -it'
alias kaf='kubectl apply -f'
alias kdel='kubectl delete'
export KUBE_EDITOR=vim
EOF
source ~/.bashrc
# 补全
echo 'source <(kubectl completion bash)' >>~/.bashrc
echo 'complete -o default -F __start_kubectl k' >>~/.bashrc
# 插件(krew)
(
set -x
cd "$(mktemp -d)" &&
OS=$(uname | tr '[:upper:]' '[:lower:]') &&
ARCH=$(uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/') &&
curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/krew-${OS}_${ARCH}.tar.gz" &&
tar zxvf krew-${OS}_${ARCH}.tar.gz &&
./krew-${OS}_${ARCH} install krew
)
kubectl krew install ctx ns neat deprecations tree score
kubectl ctx # 切换 context
kubectl ns # 切换 namespace
kubectl tree deploy web
6. 工作负载
6.1 选型
| 类型 | 用途 | 特点 |
|---|---|---|
| Pod | 单实例,不推荐直接用 | 无自愈、无滚动更新 |
| Deployment | 无状态服务 | 滚动更新、回滚、扩缩 |
| StatefulSet | 有状态服务(DB、MQ) | 稳定网络标识、有序启停、独占存储 |
| DaemonSet | 每节点一个(日志、监控、CNI) | 跟随节点生命周期 |
| Job | 一次性任务 | 完成即停,可重试 |
| CronJob | 定时任务 | 生成 Job,支持并发策略 |
6.2 Deployment 典型模板
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: prod
labels: { app: web }
spec:
replicas: 3
revisionHistoryLimit: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0 # 保证容量不降
maxSurge: 1
minReadySeconds: 10
progressDeadlineSeconds: 600
selector:
matchLabels: { app: web }
template:
metadata:
labels: { app: web, tier: frontend }
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
spec:
serviceAccountName: web
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
seccompProfile: { type: RuntimeDefault }
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels: { app: web }
containers:
- name: web
image: harbor.internal/web/app:1.0.0
imagePullPolicy: IfNotPresent
ports:
- { name: http, containerPort: 8080, protocol: TCP }
env:
- name: TZ
value: Asia/Shanghai
- name: POD_NAME
valueFrom: { fieldRef: { fieldPath: metadata.name } }
- name: NODE_NAME
valueFrom: { fieldRef: { fieldPath: spec.nodeName } }
- name: DB_PASSWORD
valueFrom:
secretKeyRef: { name: db-secret, key: password }
envFrom:
- configMapRef: { name: app-config }
resources:
requests: { cpu: 500m, memory: 512Mi }
limits: { cpu: "2", memory: 2Gi }
startupProbe:
httpGet: { path: /healthz, port: http }
failureThreshold: 30
periodSeconds: 5
readinessProbe:
httpGet: { path: /ready, port: http }
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
livenessProbe:
httpGet: { path: /healthz, port: http }
periodSeconds: 20
failureThreshold: 3
lifecycle:
preStop:
exec: { command: ["sh","-c","sleep 10 && nginx -s quit"] }
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: ["ALL"] }
volumeMounts:
- { name: tmp, mountPath: /tmp }
- { name: config, mountPath: /app/config, readOnly: true }
terminationGracePeriodSeconds: 45
volumes:
- name: tmp
emptyDir: { sizeLimit: 1Gi }
- name: config
configMap: { name: app-config }
6.3 探针
| 探针 | 失败后果 | 用途 |
|---|---|---|
startupProbe |
重启容器 | 慢启动应用(比 liveness 的 initialDelay 更可靠) |
readinessProbe |
从 Service Endpoints 摘除(不重启) | 依赖未就绪、预热 |
livenessProbe |
重启容器 | 死锁、假死 |
# 三种检查方式
httpGet: { path: /healthz, port: 8080, httpHeaders: [{name: X-Health, value: "1"}] }
tcpSocket: { port: 6379 }
exec: { command: ["sh","-c","redis-cli ping | grep PONG"] }
grpc: { port: 9090, service: health } # 1.24+ GA
参数取舍原则
| 参数 | 建议 |
|---|---|
initialDelaySeconds |
有 startupProbe 时可设小 |
periodSeconds |
liveness 20~30s(别太小,会误杀) |
timeoutSeconds |
大于 P99 响应时间 |
failureThreshold |
liveness 3,startup 30 |
successThreshold |
readiness 1,liveness 必须 1 |
坑点:liveness 探针查下游依赖(如数据库)会导致级联重启——数据库抖动时全部 pod 一起重启,雪崩。liveness 只查自身进程健康,依赖用 readiness。
6.4 滚动更新与回滚
kubectl set image deploy/web web=harbor.internal/web/app:1.0.1 -n prod
kubectl annotate deploy/web kubernetes.io/change-cause="release 1.0.1 by ops" -n prod
kubectl rollout status deploy/web -n prod
kubectl rollout history deploy/web -n prod
kubectl rollout history deploy/web --revision=2 -n prod
kubectl rollout undo deploy/web -n prod
kubectl rollout undo deploy/web --to-revision=1 -n prod
kubectl rollout pause deploy/web
kubectl rollout resume deploy/web
kubectl rollout restart deploy/web
# 金丝雀:两个 Deployment 共享 Service 的 selector
# stable: replicas 9, labels {app: web, track: stable}
# canary: replicas 1, labels {app: web, track: canary}
6.5 StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: prod
spec:
serviceName: mysql-headless
replicas: 3
podManagementPolicy: OrderedReady # 或 Parallel
updateStrategy:
type: RollingUpdate
rollingUpdate: { partition: 0 }
selector:
matchLabels: { app: mysql }
template:
metadata:
labels: { app: mysql }
spec:
containers:
- name: mysql
image: mysql:8.4
ports:
- { name: mysql, containerPort: 3306 }
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom: { secretKeyRef: { name: mysql-secret, key: root } }
volumeMounts:
- { name: data, mountPath: /var/lib/mysql }
resources:
requests: { cpu: "2", memory: 4Gi }
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests: { storage: 100Gi }
---
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
namespace: prod
spec:
clusterIP: None
selector: { app: mysql }
ports:
- { port: 3306, targetPort: 3306, name: mysql }
关键点:每个 pod 有稳定 DNS mysql-0.mysql-headless.prod.svc.cluster.local;扩缩容按序号;删除 StatefulSet 不删 PVC(数据保留)。
kubectl get sts -n prod
kubectl scale sts mysql --replicas=5 -n prod
kubectl rollout status sts/mysql -n prod
kubectl rollout undo sts/mysql -n prod
kubectl delete sts mysql -n prod --cascade=orphan # 保留 pod
6.6 DaemonSet
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-agent
namespace: kube-system
spec:
selector:
matchLabels: { app: node-agent }
updateStrategy:
type: RollingUpdate
rollingUpdate: { maxUnavailable: 10% }
template:
metadata:
labels: { app: node-agent }
spec:
hostNetwork: true
hostPID: true
tolerations:
- { operator: Exists } # 容忍所有污点,保证每节点都跑
containers:
- name: agent
image: harbor.internal/ops/node-agent:1.0
securityContext:
privileged: true
volumeMounts:
- { name: root, mountPath: /host, readOnly: true }
- { name: cgroup, mountPath: /sys/fs/cgroup, readOnly: true }
volumes:
- { name: root, hostPath: { path: / } }
- { name: cgroup, hostPath: { path: /sys/fs/cgroup } }
6.7 Job / CronJob
apiVersion: batch/v1
kind: Job
metadata:
name: migrate
namespace: prod
spec:
backoffLimit: 3
activeDeadlineSeconds: 600
ttlSecondsAfterFinished: 3600
completions: 1
parallelism: 1
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: harbor.internal/web/api:1.0.0
command: ["java","-jar","app.jar","--migrate-only"]
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: backup
namespace: prod
spec:
schedule: "0 2 * * *"
timeZone: Asia/Shanghai
concurrencyPolicy: Forbid # Allow / Forbid / Replace
startingDeadlineSeconds: 300
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
suspend: false
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: harbor.internal/ops/backup:1.0
kubectl create job --from=cronjob/backup backup-manual -n prod
kubectl get jobs -n prod
kubectl logs job/migrate -n prod
kubectl delete job migrate -n prod
kubectl patch cronjob backup -p '{"spec":{"suspend":true}}' -n prod
版本差异:
spec.timeZone在 1.27 GA。老版本只能靠容器内 TZ 环境变量。
6.8 扩缩容
# 手动
kubectl scale deploy web --replicas=5 -n prod
kubectl scale sts mysql --replicas=3 -n prod
kubectl scale deploy web --replicas=0 -n prod # 停服保配置
# HPA(需要 metrics-server)
kubectl autoscale deploy web --min=2 --max=10 --cpu-percent=70 -n prod
kubectl get hpa -n prod
kubectl describe hpa web -n prod
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web
namespace: prod
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 2
maxReplicas: 10
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- { type: Percent, value: 50, periodSeconds: 60 }
scaleUp:
stabilizationWindowSeconds: 30
policies:
- { type: Pods, value: 4, periodSeconds: 60 }
metrics:
- type: Resource
resource:
name: cpu
target: { type: Utilization, averageUtilization: 70 }
- type: Resource
resource:
name: memory
target: { type: Utilization, averageUtilization: 80 }
6.9 PDB(中断预算)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb
namespace: prod
spec:
minAvailable: 2 # 或 maxUnavailable: 1,二选一
selector:
matchLabels: { app: web }
kubectl get pdb -n prod
kubectl describe pdb web-pdb -n prod
# drain 时会遵守 PDB,导致 drain 卡住(说明预算不足)
坑点:
minAvailable等于副本数时,drain 会永远卡住(没有可用驱逐额度)。要么留 1 个余量,要么临时调大副本。
7. 调度
7.1 调度流程
Pod 创建 → 调度队列 → 过滤(Filter,可用节点)→ 打分(Score)→ 绑定(Bind)→ kubelet 拉起
Pending 的 pod 一定是卡在 Filter 阶段,事件里会写明原因(Insufficient cpu、node(s) had untolerated taint、node affinity mismatch)。
7.2 nodeSelector(最简单)
spec:
nodeSelector:
disktype: ssd
7.3 节点亲和性
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬性
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["az1", "az2"]
- key: node-type
operator: NotIn
values: ["gpu"]
preferredDuringSchedulingIgnoredDuringExecution: # 软性
- weight: 80
preference:
matchExpressions:
- key: disktype
operator: In
values: ["ssd"]
| operator | 说明 |
|---|---|
In / NotIn |
值集合包含/不包含 |
Exists / DoesNotExist |
键存在/不存在 |
Gt / Lt |
数值比较(只能单值) |
7.4 Pod 亲和与反亲和
spec:
affinity:
podAntiAffinity:
# 硬性:同 app 的 pod 不能同节点(高可用)
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels: { app: web }
topologyKey: kubernetes.io/hostname
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels: { app: cache }
topologyKey: kubernetes.io/hostname
坑点:硬性反亲和 + 副本数 > 节点数 = 部分 pod 永远 Pending。生产优先用
topologySpreadConstraints(更灵活,可ScheduleAnyway)。
7.5 拓扑分布约束
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule # 或 ScheduleAnyway
labelSelector:
matchLabels: { app: web }
minDomains: 2
# 查看节点的拓扑标签
kubectl get nodes -L topology.kubernetes.io/zone -L topology.kubernetes.io/region
7.6 污点与容忍
spec:
tolerations:
- key: dedicated
operator: Equal
value: gpu
effect: NoSchedule
- key: node.kubernetes.io/not-ready
operator: Exists
effect: NoExecute
tolerationSeconds: 300
- operator: Exists # 容忍一切
| effect | 行为 |
|---|---|
NoSchedule |
新 pod 不调度,已有不动 |
PreferNoSchedule |
尽量不调度(软性) |
NoExecute |
驱逐不容忍的已有 pod |
7.7 资源请求与限制
resources:
requests: { cpu: 500m, memory: 512Mi }
limits: { cpu: "2", memory: 2Gi }
| 情况 | 行为 |
|---|---|
| 无 requests | 按 0 计,最容易被优先驱逐 |
| requests > 节点可分配 | Pending(Insufficient cpu) |
| 超 limits(CPU) | 被限流(throttle),不会被杀 |
| 超 limits(内存) | OOMKilled(exit 137) |
| QoS Guaranteed | requests == limits,最优先保留 |
| QoS Burstable | requests < limits,中间 |
| QoS BestEffort | 都没设,最先被驱逐 |
kubectl get pod -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.qosClass}{"\n"}{end}'
7.8 优先级与抢占
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "核心业务"
spec:
priorityClassName: high-priority
7.9 调度排障
# 看 pod 为什么 Pending
kubectl describe pod <pod> | sed -n '/Events:/,$p'
# 看 scheduler 日志
kubectl logs -n kube-system -l component=kube-scheduler --tail=100
# 看节点剩余可分配
kubectl describe node <node> | grep -A10 'Allocated resources'
kubectl get node -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.allocatable.cpu}{"\t"}{.status.allocatable.memory}{"\n"}{end}'
# 模拟调度(dry-run 不落盘)
kubectl create -f pod.yaml --dry-run=server
8. 存储
8.1 概念关系
StorageClass(供给模板)
↓ 动态供给
PV(集群级存储资源) ←──绑定──→ PVC(命名空间级申请)
↓ 被
Pod 挂载
| 对象 | 作用域 | 谁创建 |
|---|---|---|
| StorageClass | 集群 | 管理员 |
| PersistentVolume | 集群 | 管理员(静态)/ provisioner(动态) |
| PersistentVolumeClaim | 命名空间 | 用户 |
| VolumeSnapshot | 命名空间 | 用户(需 CSI 支持) |
8.2 常用 StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com # 按实际 CSI 改
parameters:
type: gp3
fsType: ext4
reclaimPolicy: Delete # Delete / Retain
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer # 或 Immediate
mountOptions:
- noatime
| 参数 | 说明 |
|---|---|
reclaimPolicy: Delete |
PVC 删了 PV 和底层存储一起删(默认,易误删) |
reclaimPolicy: Retain |
保留数据,需手工清理(重要数据必须用) |
volumeBindingMode: WaitForFirstConsumer |
等 pod 调度后再创建卷(拓扑感知,推荐) |
allowVolumeExpansion: true |
允许扩容 PVC |
8.3 PVC 使用
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
namespace: prod
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 50Gi
| accessMode | 说明 |
|---|---|
ReadWriteOnce (RWO) |
单节点读写(块存储常用) |
ReadOnlyMany (ROX) |
多节点只读 |
ReadWriteMany (RWX) |
多节点读写(NFS/CephFS/EFS) |
ReadWriteOncePod (RWOP) |
单 pod 读写(1.29 GA) |
# Pod 中使用
spec:
containers:
- name: app
volumeMounts:
- { name: data, mountPath: /app/data }
volumes:
- name: data
persistentVolumeClaim:
claimName: app-data
8.4 常用运维命令
kubectl get sc
kubectl get pv
kubectl get pvc -A
kubectl get pvc -n prod -o wide
kubectl describe pvc app-data -n prod
kubectl get pv <pv> -o yaml
# 扩容(SC 需 allowVolumeExpansion)
kubectl patch pvc app-data -n prod -p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}'
kubectl get pvc app-data -n prod -w
# 快照(需 VolumeSnapshotClass)
kubectl get volumesnapshotclass
kubectl get volumesnapshot -A
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: app-data-snap
namespace: prod
spec:
volumeSnapshotClassName: csi-snapclass
source:
persistentVolumeClaimName: app-data
8.5 PVC Pending 排查
kubectl describe pvc app-data -n prod
# 看 Events:waiting for a volume to be created / no persistent volumes available
# 1) SC 是否存在
kubectl get sc
# 2) provisioner 是否正常
kubectl get pod -A | grep -E 'csi|provisioner'
kubectl logs -n kube-system <csi-pod>
# 3) 容量/accessMode 是否可满足
kubectl get pv -o wide
# 4) WaitForFirstConsumer 时无 pod 使用则不创建(正常现象)
8.6 Retain 卷的回收
# SC 为 Retain 时,PVC 删除后 PV 变 Released,数据仍在
kubectl get pv | grep Released
# 复用:清掉 claimRef 后重新绑定
kubectl patch pv <pv> -p '{"spec":{"claimRef":null}}'
kubectl get pv <pv>
# 彻底删除(底层存储要单独删)
kubectl delete pv <pv>
9. 网络
9.1 四层模型
| 层 | 组件 | 解决什么 |
|---|---|---|
| Pod 网络 | CNI(Calico/Cilium/Flannel) | pod 跨节点互通、IP 分配 |
| Service | kube-proxy / eBPF | 服务发现、负载均衡、稳定 VIP |
| 南北向入口 | Ingress / Gateway API | 外部 HTTP(S) 路由 |
| 策略 | NetworkPolicy | pod 级访问控制 |
9.2 Service 类型
| 类型 | 说明 | 场景 |
|---|---|---|
ClusterIP |
集群内 VIP(默认) | 内部服务 |
NodePort |
每节点开 30000-32767 | 测试、简单暴露 |
LoadBalancer |
云 LB / MetalLB | 生产入口 |
ExternalName |
CNAME 到外部域名 | 外部依赖抽象 |
Headless(clusterIP: None) |
不做 VIP,返回 pod IP 列表 | StatefulSet、自定义发现 |
apiVersion: v1
kind: Service
metadata:
name: web
namespace: prod
spec:
type: ClusterIP
selector: { app: web }
ports:
- { name: http, port: 80, targetPort: http, protocol: TCP }
# sessionAffinity: ClientIP
# sessionAffinityConfig: { clientIP: { timeoutSeconds: 10800 } }
kubectl get svc -n prod
kubectl get ep -n prod # Endpoints,看是否为空
kubectl get endpointslices -n prod
kubectl describe svc web -n prod
关键:
Endpoints为空是 Service 不通的第一原因,通常是selector与 pod label 不匹配,或 pod 未 Ready。
9.3 Ingress 与 Gateway API
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: prod
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
ingressClassName: nginx
tls:
- hosts: ["web.example.com"]
secretName: web-tls
rules:
- host: web.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port: { number: 80 }
# Gateway API(Ingress 的继任者)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: main
namespace: infra
spec:
gatewayClassName: cilium
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs: [{ name: wildcard-tls }]
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web
namespace: prod
spec:
parentRefs:
- { name: main, namespace: infra }
hostnames: ["web.example.com"]
rules:
- matches:
- path: { type: PathPrefix, value: / }
backendRefs:
- { name: web, port: 80 }
版本差异:Ingress NGINX 已于 2026-03 归档退役。存量可用但无安全更新,新项目用 Gateway API(Cilium/Envoy Gateway/Contour 等实现)。
9.4 NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-policy
namespace: prod
spec:
podSelector:
matchLabels: { app: web }
policyTypes: [Ingress, Egress]
ingress:
- from:
- namespaceSelector:
matchLabels: { kubernetes.io/metadata.name: ingress-nginx }
ports:
- { protocol: TCP, port: 8080 }
egress:
- to:
- podSelector:
matchLabels: { app: db }
ports:
- { protocol: TCP, port: 3306 }
- to: # DNS 必须放行
- namespaceSelector: {}
podSelector:
matchLabels: { k8s-app: kube-dns }
ports:
- { protocol: UDP, port: 53 }
- { protocol: TCP, port: 53 }
坑点:一旦某个 pod 被 NetworkPolicy 选中且
policyTypes含 Egress,默认拒绝所有出向流量,包括 DNS。必须显式放行 53 端口,否则全是"域名解析失败"。
9.5 DNS 与 CoreDNS
kubectl get pod -n kube-system -l k8s-app=kube-dns
kubectl get svc -n kube-system kube-dns
kubectl get cm -n kube-system coredns -o yaml
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
# DNS 测试
kubectl run -it --rm dnstest --image=busybox:1.36 --restart=Never -- sh
# nslookup kubernetes.default
# nslookup web.prod.svc.cluster.local
# nslookup web # 同 namespace 短名
# nslookup web.prod # 跨 namespace
DNS 命名规则:
<service>.<namespace>.svc.cluster.local
<pod-ip-dashed>.<namespace>.pod.cluster.local
<statefulset-pod>.<headless-svc>.<namespace>.svc.cluster.local
常见问题:
| 现象 | 原因 | 处理 |
|---|---|---|
nslookup 超时 |
CoreDNS 挂了 / 网络策略拦 53 | 查 coredns pod、放行 DNS |
| 解析慢(5s) | ndots:5 导致多次查询 |
用 FQDN 或调 dnsConfig.options.ndots |
| 跨 ns 解析失败 | 用了短名 | 用 svc.ns 或 FQDN |
Temporary failure in name resolution |
conntrack 表满 / iptables 规则丢失 | 调 nf_conntrack_max;重启 kube-proxy |
# 调 ndots 加速解析
spec:
dnsConfig:
options:
- { name: ndots, value: "2" }
9.6 网络排障
# 1) pod 是否拿到 IP
kubectl get pod -o wide
# 2) pod 间连通
kubectl exec -it <pod-a> -- ping -c2 <pod-b-ip>
# 3) Service 是否有 endpoints
kubectl get ep <svc> -n <ns>
kubectl get endpointslices -n <ns> -l kubernetes.io/service-name=<svc>
# 4) Service 访问
kubectl run -it --rm tmp --image=curlimages/curl --restart=Never -- \
curl -v http://web.prod.svc.cluster.local
# 5) 节点侧规则
iptables -t nat -L KUBE-SERVICES -n | head
nft list ruleset | grep -c kube
# 6) CNI 状态
kubectl get pod -n kube-system -o wide | grep -E 'calico|cilium|flannel'
kubectl logs -n kube-system <cni-pod> --tail=100
# 7) 抓包(在 pod 所在节点)
kubectl debug node/<node> -it --image=nicolaka/netshoot -- \
tcpdump -i any -nn host <pod-ip>
10. 配置与密钥
10.1 ConfigMap
kubectl create cm app-config --from-file=./conf -n prod
kubectl create cm app-config --from-literal=LOG_LEVEL=info -n prod
kubectl create cm app-config --from-env-file=./app.env -n prod
kubectl get cm -n prod
kubectl get cm app-config -n prod -o yaml
kubectl describe cm app-config -n prod
kubectl edit cm app-config -n prod
kubectl delete cm app-config -n prod
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: prod
data:
LOG_LEVEL: info
app.yaml: |
server:
port: 8080
db:
host: mysql.prod.svc.cluster.local
使用的三种方式
# 1) 单个键 → 环境变量
env:
- name: LOG_LEVEL
valueFrom: { configMapKeyRef: { name: app-config, key: LOG_LEVEL } }
# 2) 全部键 → 环境变量
envFrom:
- configMapRef: { name: app-config }
- configMapRef: { name: app-config, optional: true }
# 3) 挂载为文件
volumes:
- name: config
configMap:
name: app-config
items:
- { key: app.yaml, path: application.yaml }
defaultMode: 0644
关键区别:挂载为 volume 的 ConfigMap 更新后会在 1~2 分钟内自动同步(kubelet 周期性刷新);作为
env/envFrom引入的不会变,必须重启 pod。
10.2 Secret
kubectl create secret generic db-secret \
--from-literal=username=app --from-literal=password='S3cr3t' -n prod
kubectl create secret tls web-tls --cert=web.crt --key=web.key -n prod
kubectl create secret docker-registry harbor-cred \
--docker-server=harbor.internal --docker-username='robot$ci' \
--docker-password='xxx' -n prod
kubectl get secret -n prod
kubectl get secret db-secret -n prod -o jsonpath='{.data.password}' | base64 -d
kubectl describe secret db-secret -n prod # 只显示大小,不显示值
apiVersion: v1
kind: Secret
metadata:
name: db-secret
namespace: prod
type: Opaque
stringData: # 直接写明文,apply 时自动 base64
username: app
password: S3cr3t
---
# 不可变 Secret(1.21 GA,性能更好但不允许修改)
apiVersion: v1
kind: Secret
metadata:
name: fixed-secret
immutable: true
data:
key: dmFsdWU=
10.3 Secret 的安全现实
Secret 默认只是 base64 编码,不是加密:
# 任何有 get secret 权限的人都能读
kubectl auth can-i get secrets -n prod --as=system:serviceaccount:prod:default
# 缓解手段
# 1) 开启 etcd 静态加密(见 12.4)
# 2) 收紧 RBAC,禁止 list secrets
# 3) 用外部密钥管理(Vault / External Secrets Operator / CSI Secrets Store)
# 4) 不要提交到 Git(用 SealedSecrets / SOPS)
10.4 环境变量 vs 挂载 vs 启动参数
| 方式 | 更新生效 | 适合 |
|---|---|---|
env / envFrom |
需重启 pod | 简单配置、少量键 |
| volume 挂载 | 1~2 分钟自动同步 | 配置文件、证书 |
| 命令行参数 | 需重启 | 固定参数 |
# 引用的字段(Downward API)
env:
- name: POD_IP
valueFrom: { fieldRef: { fieldPath: status.podIP } }
- name: CPU_LIMIT
valueFrom: { resourceFieldRef: { containerName: app, resource: limits.cpu } }
volumes:
- name: podinfo
downwardAPI:
items:
- { path: labels, fieldRef: { fieldPath: metadata.labels } }
11. RBAC 与认证
11.1 模型
User/Group/ServiceAccount ──绑定──▶ Role/ClusterRole ──授予──▶ 权限(verbs + resources)
| 对象 | 作用域 | 说明 |
|---|---|---|
Role |
命名空间 | 命名空间内权限 |
ClusterRole |
集群 | 集群级资源或跨 ns |
RoleBinding |
命名空间 | 把 Role/ClusterRole 绑到 ns 内主体 |
ClusterRoleBinding |
集群 | 全局绑定 |
ServiceAccount |
命名空间 | pod 使用的身份 |
11.2 ServiceAccount
kubectl create sa web -n prod
kubectl get sa -n prod
kubectl describe sa web -n prod
# 1.24+ 不再自动创建长期 token secret,需要时手工签发
kubectl create token web -n prod --duration=24h
kubectl create token web -n prod --duration=8760h > /tmp/token
apiVersion: v1
kind: ServiceAccount
metadata:
name: web
namespace: prod
automountServiceAccountToken: false # 不需要 API 访问时关掉(安全)
11.3 Role / RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: prod
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: web-pod-reader
namespace: prod
subjects:
- kind: ServiceAccount
name: web
namespace: prod
- kind: User
name: alice
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: dev-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
11.4 ClusterRole 示例
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [""]
resources: ["nodes", "nodes/status"]
verbs: ["get", "list", "watch"]
- apiGroups: ["metrics.k8s.io"]
resources: ["nodes", "pods"]
verbs: ["get", "list"]
11.5 权限排查
kubectl auth can-i create pods -n prod
kubectl auth can-i --list -n prod
kubectl auth can-i --list --as=system:serviceaccount:prod:web -n prod
kubectl auth can-i get secrets --as=alice -n prod
kubectl auth whoami
# 谁能做什么
kubectl get rolebinding,clusterrolebinding -A -o wide | grep <user>
# 用 API 查
kubectl get clusterrolebinding -o json | jq -r '
.items[] | select(.subjects[]?.name=="alice") | .roleRef.name'
11.6 认证方式
| 方式 | 说明 | 场景 |
|---|---|---|
| 客户端证书 | kubeadm 生成 | 管理员 |
| ServiceAccount token | JWT(短期或长期) | pod |
| OIDC | 对接 IdP(Keycloak/Dex) | 企业 SSO |
| Webhook token | 自定义认证服务 | 特殊集成 |
| 静态 token 文件 | 已弃用 | 不要用 |
# OIDC 配置(apiserver)
# --oidc-issuer-url=https://keycloak.example.com/realms/k8s
# --oidc-client-id=kubernetes
# --oidc-username-claim=email
# --oidc-groups-claim=groups
11.7 最小权限原则
# 给 CI 的 SA:只能更新指定 deployment 的镜像
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deploy-updater
namespace: prod
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
resourceNames: ["web", "api"] # 限定具体资源
verbs: ["get", "patch", "update"]
禁止给业务 SA 绑
cluster-admin,也不要给secrets的list权限(list 会返回所有 secret 的完整内容)。
12. 集群安全
12.1 Pod Security Admission(PSA)
1.25 起取代 PSP(PodSecurityPolicy 已移除)。按命名空间打标签:
kubectl label ns prod pod-security.kubernetes.io/enforce=restricted
kubectl label ns prod pod-security.kubernetes.io/enforce-version=v1.37
kubectl label ns prod pod-security.kubernetes.io/audit=baseline
kubectl label ns prod pod-security.kubernetes.io/warn=restricted
kubectl get ns prod --show-labels
| 级别 | 限制 |
|---|---|
privileged |
无限制 |
baseline |
禁止 privileged、hostNetwork、hostPID、危险 capability |
restricted |
额外要求非 root、禁止特权提升、必须设 seccomp、必须 drop ALL |
坑点:
restricted会拒绝没设seccompProfile和runAsNonRoot的 pod。存量工作负载要先在audit/warn模式跑一段时间,看清楚影响再enforce。
# 检查哪些 pod 会违规(warn 模式的输出在 kubectl apply 时可见)
kubectl label ns prod pod-security.kubernetes.io/warn=restricted --overwrite
kubectl apply -f app.yaml # 会输出 Warning: would violate PodSecurity
12.2 seccomp / AppArmor
spec:
securityContext:
seccompProfile:
type: RuntimeDefault # RuntimeDefault / Localhost / Unconfined
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]
# AppArmor(1.30+ 用 securityContext.appArmorProfile)
securityContext:
appArmorProfile:
type: RuntimeDefault
12.3 准入控制
| 准入插件 | 作用 |
|---|---|
NamespaceLifecycle |
保护终止中的 ns |
LimitRanger |
应用 LimitRange 默认值 |
ResourceQuota |
强制配额 |
ServiceAccount |
注入 SA |
NodeRestriction |
限制 kubelet 权限 |
ValidatingAdmissionPolicy |
内置 CEL 策略(1.30 GA) |
| webhook(Kyverno/OPA) | 自定义策略 |
# LimitRange:给 ns 内 pod 设默认资源
apiVersion: v1
kind: LimitRange
metadata:
name: defaults
namespace: prod
spec:
limits:
- type: Container
default: { cpu: 500m, memory: 512Mi }
defaultRequest: { cpu: 100m, memory: 128Mi }
max: { cpu: "2", memory: 2Gi }
# ResourceQuota
apiVersion: v1
kind: ResourceQuota
metadata:
name: prod-quota
namespace: prod
spec:
hard:
requests.cpu: "20"
requests.memory: 40Gi
limits.cpu: "40"
limits.memory: 80Gi
pods: "50"
persistentvolumeclaims: "10"
services.loadbalancers: "2"
kubectl get resourcequota -n prod
kubectl describe resourcequota prod-quota -n prod
kubectl get limitrange -n prod
12.4 etcd 静态加密
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: ["secrets", "configmaps"]
providers:
- aescbc:
keys:
- name: key1
secret: <base64 32 字节>
- identity: {}
head -c 32 /dev/urandom | base64
# apiserver 加参数
# --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
# 加密已有数据(重要:新数据才加密,旧数据要重写)
kubectl get secrets -A -o json | kubectl replace -f -
# 验证
kubectl get secret db-secret -n prod -o jsonpath='{.data.password}' | base64 -d # 仍能读
# etcd 里已经是密文
ETCDCTL_API=3 etcdctl get /registry/secrets/prod/db-secret --print-value-only | head -c 100
12.5 审计日志
# /etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: None
users: ["system:kube-proxy"]
verbs: ["watch"]
resources: [{ group: "", resources: ["endpoints", "services"] }]
- level: Metadata
resources: [{ group: "", resources: ["secrets", "configmaps"] }]
- level: Request
verbs: ["create", "update", "patch", "delete"]
resources: [{ group: "", resources: ["pods", "services"] }]
- level: RequestResponse
users: ["system:admin"]
- level: Metadata
omitStages: ["RequestReceived"]
| level | 记录内容 |
|---|---|
None |
不记录 |
Metadata |
谁、何时、做了什么(不含 body) |
Request |
+ 请求体 |
RequestResponse |
+ 响应体 |
12.6 安全检查清单
# 1) 是否有 cluster-admin 泛滥
kubectl get clusterrolebinding -o json | jq -r '.items[] | select(.roleRef.name=="cluster-admin") | .subjects[]?.name'
# 2) 是否有特权 pod
kubectl get pod -A -o json | jq -r '.items[] | select(.spec.containers[].securityContext.privileged==true) | .metadata.namespace+"/"+.metadata.name'
# 3) hostPath 挂载
kubectl get pod -A -o json | jq -r '.items[] | select(.spec.volumes[]?.hostPath) | .metadata.namespace+"/"+.metadata.name'
# 4) 默认 SA 是否 automount
kubectl get sa -A -o json | jq -r '.items[] | select(.automountServiceAccountToken!=false) | .metadata.namespace+"/"+.metadata.name'
# 5) 证书有效期
kubeadm certs check-expiration
# 6) 组件版本与 CVE
kubectl version
kubectl get nodes -o wide
# 7) 镜像是否来自内部仓库
kubectl get pod -A -o jsonpath='{range .items[*]}{.spec.containers[*].image}{"\n"}{end}' | sort -u
# 8) 网络策略覆盖
kubectl get networkpolicy -A
13. 集群运维
13.1 证书管理
# 查看所有证书到期时间
kubeadm certs check-expiration
# 续期全部(kubeadm 集群默认 1 年)
kubeadm certs renew all
# 续期单项
kubeadm certs renew apiserver
kubeadm certs renew apiserver-etcd-client
kubeadm certs renew etcd-healthcheck-client
# 生效:控制面静态 Pod 需重启
# 方式一:删 pod(kubelet 会重建)
kubectl -n kube-system delete pod -l tier=control-plane
# 方式二:移走 manifest(更稳)
mkdir -p /tmp/manifests-bak && mv /etc/kubernetes/manifests/*.yaml /tmp/manifests-bak/
sleep 20
mv /tmp/manifests-bak/*.yaml /etc/kubernetes/manifests/
# kubelet 客户端证书(kubeadm 默认自动轮换)
# /var/lib/kubelet/pki/kubelet-client-current.pem
ls -l /var/lib/kubelet/pki/
# 非 kubeadm 集群:手工用 CA 重签,注意 SAN 必须包含所有 endpoint
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -A2 'Subject Alternative Name'
必做:把证书到期检查加进监控。kubeadm 集群默认一年,忘记续期会在某天凌晨全站挂掉。
13.2 etcd 运维
# 定义别名(控制面节点)
cat >>~/.bashrc <<'EOF'
export ETCDCTL_API=3
alias etcdctl='etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key'
EOF
source ~/.bashrc
etcdctl endpoint health
etcdctl endpoint status -w table
etcdctl member list -w table
etcdctl alarm list
备份(每天必做)
#!/usr/bin/env bash
# /opt/scripts/etcd-backup.sh
set -euo pipefail
BACKUP_DIR=/backup/etcd
mkdir -p "$BACKUP_DIR"
ETCDCTL_API=3 etcdctl \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
snapshot save "$BACKUP_DIR/etcd-$(date +%F-%H%M).db"
ETCDCTL_API=3 etcdctl \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
snapshot status "$BACKUP_DIR/etcd-$(date +%F-%H%M).db" -w table
# 保留 7 天
find "$BACKUP_DIR" -name 'etcd-*.db' -mtime +7 -delete
chmod +x /opt/scripts/etcd-backup.sh
echo '0 2 * * * /opt/scripts/etcd-backup.sh >> /var/log/etcd-backup.log 2>&1' | crontab -
恢复(灾难场景,务必演练)
# 1) 停控制面
systemctl stop kubelet
mv /etc/kubernetes/manifests /tmp/manifests-bak
# 2) 确认 etcd 容器已停
crictl ps | grep etcd
# 3) 恢复
ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd/etcd-2026-11-05-0200.db \
--data-dir=/var/lib/etcd-restore \
--name=k8s-master01 \
--initial-cluster=k8s-master01=https://10.0.0.11:2380 \
--initial-advertise-peer-urls=https://10.0.0.11:2380
# 4) 改 etcd 静态 Pod 的数据目录
# /etc/kubernetes/manifests/etcd.yaml 里 hostPath 改为 /var/lib/etcd-restore
# 5) 起服务
mv /tmp/manifests-bak /etc/kubernetes/manifests
systemctl start kubelet
# 6) 验证
kubectl get nodes
kubectl get pod -A
etcd 性能问题
etcdctl check perf # 综合性能
etcdctl defrag --cluster # 碎片整理(会阻塞,逐节点做)
etcdctl compact $(etcdctl endpoint status --write-out=json | jq '.[0].Status.header.revision')
etcdctl alarm disarm
# 常见指标
# etcd_server_leader_changes_seen_total → 频繁选主,网络或磁盘问题
# etcd_disk_wal_fsync_duration_seconds → > 10ms 说明磁盘慢
# etcd_server_proposals_committed_total → 提案积压
铁律:etcd 必须用 SSD。HDD 上
fsync延迟高会导致 apiserver 超时、集群"假死"(kubectl卡住但 pod 还在跑)。
13.3 升级
升级路径:只能一个 minor 一个 minor 升,1.35 → 1.36 → 1.37,不能跳。
# 0) 前置检查
kubeadm upgrade plan
kubeadm certs check-expiration
kubectl get nodes
# 所有节点 Ready 且版本一致;etcd 已备份;已读 release notes 的废弃项
# 1) 升级第一个控制面
dnf install -y kubeadm-1.37.2 --disableexcludes=kubernetes
kubeadm upgrade apply v1.37.2 -y
# 2) 升级 kubelet/kubectl(同一节点)
dnf install -y kubelet-1.37.2 kubectl-1.37.2 --disableexcludes=kubernetes
systemctl daemon-reload
systemctl restart kubelet
# 3) 其余控制面
kubectl drain <cp-node> --ignore-daemonsets --delete-emptydir-data
dnf install -y kubeadm-1.37.2 --disableexcludes=kubernetes
kubeadm upgrade node
dnf install -y kubelet-1.37.2 kubectl-1.37.2 --disableexcludes=kubernetes
systemctl daemon-reload && systemctl restart kubelet
kubectl uncordon <cp-node>
# 4) worker 逐台
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
dnf install -y kubeadm-1.37.2 kubelet-1.37.2 kubectl-1.37.2 --disableexcludes=kubernetes
kubeadm upgrade node
systemctl daemon-reload && systemctl restart kubelet
kubectl uncordon <node>
# 5) 验证
kubectl get nodes -o wide
kubectl get pod -A | grep -v Running
kubectl version
升级回滚:Kubernetes 不支持降级。唯一可靠的回滚是 etcd 快照恢复 + 重装旧版本二进制。所以升级前必须备份 etcd。
13.4 集群扩容
# 加 worker:新机器按 3.2-3.4 准备
kubeadm join <endpoint>:6443 --token <t> --discovery-token-ca-cert-hash sha256:<h>
# token 过期(默认 24h)时重新生成
kubeadm token create --print-join-command
kubeadm token list
# 加 control-plane
kubeadm init phase upload-certs --upload-certs
kubeadm token create --print-join-command --certificate-key <key>
13.5 节点维护标准流程
# 1) 标记不可调度
kubectl cordon <node>
# 2) 驱逐(注意 PDB,可能卡住)
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data --timeout=600s
# 3) 维护操作(升级内核、换硬件、扩磁盘)
# 4) 恢复
kubectl uncordon <node>
# 5) 验证
kubectl get node <node>
kubectl get pod -A --field-selector spec.nodeName=<node>
drain 卡住的三种原因:① PDB 预算不足;② 有裸 pod(无控制器,需
--force);③ DaemonSet pod 未加--ignore-daemonsets。
13.6 控制面故障处理
| 故障 | 表现 | 处理 |
|---|---|---|
| apiserver 全挂 | kubectl 超时,pod 仍运行 |
查容器、证书、etcd 连通 |
| etcd 单节点故障 | 多数派仍在则正常 | 替换节点,重新加入 |
| etcd 多数派丢失 | 集群只读/不可用 | 从快照恢复 |
| controller-manager 挂 | 不重建 pod、不扩缩 | 查 leader 选举日志 |
| scheduler 挂 | 新 pod Pending | 查日志、重启 |
| 证书过期 | Unable to connect to the server: x509 |
kubeadm certs renew all |
# 控制面组件是静态 Pod,看容器状态
crictl ps -a | grep -E 'kube-apiserver|kube-controller|kube-scheduler|etcd'
crictl logs <container-id> --tail 100
journalctl -u kubelet -n 100 | grep -iE 'apiserver|etcd'
14. 故障排查
14.1 分层排查法
1. 集群层:kubectl get --raw /readyz?verbose
2. 节点层:kubectl get nodes / describe node
3. 工作负载层:kubectl get pod / describe pod / logs
4. 容器层:crictl ps / crictl logs / exec
5. 应用层:日志、探针、依赖
# 一键看集群整体
kubectl get --raw='/readyz?verbose'
kubectl get nodes
kubectl get pod -A --field-selector status.phase!=Running
kubectl get events -A --sort-by=.lastTimestamp | tail -30
14.2 节点 NotReady
# 1) 节点状态与原因
kubectl describe node <node> | sed -n '/Conditions:/,/Addresses:/p'
# 2) 上节点查 kubelet
systemctl status kubelet
journalctl -u kubelet -n 100 --no-pager
journalctl -u kubelet -f | grep -iE 'error|failed'
# 3) 容器运行时
systemctl status containerd
crictl version
crictl ps | head
# 4) 资源
df -h
free -h
cat /proc/sys/fs/file-nr
# 5) 网络与证书
ping <apiserver-ip>
curl -k https://<apiserver>:6443/healthz
ls -l /var/lib/kubelet/pki/
openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -dates
# 6) 常见错误
# "failed to run Kubelet: misconfiguration: kubelet cgroup driver: systemd is different from docker cgroup driver: cgroupfs"
# → cgroup 驱动不一致(见 2.x / containerd 分册)
# "Unable to register node with API server" → 网络或 apiserver 不可达
# "PLEG is not healthy" → 运行时卡住,重启 containerd
# "node has insufficient memory" → 资源压力
14.3 Pod 异常排查表
| 状态 | 首查 | 常见原因 |
|---|---|---|
Pending |
describe pod Events |
资源不足、污点、亲和性、PVC 未绑定 |
ContainerCreating |
Events | 镜像拉取、卷挂载、CNI 分配 IP |
ImagePullBackOff |
describe pod 底部 |
镜像名错、无 imagePullSecret、网络 |
CrashLoopBackOff |
logs --previous |
启动报错、依赖不可达、探针误杀 |
Running 但 0/1 Ready |
describe pod Readiness |
探针路径/端口错、启动慢 |
OOMKilled |
inspect 的 OOMKilled |
limit 太小、内存泄漏 |
Evicted |
describe pod Message |
节点磁盘/内存压力 |
Terminating 卡住 |
-o yaml 看 finalizers |
卷卸载失败、finalizer 未清 |
Error |
logs |
应用退出码非 0 |
Completed |
— | 正常(Job) |
Unknown |
describe node |
kubelet 失联 |
# 标准五连
kubectl describe pod <pod> -n <ns>
kubectl logs <pod> -n <ns> --tail=200
kubectl logs <pod> -n <ns> --previous
kubectl get pod <pod> -n <ns> -o yaml
kubectl get events -n <ns> --sort-by=.lastTimestamp | tail -20
14.4 OOM 与资源类
# 确认 OOM
kubectl describe pod <pod> | grep -A3 'Last State'
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'
# 节点侧
dmesg -T | grep -i 'killed process' | tail
journalctl -k | grep -i oom
# JVM 容器内存对齐
# 用 -XX:MaxRAMPercentage=75,不要写死 -Xmx
# 堆 + 元空间 + 直接内存 + 线程栈 ≈ limit × 0.8
# 资源使用
kubectl top pod -n <ns> --sort-by=memory
kubectl describe node <node> | grep -A10 'Allocated resources'
14.5 Service / 网络
# 1) endpoints 是否为空
kubectl get ep <svc> -n <ns>
kubectl get endpointslices -n <ns>
# 2) selector 是否匹配 pod label
kubectl get svc <svc> -n <ns> -o jsonpath='{.spec.selector}'
kubectl get pod -n <ns> --show-labels
# 3) pod 是否 Ready(未 Ready 不会进 endpoints)
kubectl get pod -n <ns> -o wide
# 4) 从 pod 内测
kubectl run -it --rm tmp --image=curlimages/curl --restart=Never -- \
curl -v http://<svc>.<ns>.svc.cluster.local
# 5) DNS
kubectl run -it --rm dns --image=busybox:1.36 --restart=Never -- \
nslookup <svc>.<ns>.svc.cluster.local
# 6) kube-proxy
kubectl get pod -n kube-system -l k8s-app=kube-proxy
kubectl logs -n kube-system -l k8s-app=kube-proxy --tail=50
14.6 存储
kubectl get pvc -A
kubectl describe pvc <pvc> -n <ns>
kubectl get pv
kubectl get sc
kubectl get pod -A | grep csi
# 挂载失败常见事件
# "Unable to attach or mount volumes: ... timeout expired waiting for volumes to attach"
# → CSI 驱动异常 / 节点与卷不在同 AZ
# "failed to provision volume with StorageClass" → provisioner 权限或配额
14.7 控制面与 etcd
kubectl get --raw '/readyz?verbose'
kubectl get componentstatuses # 已弃用但仍可用
kubectl -n kube-system get pod
# etcd
ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
endpoint health --cluster
14.8 常用调试工具
# 临时 pod(自带网络工具)
kubectl run netshoot --rm -it --image=nicolaka/netshoot --restart=Never -- bash
# 里面:dig / curl / tcpdump / ss / nslookup / iperf3
# 节点调试
kubectl debug node/<node> -it --image=ubuntu:24.04
# 会挂载宿主根文件系统到 /host
# 复制 pod 调试(不影响原 pod)
kubectl debug <pod> --copy-to=<pod>-debug --image=ubuntu:24.04 -it
# 临时容器(1.23+,加进运行中 pod)
kubectl debug -it <pod> --image=busybox:1.36 --target=<container>
# 资源关系树
kubectl tree deploy web -n prod
# 事件时间线
kubectl get events -n prod --sort-by=.lastTimestamp -o custom-columns=\
'TIME:.lastTimestamp,TYPE:.type,REASON:.reason,OBJ:.involvedObject.name,MSG:.message'
15. 监控与日志
15.1 metrics-server
# 安装
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 自签证书环境需加 --kubelet-insecure-tls
kubectl -n kube-system patch deploy metrics-server --type=json \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
kubectl -n kube-system get pod -l k8s-app=metrics-server
kubectl top nodes
kubectl top pods -A
15.2 kube-state-metrics 与 node-exporter
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install kps prometheus-community/kube-prometheus-stack \
-n monitoring --create-namespace \
--set grafana.adminPassword='<pwd>' \
--set prometheus.prometheusSpec.retention=15d \
--set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=100Gi
关键指标(告警必配):
| 指标 | 含义 | 阈值参考 |
|---|---|---|
kube_node_status_condition{condition="Ready"} |
节点就绪 | != 1 告警 |
kube_pod_container_status_restarts_total |
重启次数 | 5 分钟增 > 3 |
kube_pod_status_phase{phase="Pending"} |
挂起 pod | > 5 分钟 |
kube_deployment_status_replicas_unavailable |
不可用副本 | > 0 持续 5 分钟 |
node_filesystem_avail_bytes |
磁盘可用 | < 15% |
node_memory_MemAvailable_bytes |
内存可用 | < 10% |
kubelet_pleg_relist_duration_seconds |
PLEG 延迟 | P99 > 3s |
apiserver_request_duration_seconds |
apiserver 延迟 | P99 > 1s |
etcd_disk_wal_fsync_duration_seconds |
etcd 磁盘 | P99 > 10ms |
15.3 日志架构
容器 stdout/stderr
↓
/var/log/pods/<ns>_<pod>_<uid>/<container>/0.log
↓ (/var/log/containers/*.log 是软链)
节点日志采集器(Fluent Bit / Filebeat / Promtail DaemonSet)
↓
Loki / Elasticsearch / 云日志
# 节点上看日志
ls /var/log/pods/ | head
ls -l /var/log/containers/ | head
kubectl logs <pod> --tail=100
# 日志被轮转后仍要查历史
# kubelet 默认保留 5 个 10Mi 文件
# /var/lib/kubelet/config.yaml: containerLogMaxSize / containerLogMaxFiles
# Fluent Bit DaemonSet 关键配置(简)
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: logging
spec:
selector: { matchLabels: { app: fluent-bit } }
template:
metadata: { labels: { app: fluent-bit } }
spec:
serviceAccountName: fluent-bit
tolerations: [{ operator: Exists }]
containers:
- name: fluent-bit
image: cr.fluentbit.io/fluent/fluent-bit:3.1
volumeMounts:
- { name: varlog, mountPath: /var/log }
- { name: containers, mountPath: /var/lib/docker/containers, readOnly: true }
- { name: config, mountPath: /fluent-bit/etc }
volumes:
- { name: varlog, hostPath: { path: /var/log } }
- { name: containers, hostPath: { path: /var/lib/docker/containers } }
- { name: config, configMap: { name: fluent-bit-config } }
15.4 巡检脚本
#!/usr/bin/env bash
set -uo pipefail
echo "===== k8s check $(date '+%F %T') ====="
echo "--- 集群就绪"
kubectl get --raw='/readyz' && echo ok
echo "--- 节点"
kubectl get nodes -o wide
echo "--- 非 Running pod"
kubectl get pod -A --field-selector status.phase!=Running --no-headers | head -20
echo "--- 高重启 pod"
kubectl get pod -A --sort-by='.status.containerStatuses[0].restartCount' -o custom-columns=\
'NS:.metadata.namespace,NAME:.metadata.name,RESTARTS:.status.containerStatuses[0].restartCount' --no-headers | tail -10
echo "--- 未就绪 deployment"
kubectl get deploy -A -o json | jq -r '.items[] | select(.status.unavailableReplicas>0) | .metadata.namespace+"/"+.metadata.name'
echo "--- Pending PVC"
kubectl get pvc -A --field-selector status.phase!=Bound --no-headers | head
echo "--- 近期 Warning 事件"
kubectl get events -A --field-selector type=Warning --sort-by=.lastTimestamp --no-headers | tail -15
echo "--- 证书"
kubeadm certs check-expiration 2>/dev/null | tail -20
echo "--- etcd"
ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key endpoint health 2>/dev/null
16. 备份与迁移
16.1 备份什么
| 对象 | 方式 | 频率 |
|---|---|---|
| etcd | etcdctl snapshot save |
每日 + 变更前 |
| 集群资源清单 | kubectl get all -A -o yaml |
每日 |
| PV 数据 | 应用层备份 / 卷快照 | 按 RPO |
| 证书与密钥 | 加密备份 /etc/kubernetes/pki |
变更时 |
| 配置 | Git(GitOps) | 每次变更 |
| 镜像 | Harbor | 每次发布 |
# 全量资源导出(不含 secret 内容时注意脱敏)
mkdir -p /backup/k8s/$(date +%F)
for r in ns deploy sts ds svc ing cm secret pvc pv sa role rolebinding clusterrole clusterrolebinding; do
kubectl get $r -A -o yaml > /backup/k8s/$(date +%F)/$r.yaml 2>/dev/null
done
secret导出是 base64 明文,注意存储加密与访问控制。
16.2 Velero(推荐的应用级备份)
# 安装 CLI
curl -fL -o velero.tar.gz https://github.com/vmware-tanzu/velero/releases/latest/download/velero-linux-amd64.tar.gz
tar xzf velero.tar.gz && mv velero-*/velero /usr/local/bin/
# 安装服务端(以 MinIO 为例)
velero install \
--provider aws \
--plugins velero/velero-plugin-for-aws:v1.10.0 \
--bucket velero \
--secret-file ./credentials-velero \
--use-volume-snapshots=false \
--backup-location-config region=minio,s3ForcePathStyle="true",s3Url=http://minio.storage:9000
# 备份
velero backup create full-$(date +%F) --include-namespaces prod
velero backup create app-only --include-namespaces prod --exclude-resources pods
velero backup get
velero backup describe full-2026-11-05 --details
velero backup logs full-2026-11-05
# 恢复
velero restore create --from-backup full-2026-11-05
velero restore get
velero restore describe <restore-name>
# 定时备份
velero schedule create daily --schedule="0 3 * * *" --include-namespaces prod --ttl 168h
velero schedule get
# 迁移到新集群
velero backup create migrate --include-cluster-resources=true
# 新集群装同版本 velero 指向同一存储,然后 restore
16.3 集群迁移
# 方案 A:Velero(推荐,保留 PVC 依赖快照或 Restic/Kopia)
# 方案 B:纯清单迁移 + 手工搬数据
for r in ns deploy sts svc ing cm sa; do
kubectl get $r -n prod -o yaml | kubectl --context=new apply -f -
done
# 数据:应用层 dump/restore
# 方案 C:蓝绿集群(改 DNS/LB 切换)
16.4 k3s 备份
# k3s 自动备份 SQLite 到 /var/lib/rancher/k3s/server/db/snapshots
ls /var/lib/rancher/k3s/server/db/snapshots/
k3s etcd-snapshot save --name manual-$(date +%F)
k3s etcd-snapshot ls
# 恢复
systemctl stop k3s
k3s server --cluster-reset --cluster-reset-restore-path=/var/lib/rancher/k3s/server/db/snapshots/<file>
systemctl start k3s
17. 卸载与重建
卸载前:① 数据已备份(etcd 快照 + PV 数据);② 业务已迁移;③ 停机窗口已批;④ 记录所有自定义资源清单。
17.1 完整卸载流程
# 0) 备份
kubectl get all -A -o yaml > /backup/k8s-all-$(date +%F).yaml
kubectl get pv,pvc,cm,secret -A -o yaml > /backup/k8s-storage-config.yaml
# etcd 快照见 13.2
# 1) master:驱逐并删除所有 worker
for n in $(kubectl get nodes --no-headers | awk '$3!="control-plane,master"{print $1}'); do
kubectl drain "$n" --ignore-daemonsets --delete-emptydir-data --force
kubectl delete node "$n"
done
# 2) 每个节点(含 master):reset
kubeadm reset -f
每个节点必须手工清理(kubeadm reset 不做这些)
# CNI 配置与状态
rm -rf /etc/cni/net.d
rm -rf /var/lib/cni/
rm -rf /opt/cni/bin # 若不再需要 CNI 插件
# kubelet 数据
rm -rf /var/lib/kubelet/*
rm -rf /etc/kubernetes/
rm -rf /var/lib/etcd
rm -rf /root/.kube /home/*/.kube
rm -rf /var/log/pods /var/log/containers /var/log/kubernetes
# 网卡(按实际 CNI 删)
ip link delete cni0 2>/dev/null
ip link delete flannel.1 2>/dev/null
ip link delete tunl0 2>/dev/null
ip link delete cali0 2>/dev/null
ip link show | grep -E 'cali|cilium|vxlan|flannel|cni'
# iptables / nftables
iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X
iptables -P INPUT ACCEPT && iptables -P FORWARD ACCEPT && iptables -P OUTPUT ACCEPT
ipvsadm --clear 2>/dev/null
nft flush ruleset 2>/dev/null # ★ 会清掉所有防火墙规则,慎用
# 路由
ip route | grep -E '10.244|10.96|flannel|cali'
# 3) 卸载软件包
dnf remove -y kubelet kubeadm kubectl cri-tools kubernetes-cni --disableexcludes=kubernetes
rm -f /etc/yum.repos.d/kubernetes.repo
systemctl daemon-reload
# Debian
# apt-mark unhold kubelet kubeadm kubectl
# apt purge -y kubelet kubeadm kubectl
# 4) 还原系统参数
rm -f /etc/sysctl.d/99-k8s.conf
rm -f /etc/modules-load.d/k8s.conf
sysctl --system
# 5) 容器运行时是否保留?
# 保留:只清 k8s 的镜像
crictl rmi --prune
# 卸载:见 containerd 分册第 15 章
# 6) 验证
kubectl version 2>&1 | head -2
systemctl status kubelet 2>&1 | head -3
ls /etc/kubernetes 2>/dev/null
ip link | grep -E 'cni|flannel|cali'
17.2 同 IP 重建集群的坑
kubeadm reset 后不清 CNI 与网络接口,用同一 IP 重建会出现:
- pod 网段冲突(
cni0里残留旧 IP) - Service 不通(iptables 规则混乱)
- 节点加入失败(旧证书残留)
必须执行 17.1 的清理后再 init。
17.3 只删某个命名空间
# 正常删除
kubectl delete ns prod
# 卡在 Terminating(有 finalizer 未处理)
kubectl get ns prod -o json | jq '.spec.finalizers'
kubectl get ns prod -o json | jq '.spec.finalizers=[]' | kubectl replace --raw "/api/v1/namespaces/prod/finalize" -f -
# 仍有资源残留时逐个清
kubectl api-resources --verbs=list --namespaced -o name | \
xargs -n1 kubectl get -n prod --show-kind --ignore-not-found
18. 高频故障速查
18.1 集群级
| 现象 | 排查 | 处理 |
|---|---|---|
kubectl 超时 |
crictl ps | grep apiserver |
apiserver 容器挂了 / etcd 不通 |
x509: certificate has expired |
kubeadm certs check-expiration |
kubeadm certs renew all |
Unable to connect to the server: EOF |
控制面 VIP 漂移 / LB 配置 | 检查 keepalived、LB 后端 |
| 所有节点 NotReady | CNI 挂了 | 查 CNI DaemonSet |
etcdserver: request timed out |
etcd 磁盘慢 / 负载高 | 换 SSD、defrag |
too many open files |
inotify 上限 | 调 fs.inotify.max_user_watches |
| apiserver 502/504 | 后端 etcd 慢 | 查 etcd 指标 |
18.2 调度与资源
| 现象 | 排查 | 处理 |
|---|---|---|
| Pod 一直 Pending | describe pod Events |
资源不足 / 污点 / 亲和性 / PVC |
Insufficient cpu |
describe node Allocated |
扩节点或降 requests |
node(s) had untolerated taint |
describe node Taints |
加 toleration 或去污点 |
0/N nodes are available: node affinity |
节点标签 | 补标签或改亲和性 |
| 部分副本 Pending | 反亲和 + 节点数不足 | 改 topologySpread 软约束 |
| 驱逐频繁 | describe node Conditions |
扩磁盘/内存;调 eviction 阈值 |
18.3 网络
| 现象 | 排查 | 处理 |
|---|---|---|
| Service 不通 | get ep 是否为空 |
修 selector / 等 pod Ready |
| 跨节点 pod 不通 | CNI pod 状态 | 重启 CNI;查路由 |
| DNS 解析失败 | CoreDNS pod、53 端口 | 修 CoreDNS;放行 DNS |
| DNS 慢 5 秒 | ndots:5 |
用 FQDN 或调 ndots:2 |
| 大量 conntrack 满 | dmesg | grep conntrack |
调 nf_conntrack_max |
| Ingress 502 | 后端 pod 端口 / readiness | 查 ingress-nginx 日志 |
| NodePort 不通 | 节点防火墙 | 放行 30000-32767 |
18.4 存储
| 现象 | 排查 | 处理 |
|---|---|---|
| PVC Pending | describe pvc |
SC 缺失 / provisioner 异常 / 容量 |
| 挂载超时 | describe pod Events |
CSI 异常;跨 AZ |
ReadWriteOnce 冲突 |
多 pod 挂同一 PVC | 用 RWX 存储或每 pod 独立 PVC |
| 扩容失败 | describe pvc |
SC 未开 allowVolumeExpansion |
| 数据丢失 | PV reclaimPolicy: Delete |
重要数据设 Retain |
18.5 应用层
| 现象 | 排查 | 处理 |
|---|---|---|
| CrashLoopBackOff | logs --previous |
修启动错误;检查配置/依赖 |
| OOMKilled | 事件、lastState |
提 limit;修内存泄漏 |
| 探针失败 | describe pod Events |
调路径/端口/阈值 |
| 配置不生效 | kubectl exec cat |
env 需重启;volume 等 1~2 分钟 |
| 镜像拉不到 | describe pod |
镜像名、secret、网络 |
| 优雅退出超时 | 事件 Killing |
调 terminationGracePeriodSeconds、preStop |
19. 附录
19.1 常用资源缩写
| 全名 | 缩写 |
|---|---|
| pods | po |
| services | svc |
| deployments | deploy |
| statefulsets | sts |
| daemonsets | ds |
| replicasets | rs |
| configmaps | cm |
| secrets | — |
| namespaces | ns |
| nodes | no |
| persistentvolumes | pv |
| persistentvolumeclaims | pvc |
| storageclasses | sc |
| serviceaccounts | sa |
| ingresses | ing |
| networkpolicies | netpol |
| poddisruptionbudgets | pdb |
| horizontalpodautoscalers | hpa |
| cronjobs | cj |
| endpointslices | — |
19.2 端口
| 端口 | 组件 |
|---|---|
| 6443 | kube-apiserver |
| 2379/2380 | etcd(客户端/peer) |
| 10250 | kubelet API |
| 10257 | controller-manager |
| 10259 | scheduler |
| 30000-32767 | NodePort |
| 8472/udp | Flannel VXLAN |
| 4789/udp | Calico VXLAN |
| 4240/tcp | Cilium health |
| 179/tcp | Calico BGP |
19.3 关键路径
| 路径 | 内容 |
|---|---|
/etc/kubernetes/manifests/ |
静态 Pod(控制面) |
/etc/kubernetes/pki/ |
证书与密钥 |
/etc/kubernetes/admin.conf |
管理员 kubeconfig |
/etc/kubernetes/kubelet.conf |
kubelet 配置 |
/var/lib/kubelet/ |
kubelet 数据(pods、卷) |
/var/lib/kubelet/config.yaml |
KubeletConfiguration |
/var/lib/etcd/ |
etcd 数据 |
/etc/cni/net.d/ |
CNI 配置 |
/opt/cni/bin/ |
CNI 插件 |
/var/log/pods/ |
容器日志 |
/var/log/containers/ |
日志软链 |
/var/lib/kubelet/pki/ |
kubelet 客户端证书 |
19.4 最小可用清单
# 一个能跑的最小 Deployment + Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo
spec:
replicas: 2
selector: { matchLabels: { app: demo } }
template:
metadata: { labels: { app: demo } }
spec:
containers:
- name: demo
image: nginx:1.25
ports: [{ containerPort: 80 }]
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: 500m, memory: 256Mi }
---
apiVersion: v1
kind: Service
metadata:
name: demo
spec:
selector: { app: demo }
ports: [{ port: 80, targetPort: 80 }]
19.5 与其他手册的配合
- 容器运行时(含 CRI、镜像拉取、
SystemdCgroup):containerd-常用运维指令手册.md - 单机容器与镜像构建:
Docker-常用运维指令手册.md/Podman-常用运维指令手册.md - 私有镜像仓库:
Harbor-镜像仓库运维指令手册.md - 宿主系统(内核、cgroup、SELinux、磁盘):
CentOS-7-10-常规运维指令手册.md/Ubuntu-常用运维指令手册.md - etcd 底层与数据库运维思路:
MySQL-常用运维指令手册.md(备份/恢复方法论可借鉴)
一句话总结:Kubernetes 的运维难点不在命令,而在排查路径——先分层(集群/节点/工作负载/容器/应用),再看事件(
describe的 Events 覆盖 80% 问题),最后才进容器。把kubectl get --raw /readyz?verbose、kubectl describe、kubectl logs --previous这三招用熟,大部分故障能在 5 分钟内定位。