Kubernetes资源对象详解:从Pod、StatefulSet到Ceph RBD持久化存储

这个系列写到第7篇,终于轮到K8s资源对象里最容易把人绕晕的一块了。Pod、Deployment、Service、StatefulSet、DaemonSet、Volume、PV、PVC,每个名词单独看都能找到一堆文档,可真到要把它们串起来、并且接入Ceph这种外置分布式存储的时候,很多人就卡住了。问题往往不在某个具体资源对象本身,而在于搞不清楚它们之间的职责边界和调用关系。

这篇文章把资源对象按“工作负载、服务接入、存储对接”三条线重新梳理一遍,配合Ceph RBD做持久化存储的完整链路,最后给出一套可以直接参考的StatefulSet示例。适合已经跑通基础Pod和Deployment、正准备接触有状态服务和持久化存储的同学,也可以当排障时的速查手册用。顺序上我会从K8s的API设计讲起,再从探针和Service切入服务治理,接着拆解StatefulSet和DaemonSet这两类特殊工作负载,最后落到Volume/PV/PVC与Ceph的实际对接。

1. 理解K8s资源对象的设计逻辑

1.1 一切操作最后都是对着API Server说话

很多人用kubectl习惯了,以为kubectl就是K8s本体。实际上kubectl只是一个客户端,它做的事情就是把你的操作翻译成HTTP请求,发给集群的API Server。API Server是唯一的入口,所有资源对象的创建、更新、删除、查询,最终都要经过它。

这里有个关键认知:K8s并不是“命令式”地告诉你“我要在这个节点上运行一个容器”,而是“声明式”地描述“我希望集群里存在一个怎样的状态”。比如你写了一个Deployment清单,说“我要3个nginx副本”,API Server会把这个期望状态存进etcd,然后Deployment控制器、ReplicaSet控制器会不断观察实际状态,发现副本数不够就去创建Pod,多了就去回收。整个系统就像是一个永不疲倦的“对账机器人”,始终在拉近期望状态和实际状态之间的距离。

生活里可以类比成酒店订房:你(用户)提交订单(期望状态),前台(API Server)记录订单,客房部(控制器)看到订单就去打扫房间、布置物品,直到客房状态和订单完全匹配。你不需要亲自去房间摆东西,只需要把“我要什么”写清楚。

所以学习资源对象时,一定要养成一个习惯:凡是能用kubectl apply提交YAML的,尽量用YAML表达,不要每次都kubectl run一把梭。声明式配置有几个实实在在的好处:可评审、可回滚、可复用,而且出错时你能从etcd里的期望状态反推出设计意图。

1.2 资源对象的分类与依赖关系

K8s资源对象乍一看很多,但归类之后其实非常清晰。按使用场景大致可以分成五类:

分类典型资源对象核心作用
工作负载类Pod、Deployment、StatefulSet、DaemonSet、Job决定容器怎么跑、跑几个、跑在哪
服务接入类Service、Endpoint、Ingress决定流量怎么进、怎么分
存储类Volume、PV、PVC、StorageClass决定数据放哪、怎么申请、怎么回收
配置类ConfigMap、Secret把配置和镜像解耦,镜像可以不变,配置变了
元信息与权限类Namespace、Label、RBAC决定隔离、标识、谁能操作什么

这些类别之间不是孤立的,而是层层依赖的关系。一个典型链路是这样的:Deployment创建Pod,Pod通过label被Service选中,Service把流量转发到Pod的端口;同时Pod声明了volumeMount,挂载了一个PVC,PVC通过StorageClass动态创建PV,PV底层对接Ceph RBD块设备。这条链路其实就是一整个应用的“骨架”:工作负载是肌肉,Service是血管,PV/PVC是存储仓库,Ceph是仓库管理员。

刚开始接触时,我强烈建议用kubectl api-resources看一下当前集群支持的资源列表,再配合kubectl explain <资源类型>查看字段说明。你不需要背字段,但要知道每个资源的用途、apiVersion、以及它跟其他资源是怎么关联的。这比你背十篇操作命令都管用。

1.3 Ceph在K8s里的角色定位

标题里把Ceph和K8s资源对象放在一起,是因为K8s本身不存数据——它只负责调度容器,数据要落到持久化存储里。Ceph就是目前最主流的开源分布式存储之一,提供三种访问接口:RBD(块存储)、CephFS(文件存储)、RGW(对象存储)。在K8s场景里,最常用的是RBD,因为它性能好、延迟低,适合数据库这类需要块设备的应用。

需要特别理解的一点是:K8s不会直接“连”Ceph,而是通过CSI(Container Storage Interface)插件来对接。CSI可以把不同存储厂商的驱动能力统一暴露给K8s,让K8s像操作本地存储一样操作远端存储。你只需要创建StorageClass,指定provisioner为rbd.csi.ceph.com,再填上Ceph集群的monitor地址、pool名称、认证用户,就能实现PVC创建时自动在Ceph上分配块设备,PVC删除时自动回收。

把这个机制想明白后,后面看任何存储资源对象都会轻松很多。PV/PVC/StorageClass本质上就是一层“介于K8s和存储系统之间的抽象”,它们存在的意义是让应用开发者不关心底层存储细节,让存储管理员不关心应用部署位置。

2. 探针与Service:把应用健康和流量入口理清楚

2.1 三种探针到底分别管什么

Pod探针是K8s对应用做健康检查的主要手段。很多新手一上来就只配livenessProbe,以为“能探测就是健康”,结果线上频繁误杀重启,其实是因为没搞懂三种探针各自的定位。

  • startupProbe(启动探针):管“启动期”。有些应用初始化特别慢,比如要加载模型、连接数据库、预热缓存,可能要一两分钟才能对外提供服务。如果不用startupProbe,livenessProbe可能启动后几秒就开始探测,连续失败几次就把容器杀了。startupProbe成功之前,其他探针都不会执行,这相当于给了慢启动应用一个“保护期”。
  • livenessProbe(存活探针):管“活着没”。探测失败,K8s会杀掉容器并按restartPolicy重启。它回答的问题是“这个容器还健不健康,要不要救一下”。
  • readinessProbe(就绪探针):管“能不能接流量”。探测失败时容器不会被杀,而是从Service的端点列表里摘掉,暂时不给它派流量。它回答的问题是“这个容器现在能不能干活”。

可以这样记:startupProbe是入职培训期,livenessProbe是定期体检,readinessProbe是红绿灯——红灯亮了车就不放行,但车还停在原地,不会拖去修。

探针参数也要理解清楚:initialDelaySeconds表示容器启动后等多久再开始探测,periodSeconds表示探测间隔,timeoutSeconds表示单次探测超时,failureThreshold表示连续失败多少次才判定失败,successThreshold表示连续成功多少次才判定成功。下面是一个用HTTP Get方式做健康检查的完整例子:

apiVersion: v1 kind: Pod metadata: name: demo-app labels: app: demo-app spec: containers: - name: app image: demo-app:1.0.0 ports: - containerPort: 8080 startupProbe: httpGet: path: /health/startup port: 8080 initialDelaySeconds: 5 periodSeconds: 5 failureThreshold: 30 livenessProbe: httpGet: path: /health/live port: 8080 initialDelaySeconds: 10 periodSeconds: 15 timeoutSeconds: 3 failureThreshold: 3 readinessProbe: httpGet: path: /health/ready port: 8080 initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 successThreshold: 1

这里有个实战体会:探针的探测方法有三种,httpGet、tcpSocket、exec。TCP探测只能说明端口能连上,不代表业务真的可用;HTTP探测更接近用户视角,因为服务端要返回正确的状态码才判成功。exec适合容器里没有HTTP服务只有脚本的场景,比如用cat某个临时文件是否存在来判断进程是否初始化完成。

要特别注意一个容易踩的坑:不要在livenessProbe里依赖那些可能因负载波动而暂时变慢的接口。比如一个接口平时10毫秒返回,高峰期要2秒,你timeoutSeconds设成1秒,那高峰期就会疯狂误杀。探针的timeoutSeconds不能太紧,一般建议至少3秒,而且要配合CPU/内存的limits一起看——如果容器被限流,探针也很容易超时。

2.2 Service的四种类型怎么选

Service是K8s做服务发现和负载均衡的抽象层。Pod的IP是会漂移的,每次重启都可能变,但Service的IP和DNS名字是稳定的。应用通过Service访问后端Pod,不需要关心Pod到底跑在哪里。

Service主要有四种类型:

  1. ClusterIP:默认类型,只能在集群内部访问,分配一个虚拟IP。适合内部服务之间的调用,比如后端API访问数据库。
  2. NodePort:在每个节点上开一个端口(默认30000-32767),通过节点IP:NodePort访问。适合测试环境或者不想上负载均衡器的场景。
  3. LoadBalancer:在云环境里,会自动创建云厂商的负载均衡器,公网IP挂到Service上,适合对外暴露HTTP/HTTPS服务。
  4. ExternalName:把Service解析到外部域名,相当于给外部的某个域名起了一个集群内的别名。

一个Service最基本的配置是selector、port和targetPort三件套:

apiVersion: v1 kind: Service metadata: name: demo-svc spec: type: ClusterIP selector: app: demo-app ports: - name: http port: 80 targetPort: 8080 protocol: TCP

这里port是Service对外暴露的端口,targetPort是Pod里容器真正监听的端口。如果你给容器端口起了名字,比如containerPort叫http,targetPort可以直接写http,这样就算容器端口换了,Service配置不用改。

还有一类特殊的Service叫Headless Service,spec里设置clusterIP: None,不分配虚拟IP,DNS解析直接返回所有后端Pod的IP列表。这东西配合StatefulSet特别有用,因为StatefulSet的每个Pod都有稳定的网络标识,比如web-0, web-1,通过Headless Service可以实现“每个Pod都有独立域名”的效果,比如web-0.default.svc.cluster.local。数据库、消息队列这种需要节点间互相直连的应用,几乎都会用到Headless Service。

3. StatefulSet与DaemonSet:两类特殊工作负载的适用边界

3.1 DaemonSet:集群级守护进程的首选

DaemonSet从名字就能猜到,它要保证在集群的每个符合条件的节点上运行一个Pod副本。节点加入集群就自动运行,节点销毁就自动清理。最典型的场景是日志采集、监控Agent、网络插件、存储插件,比如每台节点上都要部署一个日志采集器,把/var/log下的日志收集起来,或者每台节点上都要部署一个网络代理,负责容器网络的配置。

DaemonSet和Deployment的关键区别在于:Deployment关心的是“集群里总共有多少个副本”,它可以集中跑在某一台节点上;DaemonSet关心的是“每个节点上都要有”,它是按节点维度来调度的。如果一个集群有10个节点,DaemonSet的副本数就是10,而且每个节点最多一个。

DaemonSet的滚动更新方式也和Deployment类似,支持RollingUpdate和OnDelete两种策略。RollingUpdate是逐个节点更新,默认maxUnavailable设置为1,保证不会一次性把整批Agent都停掉。OnDelete则适合你自己手动控制更新节奏的场景。

一个比较常见的坑是DaemonSet想跑在控制平面节点上。K8s的Master节点默认有NoSchedule污点,普通Pod不会被调度上去,DaemonSet默认也一样。如果确实需要在Master节点上跑Agent,要加上对应的tolerations。我在实际部署日志采集器时就有过这样的经历:Agent在Worker节点都正常,但Master节点的日志一直缺失,查了半天才发现是没加容忍配置。

3.2 StatefulSet:身份、拓扑与存储都要稳定

StatefulSet是K8s里有状态应用的核心工作负载。它和Deployment最大的区别在于三件事:稳定的网络标识、稳定的存储绑定、有序的部署和伸缩。

先看网络标识。StatefulSet创建的Pod名字是固定的,比如stateful-name-0、stateful-name-1,按序号递增。Pod被删除重建后,名字和主机名都不变,这就意味着它对应的DNS域名也不变。配合Headless Service,每个Pod都可以通过podName.serviceName.namespace.svc.cluster.local这样的域名被访问。对于需要节点间互相发现和组网的分布式系统来说,这个能力至关重要。

再看存储绑定。StatefulSet支持volumeClaimTemplates,相当于“PVC模板”。每次创建Pod时,按模板自动生成一个PVC,命名规则是<volumeClaimTemplateName>-<podName>。Pod被重建后,它会重新绑定原来的PVC,保证数据不丢。比如某个Pod的PVC叫data-web-0,就算Pod被删了,data-web-0还会留在集群里,等新的web-0创建时,直接重新挂载这个PVC,数据原封不动。

最后是有序部署和伸缩。StatefulSet在扩容时按序号从0到N-1依次创建,每个Pod必须启动并进入Ready状态后,才会创建下一个Pod。缩容时则相反,从序号最大的Pod开始删。如果某个Pod一直不Ready,后续的Pod也不会启动,避免出现集群脑裂或数据不一致。

正因为这些特性,StatefulSet几乎成了数据库、消息队列、ZooKeeper、etcd这类有状态服务的唯一正确打开方式。Deployment适合无状态应用,Pod随便换、IP随便漂,数据无处可存;StatefulSet适合“我就是要固定身份、固定存储、按顺序来”的应用。

3.3 有状态服务如何与Service、存储结合

StatefulSet和Service、存储之间是“三件套”关系。一个标准的有状态服务部署,通常由三部分组成:Headless Service负责提供稳定的DNS入口、StatefulSet负责管理Pod、PVC/StorageClass负责提供持久化存储。

以部署一个3节点的分布式存储型应用为例:Headless Service给每个Pod提供podName.svcNamespace.svc.cluster.local的域名,Pod之间通过这个域名互相通信;StatefulSet通过volumeClaimTemplates为每个Pod创建独立的PVC,确保每个Pod的数据互不干扰;底层StorageClass指向Ceph RBD,每次PVC创建时自动在Ceph上分配一块RBD块设备。

这里有一个容易被忽略的细节:StatefulSet的Pod在滚动更新时是“倒序更新”的,先更新序号最大的Pod,逐步往前。RollingUpdate时如果某个Pod更新失败,后续Pod不会继续更新。这个行为对有状态应用很重要,因为很多有状态应用要求不能“同时”更新多个副本,否则可能出现新旧版本混乱导致数据不一致。

另一个常见的认知误区是:删除StatefulSet并不会删除Pod,也不会删除PVC。kubectl delete statefulset会把StatefulSet管理下的Pod一起删掉(除非用--cascade=orphan),但PVC一定会留下。这是K8s故意设计的防护机制——防止你一条命令把数据库数据全删了。很多人在测试环境删StatefulSet后,发现PVC还在,以为出问题了,其实这是保护行为。

4. Volume/PV/PVC如何与Ceph RBD真正对接

4.1 Volume、PV、PVC、StorageClass四层关系

提到存储,K8s里Volume是最基础的抽象。它有很多种类型:emptyDir、hostPath、configMap、secret、pvc等等。emptyDir是Pod级别的临时目录,Pod删除就没了;hostPath是把宿主机目录挂载进容器,能用但会很危险,因为Pod被调度到别的节点时数据就不通了。真正解决持久化问题的,是后面三个东西的组合:PV、PVC、StorageClass。

三者关系可以这样理解:

  • PV是集群层面的存储资源,相当于“仓库里的一块硬盘”。PV必须由存储管理员预先创建,或者由StorageClass动态创建。
  • PVC是应用层面的存储请求,相当于“我要申请一块100GB的硬盘”。PVC描述的是“我要什么”,不关心底层是Ceph还是本地磁盘。
  • StorageClass是动态供给的“工厂”。PVC创建后,如果指定了storageClassName,对应的provisioner就会自动去底层存储系统创建PV,再绑定到PVC。

PV的生命周期有四个状态:Available(空闲)、Bound(被PVC绑定)、Released(PVC被删但PV还没回收)、Failed(回收失败)。静态供给的时候,PV和PVC的绑定条件包括存储大小、访问模式(比如ReadWriteOnce)、storageClassName是否匹配。动态供给则不需要预先创建PV,一切都由StorageClass自动完成。

AccessMode是经常被搞混的一个点:ReadWriteOnce(RWO)表示只能被单个节点挂载,ReadWriteMany(RWX)表示可以被多个节点同时挂载,ReadOnlyMany(ROX)表示多个节点只读挂载。这里“Once”和“Many”说的都是节点维度,不是Pod维度。比如RBD块存储天然只支持RWO,如果你要多个Pod同时读写同一块存储,就不能用RBD,得用CephFS或其他支持RWX的存储。

4.2 Ceph侧准备:pool、用户、keyring

要从K8s动态创建Ceph RBD块设备,Ceph侧必须准备好三样东西:一个存储池(pool)、一个认证用户、一份keyring密钥。

先创建存储池。Ceph的pool是逻辑上的存储空间划分,RBD块设备都要放在某个pool里。创建命令大概是ceph osd pool create kubernetes 128,后面的128是PG(Placement Group)数量,具体值要根据OSD数量和数据量来定,一般每个OSD建议分配50到100个PG。PG数设太小会导致数据分布不均匀,设太大又增加开销。

再创建认证用户。Ceph默认启用cephx认证,K8s的CSI插件连接Ceph时必须使用有效身份。创建一个专门给K8s用的用户,并授权它访问刚才创建的pool:

ceph auth get-or-create client.kubernetes mon 'allow r' osd 'allow rwx pool=kubernetes' -o /etc/ceph/ceph.client.kubernetes.keyring

这条命令的意思是:创建一个名为client.kubernetes的用户,允许它从monitor读取信息,允许对kubernetes这个pool进行读写。生成的keyring文件里包含用户ID和密钥,后面要放到K8s的Secret里。

然后就是部署ceph-csi插件。现在新版K8s都推荐使用ceph-csi,老一代的rbd-provisioner已经在K8s 1.13之后逐步废弃了。ceph-csi由Controller和Node两个组件组成,Controller是Deployment,负责PV创建和删除;Node是DaemonSet,负责块设备的挂载和卸载。这部分部署完成后,K8s里才能识别rbd.csi.ceph.com这个provisioner。

4.3 动态供给完整链路示例

假设Ceph集群和ceph-csi都准备好了,接下来就是K8s侧的配置。第一步是创建Secret,保存CEPH的认证信息:

apiVersion: v1 kind: Secret metadata: name: csi-rbd-secret namespace: kube-system stringData: userID: kubernetes userKey: AQDnxxxxxxxxxxxxxxxxxxxx

注意:Secret的namespace要跟ceph-csi插件本身所在的namespace保持一致,否则CSI组件读取不到认证信息。userID和userKey就来自刚才创建的ceph.client.kubernetes.keyring。

然后是创建StorageClass:

apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: ceph-rbd-sc provisioner: rbd.csi.ceph.com parameters: clusterID: 6c7a8c42-xxxx-xxxx-xxxx-xxxxxxxxxxxx pool: kubernetes csi.storage.k8s.io/provisioner-secret-name: csi-rbd-secret csi.storage.k8s.io/provisioner-secret-namespace: kube-system csi.storage.k8s.io/controller-expand-secret-name: csi-rbd-secret csi.storage.k8s.io/controller-expand-secret-namespace: kube-system csi.storage.k8s.io/node-stage-secret-name: csi-rbd-secret csi.storage.k8s.io/node-stage-secret-namespace: kube-system reclaimPolicy: Delete volumeBindingMode: Immediate allowVolumeExpansion: true

这里的参数有几个值得注意:clusterID是Ceph集群的fsid,通过ceph fsid命令查;pool必须是Ceph里真实存在的pool;reclaimPolicy设为Delete表示PVC删除时PV和底层RBD块设备一起删除,设为Retain则表示保留,用于数据归档;volumeBindingMode设为WaitForFirstConsumer可以让PV在Pod被调度到节点后再创建,这样可以结合节点拓扑避免跨可用区分配块设备。

有了StorageClass,PVC就可以动态创建了:

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mydata spec: accessModes: - ReadWriteOnce storageClassName: ceph-rbd-sc resources: requests: storage: 10Gi

创建后,kubectl get pvc看状态应该是Bound,kubectl get pv会看到一个由ceph-csi动态创建的PV。此时在Pod里声明volumeMount和volumes,引用这个PVC,就可以把10GB的Ceph RBD块设备挂载到容器里了。

这套链路的关键点在于:应用开发者只需要关心PVC的申请和使用,存储管理员只需要关心Ceph pool和StorageClass的配置,两边通过PVC和storageClassName这两个字段解耦。你要是一周前还分不清PV和PVC的区别,现在应该能明白:PVC是需求单,PV是采购回来的硬盘,StorageClass是自动采购系统,Ceph是仓库。

5. 常见问题与排查实录

5.1 PVC一直Pending,卡在动态供给环节

这应该是存储接入时遇到最多的问题,没有之一。PVC状态一直Pending,说明K8s尝试调用StorageClass的provisioner去创建PV,但没成功。排查步骤我建议从下往上查:

  1. 先kubectl describe pvc <名称>看PVC的事件列表。事件里会明文写清楚是provisioner不认识、Secret缺失、还是连接存储失败。
  2. 检查StorageClass的provisioner名字是否和ceph-csi部署的插件匹配。一个最常见的翻车现场:安装的ceph-csi版本是v3.x,但StorageClass里的provisioner写成了rbd.csi.ceph.com的旧拼法。
  3. 检查Secret的namespace和名称。ceph-csi在kube-system下部署,如果Secret放在default namespace,CSI根本找不到。
  4. 检查Ceph侧的pool是否存在、monitor地址是否可达、认证用户是否有权限。可以用rbd ls -p kubernetes从Ceph侧直接验证。

我之前碰到一次很隐蔽的问题:Ceph集群的fsid不对,StorageClass里的clusterID填成了集群名字,导致CSI无法定位集群。这个字段不是“名字”,是fsid,必须用ceph fsid查出来的UUID。

5.2 Pod反复重启,探针配置背锅

应用日志明明没有异常,但Pod一直重启,kubectl get pod显示CrashLoopBackOff,首先看kubectl describe pod里的Last State,如果是“Error”或“OOMKilled”,那大概率不是探针问题,是应用本身或者内存限制的问题。如果Last State显示容器是因为探针失败被杀掉的,那就要审视探针配置了。

常见问题有三个:一是startupProbe没配,慢启动应用被livenessProbe反复误杀;二是livenessProbe探测的接口本身不稳定,高峰期稍微慢一点就超时;三是readinessProbe和livenessProbe共用同一个接口,就绪失败会导致流量摘除,但liveness失败会直接重启,两种后果完全不一样。

给探针调参时,我会先观察应用启动到Ready需要多长时间,再观察高峰期接口响应时间,然后留出2倍的冗余。failureThreshold也不要设太小,至少3次,避免网络抖动触发误杀。

5.3 DaemonSet没有调度到某些节点

DaemonSet调度不上去,绝大多数情况是节点污点和容忍度的问题。先kubectl describe node看节点有哪些Taints,再看DaemonSet是否在Pod模板里定义了匹配的tolerations。如果某节点有node-role.kubernetes.io/control-plane:NoSchedule这样的污点,普通Pod和DaemonSet默认都不会调度上去。

还有一个容易被忽略的点:如果DaemonSet设置了nodeSelector或nodeAffinity,而目标节点没有匹配的label,Pod也会卡在Pending。排查时用kubectl get pods -o wide -n kube-system看具体Pod卡在哪个节点,再用kubectl describe ds <名称>看事件,事件里会把调度失败的原因写得很清楚。

5.4 StatefulSet的PVC和Pod重建后数据表现

如果你删掉一个StatefulSet管理的Pod,等它重新创建,PVC会重新绑定,数据还在,这是正常的。但如果你连PVC也一起删了,且StorageClass的reclaimPolicy是Delete,那么底层Ceph块设备也会被删,数据就真没了。如果只是想“重置一下应用”,但想保留数据,千万别回收PVC。

另外StatefulSet缩容不会删除对应PVC,扩容时如果希望重用旧的PVC,要保证Pod名字一致。比如原来有web-0到web-2,缩容到web-0后,再扩容到3副本,web-1和web-2会重新绑定原来的PVC,数据还是老数据。这个特性对恢复集群特别有用,但也要注意:如果你的应用本身数据有问题,绑回旧PVC反而会把脏数据带回来。

存储数据安全这块我再多说一句:Dynamic Provisioning虽然省事,但Delete策略意味着你在删除PVC时要格外谨慎。生产环境我倾向把reclaimPolicy设为Retain,通过定期任务主动备份和清理,避免一条误删命令把底层存储数据全清掉。

5.5 常见问题速查表

现象常见原因排查手段
PVC一直PendingStorageClass provisioner不匹配 / Secret缺失 / pool不存在kubectl describe pvc查看事件
Pod反复CrashLoopBackOff探针误杀 / 应用启动失败 / OOMkubectl describe pod查看Last State
Service无法访问selector不匹配 / targetPort写错 / Pod未Readykubectl get endpoints查看Endpoints
DaemonSet部分节点没PodTaints未容忍 / nodeSelector不匹配kubectl describe node / ds
StatefulSet Pod启动卡在PendingPVC无法绑定 / 有序启动等待前置Podkubectl describe pod查看事件
Ceph认证失败Secret namespace不对 / userKey错误用keyring手动执行rados命令验证

这几个月里我把K8s资源对象从API到存储整体过了一遍之后,最大的感触是:K8s的设计其实就是在“解耦”两个字上反复做文章。工作量负载和网络解耦,于是有了Service;应用和数据解耦,于是有了PV/PVC/StorageClass;存储实现和存储接口解耦,于是有了CSI;有状态和无状态解耦,于是有了StatefulSet和Deployment的区分。你只要抓住每个资源对象“它到底在解决哪两个组件之间的解耦问题”,就再也不会觉得K8s是一堆名词的堆砌了。

最后再分享一个个人习惯:碰到不懂的资源对象,不要急着去网上找“模板”,先kubectl explain <资源类型>看官方字段说明,再kubectl get <资源> -o yaml看一个真实存在的对象是怎么写的。这两个命令配合起来比看十篇教程都管用。Ceph接入也一样,先把pool、user、keyring这三样基础搞定,再往StorageClass上怼参数,问题会少一大半。

相关新闻

MinGW、MSYS2、MSVC与gcc/g++:Windows C/C++环境配置全解析

MinGW、MSYS2、MSVC与gcc/g++:Windows C/C++环境配置全解析

刚接触C/C的新人&#xff0c;进度条往往不是卡在语法&#xff0c;而是卡在“装环境”这一关&#xff1a;MinGW、MSYS2、MSVC、gcc/g&#xff0c;四个名字长得像四胞胎&#xff0c;搜到的教程却各说各话——有的让你装MinGW&#xff0c;有的说直接上MSYS2&#xff0c;还有的说不…

2026/10/21 6:17:31 阅读更多 →
Playwright实战:破解动态渲染与反爬限制的高效数据采集方案

Playwright实战:破解动态渲染与反爬限制的高效数据采集方案

Playwright 这个工具&#xff0c;这几年的热度一直没降过。有人把它单纯当测试框架用&#xff0c;有人把它当自动化脚本工具&#xff0c;但真正让它在我这儿“封神”的&#xff0c;是它在数据采集场景下解决访问限制问题的能力。过去用请求库直接抓静态 HTML 的那套思路&#x…

2026/10/21 6:17:31 阅读更多 →
Spring Boot微服务Docker镜像打包:环境一致性与部署实践

Spring Boot微服务Docker镜像打包:环境一致性与部署实践

你在自己电脑上启动 Spring Boot 微服务&#xff0c;三秒起来&#xff0c;接口全部正常。一旦部署到服务器&#xff0c;要么 JDK 版本不对&#xff0c;要么依赖缺了几个&#xff0c;要么容器里时间是 UTC&#xff0c;日志和数据库差了八小时。折腾半天&#xff0c;最后发现根本…

2026/10/21 6:17:31 阅读更多 →

最新新闻

后仿真中的race问题:三类典型场景与五步定位法

后仿真中的race问题:三类典型场景与五步定位法

干后仿仿真时间长了&#xff0c;我最怕的不是功能跑挂&#xff0c;而是那种“功能仿真全绿、门级仿真一开就莫名出现X”的情况。这类问题里&#xff0c;race-condition占了相当大一部分。前两篇讲了一些常见场景&#xff0c;这篇继续补充三个我实际遇到过、而且定位过程比较有代…

2026/10/21 7:38:02 阅读更多 →
STM32F103内部Flash读写:免外部EEPROM,实现掉电数据保存

STM32F103内部Flash读写:免外部EEPROM,实现掉电数据保存

简介&#xff1a;面向STM32F103嵌入式开发者的内部Flash读写资料包&#xff0c;覆盖Flash存储器原理、读取操作、扇区擦除、写入时序与EEPROM模拟方法&#xff0c;帮助解决程序代码存储、配置数据保存及非易失性数据读写等实用问题。资源包共156个文件&#xff0c;以C源码和头文…

2026/10/21 7:38:02 阅读更多 →
杰理AC696N开发板Windows开发环境搭建完整指南

杰理AC696N开发板Windows开发环境搭建完整指南

很多人拿到一块新开发板&#xff0c;第一反应都是先插USB、找资料、装IDE。我拿到AC6966B开发板的时候也差不多&#xff0c;但实际走了一遍才发现&#xff0c;这套AC696N开发环境并不是一个安装包能搞定的&#xff0c;它由固件工程、编译工具链、下载工具和调试串口四部分拼装而…

2026/10/21 7:38:02 阅读更多 →
私域流量自动化运营系统架构与实操:事件驱动规则引擎

私域流量自动化运营系统架构与实操:事件驱动规则引擎

1. 私域自动化运营的底层逻辑与方案选型1.1 为什么私域运营必须走自动化路线做私域的人都有一个共同的痛&#xff1a;手动运营的天花板来得特别快。一个人管三五个群还能应付&#xff0c;一旦群数量上到二十个、三十个&#xff0c;每天光是欢迎新成员、发公告、回答重复问题就能…

2026/10/21 7:38:02 阅读更多 →
MySQL生产环境压测实战:用sysbench提前暴露上线隐患

MySQL生产环境压测实战:用sysbench提前暴露上线隐患

接手过几套生产 MySQL 之后&#xff0c;我越发觉得&#xff1a;sysbench 压测这件事&#xff0c;不是跑个命令把 TPS 截图发群里就算干完了。它最大的价值&#xff0c;是在新库上线前、或者一次大的参数调整之后&#xff0c;用一套标准化的负载提前暴露那些“平时看着没事、一上…

2026/10/21 7:38:02 阅读更多 →
扫地机器人控制端开发实战:通信、地图与任务调度全解析

扫地机器人控制端开发实战:通信、地图与任务调度全解析

我最早做扫地机器人控制端的时候&#xff0c;以为就是给机器人套个App界面、加几个按钮的事。真上手才发现&#xff0c;这玩意儿远比"远程开个机、设个定时"复杂得多——你要处理的不是一条指令&#xff0c;而是一整套状态流&#xff1a;传感器数据怎么上得来、地图怎…

2026/10/21 7:37:01 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →