Repository Reading Site
本轮操作记录:StatefulSet、Headless Service 与稳定身份实验
这一轮我不是只想“创建一个 StatefulSet”。 我要把下面这些事情做成可验证的事实: 1. StatefulSet 为什么不是 Deployment 的简单替代品 2. Headless Service 到底如何提供成员发现 3. `serviceName`、Pod ordinal、DNS 之间是什么关系 4. `volumeClaimTemplat
本轮操作记录:StatefulSet、Headless Service 与稳定身份实验
本轮目标
这一轮我不是只想“创建一个 StatefulSet”。
我要把下面这些事情做成可验证的事实:
- StatefulSet 为什么不是 Deployment 的简单替代品
- Headless Service 到底如何提供成员发现
serviceName、Pod ordinal、DNS 之间是什么关系volumeClaimTemplates如何把状态和副本序号绑定起来- 缩容时为什么删的是最大 ordinal
- 缩容后数据为什么还能保留
- 真正排障时应该沿着哪条链路查
Step 1:先核对仓库现状和上一课风格
实际命令
ls -la
find . -maxdepth 2 -type d | sort
sed -n '1,220p' 08-第八课-存储持久化PV-PVC-StorageClass与NFS原理.md
sed -n '1,240p' 08-操作记录-存储持久化与NFS实验.md
为什么先做这一步
因为这套学习路径已经形成了固定结构:
- 一份教学文档
- 一份操作记录
- 一组对应的 manifests
如果我不先看前一课格式,后面的内容就会断层,学习体验会变差。
我确认到的事实
当前仓库里已经完成了:
- 环境验证
kubectl apply- 调度
- 网络
- NetworkPolicy
- 认证授权
- ConfigMap / Secret
- 存储 / NFS
所以第九课最自然的下一步就是:
StatefulSet
也就是把“有盘”继续推进到“有身份的状态副本”。
Step 2:先看真实集群里已经有哪些 StatefulSet 样本
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl get statefulset -A -o wide
KUBECONFIG=~/.kube/config-k8s-lab kubectl get svc -A | rg 'headless|hl|operated'
sed -n '1,240p' phase-1/03-statefulset-daemonset-job.md
为什么必须先看真实样本
因为我不想把这课教成“纸上理解”。
我需要先确认这套集群里,StatefulSet 到底是不是实际在用。
只有这样,后面讲:
- Pod 身份
- 稳定 DNS
- 每副本独立卷
- 顺序控制
这些内容才不是空讲概念。
我看到的结果
真实集群里已经有:
gitea/gitea-postgresqlmonitoring/prometheus-monitoring-kube-prometheus-prometheusmonitoring/alertmanager-monitoring-kube-prometheus-alertmanagermonitoring/lokidev/web
Headless Service 也真实存在:
dev/web-headlessgitea/gitea-postgresql-hlmonitoring/alertmanager-operatedmonitoring/prometheus-operated
原理解释
这一步证明了:
- StatefulSet 不是教材里的特殊资源
- 你的集群里正在用它承载真实状态型组件
这对学习非常重要。
因为你以后排障,排的不是示例,而是这些真实对象。
Step 3:抽出真实生产样本的关键字段
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n gitea get sts gitea-postgresql -o jsonpath='{.spec.podManagementPolicy}{"\n"}{.spec.serviceName}{"\n"}{.spec.persistentVolumeClaimRetentionPolicy.whenDeleted}{"\n"}{.spec.persistentVolumeClaimRetentionPolicy.whenScaled}{"\n"}{range .spec.volumeClaimTemplates[*]}{.metadata.name}{" "}{.spec.resources.requests.storage}{" "}{.spec.storageClassName}{"\n"}{end}'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n monitoring get sts prometheus-monitoring-kube-prometheus-prometheus -o jsonpath='{.spec.podManagementPolicy}{"\n"}{.spec.serviceName}{"\n"}{.spec.persistentVolumeClaimRetentionPolicy.whenDeleted}{"\n"}{.spec.persistentVolumeClaimRetentionPolicy.whenScaled}{"\n"}'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n monitoring get sts alertmanager-monitoring-kube-prometheus-alertmanager -o yaml | \
rg 'podManagementPolicy|serviceName|cluster.peer|persistentVolumeClaimRetentionPolicy' -n -C 2
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n monitoring get svc alertmanager-operated -o yaml | sed -n '1,120p'
为什么这一步很关键
因为我想把“示例 YAML”升级为“生产式样本”。
我希望你看到:
- 为什么有的组件选
OrderedReady - 为什么有的组件选
Parallel - 为什么 Alertmanager 需要稳定 peer DNS
- 为什么 Headless Service 在真实系统里不是可有可无
我确认到的事实
gitea-postgresql
podManagementPolicy: OrderedReadyserviceName: gitea-postgresql-hlpersistentVolumeClaimRetentionPolicy: Retain / RetainvolumeClaimTemplates: data 10Gi nfs-dynamic
Prometheus
podManagementPolicy: ParallelserviceName: prometheus-operatedpersistentVolumeClaimRetentionPolicy: Retain / Retain
Alertmanager
podManagementPolicy: ParallelserviceName: alertmanager-operated--cluster.peer=alertmanager-monitoring-kube-prometheus-alertmanager-0.alertmanager-operated:9094alertmanager-operated的clusterIP: NonepublishNotReadyAddresses: true
原理解释
这一步告诉我们:
- StatefulSet 不是只有一种固定玩法
- 它既可以表达“顺序严格”的数据库类系统
- 也可以表达“成员发现稳定,但启动顺序不严格”的监控类系统
这才是工程视角,而不是死背字段。
Step 4:设计我们自己的实验对象
我创建了哪些文件
路径:
manifests/09-statefulset/00-namespace-stateful-lab.yamlmanifests/09-statefulset/10-web-headless-service.yamlmanifests/09-statefulset/20-web-statefulset.yaml
为什么实验要这样设计
我需要一个足够简单,但又能同时验证:
- 稳定名字
- Headless DNS
- 自动生成 PVC
- 顺序启动
- 缩扩容与数据保留
的对象。
所以我用了:
- 一个 Headless Service
- 一个 3 副本 StatefulSet
volumeClaimTemplatesbusybox httpd
YAML 里最关键的设计点
Headless Service
clusterIP: None
表示:
- DNS 返回后端 Pod IP
- 不使用 ClusterIP VIP
StatefulSet
serviceName: web-hl
podManagementPolicy: OrderedReady
persistentVolumeClaimRetentionPolicy:
whenDeleted: Retain
whenScaled: Retain
表示:
- 稳定 DNS 挂在
web-hl名下 - 创建顺序必须递进
- 缩容 / 删除时,PVC 不自动清理
volumeClaimTemplates
metadata:
name: data
意味着后续会自动生成:
data-web-0data-web-1data-web-2
原理解释
我这里不是追求“最短 YAML”。
我是故意把关键字段写全,好让每个实验现象都能回到具体字段。
这才是能学会原理的写法。
Step 5:应用实验资源,观察最初始状态
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl apply \
-f manifests/09-statefulset/00-namespace-stateful-lab.yaml \
-f manifests/09-statefulset/10-web-headless-service.yaml \
-f manifests/09-statefulset/20-web-statefulset.yaml
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab get all,pvc
我为什么立刻看 all,pvc
因为 StatefulSet 和无状态控制器的差异,一上来就会体现在:
- Pod 是否一次性都出现
- PVC 是否按序号生成
我看到的第一个关键结果
刚应用完时,集群里只出现了:
data-web-0
而不是一下子就出来:
data-web-0data-web-1data-web-2
原理解释
这说明 StatefulSet 的控制器行为不是:
- “想要 3 个副本,就全都并发造出来”
而是:
- 先处理第 0 号成员
- 后续成员要等前面的状态满足条件再推进
这就是 StatefulSet 和 Deployment 的第一层直观区别。
Step 6:第一次实验就遇到故障,我按真实排障路径处理
我看到的问题
继续观察后,web-0 卡在了:
Init:CrashLoopBackOff
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab get pod,pvc -o wide
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab get events --sort-by=.lastTimestamp | tail -n 60
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab describe pod web-0
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab logs web-0 -c bootstrap --previous
为什么这套排障顺序是标准动作
当 Pod 起不来时,你不能一上来就猜。
标准顺序应该是:
get看全局状态events看控制器和 kubelet 报了什么describe看容器状态、最近事件、探针、挂载logs直接看容器自己怎么报错
我定位到的直接原因
bootstrap init 容器日志明确报:
sh: syntax error: unexpected end of file (expecting "fi")
故障的本质是什么
这不是 Kubernetes 机制问题。
而是我在 init 容器脚本里用了 here-doc 写文件,在 busybox sh 里最终解析失败,导致脚本语法不完整。
我怎么修复
我把写文件逻辑改成了更稳妥的 printf:
printf 'pod=%s\nordinal=%s\nfirst_boot=%s\nnode=%s\n' ...
然后重新应用 StatefulSet 模板,再删除 web-0 让控制器按新模板重建。
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl apply -f manifests/09-statefulset/20-web-statefulset.yaml
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab delete pod web-0 --wait=false
原理解释
这一段非常值得你学:
- init 容器失败,会阻止主容器启动
- StatefulSet 不会跳过坏掉的 ordinal 继续造后面的成员
- 所以前一个成员卡住,后续成员也会跟着卡住
这正是有序控制的另一面:
- 它能保护依赖顺序的系统
- 也会让前序错误阻塞整条链路
Step 7:修复后,验证 OrderedReady 的顺序创建
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab rollout status statefulset/web --timeout=240s
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n stateful-lab get pods -o 'custom-columns=NAME:.metadata.name,PHASE:.status.phase,READY:.status.containerStatuses[0].ready,NODE:.spec.nodeName,IP:.status.podIP,CREATED:.metadata.creationTimestamp'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n stateful-lab get pvc -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,VOLUME:.spec.volumeName,STORAGECLASS:.spec.storageClassName,SIZE:.spec.resources.requests.storage
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n stateful-lab get events --sort-by=.lastTimestamp | tail -n 80
命令解释
rollout status
用来观察控制器是否完成本轮模板下发。
custom-columns
把最关心的字段按列打印出来,适合观察:
- 创建时间
- IP
- 节点
- Ready 状态
为什么这里给 custom-columns 加了引号
因为当前 shell 是 zsh。
[0] 这种写法如果不加引号,zsh 会把它当作通配模式解析,报:
no matches found
这是 shell 层面的坑,不是 kubectl 的坑。
我看到的关键结果
创建时间是:
web-0:2026-04-10T02:17:31Zweb-1:2026-04-10T02:17:48Zweb-2:2026-04-10T02:18:05Z
PVC 也依次出现:
data-web-0data-web-1data-web-2
事件里能看到典型过程:
create Claim data-web-0 Pod web-0ProvisioningSucceededScheduledCreated container
然后才轮到 1 号、2 号。
原理解释
这一步说明:
- StatefulSet 的“副本数 = 3”不是并行目标
- 而是一个有顺序约束的目标
在默认策略下,控制器会先保证:
web-0完整可用
然后才会继续。
Step 8:验证 Headless Service 与稳定 DNS
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab get svc,endpoints,endpointslice
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n stateful-lab exec web-0 -- sh -c 'nslookup web-hl.stateful-lab.svc.cluster.local'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n stateful-lab exec web-1 -- sh -c 'nslookup web-0.web-hl.stateful-lab.svc.cluster.local'
为什么先看 endpoints 和 endpointslice
因为 Headless Service 的本质就是:
- DNS 返回后端成员
而后端成员列表实际来自:
- Endpoints / EndpointSlice
如果这里就不对,DNS 后面也不会对。
我看到的结果
web-hl 的:
clusterIP = None
endpoints 里有三个地址:
10.244.119.242:808010.244.169.12:808010.244.242.42:8080
nslookup web-hl.stateful-lab.svc.cluster.local 返回了三个 Pod IP。
nslookup web-0.web-hl.stateful-lab.svc.cluster.local 返回的是单个:
10.244.119.242
原理解释
这正是 Headless Service 的经典行为:
- 集合域名返回全体成员
- 成员域名返回具体成员
这就为:
- peer 发现
- 主从通信
- 集群成员寻址
提供了基础。
Step 9:故意踩一个最容易误解的坑,讲清楚端口语义
第一次尝试的实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n stateful-lab exec web-0 -- sh -c 'wget -qO- http://web-2.web-hl.stateful-lab.svc.cluster.local'
我看到的结果
报错:
connection refused
为什么这一步特别有教学价值
很多人会误以为:
- 既然 Service 写了
port: 80 -> targetPort: 8080 - 那访问
web-2.web-hl...就会自动走 80 转 8080
这是不对的。
真正的原理
web-2.web-hl... 这个名字解析出来的是:
web-2这个 Pod 的真实 IP
客户端随后是直接去连这个 Pod。
这时默认端口是:
- HTTP 的 80
但我们的应用实际监听的是:
8080
所以你不写端口,当然会拒绝连接。
正确验证命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n stateful-lab exec web-0 -- sh -c 'wget -qO- http://web-2.web-hl.stateful-lab.svc.cluster.local:8080'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n stateful-lab exec web-0 -- sh -c 'wget -qO- http://web-hl.stateful-lab.svc.cluster.local:8080'
我看到的成功结果
访问具体成员 web-2 时,返回内容里有:
pod=web-2ordinal=2
访问集合域名 web-hl 时,也能取回某个成员的内容。
原理解释
这一段你一定要记住:
Headless Service 重点是发现成员,不是替你做普通 Service 那种 VIP/NAT 转发。
如果你未来团队里有人把这块混了,后面做 Kafka、Redis Cluster、数据库副本集时会掉进很多坑里。
Step 10:验证每个副本都有自己的独立卷
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n stateful-lab exec web-0 -- cat /data/index.html
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n stateful-lab exec web-2 -- cat /data/index.html
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n stateful-lab get pvc -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,VOLUME:.spec.volumeName,STORAGECLASS:.spec.storageClassName,SIZE:.spec.resources.requests.storage
我看到的结果
web-0 的卷里记录的是:
pod=web-0ordinal=0
web-2 的卷里记录的是:
pod=web-2ordinal=2
PVC 也是一一对应:
data-web-0 -> pvc-be680ea6-afd0-4bc9-b9e0-a8ca7fda6e54data-web-1 -> pvc-4c85cc11-0c81-428a-8002-a92fd06cabd9data-web-2 -> pvc-ea7bd08e-17c2-4f8c-9a7e-41dcbb393e12
原理解释
这证明 volumeClaimTemplates 的行为是:
- 每个 ordinal 自动派生自己的 PVC
不是:
- 大家共用一块卷
这对数据库类系统至关重要。
因为每个副本的账本、索引、WAL、缓存都应当独立。
Step 11:把 web-2 的状态一路追到真实 NFS 目录
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n stateful-lab exec web-2 -- sh -c 'echo lesson9-marker=$(date +%Y%m%dT%H%M%S) > /data/marker.txt; ls -lah /data; echo; cat /data/marker.txt'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n stateful-lab get pvc data-web-2 -o jsonpath='{.spec.volumeName}{"\n"}{.metadata.name}{"\n"}'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl get pv pvc-ea7bd08e-17c2-4f8c-9a7e-41dcbb393e12 -o jsonpath='{.spec.nfs.server}{"\n"}{.spec.nfs.path}{"\n"}'
ssh root@154.219.104.66 \
'sudo ls -lah /srv/nfs/k8s/stateful-lab-data-web-2-pvc-ea7bd08e-17c2-4f8c-9a7e-41dcbb393e12 && echo && sudo cat /srv/nfs/k8s/stateful-lab-data-web-2-pvc-ea7bd08e-17c2-4f8c-9a7e-41dcbb393e12/marker.txt'
为什么一定要追到 NFS 服务器
因为很多人学到 PVC 就停了。
但如果你以后要做故障排查,你必须能继续往下追:
- PVC 绑定到了哪个 PV
- PV 背后是哪种存储
- 实际数据目录在什么地方
我看到的结果
data-web-2 绑定到:
pvc-ea7bd08e-17c2-4f8c-9a7e-41dcbb393e12
对应的真实后端是:
- NFS server:
10.10.0.5 - path:
/srv/nfs/k8s/stateful-lab-data-web-2-pvc-ea7bd08e-17c2-4f8c-9a7e-41dcbb393e12
我写入的文件是:
lesson9-marker=20260410T022019
在 NFS 服务器上也能直接看到同样内容。
原理解释
这一步把“状态”从抽象概念落成了真实对象:
- 这不是某个 Pod 私有的幻觉文件系统
- 它已经是后端存储里一份真实目录
所以后面只要这块卷还在,Pod 换了,数据也可能还在。
Step 12:缩容到 2,验证删除顺序和 PVC 保留
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab scale statefulset web --replicas=2
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab get pods -o wide
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab get pvc -o wide
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab get events --sort-by=.lastTimestamp | tail -n 40
ssh root@154.219.104.66 \
'sudo ls -lah /srv/nfs/k8s/stateful-lab-data-web-2-pvc-ea7bd08e-17c2-4f8c-9a7e-41dcbb393e12 && echo && sudo cat /srv/nfs/k8s/stateful-lab-data-web-2-pvc-ea7bd08e-17c2-4f8c-9a7e-41dcbb393e12/marker.txt'
我为什么同时看 pods、pvc、events、NFS
因为只看一个维度会误判。
我要同时确认:
- 控制器删掉的是哪个 Pod
- PVC 有没有一起消失
- 事件里控制器到底宣布了什么
- 底层目录是不是真的还在
我看到的结果
缩容后剩下的是:
web-0web-1
被删掉的是:
web-2
事件里明确写着:
delete Pod web-2 in StatefulSet web successful
与此同时,data-web-2 仍然是:
Bound
而且 NFS 目录和 marker.txt 还在。
原理解释
这一步完全验证了两条规则:
- StatefulSet 缩容按逆序删除最大 ordinal
whenScaled: Retain会保留被缩掉成员的 PVC
这就是为什么你以后缩数据库副本时,不能想当然地以为“缩掉就清干净了”。
Step 13:再扩回 3,验证同一 ordinal 重新挂回原状态
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab scale statefulset web --replicas=3
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab get pods -o wide
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n stateful-lab get pvc data-web-2 -o wide
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n stateful-lab exec web-2 -- sh -c 'hostname; echo; cat /data/index.html; echo; cat /data/marker.txt'
我看到的结果
新的 web-2 又回来了。
它现在的 Pod IP 是:
10.244.242.43
而之前老的 web-2 是:
10.244.242.42
但它进入容器后看到的状态仍是原来的:
hostname = web-2first_boot = 2026-04-10T02:18:25+0000lesson9-marker=20260410T022019
对应的 PVC 仍然是:
data-web-2 -> pvc-ea7bd08e-17c2-4f8c-9a7e-41dcbb393e12
原理解释
这里你必须彻底建立一个正确模型:
- StatefulSet 保的是“逻辑成员身份”
- 不是“永远同一 IP”
这个实验里:
web-2这个成员名保住了- 它对应的卷保住了
- 它的旧数据保住了
但:
- Pod IP 可以变
这非常符合 Kubernetes 的设计哲学。
Step 14:把实验现象和真实生产组件对应起来
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n gitea get sts gitea-postgresql -o jsonpath='{.spec.podManagementPolicy}{"\n"}{.spec.serviceName}{"\n"}{.spec.persistentVolumeClaimRetentionPolicy.whenDeleted}{"\n"}{.spec.persistentVolumeClaimRetentionPolicy.whenScaled}{"\n"}{range .spec.volumeClaimTemplates[*]}{.metadata.name}{" "}{.spec.resources.requests.storage}{" "}{.spec.storageClassName}{"\n"}{end}'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n monitoring get sts prometheus-monitoring-kube-prometheus-prometheus -o jsonpath='{.spec.podManagementPolicy}{"\n"}{.spec.serviceName}{"\n"}{.spec.persistentVolumeClaimRetentionPolicy.whenDeleted}{"\n"}{.spec.persistentVolumeClaimRetentionPolicy.whenScaled}{"\n"}'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n monitoring get sts alertmanager-monitoring-kube-prometheus-alertmanager -o yaml | \
rg 'podManagementPolicy|serviceName|cluster.peer|persistentVolumeClaimRetentionPolicy' -n -C 2
我得到的结论
数据库类样本
gitea-postgresql 选择:
OrderedReady- Headless Service
- 独立卷模板
- PVC Retain
这说明数据库更强调:
- 成员顺序
- 数据归属
监控类样本
Prometheus / Alertmanager 选择:
Parallel- 稳定 serviceName
- PVC Retain
Alertmanager 甚至直接把固定 peer 地址写进启动参数:
alertmanager-monitoring-kube-prometheus-alertmanager-0.alertmanager-operated:9094
原理解释
这一步非常关键。
因为它把我们的教学实验,从:
- “一个简单 demo”
接到了:
- 你集群里真实在跑的架构模式
以后你看到这些 YAML,就不会只觉得“字段很多”,而是会知道每个字段是在解决什么系统问题。
Step 15:本轮最值得你背下来的排障链路
如果以后某个 StatefulSet 起不来,我建议你先按这个顺序查。
1. 先看控制器和副本整体
kubectl -n <ns> get sts,pod,pvc
先回答:
- 哪个 ordinal 没起来
- 对应 PVC 有没有创建出来
2. 再看事件
kubectl -n <ns> get events --sort-by=.lastTimestamp
先区分:
- 是调度失败
- 是卷没绑上
- 还是容器自己挂了
3. 看 Pod 细节和容器日志
kubectl -n <ns> describe pod <pod>
kubectl -n <ns> logs <pod> -c <container> --previous
尤其别忘了:
- init 容器单独看
4. 看 DNS 和成员发现
kubectl -n <ns> get svc,endpoints,endpointslice
kubectl -n <ns> exec <pod> -- nslookup <svc>
kubectl -n <ns> exec <pod> -- nslookup <pod>.<svc>.<ns>.svc.cluster.local
5. 看存储真实后端
kubectl -n <ns> get pvc <claim> -o wide
kubectl get pv <pv> -o yaml
必要时继续追到:
- NFS 目录
- 云盘 ID
- 节点挂载状态
本轮结论
这一轮我已经把 StatefulSet 最关键的四个承诺都做成了真实证据:
- 稳定序号和有序创建:
web-0 -> web-1 -> web-2 - 稳定 DNS:
web-0.web-hl...这类成员级域名可用 - 每副本独立卷:
data-web-0/1/2各自绑定不同 PV - 缩容后状态保留:
web-2删除,但data-web-2和后端 NFS 数据仍在
这意味着你已经不只是“会敲 StatefulSet YAML”了。
你已经开始真正理解:
- Kubernetes 怎样把“成员身份”
- “网络发现”
- “存储生命周期”
组合成有状态系统的基础编排模型。