Repository Reading Site
本轮操作记录:环境验证、集群基线盘点与故障样本采集
用户确认: 基于这个前提,我这轮的目标不是“再看一遍目录”,而是做三件事: 1. 验证 5 台机器的 SSH 连通性和关键系统服务状态 2. 验证本机 `kubectl` 对集群的访问,并建立基线快照 3. 抓取几个真实故障样本,为后续学习和排障训练做材料准备 这一步的意义是: --- 我对 5 台机器都执行了同一类命令,结构如下: 远端执行的内容大致是:
本轮操作记录:环境验证、集群基线盘点与故障样本采集
本轮目标
用户确认:
- 文档里的机器都可以免密登录
- 希望我继续推进
基于这个前提,我这轮的目标不是“再看一遍目录”,而是做三件事:
- 验证 5 台机器的 SSH 连通性和关键系统服务状态
- 验证本机
kubectl对集群的访问,并建立基线快照 - 抓取几个真实故障样本,为后续学习和排障训练做材料准备
这一步的意义是:
把之前的“仓库分析”升级成“基于真实集群状态的学习入口”。
Step 1: 验证 5 台机器能否免密 SSH 登录
实际命令模式
我对 5 台机器都执行了同一类命令,结构如下:
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=8 root@<IP> '...'
远端执行的内容大致是:
printf "host=%s user=%s\n" "$(hostname)" "$(whoami)"
printf "kernel=%s\n" "$(uname -r)"
printf "ips=%s\n" "$(hostname -I | xargs)"
printf "kubelet=%s\n" "$(systemctl is-active kubelet 2>/dev/null || true)"
printf "containerd=%s\n" "$(systemctl is-active containerd 2>/dev/null || true)"
printf "wireguard=%s\n" "$(systemctl is-active wg-quick@wg0 2>/dev/null || true)"
为什么这么设计命令
这条 SSH 命令不是随便拼的,而是有明确目的:
hostname:确认登录到的到底是哪台主机whoami:确认当前远端身份是rootuname -r:确认宿主机内核版本hostname -I:查看该节点当前拥有的所有 IPsystemctl is-active kubelet:确认节点代理是否活着systemctl is-active containerd:确认容器运行时是否活着systemctl is-active wg-quick@wg0:确认 WireGuard 虚拟内网是否活着
为什么不用更简单的 ssh root@ip hostname
因为“能连上”只是最低验证。
真正有价值的是一次远程命令里同时确认:
- 机器身份
- 系统版本
- 网络身份
- K8s 节点工作必需服务
这样一次就能建立环境可信度。
Step 2: 解释 SSH 参数背后的原理
-o BatchMode=yes
作用:
- 禁止 SSH 在需要密码或交互确认时进入交互模式
为什么重要:
- 如果没有免密配置,这个参数会让 SSH 直接失败
- 这比卡在密码输入提示上更适合自动化验证
这实际上是在验证:
是否真的具备“非交互式免密登录能力”。
-o StrictHostKeyChecking=no
作用:
- 首次连接某台机器时,不要求人工确认 host key
为什么重要:
- 自动化验证环境时,经常不希望被 “Are you sure you want to continue connecting?” 卡住
注意:
这在实验环境中方便,但生产环境要更谨慎,因为 host key 校验本来是为了防止中间人攻击。
-o ConnectTimeout=8
作用:
- 连接超时时间设为 8 秒
为什么重要:
- 如果某台机器失联,不希望 SSH 一直挂着
- 这样结果会更快返回,便于批量验证
Step 3: 为什么用 systemctl is-active
命令
远程机器上执行了:
systemctl is-active kubelet
systemctl is-active containerd
systemctl is-active wg-quick@wg0
原理
在 systemd 管理的 Linux 系统里,服务不是抽象概念,而是明确的 unit。
systemctl is-active 的好处是:
- 输出简单,适合脚本判断
- 不像
status那样内容很多 - 能快速知道服务当前是否活着
为什么只验证这 3 个服务
因为它们几乎构成了这个集群数据面的最小闭环:
kubelet:节点代理,负责 Pod 生命周期containerd:实际拉镜像、起容器wg0:跨机房统一内网,保证节点通信地址一致
如果这三者都挂了,节点基本就不具备正常参与集群工作的条件。
Step 4: SSH 验证结果解读
Master
输出摘要:
host=us480851516617a user=root
kernel=6.8.0-48-generic
ips=10.2.207.3 10.244.248.64 10.10.0.1
kubelet=active
containerd=active
wireguard=active
解读:
- 远端身份正常
- kubelet / containerd / wg0 都正常
- 节点同时具备云内网 IP、Pod 相关地址、WireGuard IP
Worker-1
输出摘要:
host=us590068728056 user=root
kernel=6.8.0-107-generic
ips=10.2.207.2 10.244.119.192 10.10.0.2
kubelet=active
containerd=active
wireguard=active
解读:
- 同样具备多层地址
- 服务正常
Worker-2
输出摘要:
host=cp-3 user=root
kernel=6.8.0-48-generic
ips=154.9.27.60 10.0.4.169 10.10.0.3 10.244.242.0 172.17.0.1
kubelet=active
containerd=active
wireguard=active
解读:
- 这是一个很好的教学样本,因为它同时显示了公网 IP、云内网 IP、WireGuard IP、Pod 网络相关地址和 Docker bridge 地址
- 非常适合用来讲“多层网络身份”
Worker-3
输出摘要:
host=hk652699382121 user=root
kernel=6.8.0-48-generic
ips=10.4.225.2 172.17.0.1 172.18.0.1 10.10.0.4 10.244.169.0
kubelet=active
containerd=active
wireguard=active
解读:
- 节点服务正常
- 节点上存在多张桥接/容器网络相关地址
Worker-4
输出摘要:
host=wk-1 user=root
kernel=5.15.0-174-generic
ips=154.219.104.66 10.10.0.5 10.244.147.64
kubelet=active
containerd=active
wireguard=active
解读:
- 这个节点和其他节点相比更异构:Ubuntu 22.04 + 5.15 内核 + containerd 1.7.x
- 但仍成功作为 K8s worker 参与集群
阶段结论
5 台机器的 SSH 和关键服务验证全部通过。
这说明后续:
- 我们可以直接进入远程宿主机层面的排障
- 也可以放心基于真实节点状态写教学材料
Step 5: 验证本机 kubectl 是否能访问集群
命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl get nodes -o wide
KUBECONFIG=~/.kube/config-k8s-lab kubectl get ns
为什么要显式写 KUBECONFIG=...
因为 Kubernetes 客户端默认读的是:
~/.kube/config
但你这里的实验环境配置文件是:
~/.kube/config-k8s-lab
通过在命令前临时指定环境变量 KUBECONFIG,可以确保:
- 当前命令明确使用实验集群配置
- 不会误用默认 kubeconfig
- 不影响其它 shell 会话的默认环境
原理
kubectl 本质是一个 API 客户端。
它需要从 kubeconfig 中知道:
- API Server 地址
- 使用哪个用户或证书
- 当前上下文是哪个集群
所以 kubeconfig 就是:
kubectl的“连接配置 + 身份凭据 + 上下文切换器”。
Step 6: 为什么 kubectl get nodes -o wide 这么重要
命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl get nodes -o wide
-o wide 的作用
不加 -o wide 时,kubectl 只给你基础列。
加上 -o wide 后,会补充:
INTERNAL-IPOS-IMAGEKERNEL-VERSIONCONTAINER-RUNTIME
为什么这很关键
因为节点问题从来不是单一维度:
- 不是只看 Ready/NotReady
- 也要看内核、OS、runtime、地址身份
输出解读出的关键信息
我从输出里确认了:
- 5 个节点全部
Ready - 内部通信地址统一是
10.10.0.x - 控制面只有 1 个节点
- 其中 1 台节点环境明显异构
这些信息后来都被我写进学习文档作为第一课的真实例子。
Step 7: 为什么再看命名空间
命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl get ns
原理
命名空间可以帮助你快速判断:
- 集群是不是只装了核心组件
- 还是已经有大量平台能力
- 是否有环境隔离设计
输出反映出的信息
我看到了:
argocdgiteaharbormonitoringingress-nginxml-platformdev/staging/prod
我得到的结论
这说明当前集群已经是:
- 基础设施层就绪
- 平台组件层较完整
- 业务实验层也已有内容
所以后续教学不能只停留在“刚装好 Kubernetes”的视角,而要直接按真实平台来讲。
Step 8: 盘点集群 Pod、存储和资源基线
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl get pods -A -o wide
KUBECONFIG=~/.kube/config-k8s-lab kubectl get sc
KUBECONFIG=~/.kube/config-k8s-lab kubectl get pvc -A
KUBECONFIG=~/.kube/config-k8s-lab kubectl top nodes
为什么要看这四条
kubectl get pods -A -o wide
作用:
- 一次性查看所有命名空间的 Pod 状态
- 识别当前是否存在故障样本
- 看 Pod 分布在哪些节点
kubectl get sc
作用:
- 看默认存储类是什么
- 判断 PVC 是如何被动态分配的
kubectl get pvc -A
作用:
- 看哪些组件依赖持久化存储
- 看卷是否都
Bound
kubectl top nodes
作用:
- 看节点当前资源快照
- 判断当前崩溃是否可能来自整体资源耗尽
结果摘要
存储
默认存储类是:
nfs-dynamic
PVC 已经被这些组件使用:
- Gitea
- Harbor
- Monitoring
- Loki
- ML Platform
节点资源
当前节点 CPU、内存整体不高,未见全局资源挤压迹象。
我得到的结论
当前集群资源整体不紧张,所以一些 CrashLoop 不太像“全局资源不够”导致,更可能是:
- 应用配置问题
- 平台组件集成问题
- 探针与存储行为不匹配
- CRD/控制器依赖不完整
Step 9: 为什么抓取故障样本
在 kubectl get pods -A -o wide 的输出里,我发现了几类明显异常:
argocd-applicationset-controller:CrashLoopBackOffgitea-postgresql-0:CrashLoopBackOffgitea主 Pod:Init:CrashLoopBackOffmonitoring-grafana:CrashLoopBackOff
这是极好的学习素材。
因为真实故障能帮你训练两件事:
- 从现象判断问题大概在哪一层
- 用证据链而不是直觉下结论
所以我决定继续抓:
describelogs --previous
Step 10: 抓取 describe 和 logs --previous
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n argocd describe pod argocd-applicationset-controller-... | tail -n 80
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n argocd logs argocd-applicationset-controller-... --previous
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n gitea describe pod gitea-postgresql-0 | tail -n 80
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n gitea logs gitea-postgresql-0 --previous
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n monitoring describe pod monitoring-grafana-... | tail -n 100
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n monitoring logs monitoring-grafana-... grafana --previous
为什么这里要用 --previous
这是 Kubernetes 排障里一个非常关键的参数。
在 CrashLoop 场景下,当前容器可能已经重启了,你看到的当前日志未必包含上一次崩溃前的关键信息。
--previous 的意义是:
- 去拿上一个容器实例的日志
- 看到“它上一次为什么死”
这对排查启动即崩溃的程序非常重要。
为什么 describe 后面加 tail
因为 describe 输出很长,而最有排障价值的部分通常集中在后半段:
- State
- Last State
- Exit Code
- Probes
- Events
tail -n 80/100 可以更聚焦地看到这些信息。
Step 11: ArgoCD 故障的证据链
观察到的现象
describe 里看到:
- 容器反复重启
Back-off restarting failed container
日志里看到关键错误:
failed to get restmapping: no matches for kind "ApplicationSet" in version "argoproj.io/v1alpha1"
我如何进一步验证
为了避免仅凭日志猜测,我又执行:
KUBECONFIG=~/.kube/config-k8s-lab kubectl get crd applicationsets.argoproj.io
返回:
NotFound
我得到的结论
这是非常清晰的证据链:
- 应用控制器在启动
- 它想 watch
ApplicationSet - API Server 里没有对应 CRD
- Cache sync 失败
- 控制器退出
这一步体现的排障原则
日志给方向,API 对象给确认。
不要只凭日志说“应该是 CRD 缺了”,而要再用 kubectl get crd 验证一次。
Step 12: Gitea PostgreSQL 故障的证据链
观察到的现象
describe 里看到:
CrashLoopBackOffExit Code: 137- Liveness / Readiness probe 大量失败
日志里看到:
- 数据库启动恢复时间长
- recovery 过程反复出现
- 最后出现文件缺失
为什么这个案例有价值
因为这不是简单的:
- 镜像错了
- 命令写错了
而是涉及:
- 数据恢复
- 存储后端性能
- 探针节奏
- 容器重启策略
我从这里推导出的结论
更可能是:
- 数据库恢复慢
- 探针太急
- kubelet 反复重启容器
- NFS 存储使恢复过程更脆弱
这一步体现的排障原则
有状态工作负载的故障,不要只盯着 Pod 状态,一定要把探针、存储和恢复行为一起看。
Step 13: Grafana 故障的证据链
观察到的现象
Grafana 日志里出现:
Datasource provisioning error: datasource.yaml config is invalid. Only one datasource per organization can be marked as default
为什么不能只停在这句日志
因为你还需要知道:
- 到底是谁把两个 datasource 都配成了 default
所以我继续查带 grafana_datasource=1 标签的 ConfigMap:
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n monitoring get cm -l grafana_datasource=1 -o name
返回两个对象:
configmap/loki-loki-stackconfigmap/monitoring-kube-prometheus-grafana-datasource
随后我分别查看配置内容。
关键发现
Prometheus 的 datasource 配置里:
isDefault: true
Loki 的 datasource 配置里:
isDefault: true
我得到的结论
这不是 Grafana 自己随机崩溃,而是两个 chart 的 provisioning 配置在集成层发生冲突。
这一步体现的排障原则
当日志说“配置冲突”时,下一步不是重启 Pod,而是顺着配置对象把冲突双方都找出来。
Step 14: 本轮为什么没有直接修复这些故障
因为这轮的用户目标不是“帮我把集群修好”,而是:
- 建立学习主线
- 记录操作
- 讲清原理
- 以成为专家为目标
所以当前更合理的动作是:
- 先把这些故障当作学习样本固化下来
- 在教学文档里解释它们分别对应哪一层知识
- 后续如果你要,我再逐个系统排查和修复
这也是工程上很重要的一点:
不是看到异常就立刻动手改,而是先判断当前任务目标是什么。
Step 15: 将结果写成学习文档
基于本轮采集的数据,我新建了:
01-环境验证与第一课-认识你的真实集群.md
这份文档不是简单贴输出,而是把这轮验证转换成了第一课学习内容,重点讲了:
- 为什么先做环境验证
- 节点的四类 IP 分别是什么
- 为什么
INTERNAL-IP是 WireGuard 地址 - 为什么同一台机器会出现这么多地址
kubectl get nodes -o wide每一列是什么意思- 当前集群有哪些平台能力
- 当前 3 个真实故障样本分别在教你什么
这意味着我们已经从“审查仓库”正式进入“基于真实集群讲 Kubernetes”阶段。
本轮命令清单
下面是本轮实际用到的核心命令清单,后续可以直接复用:
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=8 root@107.148.176.193 '...'
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=8 root@107.148.164.118 '...'
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=8 root@154.9.27.60 '...'
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=8 root@38.76.221.17 '...'
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=8 root@154.219.104.66 '...'
KUBECONFIG=~/.kube/config-k8s-lab kubectl get nodes -o wide
KUBECONFIG=~/.kube/config-k8s-lab kubectl get ns
KUBECONFIG=~/.kube/config-k8s-lab kubectl get pods -A -o wide
KUBECONFIG=~/.kube/config-k8s-lab kubectl get sc
KUBECONFIG=~/.kube/config-k8s-lab kubectl get pvc -A
KUBECONFIG=~/.kube/config-k8s-lab kubectl top nodes
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n argocd describe pod argocd-applicationset-controller-... | tail -n 80
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n argocd logs argocd-applicationset-controller-... --previous
KUBECONFIG=~/.kube/config-k8s-lab kubectl get crd applicationsets.argoproj.io
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n gitea describe pod gitea-postgresql-0 | tail -n 80
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n gitea logs gitea-postgresql-0 --previous
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n monitoring describe pod monitoring-grafana-... | tail -n 100
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n monitoring logs monitoring-grafana-... grafana --previous
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n monitoring get cm
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n monitoring get cm -l grafana_datasource=1 -o name
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n monitoring get cm monitoring-kube-prometheus-grafana-datasource -o yaml
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n monitoring get cm loki-loki-stack -o yaml
本轮最终结论
结论一:环境已经足够真实,适合进入正式深学
不是纸面环境,不是模拟集群,而是:
- 5 台真实服务器
- 真实 SSH 免密
- 真实 kubelet/containerd/WireGuard
- 真实平台组件
- 真实业务工作负载
结论二:当前集群已经有很好的教学价值
因为它不只有“成功路径”,还已经自然存在几类非常典型的故障样本。
结论三:下一步应该进入控制面主链路讲解
也就是下一课最核心的问题:
一条
kubectl apply请求,是怎么穿过 API Server、etcd、Scheduler、Controller、kubelet,最后变成真实容器的?
把这条主链路讲透,你后面学所有对象、网络、存储、监控和 Operator 都会更稳。