Repository Reading Site
环境验证与第一课:认识你的真实集群
很多人学 Kubernetes,一上来就: 然后很快进入一种错觉: “我会了。” 但真正的工程学习,第一步不是敲命令,而是**确认你手上的环境到底是什么**。 原因很简单: 1. 你不清楚环境边界,就不知道后面每个现象是正常还是异常。 2. 你不先确认连通性,后续所有排障都可能混进“环境没配好”的噪音。 3. 你不认识自己的集群,就永远学不到“从真实系统反推
环境验证与第一课:认识你的真实集群
为什么先做环境验证,而不是直接学命令
很多人学 Kubernetes,一上来就:
kubectl get podskubectl apply -f xxx.yamlhelm install ...
然后很快进入一种错觉:
“我会了。”
但真正的工程学习,第一步不是敲命令,而是确认你手上的环境到底是什么。
原因很简单:
- 你不清楚环境边界,就不知道后面每个现象是正常还是异常。
- 你不先确认连通性,后续所有排障都可能混进“环境没配好”的噪音。
- 你不认识自己的集群,就永远学不到“从真实系统反推原理”的能力。
所以我们这一步做的不是“杂活”,而是专家工作流的第一步:
先确认环境可信,再基于真实系统建立认知模型。
本轮验证完成了什么
我实际验证了三层能力:
- 基础接入层:本机是否能免密 SSH 登录 5 台服务器
- 集群控制层:本机
kubectl是否能访问 API Server - 节点运行层:每台节点上的
kubelet、containerd、wg0是否都在工作
这三层分别对应:
- 你能不能进机器
- 你能不能看集群
- 集群能不能真的跑
如果三层少一层,后面学习都会失真。
验证结果总览
1. SSH 验证结果
5 台机器都可以免密登录,且返回了实际主机名、内核版本、IP 和关键服务状态。
| 文档角色 | 公网 IP | 实际主机名 | 关键 IP | kubelet | containerd | WireGuard |
|---|---|---|---|---|---|---|
| Master | 107.148.176.193 |
us480851516617a |
10.10.0.1 |
active | active | active |
| Worker-1 | 107.148.164.118 |
us590068728056 |
10.10.0.2 |
active | active | active |
| Worker-2 | 154.9.27.60 |
cp-3 |
10.10.0.3 |
active | active | active |
| Worker-3 | 38.76.221.17 |
hk652699382121 |
10.10.0.4 |
active | active | active |
| Worker-4 | 154.219.104.66 |
wk-1 |
10.10.0.5 |
active | active | active |
2. kubectl 验证结果
本机 KUBECONFIG=~/.kube/config-k8s-lab kubectl get nodes -o wide 返回正常,5 个节点全部为 Ready。
| 节点名 | 角色 | K8s 版本 | Internal-IP | OS | Kernel | Runtime |
|---|---|---|---|---|---|---|
us480851516617a |
control-plane |
v1.30.14 |
10.10.0.1 |
Ubuntu 24.04.1 | 6.8.0-48 |
containerd://2.2.1 |
us590068728056 |
worker | v1.30.14 |
10.10.0.2 |
Ubuntu 24.04.4 | 6.8.0-107 |
containerd://2.2.1 |
cp-3 |
worker | v1.30.14 |
10.10.0.3 |
Ubuntu 24.04.1 | 6.8.0-48 |
containerd://2.2.1 |
hk652699382121 |
worker | v1.30.14 |
10.10.0.4 |
Ubuntu 24.04.4 | 6.8.0-48 |
containerd://2.2.1 |
wk-1 |
worker | v1.30.14 |
10.10.0.5 |
Ubuntu 22.04 | 5.15.0-174 |
containerd://1.7.24 |
3. 当前集群基线状态
集群存在以下命名空间:
kube-systemmonitoringingress-nginxargocdharborgiteaml-platformdevstagingprod
这说明它已经不再是“刚装完的裸 K8s”,而是一套已经承载多类平台组件和实验工作负载的真实实验集群。
第一课:先认识你这套集群的“身份系统”
在 Kubernetes 里,最容易把初学者绕晕的不是对象名字,而是“谁的身份是什么”。
你这套集群至少有四种不同的“身份”:
- 机器的公网 IP
- 机器在云厂商内部网络的私网 IP
- WireGuard 虚拟内网 IP
- Pod 网段里的 IP
如果这四类 IP 你分不清,后面所有网络、调度、日志、探针、故障排查都会乱。
四类 IP 到底分别是什么
1. 公网 IP
例如:
107.148.176.193154.9.27.6038.76.221.17
公网 IP 的作用是:
- 让你从本机 SSH 到服务器
- 让外部世界访问你暴露出来的 NodePort / API Server
它是“互联网能找到这台机器的地址”。
但它不一定是 Kubernetes 节点内部通信时使用的地址。
2. 云内网 IP
例如我们在节点上看到的:
10.2.207.310.4.225.210.0.4.169
这些通常是云主机所在私有网络的地址,只在该云网络/机房内部有意义。
问题在于:
- 这些地址不一定跨机房互通
- 不同云商、不同区域的私网并不天然打通
所以它们不能直接拿来做整个跨地域集群的统一节点地址。
3. WireGuard IP
你当前集群真正使用的是这组地址:
10.10.0.110.10.0.210.10.0.310.10.0.410.10.0.5
这组地址为什么最重要?
因为它们是:
- 所有节点都能互通的统一私网地址
- 写在本机真实网卡
wg0上的地址 kubectl get nodes -o wide里展示的INTERNAL-IP- kubelet 向控制面上报的节点标识地址
换句话说:
对这个集群来说,节点真正的“工作身份证”是 WireGuard IP,而不是公网 IP。
这也是为什么 kubectl get nodes -o wide 里看到的是 10.10.0.x。
4. Pod 网段 IP
你在输出里还看到了类似:
10.244.248.6410.244.119.19210.244.169.010.244.147.64
以及 Pod 自己的 IP:
10.244.242.310.244.147.6810.244.119.195
这些都是 Pod 网络相关地址。
这里你要建立两个重要概念:
- Pod 有自己的 IP,不是复用宿主机 IP
- 节点上也可能看到 Pod 网段地址,因为 CNI/overlay 网络组件会在宿主机上创建额外的网络接口或隧道端点
所以:
10.10.0.x是节点身份10.244.x.x是 Pod/overlay 网络身份
这两个层次不能混为一谈。
为什么 kubectl get nodes -o wide 是理解集群的第一条命令
这条命令的价值非常高,因为它一次性把节点最关键的身份信息都给你了。
每一列分别代表什么
NAME
节点对象名。
它来自 kubelet 注册到 API Server 时使用的节点名,通常是主机名。
它不是公网 IP,也不是 Pod 名。
STATUS
当前节点是否就绪,最常见是:
ReadyNotReady
Ready 不表示机器绝对没问题,只表示 kubelet 当前心跳、容器运行时、网络基本满足调度要求。
ROLES
例如:
control-plane<none>
control-plane 说明这个节点承担控制面角色。
你的集群里只有 us480851516617a 是控制面,其余都是 worker。
VERSION
节点 kubelet 对应的 Kubernetes 版本。
你现在看到的是统一的 v1.30.14,这说明节点版本没有明显漂移。
INTERNAL-IP
这是最关键的一列。
对于你的集群,它不是云私网地址,也不是公网地址,而是:
10.10.0.110.10.0.210.10.0.310.10.0.410.10.0.5
也就是 WireGuard 地址。
这意味着 kubelet 上报给控制面的节点主地址,正是这套虚拟内网地址。
OS-IMAGE / KERNEL-VERSION
这两列告诉你节点的宿主机环境。
为什么重要?
因为 Kubernetes 不是抽象平台,它最终依赖:
- Linux 内核
- cgroups
- filesystem
- 网络协议栈
不同内核版本、不同 OS 发行版,会影响:
- CNI 能力
- eBPF 能力
- containerd 行为
- 性能与兼容性
CONTAINER-RUNTIME
告诉你 kubelet 实际是通过哪个容器运行时管理容器。
你这里是:
- 大部分节点:
containerd://2.2.1 wk-1:containerd://1.7.24
这非常值得注意。
一个真实专家会从这张表里看出什么
1. 这是一个跨地域、异构节点集群
你当前集群不是 5 台一模一样的机器,而是明显异构的:
- 有 LA 节点,也有 HK 节点
- 有 Ubuntu 24.04,也有 Ubuntu 22.04
- 有 containerd 2.2.1,也有 1.7.24
- 有大内存节点,也有小规格节点
这很像真实世界,而不是实验室教科书。
专家看到异构节点,会立刻想到:
- 调度策略
- 节点标签
- 容器镜像兼容性
- DaemonSet 行为
- 性能基线
- 版本一致性风险
2. 节点的“控制地址”已经被成功统一到了 WireGuard
这说明你 Phase 0 的组网思路是对的。
如果这里出现的是各机房私网地址,跨地域通信很可能会出问题。
3. 这是单控制面集群,不是高可用控制面
你当前只有一个 control-plane 节点。
这意味着:
- 控制面挂了,已有 Pod 还可能继续跑
- 但 API Server 不可用,集群无法继续变更
- 不能创建/更新/删除资源
- 不能调度新 Pod
这正是后续你要补的 HA 课题。
节点上为什么会出现这么多 IP
从 SSH 输出里你可以看到,有些节点上不止一个地址,例如 cp-3:
154.9.27.6010.0.4.16910.10.0.310.244.242.0172.17.0.1
一个初学者看到这里经常会懵:
“为什么一台机器会有这么多 IP?到底哪个才算它的 IP?”
答案是:
一台 Linux 主机可以同时拥有多个网络接口,每个接口都可以有自己的地址。
这些地址通常对应什么
公网 IP
外部访问机器时使用。
云内网 IP
云平台内部网络地址。
WireGuard IP
跨机房 K8s 节点互通时的工作地址。
172.17.0.1
通常是 Docker bridge 网桥地址,说明这台机器上可能还存在 Docker 网络栈或历史容器网络痕迹。
10.244.x.x
通常与 Calico / Pod overlay 网络有关,是宿主机上的额外接口地址或隧道地址。
这对排障意味着什么
以后你排障时,一定不要笼统地说“这台机器的 IP 是多少”。
你要先问:
- 你说的是哪一层的 IP?
- 是 SSH 接入 IP?
- 是 kubelet 上报的节点 IP?
- 是 Pod IP?
- 是 Service IP?
- 还是 overlay 网络里的地址?
专家和初学者的差别,常常就体现在这种“先区分层次再开查”的习惯上。
集群里已经有哪些平台组件
通过命名空间和 Pod 列表,你现在可以反推出这套集群已经具备哪些能力。
基础系统层
kube-apiserveretcdkube-schedulerkube-controller-managercorednskube-proxycalicometrics-server
这说明控制面、服务代理、DNS、CNI、基础监控入口都已经到位。
平台增强层
ingress-nginxnfs-provisionerPrometheusGrafanaLokinode-exporterkube-state-metricsHarborArgoCDGitea
这说明你这个集群已经不只是“跑业务容器”的集群,而是具备了:
- 入口流量治理
- 动态存储
- 观测
- 镜像管理
- GitOps
- Git 服务
业务实验层
dev / staging / prodml-platform
这说明你已经有了:
- 环境隔离思维
- 业务工作负载
- Operator/ML 管理实验
存储基线说明:你现在使用的是哪种存储模式
通过 kubectl get sc 和 kubectl get pvc -A,我们确认:
- 默认
StorageClass是nfs-dynamic - PVC 已被多个系统组件和业务组件使用
什么是 StorageClass
你可以把它理解为:
“Kubernetes 创建卷时要使用哪种存储后端、按什么规则创建”的模板。
在你的集群里,默认模板是:
- 使用 NFS 作为后端
- 自动动态创建目录/卷
这说明了什么
这说明你已经具备了“声明一个 PVC,就自动拿到存储”的能力,而不是手工建 PV。
这就是平台化能力的一个典型表现:
- 用户提需求
- 平台自动满足
为什么这很重要
因为 Kubernetes 的精髓之一,就是把底层实现抽象成统一声明接口。
用户只写:
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
不用关心后面到底是:
- NFS
- Ceph
- 云盘
- 本地盘
这就是“声明式基础设施”思想。
但也要注意:NFS 不是万能存储
从当前基线我们看到:
HarborGrafanaPrometheusLokiGiteaPostgreSQL
都在不同程度上使用了 NFS 动态卷。
这适合教学,也方便统一管理,但你必须知道:
NFS 适合共享文件和教学环境,不等于适合所有生产级数据库负载。
尤其是数据库类工作负载,常见问题包括:
- 启动恢复慢
- fsync 延迟高
- 文件锁/一致性表现不理想
- liveness probe 过严时容易被误杀
后面你会看到,gitea-postgresql-0 现在的故障样本就很像这一类问题。
kubectl top nodes 到底告诉了你什么
我们当前看到的节点资源大致是:
- CPU 使用都不高,约 1% 到 2%
- 内存使用也不高,约 7% 到 17%
这意味着什么
这意味着当前集群整体资源压力不大。
这很重要,因为它帮助我们排除一类常见误判:
- “是不是因为整个集群资源满了,所以这些 Pod 才崩?”
从当前数据看,不像。
但你必须知道一个关键原理
kubectl top nodes 的数据来源不是 Prometheus,而是 metrics-server。
也就是说:
- 它提供的是“当前快照”
- 不是长期时序趋势
- 适合快速排查
- 不适合做长期容量分析
以后如果你要看:
- 过去 6 小时 CPU 峰值
- 某节点内存曲线
- 某工作负载的历史趋势
那就要去 Prometheus / Grafana,而不是 kubectl top。
当前集群里已经存在的 3 个真实故障样本
这部分非常重要。
因为它说明你的集群不是“摆拍式成功环境”,而是一个能让你真实训练排障能力的环境。
故障样本 1:ArgoCD ApplicationSet Controller CrashLoopBackOff
现象
argocd-applicationset-controller 一直在 CrashLoopBackOff。
证据链
日志里明确出现:
- 控制器启动成功
- 但随后报错找不到
ApplicationSet这个资源类型 - 最终 cache sync 超时,进程退出
进一步确认:
kubectl get crd applicationsets.argoproj.io
返回:
NotFound
这说明了什么
这说明:
- ArgoCD 的 ApplicationSet 控制器进程已经部署了
- 但对应的 CRD
applicationsets.argoproj.io并不存在 - 控制器想 watch 一种资源,但 API Server 根本没有这种类型
原理
Controller 的工作前提是:
- API 里必须存在对应的资源类型
- Controller 才能 watch 这类对象
- 有变化时再进入 Reconcile Loop
如果 CRD 没装,Controller 相当于“想监听一个根本不存在的频道”,当然会失败。
这教你什么
这就是典型的“控制器依赖 API 扩展资源”的例子。
它会帮助你真正理解:
- CRD 是 API 扩展
- Controller 是事件消费者
- 没有 CRD,Controller 没有工作对象
故障样本 2:Gitea PostgreSQL CrashLoopBackOff
现象
gitea-postgresql-0 反复重启,Restart Count 很高。
证据链
describe 里看到:
CrashLoopBackOffExit Code: 137- Liveness / Readiness probe 大量失败
日志里看到:
- PostgreSQL 启动恢复很慢
- 反复进入 recovery
- 出现文件缺失错误,例如
global/pg_filenode.map缺失 - 随后进程被要求关闭
这意味着什么
这是一个很好的真实案例,因为它不是“镜像拉不下来”那种简单错误,而是更接近生产里会遇到的复杂故障。
可能的核心链路是:
- PostgreSQL 启动或恢复很慢
- 探针在数据库尚未完全可用时持续失败
- kubelet 判断容器不健康并触发重启
- 重启过程中数据库还没完全恢复,又被打断
- 在 NFS 存储上,这类恢复和 fsync 行为可能更慢
- 最终形成反复恢复、反复失败的循环
为什么 Exit Code 137 不能只机械理解成 OOM
很多初学者一看到 137,立刻说:
- “OOMKilled!”
这是不严谨的。
137 = 128 + 9,本质是进程收到了 SIGKILL。
它可能来自:
- Linux OOM Killer
- kubelet 因 probe 失败而强制杀容器
- 其他外部强制终止
所以你必须结合:
describeLast StateEvents- 日志
一起判断,而不能只看一个数字。
这教你什么
这个案例会逼你学会:
- 探针不是越严格越好
- 有状态服务对底层存储性能非常敏感
- CrashLoop 的根因不一定是应用代码
- 数据恢复时间要和探针策略匹配
故障样本 3:Grafana CrashLoopBackOff
现象
monitoring-grafana 的主容器反复重启。
证据链
Grafana 日志里出现了关键错误:
Datasource provisioning errorOnly one datasource per organization can be marked as default
进一步查看带 grafana_datasource=1 标签的 ConfigMap,发现有两个:
monitoring-kube-prometheus-grafana-datasourceloki-loki-stack
其中:
- Prometheus datasource 被标记为
isDefault: true - Loki datasource 也被标记为
isDefault: true
原理
Grafana 启动时会加载 provisioning 配置。
如果多个数据源都声明自己是同一个组织的默认数据源,Grafana 会认为配置非法,直接启动失败。
这不是 Kubernetes 本身的问题,而是:
- 两套 Helm chart 各自都想“成为默认”
- 最后在 Grafana 启动时发生冲突
这教你什么
这是非常典型的平台集成问题:
- 单个组件各自看都没错
- 组合在一起就冲突了
这和很多生产事故非常像。
专家不能只会看“单个 YAML 写对没有”,还要会看:
- 组件之间是否有隐性耦合
- 配置是否在集成层面冲突
这三个故障样本分别对应哪一类知识
| 故障 | 对应知识层 |
|---|---|
| ArgoCD ApplicationSet Controller 崩溃 | CRD / Controller / API 扩展 |
| Gitea PostgreSQL 崩溃 | Stateful workload / Probe / 存储 / 恢复 |
| Grafana 崩溃 | 配置管理 / Helm 集成 / 平台组件冲突 |
这意味着你的集群已经天然提供了三类很好的练习场景。
后续我们完全可以拿它们做系统排障训练。
第一课的核心认知模型
学到这里,你必须先把下面这张“心智图”建立起来。
第一层:机器层
真实服务器、真实内核、真实 systemd 服务。
关键对象:
sshsystemctljournalctlip addr
第二层:节点层
Kubernetes 把机器抽象成 Node。
关键对象:
kubeletcontainerdkube-proxycalico
第三层:控制面层
集群的大脑。
关键对象:
kube-apiserveretcdkube-schedulerkube-controller-manager
第四层:工作负载层
真正跑业务和平台组件的地方。
关键对象:
- Pod
- Deployment
- StatefulSet
- Service
第五层:平台能力层
建立在 K8s 之上的工程能力。
关键对象:
- Ingress
- StorageClass
- Prometheus / Grafana / Loki
- Harbor
- ArgoCD
- Gitea
- Operator
以后你排任何问题,先判断问题在哪一层,再往上下游追。
你现在应该能够回答的 10 个问题
如果你读完这份文档还回答不上来,说明第一课还没学透。
- 为什么你的节点
INTERNAL-IP是10.10.0.x,而不是公网 IP? - 为什么一台主机会同时出现公网 IP、私网 IP、WireGuard IP 和 Pod 网段地址?
kubectl get nodes -o wide每一列是什么意思?- 为什么
wk-1虽然内核和 containerd 版本不同,仍然能加入同一集群? kubelet、containerd、wg0三者分别解决什么问题?- 为什么
kubectl top nodes不是 Prometheus 数据? - 什么是
StorageClass,为什么它体现了声明式基础设施思想? - 为什么 CRD 缺失会导致一个 Controller 直接 CrashLoop?
- 为什么
Exit Code 137不能脱离上下文就断言是 OOM? - 为什么 Grafana 的故障属于“集成配置冲突”而不是“节点故障”?
你下一步该怎么学
现在还不适合立刻冲进 Helm、Operator 或复杂排障。
下一步应该先把控制面主链路讲透:
你执行一条
kubectl apply -f deployment.yaml,到底发生了什么?
这是 Kubernetes 学习最关键的一条主线。
因为你后面学的所有东西,本质上都只是往这条主链路上加规则、加约束、加扩展。
下一课预告
下一课我会继续用你这套真实环境来讲:
kubectl到底在和谁说话- API Server 为什么是唯一入口
- etcd 为什么是集群状态中枢
- Scheduler 怎么决定 Pod 去哪
- Controller 为什么叫“控制循环”
- kubelet 如何把“期望状态”变成“真实运行中的容器”
你要开始把 Kubernetes 理解成一个持续调和系统,而不是一堆 YAML 文件。
第一课总结
你现在已经不是在“看一套教程目录”,而是在认识一套真实运行中的 Kubernetes 集群。
你应该记住本课最重要的一句话:
Kubernetes 学习的第一步,不是背资源对象,而是先认清这套系统里每个身份、每一层职责、每一个地址到底是谁。
把这一点吃透,后面所有学习都会轻松很多。