K8s Lab 把当前仓库文档整理成一个可阅读的网页站点

Repository Reading Site

环境验证与第一课:认识你的真实集群

很多人学 Kubernetes,一上来就: 然后很快进入一种错觉: “我会了。” 但真正的工程学习,第一步不是敲命令,而是**确认你手上的环境到底是什么**。 原因很简单: 1. 你不清楚环境边界,就不知道后面每个现象是正常还是异常。 2. 你不先确认连通性,后续所有排障都可能混进“环境没配好”的噪音。 3. 你不认识自己的集群,就永远学不到“从真实系统反推

Markdown01-环境验证与第一课-认识你的真实集群.md2026年4月9日 17:31

环境验证与第一课:认识你的真实集群

为什么先做环境验证,而不是直接学命令

很多人学 Kubernetes,一上来就:

  • kubectl get pods
  • kubectl apply -f xxx.yaml
  • helm install ...

然后很快进入一种错觉:

“我会了。”

但真正的工程学习,第一步不是敲命令,而是确认你手上的环境到底是什么

原因很简单:

  1. 你不清楚环境边界,就不知道后面每个现象是正常还是异常。
  2. 你不先确认连通性,后续所有排障都可能混进“环境没配好”的噪音。
  3. 你不认识自己的集群,就永远学不到“从真实系统反推原理”的能力。

所以我们这一步做的不是“杂活”,而是专家工作流的第一步:

先确认环境可信,再基于真实系统建立认知模型。


本轮验证完成了什么

我实际验证了三层能力:

  1. 基础接入层:本机是否能免密 SSH 登录 5 台服务器
  2. 集群控制层:本机 kubectl 是否能访问 API Server
  3. 节点运行层:每台节点上的 kubeletcontainerdwg0 是否都在工作

这三层分别对应:

  • 你能不能进机器
  • 你能不能看集群
  • 集群能不能真的跑

如果三层少一层,后面学习都会失真。


验证结果总览

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-system
  • monitoring
  • ingress-nginx
  • argocd
  • harbor
  • gitea
  • ml-platform
  • dev
  • staging
  • prod

这说明它已经不再是“刚装完的裸 K8s”,而是一套已经承载多类平台组件和实验工作负载的真实实验集群。


第一课:先认识你这套集群的“身份系统”

在 Kubernetes 里,最容易把初学者绕晕的不是对象名字,而是“谁的身份是什么”。

你这套集群至少有四种不同的“身份”:

  1. 机器的公网 IP
  2. 机器在云厂商内部网络的私网 IP
  3. WireGuard 虚拟内网 IP
  4. Pod 网段里的 IP

如果这四类 IP 你分不清,后面所有网络、调度、日志、探针、故障排查都会乱。


四类 IP 到底分别是什么

1. 公网 IP

例如:

  • 107.148.176.193
  • 154.9.27.60
  • 38.76.221.17

公网 IP 的作用是:

  • 让你从本机 SSH 到服务器
  • 让外部世界访问你暴露出来的 NodePort / API Server

它是“互联网能找到这台机器的地址”。

但它不一定是 Kubernetes 节点内部通信时使用的地址

2. 云内网 IP

例如我们在节点上看到的:

  • 10.2.207.3
  • 10.4.225.2
  • 10.0.4.169

这些通常是云主机所在私有网络的地址,只在该云网络/机房内部有意义。

问题在于:

  • 这些地址不一定跨机房互通
  • 不同云商、不同区域的私网并不天然打通

所以它们不能直接拿来做整个跨地域集群的统一节点地址。

3. WireGuard IP

你当前集群真正使用的是这组地址:

  • 10.10.0.1
  • 10.10.0.2
  • 10.10.0.3
  • 10.10.0.4
  • 10.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.64
  • 10.244.119.192
  • 10.244.169.0
  • 10.244.147.64

以及 Pod 自己的 IP:

  • 10.244.242.3
  • 10.244.147.68
  • 10.244.119.195

这些都是 Pod 网络相关地址。

这里你要建立两个重要概念:

  1. Pod 有自己的 IP,不是复用宿主机 IP
  2. 节点上也可能看到 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

当前节点是否就绪,最常见是:

  • Ready
  • NotReady

Ready 不表示机器绝对没问题,只表示 kubelet 当前心跳、容器运行时、网络基本满足调度要求。

ROLES

例如:

  • control-plane
  • <none>

control-plane 说明这个节点承担控制面角色。

你的集群里只有 us480851516617a 是控制面,其余都是 worker。

VERSION

节点 kubelet 对应的 Kubernetes 版本。

你现在看到的是统一的 v1.30.14,这说明节点版本没有明显漂移。

INTERNAL-IP

这是最关键的一列。

对于你的集群,它不是云私网地址,也不是公网地址,而是:

  • 10.10.0.1
  • 10.10.0.2
  • 10.10.0.3
  • 10.10.0.4
  • 10.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-1containerd://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.60
  • 10.0.4.169
  • 10.10.0.3
  • 10.244.242.0
  • 172.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-apiserver
  • etcd
  • kube-scheduler
  • kube-controller-manager
  • coredns
  • kube-proxy
  • calico
  • metrics-server

这说明控制面、服务代理、DNS、CNI、基础监控入口都已经到位。

平台增强层

  • ingress-nginx
  • nfs-provisioner
  • Prometheus
  • Grafana
  • Loki
  • node-exporter
  • kube-state-metrics
  • Harbor
  • ArgoCD
  • Gitea

这说明你这个集群已经不只是“跑业务容器”的集群,而是具备了:

  • 入口流量治理
  • 动态存储
  • 观测
  • 镜像管理
  • GitOps
  • Git 服务

业务实验层

  • dev / staging / prod
  • ml-platform

这说明你已经有了:

  • 环境隔离思维
  • 业务工作负载
  • Operator/ML 管理实验

存储基线说明:你现在使用的是哪种存储模式

通过 kubectl get sckubectl get pvc -A,我们确认:

  • 默认 StorageClassnfs-dynamic
  • PVC 已被多个系统组件和业务组件使用

什么是 StorageClass

你可以把它理解为:

“Kubernetes 创建卷时要使用哪种存储后端、按什么规则创建”的模板。

在你的集群里,默认模板是:

  • 使用 NFS 作为后端
  • 自动动态创建目录/卷

这说明了什么

这说明你已经具备了“声明一个 PVC,就自动拿到存储”的能力,而不是手工建 PV。

这就是平台化能力的一个典型表现:

  • 用户提需求
  • 平台自动满足

为什么这很重要

因为 Kubernetes 的精髓之一,就是把底层实现抽象成统一声明接口。

用户只写:

spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

不用关心后面到底是:

  • NFS
  • Ceph
  • 云盘
  • 本地盘

这就是“声明式基础设施”思想。


但也要注意:NFS 不是万能存储

从当前基线我们看到:

  • Harbor
  • Grafana
  • Prometheus
  • Loki
  • Gitea
  • PostgreSQL

都在不同程度上使用了 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 的工作前提是:

  1. API 里必须存在对应的资源类型
  2. Controller 才能 watch 这类对象
  3. 有变化时再进入 Reconcile Loop

如果 CRD 没装,Controller 相当于“想监听一个根本不存在的频道”,当然会失败。

这教你什么

这就是典型的“控制器依赖 API 扩展资源”的例子。

它会帮助你真正理解:

  • CRD 是 API 扩展
  • Controller 是事件消费者
  • 没有 CRD,Controller 没有工作对象

故障样本 2:Gitea PostgreSQL CrashLoopBackOff

现象

gitea-postgresql-0 反复重启,Restart Count 很高。

证据链

describe 里看到:

  • CrashLoopBackOff
  • Exit Code: 137
  • Liveness / Readiness probe 大量失败

日志里看到:

  • PostgreSQL 启动恢复很慢
  • 反复进入 recovery
  • 出现文件缺失错误,例如 global/pg_filenode.map 缺失
  • 随后进程被要求关闭

这意味着什么

这是一个很好的真实案例,因为它不是“镜像拉不下来”那种简单错误,而是更接近生产里会遇到的复杂故障。

可能的核心链路是:

  1. PostgreSQL 启动或恢复很慢
  2. 探针在数据库尚未完全可用时持续失败
  3. kubelet 判断容器不健康并触发重启
  4. 重启过程中数据库还没完全恢复,又被打断
  5. 在 NFS 存储上,这类恢复和 fsync 行为可能更慢
  6. 最终形成反复恢复、反复失败的循环

为什么 Exit Code 137 不能只机械理解成 OOM

很多初学者一看到 137,立刻说:

  • “OOMKilled!”

这是不严谨的。

137 = 128 + 9,本质是进程收到了 SIGKILL

可能来自:

  • Linux OOM Killer
  • kubelet 因 probe 失败而强制杀容器
  • 其他外部强制终止

所以你必须结合:

  • describe
  • Last State
  • Events
  • 日志

一起判断,而不能只看一个数字。

这教你什么

这个案例会逼你学会:

  • 探针不是越严格越好
  • 有状态服务对底层存储性能非常敏感
  • CrashLoop 的根因不一定是应用代码
  • 数据恢复时间要和探针策略匹配

故障样本 3:Grafana CrashLoopBackOff

现象

monitoring-grafana 的主容器反复重启。

证据链

Grafana 日志里出现了关键错误:

  • Datasource provisioning error
  • Only one datasource per organization can be marked as default

进一步查看带 grafana_datasource=1 标签的 ConfigMap,发现有两个:

  • monitoring-kube-prometheus-grafana-datasource
  • loki-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 服务。

关键对象:

  • ssh
  • systemctl
  • journalctl
  • ip addr

第二层:节点层

Kubernetes 把机器抽象成 Node。

关键对象:

  • kubelet
  • containerd
  • kube-proxy
  • calico

第三层:控制面层

集群的大脑。

关键对象:

  • kube-apiserver
  • etcd
  • kube-scheduler
  • kube-controller-manager

第四层:工作负载层

真正跑业务和平台组件的地方。

关键对象:

  • Pod
  • Deployment
  • StatefulSet
  • Service

第五层:平台能力层

建立在 K8s 之上的工程能力。

关键对象:

  • Ingress
  • StorageClass
  • Prometheus / Grafana / Loki
  • Harbor
  • ArgoCD
  • Gitea
  • Operator

以后你排任何问题,先判断问题在哪一层,再往上下游追。


你现在应该能够回答的 10 个问题

如果你读完这份文档还回答不上来,说明第一课还没学透。

  1. 为什么你的节点 INTERNAL-IP10.10.0.x,而不是公网 IP?
  2. 为什么一台主机会同时出现公网 IP、私网 IP、WireGuard IP 和 Pod 网段地址?
  3. kubectl get nodes -o wide 每一列是什么意思?
  4. 为什么 wk-1 虽然内核和 containerd 版本不同,仍然能加入同一集群?
  5. kubeletcontainerdwg0 三者分别解决什么问题?
  6. 为什么 kubectl top nodes 不是 Prometheus 数据?
  7. 什么是 StorageClass,为什么它体现了声明式基础设施思想?
  8. 为什么 CRD 缺失会导致一个 Controller 直接 CrashLoop?
  9. 为什么 Exit Code 137 不能脱离上下文就断言是 OOM?
  10. 为什么 Grafana 的故障属于“集成配置冲突”而不是“节点故障”?

你下一步该怎么学

现在还不适合立刻冲进 Helm、Operator 或复杂排障。

下一步应该先把控制面主链路讲透:

你执行一条 kubectl apply -f deployment.yaml,到底发生了什么?

这是 Kubernetes 学习最关键的一条主线。

因为你后面学的所有东西,本质上都只是往这条主链路上加规则、加约束、加扩展。


下一课预告

下一课我会继续用你这套真实环境来讲:

  • kubectl 到底在和谁说话
  • API Server 为什么是唯一入口
  • etcd 为什么是集群状态中枢
  • Scheduler 怎么决定 Pod 去哪
  • Controller 为什么叫“控制循环”
  • kubelet 如何把“期望状态”变成“真实运行中的容器”

你要开始把 Kubernetes 理解成一个持续调和系统,而不是一堆 YAML 文件。


第一课总结

你现在已经不是在“看一套教程目录”,而是在认识一套真实运行中的 Kubernetes 集群。

你应该记住本课最重要的一句话:

Kubernetes 学习的第一步,不是背资源对象,而是先认清这套系统里每个身份、每一层职责、每一个地址到底是谁。

把这一点吃透,后面所有学习都会轻松很多。