Day 0 新手现场接管 Runbook:先看懂,再动手
这篇是给完全没接触过 K8s 的人用的现场操作版。目标不是一次把所有组件讲完,而是先学会三件事:
- 这 5 台机器分别是什么角色。
- 怎么用命令确认集群是否健康。
- 出问题时先看哪里、为什么这么看。
本文所有命令都按“先读状态,再改配置”的顺序写。读状态的命令不会改变机器;写配置的命令会明确标出来。
0. 当前真实机器
| 公网 IP | 内网 IP | hostname | 角色 | 说明 |
|---|---|---|---|---|
154.201.73.31 | 10.0.24.31 | k8s-cp-1 | control plane | 第一个控制面节点,kubeadm init 所在节点 |
154.201.73.81 | 10.0.24.29 | k8s-cp-2 | control plane | 第二个控制面节点 |
45.205.31.214 | 10.0.24.32 | k8s-cp-3 | control plane | 第三个控制面节点 |
45.205.31.180 | 10.0.24.28 | k8s-w-1 | worker | 业务负载节点 |
45.205.31.10 | 10.0.24.30 | k8s-w-2 | worker | 业务负载节点 |
control plane 是什么:K8s 的“大脑”。里面跑 apiserver、scheduler、controller-manager、etcd。你用 kubectl 发的命令先进 apiserver,再由控制面决定集群怎么变。
worker 是什么:真正跑业务 Pod 的机器。worker 上最重要的是 kubelet 和 containerd。
etcd 是什么:K8s 的数据库。Deployment、Service、Secret、节点状态都存在 etcd。3 个 control plane 会组成 3 节点 etcd,坏 1 个还能工作。
1. 第一步只做探测
先确认 SSH 能连、机器名是什么、内网 IP 是什么、系统版本是什么。
for ip in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10; do
ssh -o BatchMode=yes -o ConnectTimeout=8 root@$ip '
echo PUBLIC_IP='"$ip"'
echo HOSTNAME=$(hostname)
echo IPS=$(hostname -I)
. /etc/os-release && echo OS="$PRETTY_NAME"
echo KERNEL=$(uname -r)
echo VIRT=$(systemd-detect-virt || true)
echo CONTAINER=$(systemd-detect-virt --container || true)
'
done
命令解释:
ssh root@$ip '...':登录远端机器并执行引号里的命令。BatchMode=yes:只用密钥登录,不弹密码交互;自动化脚本必须这样写。hostname:看机器名,K8s 节点名通常就用它。hostname -I:看这台机器的所有 IP,重点找10.0.24.x内网地址。systemd-detect-virt:确认是不是 KVM/VMware 这种真虚拟机。LXC 容器里跑 K8s 会很痛苦。
2. 看集群是否健康
在任意 control plane 上执行。这里用 k8s-cp-1:
ssh root@154.201.73.31 'kubectl get nodes -o wide'
你应该看到 5 个节点都是 Ready:
NAME STATUS ROLES INTERNAL-IP
k8s-cp-1 Ready control-plane 10.0.24.31
k8s-cp-2 Ready control-plane 10.0.24.29
k8s-cp-3 Ready control-plane 10.0.24.32
k8s-w-1 Ready <none> 10.0.24.28
k8s-w-2 Ready <none> 10.0.24.30
命令解释:
kubectl:K8s 客户端。你用它跟 apiserver 说话。get nodes:列出集群节点。-o wide:显示更多列,比如节点内网 IP、系统版本、container runtime。Ready:kubelet 正常上报,CNI 网络正常,节点可以调度或运行 Pod。
如果节点是 NotReady,下一步看 kubelet:
ssh root@154.201.73.31 'journalctl -u kubelet --since "10 min ago" --no-pager | tail -80'
kubelet 是什么:每台机器上的 K8s 代理。它负责向 apiserver 汇报节点状态,也负责让 containerd 启动/停止 Pod。
3. 当前集群组件一览
ssh root@154.201.73.31 'helm list -A'
当前集群通过 Helm 安装了这些组件:
| release | namespace | 组件作用 |
|---|---|---|
cilium | kube-system | CNI 网络插件,让 Pod 跨节点通信 |
harbor | harbor | 私有镜像仓库,存放业务镜像 |
argocd | argocd | GitOps CD,从 Git 自动同步部署 YAML |
gitea | gitea | 集群内 Git 服务 |
jenkins | jenkins | CI 构建服务,配合 Kaniko 构建镜像 |
kps | monitoring | kube-prometheus-stack,包含 Prometheus/Grafana/Alertmanager |
loki | monitoring | 日志系统,Promtail 采日志,Loki 存和查 |
Helm 是什么:K8s 的包管理器。类似 Ubuntu 的 apt,但安装对象是 K8s YAML。
常用页面入口:
| 组件 | URL | 说明 |
|---|---|---|
| Grafana | http://154.201.73.31:32380 | 监控看板,登录页返回 302 属于正常 |
| Prometheus | http://154.201.73.31:30090 | 指标查询,通常会跳转到 UI 页面 |
| Jenkins | http://154.201.73.31:30808 | CI 页面,未登录时可能返回 403 |
| ArgoCD | http://154.201.73.31:30080 | GitOps CD 页面 |
| Harbor | http://154.201.73.31:30002 | 私有镜像仓库 |
| Longhorn | http://154.201.73.31:31172 | 分布式存储 UI |
NodePort 可以换成任意节点公网 IP 访问,比如 http://45.205.31.214:32380 也应指向 Grafana。如果某个入口浏览器打不开,先查云厂商防火墙/安全组,再查 Service。
4. 看 Pod 是否健康
ssh root@154.201.73.31 \
'kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded -o wide'
命令解释:
get pods -A:列出所有 namespace 的 Pod。--field-selector=status.phase!=Running,status.phase!=Succeeded:只看异常或未完成的 Pod。Running:Pod 正在运行。Succeeded:一次性任务正常结束。CrashLoopBackOff:容器反复崩溃。OOMKilled:容器内存超限被杀。
当前看到的 day12-test/mem-hog 和 mem-hog-v2 是 Day12 故障演练留下的 OOMKilled Pod,不是平台故障。
5. 现场故障:k8s-api 解析丢失
5.1 现象
在 cp-1 上执行:
kubectl get nodes
报错:
Unable to connect to the server:
dial tcp: lookup k8s-api on 127.0.0.53:53: server misbehaving
kubelet 日志也在报:
Unable to register node with API server:
Post "https://k8s-api:16443/api/v1/nodes":
dial tcp: lookup k8s-api on 127.0.0.53:53: server misbehaving
5.2 根因
这个集群没有外部负载均衡器。每台机器本地跑 HAProxy,监听:
127.0.0.1:16443 -> 3 个 apiserver 的 6443
所以 /etc/hosts 必须有:
127.0.0.1 localhost k8s-api
k8s-api 不是 K8s 自动提供的 DNS 名字,它只是我们自己写进 /etc/hosts 的本机别名。
这次 cp-1/cp-2 的 /etc/hosts 被 cloud-init 重写,k8s-api 丢了。kubelet 找不到 apiserver,就停止上报节点状态,节点变成 NotReady。
5.3 修复命令
这是写配置的命令,会修改 5 台机器的 /etc/hosts 和 /etc/cloud/cloud.cfg。执行前会备份。
for ip in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10; do
ssh root@$ip 'set -eu
cp -a /etc/hosts /etc/hosts.bak.$(date +%Y%m%d-%H%M%S)
cp -a /etc/cloud/cloud.cfg /etc/cloud/cloud.cfg.bak.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true
if grep -q "^manage_etc_hosts:" /etc/cloud/cloud.cfg 2>/dev/null; then
sed -i "s/^manage_etc_hosts:.*/manage_etc_hosts: false/" /etc/cloud/cloud.cfg
else
printf "\nmanage_etc_hosts: false\n" >> /etc/cloud/cloud.cfg
fi
cat > /etc/hosts <<HOSTS
127.0.0.1 localhost k8s-api
127.0.1.1 $(hostname)
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
10.0.24.31 k8s-cp-1 m1
10.0.24.29 k8s-cp-2 m2
10.0.24.32 k8s-cp-3 m3
10.0.24.28 k8s-w-1 m4
10.0.24.30 k8s-w-2 m5
HOSTS
getent hosts k8s-api
getent hosts k8s-cp-1
'
done
命令解释:
cp -a:保留权限/时间戳做备份,出错能回滚。manage_etc_hosts: false:告诉 cloud-init 不要再接管/etc/hosts。cat > /etc/hosts <<HOSTS ... HOSTS:用固定内容重写 hosts。getent hosts k8s-api:验证系统真实解析结果。比dig更适合,因为/etc/hosts走的是系统 NSS 解析链。
5.4 修复后验证
ssh root@154.201.73.31 'kubectl get nodes -o wide'
5 个节点应恢复 Ready。
再看控制面核心 Pod:
ssh root@154.201.73.31 \
'kubectl get pods -n kube-system -o wide | grep -E "kube-apiserver|kube-scheduler|cilium-|node-local"'
应该看到 apiserver、scheduler、Cilium、node-local-dns 都是 1/1 Running。
6. CNI / DNS / HAProxy 分别是什么
CNI:Container Network Interface。K8s 自己不负责 Pod 网络,交给 CNI 插件。本集群用 Cilium。没有 CNI,Pod 可以被创建,但跨节点通信和 Service 基本不可用。
ssh root@154.201.73.31 'cilium status --wait=false'
CoreDNS:集群 DNS。Pod 访问 harbor.harbor.svc、kubernetes.default.svc 这种名字时靠它解析。
node-local-dns:每台节点本地的 DNS 缓存。Pod 先问本机 169.254.20.10,减少跨节点 DNS 往返。
HAProxy:本集群没有云厂商 LoadBalancer,所以每台机器本地跑 HAProxy:
ssh root@154.201.73.31 'ss -tlnp | grep 16443'
127.0.0.1:16443 是本地入口,后端转发到 3 个 apiserver 的 :6443。
7. Longhorn 存储怎么看
Longhorn 是当前集群的分布式块存储。PVC 创建出来后,Longhorn 负责在多台机器上放副本。
ssh root@154.201.73.31 'kubectl get volumes.longhorn.io -n longhorn-system'
关键列:
STATE=attached:卷已经挂到某个节点使用。ROBUSTNESS=healthy:副本健康。ROBUSTNESS=degraded:副本数不足,通常是节点刚恢复、正在重建,先观察。STATE=attaching+ROBUSTNESS=unknown:需要继续查 Longhorn manager 日志和 UI。
刚修复 cp-1/cp-2 后,部分卷可能短时间 degraded,等 Longhorn 重建副本后会恢复。
8. Grafana 常见故障:datasource 重复
Grafana 是监控页面。Prometheus 提供指标数据,Loki 提供日志数据,它们都可以作为 Grafana 的 datasource。
如果 Grafana 反复重启,先看日志:
ssh root@154.201.73.31 \
'kubectl logs -n monitoring -l app.kubernetes.io/name=grafana -c grafana --tail=80'
如果看到:
Only one datasource per organization can be marked as default
说明有两个 datasource 都写了 isDefault: true。检查:
ssh root@154.201.73.31 \
'kubectl get configmap -n monitoring -l grafana_datasource=1 -o yaml | grep -n "name:\|isDefault"'
本集群应保留 Prometheus 为默认 datasource,Loki 的 datasource 必须是 isDefault: false。
Grafana 还挂了一个 Longhorn RWO PVC。RWO 的意思是同一时间只能挂到一个节点。如果 Grafana 只有 1 个副本,Deployment 发布策略应使用 Recreate,不要用默认 RollingUpdate,否则新旧 Pod 会争同一个卷,事件里会看到 Multi-Attach error。
ssh root@154.201.73.31 \
'kubectl get deploy -n monitoring kps-grafana -o jsonpath="{.spec.strategy.type}"'
期望输出:
Recreate
9. 每次操作后的最小验收
ssh root@154.201.73.31 'kubectl get nodes'
ssh root@154.201.73.31 \
'kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded'
ssh root@154.201.73.31 'cilium status --wait=false'
ssh root@154.201.73.31 'helm list -A'
这 4 条够你回答:
- 节点是否活着。
- 是否有异常 Pod。
- 网络插件是否正常。
- 集群装了哪些大组件。
不要一上来就改 YAML。先用这 4 条判断“是节点问题、网络问题、存储问题、还是应用问题”。