深入理解 Kubernetes StatefulSet 与本地 PV:从部署到调度过程全解析
深入理解 Kubernetes StatefulSet 与本地 PV:从部署到调度过程全解析
概述
在 Kubernetes 中部署有状态应用时,StatefulSet 和 PersistentVolume 是两个核心概念。本文将详细介绍如何部署一个使用本地 PV 的 Nginx StatefulSet,并通过调整调度器日志级别来深入观察 Pod 的调度过程。
环境准备
本次基于该环境操作K8S集群部署(国内网络)
集群节点规划
- master 节点:运行控制平面组件
- node1 节点:工作节点1
- node2 节点:工作节点2
存储目录准备
在每个节点上创建对应的存储目录:
# 在 master 节点执行
sudo mkdir -p /mnt/data/nginx-0 && sudo chmod 777 /mnt/data/nginx-0
# 在 node1 节点执行
sudo mkdir -p /mnt/data/nginx-1 && sudo chmod 777 /mnt/data/nginx-1
# 在 node2 节点执行
sudo mkdir -p /mnt/data/nginx-2 && sudo chmod 777 /mnt/data/nginx-2
配置详解
1. 创建本地 PV
首先创建三个本地 PV,分别对应三个节点:
# nginx-pvs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nginx-pv-0
spec:
capacity:
storage: 1Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /mnt/data/nginx-0
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- master
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nginx-pv-1
spec:
capacity:
storage: 1Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /mnt/data/nginx-1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node1
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nginx-pv-2
spec:
capacity:
storage: 1Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /mnt/data/nginx-2
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node2
2. 创建 Headless Service(本次实验可以忽略 因为pod之间不需要访问)
StatefulSet 需要一个 Headless Service 来提供稳定的网络标识:
# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
clusterIP: None # Headless Service 的关键配置
ports:
- port: 80
name: web
selector:
app: nginx
3. 创建 StatefulSet
# nginx-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
serviceName: nginx
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
tolerations: # 添加容忍配置 这样可以在master节点部署pod
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: nginx
image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/library/nginx:1.25.3
ports:
- containerPort: 80
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: local-storage
resources:
requests:
storage: 1Gi
部署流程
# 1. 创建 PV
kubectl apply -f nginx-pvs.yaml
# 2. 创建 Headless Service
kubectl apply -f nginx-service.yaml
# 3. 创建 StatefulSet
kubectl apply -f nginx-statefulset.yaml
# 4. 验证部署
kubectl get pv
kubectl get pvc
kubectl get pods -l app=nginx -o wide
部署完成后实际效果如下
[root@master test]# pwd
/root/test
[root@master test]# ll
total 12
-rw-r--r-- 1 root root 1485 Oct 4 02:08 nginx-pvs.yaml
-rw-r--r-- 1 root root 201 Oct 4 02:42 nginx-service.yaml
-rw-r--r-- 1 root root 846 Oct 4 02:03 nginx-statefulset.yaml
[root@master test]# kubectl apply -f nginx-pvs.yaml
persistentvolume/nginx-pv-0 created
persistentvolume/nginx-pv-1 created
persistentvolume/nginx-pv-2 created
[root@master test]# kubectl apply -f nginx-service.yaml
service/nginx created
[root@master test]# kubectl apply -f nginx-statefulset.yaml
statefulset.apps/nginx created
[root@master test]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
loki-pv 10Gi RWO Retain Bound istio-system/storage-loki-0 local-storage 16d
nginx-pv-0 1Gi RWO Retain Bound default/www-nginx-0 local-storage 21s
nginx-pv-1 1Gi RWO Retain Bound default/www-nginx-2 local-storage 21s
nginx-pv-2 1Gi RWO Retain Bound default/www-nginx-1 local-storage 21s
[root@master test]# kubectl get pvv
error: the server doesn't have a resource type "pvv"
[root@master test]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-nginx-0 Bound nginx-pv-0 1Gi RWO local-storage 18s
www-nginx-1 Bound nginx-pv-2 1Gi RWO local-storage 16s
www-nginx-2 Bound nginx-pv-1 1Gi RWO local-storage 14s
[root@master test]# kubectl get pods -l app=nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-0 1/1 Running 0 25s 10.244.0.8 master <none> <none>
nginx-1 1/1 Running 0 23s 10.244.2.14 node2 <none> <none>
nginx-2 1/1 Running 0 21s 10.244.1.20 node1 <none> <none>
[root@master test]#
这里我发现我的pod nginx-1对应的pvc www-nginx-1 绑定了pv nginx-pv-2 (我以为会按顺序一一对应即www-nginx-1绑定nginx-pv-1 实际并不是)
调整调度器日志级别
为了深入观察调度过程,我们需要调整 kube-scheduler 的日志级别,显示详细的调度过程。
修改调度器配置
# 编辑调度器静态 Pod 清单
sudo vim /etc/kubernetes/manifests/kube-scheduler.yaml
在 spec.containers.command 部分添加日志级别参数:
spec:
containers:
- command:
- kube-scheduler
- --authentication-kubeconfig=/etc/kubernetes/scheduler.conf
- --authorization-kubeconfig=/etc/kubernetes/scheduler.conf
- --kubeconfig=/etc/kubernetes/scheduler.conf
- --v=4 # 添加此行,调整日志级别
保存后,kubelet 会自动重启调度器 Pod。
查看调度器日志
# 查看调度器 Pod
kubectl get pods -n kube-system -l component=kube-scheduler
# 查看调度器日志
kubectl logs -n kube-system kube-scheduler-master
调度过程深度解析
通过分析调度器日志,我们可以观察到完整的调度决策过程。
[root@master test]# kubectl logs -n kube-system kube-scheduler-master --tail=100
I1003 20:07:35.643837 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="44.53µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:60804" resp=200
I1003 20:07:45.643854 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="47.081µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:39128" resp=200
I1003 20:07:55.644837 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="50.321µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:40756" resp=200
I1003 20:07:58.441276 1 reflector.go:790] vendor/k8s.io/client-go/informers/factory.go:150: Watch close - *v1.PodDisruptionBudget total 11 items received
I1003 20:08:02.443260 1 reflector.go:790] vendor/k8s.io/client-go/informers/factory.go:150: Watch close - *v1.Node total 13 items received
I1003 20:08:05.432085 1 reflector.go:790] pkg/authentication/request/headerrequest/requestheader_controller.go:172: Watch close - *v1.ConfigMap total 7 items received
I1003 20:08:05.643954 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="48µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:36900" resp=200
I1003 20:08:15.643630 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="46.93µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:43480" resp=200
I1003 20:08:25.643553 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="47.25µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:49038" resp=200
I1003 20:08:35.643812 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="83.54µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:35842" resp=200
I1003 20:08:45.643244 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="136.811µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:36912" resp=200
I1003 20:08:55.642782 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="45µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:41650" resp=200
I1003 20:09:01.180796 1 eventhandlers.go:218] "Update event for scheduled pod" pod="default/nginx-0"
I1003 20:09:01.182884 1 eventhandlers.go:218] "Update event for scheduled pod" pod="default/nginx-2"
I1003 20:09:01.185886 1 eventhandlers.go:218] "Update event for scheduled pod" pod="default/nginx-1"
I1003 20:09:01.382485 1 eventhandlers.go:244] "Delete event for scheduled pod" pod="default/nginx-0"
I1003 20:09:01.389041 1 eventhandlers.go:244] "Delete event for scheduled pod" pod="default/nginx-1"
I1003 20:09:01.409862 1 eventhandlers.go:244] "Delete event for scheduled pod" pod="default/nginx-2"
I1003 20:09:05.644020 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="46.77µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:59032" resp=200
I1003 20:09:15.642931 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="48.54µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:34564" resp=200
I1003 20:09:22.438085 1 reflector.go:790] vendor/k8s.io/client-go/informers/factory.go:150: Watch close - *v1.CSIDriver total 8 items received
I1003 20:09:25.642702 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="46.76µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:34608" resp=200
I1003 20:09:35.643197 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="45.38µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:46220" resp=200
I1003 20:09:45.643143 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="44.04µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:56604" resp=200
I1003 20:09:55.643576 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="49.04µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:57146" resp=200
I1003 20:10:05.447002 1 reflector.go:790] vendor/k8s.io/client-go/informers/factory.go:150: Watch close - *v1.StatefulSet total 19 items received
I1003 20:10:05.643927 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="106.831µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:59170" resp=200
I1003 20:10:15.643920 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="174.691µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:35690" resp=200
I1003 20:10:25.647790 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="47.97µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:40958" resp=200
I1003 20:10:35.643645 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="58.721µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:49172" resp=200
I1003 20:10:39.439812 1 reflector.go:790] vendor/k8s.io/client-go/informers/factory.go:150: Watch close - *v1.ReplicaSet total 9 items received
I1003 20:10:40.439303 1 reflector.go:790] vendor/k8s.io/client-go/informers/factory.go:150: Watch close - *v1.Service total 7 items received
I1003 20:10:45.644078 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="126.301µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:47514" resp=200
I1003 20:10:53.348971 1 eventhandlers.go:126] "Add event for unscheduled pod" pod="default/nginx-0"
I1003 20:10:53.349250 1 schedule_one.go:80] "About to try and schedule pod" pod="default/nginx-0"
I1003 20:10:53.349265 1 schedule_one.go:93] "Attempting to schedule pod" pod="default/nginx-0"
I1003 20:10:53.350658 1 binder.go:930] "Found matching volumes for pod" pod="default/nginx-0" node="node1"
I1003 20:10:53.350705 1 binder.go:930] "Found matching volumes for pod" pod="default/nginx-0" node="node2"
I1003 20:10:53.351099 1 binder.go:930] "Found matching volumes for pod" pod="default/nginx-0" node="master"
I1003 20:10:53.354530 1 binder.go:438] "AssumePodVolumes" pod="default/nginx-0" node="master"
I1003 20:10:53.354581 1 assume_cache.go:328] "Assumed object" description="v1.PersistentVolume" cacheKey="nginx-pv-0" version=573150
I1003 20:10:53.354620 1 binder.go:509] "BindPodVolumes" pod="default/nginx-0" node="master"
I1003 20:10:53.362200 1 binder.go:584] "Updated PersistentVolume with claim. Waiting for binding to complete" pod="default/nginx-0" PV="nginx-pv-0" PVC="default/www-nginx-0"
I1003 20:10:54.363369 1 binder.go:747] "All PVCs for pod are bound" pod="default/nginx-0"
I1003 20:10:54.363587 1 default_binder.go:53] "Attempting to bind pod to node" pod="default/nginx-0" node="master"
I1003 20:10:54.367568 1 schedule_one.go:286] "Successfully bound pod to node" pod="default/nginx-0" node="master" evaluatedNodes=3 feasibleNodes=3
I1003 20:10:54.370977 1 eventhandlers.go:171] "Delete event for unscheduled pod" pod="default/nginx-0"
I1003 20:10:54.371017 1 eventhandlers.go:197] "Add event for scheduled pod" pod="default/nginx-0"
I1003 20:10:54.387189 1 eventhandlers.go:218] "Update event for scheduled pod" pod="default/nginx-0"
I1003 20:10:55.367641 1 eventhandlers.go:218] "Update event for scheduled pod" pod="default/nginx-0"
I1003 20:10:55.380166 1 eventhandlers.go:126] "Add event for unscheduled pod" pod="default/nginx-1"
I1003 20:10:55.380229 1 schedule_one.go:80] "About to try and schedule pod" pod="default/nginx-1"
I1003 20:10:55.380235 1 schedule_one.go:93] "Attempting to schedule pod" pod="default/nginx-1"
I1003 20:10:55.380528 1 binder.go:917] "No matching volumes for pod" pod="default/nginx-1" PVC="default/www-nginx-1" node="master"
I1003 20:10:55.380557 1 binder.go:957] "Storage class of claim does not support dynamic provisioning" storageClassName="local-storage" PVC="default/www-nginx-1"
I1003 20:10:55.380580 1 binder.go:930] "Found matching volumes for pod" pod="default/nginx-1" node="node1"
I1003 20:10:55.380598 1 binder.go:930] "Found matching volumes for pod" pod="default/nginx-1" node="node2"
I1003 20:10:55.381373 1 binder.go:438] "AssumePodVolumes" pod="default/nginx-1" node="node2"
I1003 20:10:55.381417 1 assume_cache.go:328] "Assumed object" description="v1.PersistentVolume" cacheKey="nginx-pv-2" version=573154
I1003 20:10:55.381439 1 binder.go:509] "BindPodVolumes" pod="default/nginx-1" node="node2"
I1003 20:10:55.387554 1 binder.go:584] "Updated PersistentVolume with claim. Waiting for binding to complete" pod="default/nginx-1" PV="nginx-pv-2" PVC="default/www-nginx-1"
I1003 20:10:55.644982 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="210.512µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:54856" resp=200
I1003 20:10:56.388639 1 binder.go:747] "All PVCs for pod are bound" pod="default/nginx-1"
I1003 20:10:56.388693 1 default_binder.go:53] "Attempting to bind pod to node" pod="default/nginx-1" node="node2"
I1003 20:10:56.392849 1 schedule_one.go:286] "Successfully bound pod to node" pod="default/nginx-1" node="node2" evaluatedNodes=3 feasibleNodes=2
I1003 20:10:56.392855 1 eventhandlers.go:171] "Delete event for unscheduled pod" pod="default/nginx-1"
I1003 20:10:56.393049 1 eventhandlers.go:197] "Add event for scheduled pod" pod="default/nginx-1"
I1003 20:10:56.403326 1 eventhandlers.go:218] "Update event for scheduled pod" pod="default/nginx-1"
I1003 20:10:57.423570 1 eventhandlers.go:218] "Update event for scheduled pod" pod="default/nginx-1"
I1003 20:10:57.447190 1 eventhandlers.go:126] "Add event for unscheduled pod" pod="default/nginx-2"
I1003 20:10:57.447511 1 schedule_one.go:80] "About to try and schedule pod" pod="default/nginx-2"
I1003 20:10:57.447579 1 schedule_one.go:93] "Attempting to schedule pod" pod="default/nginx-2"
I1003 20:10:57.448252 1 binder.go:917] "No matching volumes for pod" pod="default/nginx-2" PVC="default/www-nginx-2" node="master"
I1003 20:10:57.448335 1 binder.go:957] "Storage class of claim does not support dynamic provisioning" storageClassName="local-storage" PVC="default/www-nginx-2"
I1003 20:10:57.448450 1 binder.go:917] "No matching volumes for pod" pod="default/nginx-2" PVC="default/www-nginx-2" node="node2"
I1003 20:10:57.448514 1 binder.go:957] "Storage class of claim does not support dynamic provisioning" storageClassName="local-storage" PVC="default/www-nginx-2"
I1003 20:10:57.448450 1 binder.go:930] "Found matching volumes for pod" pod="default/nginx-2" node="node1"
I1003 20:10:57.449365 1 binder.go:438] "AssumePodVolumes" pod="default/nginx-2" node="node1"
I1003 20:10:57.449492 1 assume_cache.go:328] "Assumed object" description="v1.PersistentVolume" cacheKey="nginx-pv-1" version=573152
I1003 20:10:57.449799 1 binder.go:509] "BindPodVolumes" pod="default/nginx-2" node="node1"
I1003 20:10:57.461274 1 binder.go:584] "Updated PersistentVolume with claim. Waiting for binding to complete" pod="default/nginx-2" PV="nginx-pv-1" PVC="default/www-nginx-2"
I1003 20:10:58.462218 1 binder.go:747] "All PVCs for pod are bound" pod="default/nginx-2"
I1003 20:10:58.462265 1 default_binder.go:53] "Attempting to bind pod to node" pod="default/nginx-2" node="node1"
I1003 20:10:58.466668 1 eventhandlers.go:171] "Delete event for unscheduled pod" pod="default/nginx-2"
I1003 20:10:58.466918 1 schedule_one.go:286] "Successfully bound pod to node" pod="default/nginx-2" node="node1" evaluatedNodes=3 feasibleNodes=1
I1003 20:10:58.467184 1 eventhandlers.go:197] "Add event for scheduled pod" pod="default/nginx-2"
I1003 20:10:58.480314 1 eventhandlers.go:218] "Update event for scheduled pod" pod="default/nginx-2"
I1003 20:10:59.728081 1 eventhandlers.go:218] "Update event for scheduled pod" pod="default/nginx-2"
I1003 20:11:05.642943 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="45.2µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:50170" resp=200
I1003 20:11:15.643171 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="109.321µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:58568" resp=200
I1003 20:11:25.642871 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="46.47µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:55356" resp=200
I1003 20:11:35.643049 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="48.65µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:59226" resp=200
I1003 20:11:40.434397 1 reflector.go:790] pkg/server/dynamiccertificates/configmap_cafile_content.go:206: Watch close - *v1.ConfigMap total 8 items received
I1003 20:11:45.643843 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="47.691µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:58722" resp=200
I1003 20:11:55.643818 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="122.311µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:39812" resp=200
I1003 20:12:05.643683 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="44.73µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:49594" resp=200
I1003 20:12:15.643838 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="222.882µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:50928" resp=200
I1003 20:12:25.643033 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="46.681µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:54648" resp=200
I1003 20:12:35.643424 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="43.74µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:54474" resp=200
I1003 20:12:45.643569 1 httplog.go:132] "HTTP" verb="GET" URI="/healthz" latency="48.341µs" userAgent="kube-probe/1.28" audit-ID="" srcIP="127.0.0.1:33112" resp=200
[root@master test]#
1. Pod 创建事件
I1003 20:10:55.380166 1 eventhandlers.go:126] "Add event for unscheduled pod" pod="default/nginx-1"
调度器检测到新的未调度 Pod,开始处理调度请求。
2. 存储匹配检查
I1003 20:10:55.380528 1 binder.go:917] "No matching volumes for pod" pod="default/nginx-1" PVC="default/www-nginx-1" node="master"
I1003 20:10:55.380557 1 binder.go:957] "Storage class of claim does not support dynamic provisioning" storageClassName="local-storage" PVC="default/www-nginx-1"
I1003 20:10:55.380580 1 binder.go:930] "Found matching volumes for pod" pod="default/nginx-1" node="node1"
I1003 20:10:55.380598 1 binder.go:930] "Found matching volumes for pod" pod="default/nginx-1" node="node2"
调度器检查每个节点的 PV 匹配情况:
- master 节点:无匹配 PV(nginx-pv-0 已被 nginx-0 使用)
- 不支持动态分配因为使用的是local-storage本地pv
- node1 节点:有匹配 PV(nginx-pv-1)
- node2 节点:有匹配 PV(nginx-pv-2)
3. PV 预占与绑定
I1003 20:10:55.381373 1 binder.go:438] "AssumePodVolumes" pod="default/nginx-1" node="node2"
I1003 20:10:55.381417 1 assume_cache.go:328] "Assumed object" description="v1.PersistentVolume" cacheKey="nginx-pv-2" version=573154
调度器选择 node2 并预占 nginx-pv-2。
4. 最终绑定决策
I1003 20:10:56.392849 1 schedule_one.go:286] "Successfully bound pod to node" pod="default/nginx-1" node="node2" evaluatedNodes=3 feasibleNodes=2
调度完成,Pod nginx-1 被绑定到 node2 节点。(具体为什么调度到node2我也没搞懂,因为根据资源计算得分的话node1应该更空闲一点,复现五六次都是调度到node2 ,有大佬懂的指点下)
我问了gpt和deepseek搞了半天也还是没找到原因,说是volume绑定在pod调度之前,会遍历可用的pv列表,一旦有就assume后续confirm调度到该pv所在的node 那也不对呀,因为我又创建了2个pv pv3 pv4分别在node1和node2 pod1还是稳定调度到node2 只不过随机选择pv2或者pv4 所以这可以说明就是先选择node再根据node选择pv吧
[root@master test]# kubectl top node
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
master 119m 5% 2081Mi 59%
node1 68m 3% 2262Mi 64%
node2 66m 3% 2453Mi 69%
[root@master test]#
如何确认 Kubernetes 调度器使用的关键调度插件
在 Kubernetes 中,调度器通过一系列插件来决定 Pod 的最佳调度位置。了解当前调度器使用的关键插件对于优化集群性能和排查调度问题至关重要。以下是几种确认调度器插件的方法:
方法一:查看调度器配置文件(推荐)
1. 获取调度器配置 ConfigMap
kubectl get configmap -n kube-system kube-scheduler-config -o yaml
2. 查找插件配置
在输出中查找 profiles.plugins 部分:
apiVersion: kubescheduler.config.k8s.io/v1beta2
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
# 预选阶段插件
preFilter:
enabled:
- name: NodeResourcesFit
- name: VolumeBinding
# 优选阶段插件
score:
enabled:
- name: NodeResourcesBalancedAllocation
weight: 1
- name: NodeResourcesLeastAllocated
weight: 1
- name: VolumeBinding
weight: 1
3. 关键插件解析
| 插件名称 | 类型 | 功能描述 | 权重 |
|---|---|---|---|
| NodeResourcesFit | preFilter | 检查节点是否有足够资源 | - |
| VolumeBinding | preFilter | 检查卷绑定情况 | - |
| NodeResourcesBalancedAllocation | score | 平衡 CPU/内存分配 | 1 |
| NodeResourcesLeastAllocated | score | 优先选择资源使用率低的节点 | 1 |
| VolumeBinding | score | 优先选择有预绑定卷的节点 | 1 |
方法二:查看调度器启动参数
1. 查看调度器 Pod 详情
kubectl describe pod -n kube-system kube-scheduler-master
2. 查找启动参数
在输出中查找 --config 参数,它指向调度器配置文件:
Containers:
kube-scheduler:
Command:
kube-scheduler
--config=/etc/kubernetes/scheduler-config.yaml
...
3. 检查配置文件内容
如果使用配置文件,按照方法一查看。如果使用命令行参数,查找 --plugins 参数:
--plugins=NodeResourcesFit,VolumeBinding,NodeResourcesBalancedAllocation,...
方法三:查看调度器日志
1. 提高日志级别
编辑调度器清单文件:
sudo vim /etc/kubernetes/manifests/kube-scheduler.yaml
添加日志级别参数:
spec:
containers:
- command:
- kube-scheduler
- --v=5 # 提高日志级别
...
2. 查看初始化日志
kubectl logs -n kube-system kube-scheduler-master | grep "Loaded profile"
输出示例:
I1003 19:13:06.404805 configfile.go:101] "Loaded profile" profile="default-scheduler" plugins="[PrioritySort NodeUnschedulable NodeName TaintToleration NodeAffinity NodePorts NodeResourcesFit VolumeRestrictions ...]"
方法四:使用调度器 API
1. 端口转发到调度器
kubectl port-forward -n kube-system kube-scheduler-master 10259:10259
2. 查询调度器配置
curl http://localhost:10259/configz | jq .kubeschedulerconfig
关键调度插件解析
1. 预选阶段插件 (Predicates)
| 插件名称 | 功能 |
|---|---|
| NodeResourcesFit | 检查节点是否有足够 CPU/内存资源 |
| VolumeBinding | 检查卷绑定情况(PV/PVC) |
| NodeAffinity | 检查节点亲和性规则 |
| TaintToleration | 检查污点容忍规则 |
| PodTopologySpread | 检查拓扑分布约束 |
2. 优选阶段插件 (Priorities)
| 插件名称 | 功能 | 权重 |
|---|---|---|
| NodeResourcesBalancedAllocation | 平衡 CPU/内存分配 | 1 |
| NodeResourcesLeastAllocated | 优先资源使用率低的节点 | 1 |
| VolumeBinding | 优先有预绑定卷的节点 | 1 |
| ImageLocality | 优先有镜像的节点 | 1 |
| InterPodAffinity | 处理 Pod 间亲和性 | 2 |
3. 绑定阶段插件 (Bind)
| 插件名称 | 功能 |
|---|---|
| DefaultBinder | 默认绑定插件 |
验证插件效果
1. 查看调度决策详情
kubectl describe pod <pod-name> | grep Events -A 20
2. 模拟调度过程
kubectl create -f pod.yaml --dry-run=server -o yaml
自定义调度插件
1. 修改调度器配置
编辑 ConfigMap:
kubectl edit configmap -n kube-system kube-scheduler-config
2. 添加/修改插件
profiles:
- schedulerName: default-scheduler
plugins:
score:
enabled:
- name: MyCustomPlugin
weight: 5
3. 重启调度器
# 删除调度器 Pod 使其自动重建
kubectl delete pod -n kube-system kube-scheduler-master
总结
确认 Kubernetes 调度器使用的关键插件主要有四种方法:
- 查看调度器 ConfigMap - 最直接的方法,显示完整配置
- 检查调度器启动参数 - 适用于命令行参数配置
- 分析调度器日志 - 需要提高日志级别
- 使用调度器 API - 通过 /configz 端点查询
关键插件包括:
- 预选插件:NodeResourcesFit, VolumeBinding
- 优选插件:NodeResourcesBalancedAllocation, NodeResourcesLeastAllocated
- 绑定插件:DefaultBinder
了解这些插件及其权重可以帮助您:
- 优化集群资源利用率
- 排查 Pod 调度失败问题
- 定制调度策略以满足特定需求
- 理解调度决策背后的逻辑
但是目前我们的环境启动参数没有相关配置 也没使用configmap 配置文件指定的是CA证书
Command:
kube-scheduler
--authentication-kubeconfig=/etc/kubernetes/scheduler.conf
--authorization-kubeconfig=/etc/kubernetes/scheduler.conf
--bind-address=127.0.0.1
--kubeconfig=/etc/kubernetes/scheduler.conf
--leader-elect=true
--v=4
[root@master test]# cat /etc/kubernetes/scheduler.conf
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJZVR2R2RYL2hDOFF3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TlRBNE1qQXhNelF6TURWYUZ3MHpOVEE0TVRneE16UTRNRFZhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUNlT2FoSUtyVFZvUVNlVUY0bkE5ZFgyYlIramk5M0tyMkJ6Sm9ib2JLd29ML3djd0E1aVE4dEpucDMKc2RvVFdFem5TTERQeHdUZWJEdVZsZXpERGV5aGJJRjh6cDMzMFFPaUxOLzRxNGNMOGY2NTI3cmJDcStvb0plagptVHE2RHBPWStMVDhTNVBhWEQxcHZmZG5GUThtMDQzRGxxMm9NajA3eStlYnE0RWh6WW4xYnJYckxMUTlHUHFKCnExNFArMUxlWmNHT3NwZkJpem9PdC9WcmUwVmFZU0hDYWcxWUtJYnNnclF5R3BENUROUG41MjVRWkRaRU9qVVcKU2ZoTzdBNFVhQjEvNDhtakgzUE9UNitrbnNIeUpmK2I3bWhVR2w1SmtxT3o2WVNQMEtMVUt0Z1JKWERBNVY0MgpUay9WMjlxQTJZMlprMnU4dFdKeENublNvcFNQQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJTekVwVlV6aWdSL3F3cCttc1dURC9jM0dlM0lqQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQWtyK0Z5eG5yTwpMenl6dmNYSXFwZmxhckpOakFlV21NVWEyaTFFRndMZGhvS3c3Y3B6UmlEVXEzK280ODVYbjhQWnhrK2Y0SWw2CmpiQjlsWXZBanFOWUtuOEp1dkRlVGczSDVCd0Q3YitBY3d6bis5aFZoSHMzNW4rNGhZY25xdkhtY0ZHVElFK1gKbEw0ZVNYSC9jdWxac2RZcDR5cExiUkNDYXB1YzBrbXpVdFlZcW9GRXhCMStvZlc5ODlXKzlCOElIdUVJT08xYgpVNCtuSURKK0twUXpha3o1VitaMFFSR3VBWnRRcXNUcXRRUW56YkNvb0cwUWRxelZCR0creHFGVkFqUXJiK1NwCmhmZktnMUdseE9GUEN2Zmo5YWpDYXNuQmg5STRDVGdZcWllT0pWUzJXTmMycXUrT01XVnMxMHBncmpHYnR6bjYKa3dhQnUrQ2lNS01jCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
server: https://192.168.124.128:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: system:kube-scheduler
name: system:kube-scheduler@kubernetes
current-context: system:kube-scheduler@kubernetes
kind: Config
preferences: {}
users:
- name: system:kube-scheduler
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUREVENDQWZXZ0F3SUJBZ0lJRElQN2RNbjdhZmN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TlRBNE1qQXhNelF6TURWYUZ3MHlOakE0TWpBeE16UTRNRFphTUNBeApIakFjQmdOVkJBTVRGWE41YzNSbGJUcHJkV0psTFhOamFHVmtkV3hsY2pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCCkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU1sQjBRTnRGdmREUWljNkJ0eVlHUFpCWWp0aUFlR1hZaWxuVlZBM0lKRG4KL2NYOEZ1TC9vaTFXa0ZtVXFPOXhlMld0SFJobkc3UlIyWVl6WDd3cmVUUENIcFQ0ZVdManpCalgvQTA1NXNKNgpoL2VpdTIyZWFueGxXaFc0ZlFISityYndPM0htWWhGVE5ka3lZdGs2NGlzVHozY0dnSXR0T0ZSR3ErUGxHczduCjFSZ3lQck5PK21CeTRLQmxqWVM0R3AwRU13M3NuMFNnbjlLR0FhQnMrRExPTkJudDNPdVcyVTA3YXBDS0hLd1AKUHFqZEluaGxzd1RhMzVSM0t0a0dPelJGaExtT0xId1ZjR1B0aEhvUXFUb2I0ZjlCWXNmNUlqSVBWNVFQOVhocApuaTNINGxsVXFxNnhTRDZHZUIrZjRHS2hsNXhmaHQ2WVdPNmIzQXdQbjRrQ0F3RUFBYU5XTUZRd0RnWURWUjBQCkFRSC9CQVFEQWdXZ01CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUNNQXdHQTFVZEV3RUIvd1FDTUFBd0h3WUQKVlIwakJCZ3dGb0FVc3hLVlZNNG9FZjZzS2ZwckZrdy8zTnhudHlJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQgpBQjFUMlpLdlFyOXEvZGh5c0RleDFIdTBrOXI0cTJ5T0psdU4wc0ZKQ1ozTGZFZWhLN2xZeW5VVDhlUGZSMjBZCmJYbkdQamhrdFUxVXR0ZndORTd6YXhVejMwTmdrcDU1ZjkyOE41SDFOQUwxWjh2UkY5OW56dHcwQVpyT09NeFkKVTcwSGRGNjdxSU9DV0tSODIvdlgxSjBUZ1QwSGdCbTErZm5qUWlDd01nMFBBdzczcHNra2QvOEoxSXhKbXIrSgp6L0F6UGZUOGp3dUpCZVRyS1ErdkhVZGllanpYSytzYzMrQkVORldRdEZrSXhRTU9oREFlbklOTmRnWitJZ0tYCm5CSTRrRXA0elNxbU51aEtVVXFDN3pIb1g5RlgvTW15N0tWamlKQlJINW1pTUN2REFtMEN3WDVwT2hFS1piazAKVVJVUmpjanUvaWRRRHJlQitLT25lOVk9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBeVVIUkEyMFc5ME5DSnpvRzNKZ1k5a0ZpTzJJQjRaZGlLV2RWVURjZ2tPZjl4ZndXCjR2K2lMVmFRV1pTbzczRjdaYTBkR0djYnRGSFpoak5mdkN0NU04SWVsUGg1WXVQTUdOZjhEVG5td25xSDk2SzcKYlo1cWZHVmFGYmg5QWNuNnR2QTdjZVppRVZNMTJUSmkyVHJpS3hQUGR3YUFpMjA0VkVhcjQrVWF6dWZWR0RJKwpzMDc2WUhMZ29HV05oTGdhblFRekRleWZSS0NmMG9ZQm9HejRNczQwR2UzYzY1YlpUVHRxa0lvY3JBOCtxTjBpCmVHV3pCTnJmbEhjcTJRWTdORVdFdVk0c2ZCVndZKzJFZWhDcE9odmgvMEZpeC9raU1nOVhsQS8xZUdtZUxjZmkKV1ZTcXJyRklQb1o0SDUvZ1lxR1huRitHM3BoWTdwdmNEQStmaVFJREFRQUJBb0lCQUdWUW5NZjh3dkwzQ1BaMgpYNzN0VTZPa2hxOGVSNVFwZ1dFV2cvdzl4RWN1Z3JLSWxYenc4OU9mSUV2NGFwTWZyZDhocHVRQ0JCQmlvLzdMCkhnYThXK2VTY1pMTkEwNmxIcm5qQVNBblplUEJpM0UrbXR3MFE2Y2IzcDNRb2gya2NISm52WVU1VldValhwaFkKcFNwMldpVVZjL3VYYWw3R1BQVXdIcGc3WGFPS2JFUUFXOWxTRVlUTXRFenlHQXkwWFJTUUNMd3pkWU9SdWRLaQo1djR5cWQ0NFhIZTI3eWpTa0tlbWRXVXJiRzUwVDJ6cjFLb2NvY1ZuLzgyNGZqQ0ZTUHhmejR6NHRXWDNuT1U5CjIxOC9wOXdONEpFbU1JLzhIQ2pQaUFJWmVtT1VXRlZyV1VnUVFvK3R4ZzB3aDRkVDNsSXVhUDhuc09RR0lVcFEKRVJseFFZRUNnWUVBK2VnS2hlSlhsVHJHVkFVYlZ1SkJCbjVRejhWSGJrN29Td1gzWlVYbDIwOElrUk10eXZHWQpBbndHMTZta2o2cmFPYTE0dHVucWE2NjloZkZoN3hZbzNLOXdOODFQUEdERlRMdGFpdjBEcDEzbGZqd2o1S3FWCktVdm12YkhaeVdqZWR6aXd3M01NQXRYLzNMc1BwRXZ0Nm9ydE8yNFRjWXJYN2RCNlBBcFZUNmNDZ1lFQXppb1oKRXNBNDNPWmU0bEg5c293RmgwTlpCdEg4ZE5EcVFxT2MwRDJHamJ5RjNVQ3oyWjBkN1hVbmI4MFJIWkI0MHBFbgowbTZRaWx5K05iYUNwSG1XcDBJNFI5dUxwU0VyM1duTFZhR1hBaVJNdzEzQlcremZpR0U1Mmc2bGNGM2trcVViCnVYRUN6TmdSMTFEaUxvVkVhai9ra1hYdFd3Smd4OXIwZURSYy9VOENnWUJsQnZkcUZJeVBtWWtmNGtpaElTcGsKYzZBN3ZtY1lJdklwa2lubldSQ0pUalFLWWhSN0hKdjFOU3FXK00rNy9MZm41VWNOdXhhM25aYWtsV2FmL3ZkWgo3OXFQMUlJWnlJMDZiZXMya1A1dkpMaG9CZXdFdnlrNTNxTlRRSmpvb1dwK0MvNzVwSUxRaXE2N0R5eE5vUngzCld0NTR6aEV5TDQwSGFPWmhhMVA5dFFLQmdRQzNXRk1DRFBiRTVROGZBTUc1QnJObjdxbisyY0pGZFFIUWo4YkcKbnVESnJnM3lqVGNrNFlpYkErUTFsazZSVjBsTUloRWpJSGJreGNQVzZ0L1dPOWVTR2Q4SmNLTUpFSzM4ODdnRQpDSGZPVE5BRHNwNWlEUTlpTHUwUEVwVm9qK2ZWem9ZUWJnT0tmdUdtOWVTZ2NKNCtTMklQUnF2Mmt1L1U3TkViCkVaNWRHd0tCZ0dRdXFxSzhRTzdkbHowZFFaRUprYTJGVDFxS2Q4RFAyY2NqRXpwTkFoQm9VZ0dmMlBlZGQ2SksKVmJJMEI0emh3NStFM0hwSjQzM3dVM2tUMlhYNWVzQUdGdktvM1VaMmppeXl5eThoNHlPYXlxTTdVWE53aWlXMgpQTTVCc0ZwOGlRZUpVa1g0M25PM1hBeVZXS0ZJVXJDRGNVc0ppNENTNFZZMy9jYVRyNThSCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
[root@master test]#
所以我们使用修改日志级别为5的方法间接判断,修改后重新查看日志
kubectl logs -n kube-system kube-scheduler-master
I1003 20:41:34.287745 1 requestheader_controller.go:244] Loaded a new request header values for RequestHeaderAuthRequestController
I1003 20:41:34.301864 1 configfile.go:101] "Using component config" config=<
apiVersion: kubescheduler.config.k8s.io/v1
clientConnection:
acceptContentTypes: ""
burst: 100
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /etc/kubernetes/scheduler.conf
qps: 50
enableContentionProfiling: true
enableProfiling: true
kind: KubeSchedulerConfiguration
leaderElection:
leaderElect: true
leaseDuration: 15s
renewDeadline: 10s
resourceLock: leases
resourceName: kube-scheduler
resourceNamespace: kube-system
retryPeriod: 2s
parallelism: 16
percentageOfNodesToScore: 0
podInitialBackoffSeconds: 1
podMaxBackoffSeconds: 10
profiles:
- pluginConfig:
- args:
apiVersion: kubescheduler.config.k8s.io/v1
kind: DefaultPreemptionArgs
minCandidateNodesAbsolute: 100
minCandidateNodesPercentage: 10
name: DefaultPreemption
- args:
apiVersion: kubescheduler.config.k8s.io/v1
hardPodAffinityWeight: 1
ignorePreferredTermsOfExistingPods: false
kind: InterPodAffinityArgs
name: InterPodAffinity
- args:
apiVersion: kubescheduler.config.k8s.io/v1
kind: NodeAffinityArgs
name: NodeAffinity
- args:
apiVersion: kubescheduler.config.k8s.io/v1
kind: NodeResourcesBalancedAllocationArgs
resources:
- name: cpu
weight: 1
- name: memory
weight: 1
name: NodeResourcesBalancedAllocation
- args:
apiVersion: kubescheduler.config.k8s.io/v1
kind: NodeResourcesFitArgs
scoringStrategy:
resources:
- name: cpu
weight: 1
- name: memory
weight: 1
type: LeastAllocated
name: NodeResourcesFit
- args:
apiVersion: kubescheduler.config.k8s.io/v1
defaultingType: System
kind: PodTopologySpreadArgs
name: PodTopologySpread
- args:
apiVersion: kubescheduler.config.k8s.io/v1
bindTimeoutSeconds: 600
kind: VolumeBindingArgs
name: VolumeBinding
plugins:
bind: {}
filter: {}
multiPoint:
enabled:
- name: PrioritySort
weight: 0
- name: NodeUnschedulable
weight: 0
- name: NodeName
weight: 0
- name: TaintToleration
weight: 3
- name: NodeAffinity
weight: 2
- name: NodePorts
weight: 0
- name: NodeResourcesFit
weight: 1
- name: VolumeRestrictions
weight: 0
- name: EBSLimits
weight: 0
- name: GCEPDLimits
weight: 0
- name: NodeVolumeLimits
weight: 0
- name: AzureDiskLimits
weight: 0
- name: VolumeBinding
weight: 0
- name: VolumeZone
weight: 0
- name: PodTopologySpread
weight: 2
- name: InterPodAffinity
weight: 2
- name: DefaultPreemption
weight: 0
- name: NodeResourcesBalancedAllocation
weight: 1
- name: ImageLocality
weight: 1
- name: DefaultBinder
weight: 0
- name: SchedulingGates
weight: 0
permit: {}
postBind: {}
postFilter: {}
preBind: {}
preEnqueue: {}
preFilter: {}
preScore: {}
queueSort: {}
reserve: {}
score: {}
schedulerName: default-scheduler
"Using component config" 表示调度器正在加载和应用其核心配置
以下是整理后的 Kubernetes 调度器配置,已按字段层级重新格式化并调整缩进:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
clientConnection:
acceptContentTypes: ""
burst: 100
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /etc/kubernetes/scheduler.conf
qps: 50
enableContentionProfiling: true
enableProfiling: true
leaderElection:
leaderElect: true
leaseDuration: 15s
renewDeadline: 10s
resourceLock: leases
resourceName: kube-scheduler
resourceNamespace: kube-system
retryPeriod: 2s
parallelism: 16
percentageOfNodesToScore: 0
podInitialBackoffSeconds: 1
podMaxBackoffSeconds: 10
profiles:
- schedulerName: default-scheduler
pluginConfig:
- name: DefaultPreemption
args:
apiVersion: kubescheduler.config.k8s.io/v1
kind: DefaultPreemptionArgs
minCandidateNodesAbsolute: 100
minCandidateNodesPercentage: 10
- name: InterPodAffinity
args:
apiVersion: kubescheduler.config.k8s.io/v1
hardPodAffinityWeight: 1
ignorePreferredTermsOfExistingPods: false
kind: InterPodAffinityArgs
- name: NodeAffinity
args:
apiVersion: kubescheduler.config.k8s.io/v1
kind: NodeAffinityArgs
- name: NodeResourcesBalancedAllocation
args:
apiVersion: kubescheduler.config.k8s.io/v1
kind: NodeResourcesBalancedAllocationArgs
resources:
- name: cpu
weight: 1
- name: memory
weight: 1
- name: NodeResourcesFit
args:
apiVersion: kubescheduler.config.k8s.io/v1
kind: NodeResourcesFitArgs
scoringStrategy:
type: LeastAllocated
resources:
- name: cpu
weight: 1
- name: memory
weight: 1
- name: PodTopologySpread
args:
apiVersion: kubescheduler.config.k8s.io/v1
kind: PodTopologySpreadArgs
defaultingType: System
- name: VolumeBinding
args:
apiVersion: kubescheduler.config.k8s.io/v1
kind: VolumeBindingArgs
bindTimeoutSeconds: 600
plugins:
multiPoint:
enabled:
- name: PrioritySort
weight: 0
- name: NodeUnschedulable
weight: 0
- name: NodeName
weight: 0
- name: TaintToleration
weight: 3
- name: NodeAffinity
weight: 2
- name: NodePorts
weight: 0
- name: NodeResourcesFit
weight: 1
- name: VolumeRestrictions
weight: 0
- name: EBSLimits
weight: 0
- name: GCEPDLimits
weight: 0
- name: NodeVolumeLimits
weight: 0
- name: AzureDiskLimits
weight: 0
- name: VolumeBinding
weight: 0
- name: VolumeZone
weight: 0
- name: PodTopologySpread
weight: 2
- name: InterPodAffinity
weight: 2
- name: DefaultPreemption
weight: 0
- name: NodeResourcesBalancedAllocation
weight: 1
- name: ImageLocality
weight: 1
- name: DefaultBinder
weight: 0
- name: SchedulingGates
weight: 0
# 空插件阶段(保持原样)
bind: {}
filter: {}
permit: {}
postBind: {}
postFilter: {}
preBind: {}
preEnqueue: {}
preFilter: {}
preScore: {}
queueSort: {}
reserve: {}
score: {}
以下是针对 Kubernetes 调度器配置中 profiles 和 pluginConfig 等关键字段的详细解释:
1. profiles 配置组
profiles:
- schedulerName: default-scheduler
pluginConfig: [...] # 插件参数配置
plugins: [...] # 插件启用配置
- 作用:定义调度器的行为模板,支持多套调度策略
schedulerName: default-scheduler
标识此配置用于系统默认调度器
2. pluginConfig 插件参数配置
pluginConfig:
- name: DefaultPreemption
args:
minCandidateNodesAbsolute: 100 # 抢占时至少考虑100个节点
minCandidateNodesPercentage: 10 # 或集群节点数的10%(取较大值)
核心插件解析:
| 插件名称 | 参数说明 |
|---|---|
| DefaultPreemption | 抢占配置:当高优先级 Pod 需调度时,低优先级 Pod 可被驱逐 |
| InterPodAffinity | Pod 间亲和性:hardPodAffinityWeight=1 控制硬亲和性规则的权重 |
| NodeResourcesFit | 节点资源匹配:scoringStrategy: LeastAllocated 优先选择资源空闲率高的节点 |
| NodeResourcesBalancedAllocation | 资源均衡分配:CPU/内存按 1:1 权重平衡使用率 |
| PodTopologySpread | 拓扑分布:defaultingType: System 使用系统默认的拓扑约束规则 |
| VolumeBinding | 卷绑定:bindTimeoutSeconds=600 卷绑定操作超时时间(10分钟) |
3. plugins 插件启用配置
plugins:
multiPoint:
enabled:
- name: TaintToleration
weight: 3 # 污点容忍权重最高
- name: NodeAffinity
weight: 2 # 节点亲和性中等权重
- name: NodeResourcesFit
weight: 1 # 资源匹配基础权重
关键设计:
- multiPoint 机制:允许插件在多个调度阶段生效(如同时参与过滤和打分)
- 权重系统:调度决策时各插件分数的加权计算方式(权重越高影响力越大)
- 典型权重分布:
- 污点容忍(3) > 拓扑分布(2) = 亲和性(2) > 资源分配(1) = 镜像本地性(1)
- 权重为0的插件仅参与过滤阶段(如 VolumeBinding)
4. 其他核心配置解析
parallelism: 16 # 并发调度16个Pod
leaderElection: # 高可用配置
leaderElect: true # 启用Leader选举
leaseDuration: 15s # 领导租约有效期
resourceNamespace: kube-system # 选举锁存放位置
percentageOfNodesToScore: 0 # 0表示自动计算需评估的节点比例
调度流程示意
总结设计思想
- 模块化插件:每个调度功能独立可配置(如亲和性/卷绑定)
- 策略可定制:通过权重系统调整调度优先级
- 资源优化:
parallelism=16和自动节点评估比例提升调度吞吐量 - 生产就绪:Leader选举确保高可用,Profiles支持多租户调度策略
此配置体现了 Kubernetes 调度器的核心设计理念:可扩展性(插件体系)、灵活性(权重调节)和稳定性(资源控制与高可用)。
实践总结
成功部署验证
部署完成后,验证所有资源状态:
[root@master test]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
loki-pv 10Gi RWO Retain Bound istio-system/storage-loki-0 local-storage 16d
nginx-pv-0 1Gi RWO Retain Bound default/www-nginx-0 local-storage 8s
nginx-pv-1 1Gi RWO Retain Bound default/www-nginx-2 local-storage 8s
nginx-pv-2 1Gi RWO Retain Bound default/www-nginx-1 local-storage 8s
[root@master test]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-nginx-0 Bound nginx-pv-0 1Gi RWO local-storage 10s
www-nginx-1 Bound nginx-pv-2 1Gi RWO local-storage 8s
www-nginx-2 Bound nginx-pv-1 1Gi RWO local-storage 5s
[root@master test]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-0 1/1 Running 0 21s 10.244.0.10 master <none> <none>
nginx-1 1/1 Running 0 19s 10.244.2.16 node2 <none> <none>
nginx-2 1/1 Running 0 16s 10.244.1.22 node1 <none> <none>
[root@master test]#
关键发现
- 有序绑定特性:StatefulSet 按顺序创建 Pod(nginx-0 → nginx-1 → nginx-2)
- 存储感知调度:调度器优先考虑有匹配 PV 的节点
- 资源优化:当多个节点满足条件时,选择资源最空闲的节点
- 网络稳定性:每个 Pod 获得稳定的网络标识(nginx-0.nginx, nginx-1.nginx 等)
故障排查技巧
常见问题解决
-
PV 无法绑定:
# 检查 PV 节点亲和性 kubectl get pv nginx-pv-0 -o jsonpath='{.spec.nodeAffinity}' # 检查节点标签 kubectl get node --show-labels -
Pod 调度失败:
# 查看 Pod 事件 kubectl describe pod nginx-0 # 检查节点资源 kubectl describe node node1 kubectl top nodes -
存储类问题:
# 验证存储类配置 kubectl get storageclass local-storage -o yaml
最佳实践建议
- 存储规划:提前规划 PV 与节点的对应关系
- 资源预留:为系统组件预留足够的 CPU 和内存资源
- 监控告警:设置 PVC 绑定失败和 Pod 调度失败的告警
- 备份策略:本地 PV 需要额外的数据备份方案
- 扩展性考虑:预留 PV 容量以支持 StatefulSet 扩容
结论
通过本次实践,我们深入理解了 Kubernetes StatefulSet 与本地 PV 的协同工作机制。关键收获包括:
- StatefulSet 提供了有状态应用所需的所有特性:有序部署、稳定网络标识、持久化存储
- 本地 PV 需要精细的节点规划:通过节点亲和性确保数据本地性
- 调度器日志是理解调度决策的宝贵工具:通过调整日志级别可以观察完整的调度过程
- 存储感知调度确保数据一致性:调度器会优先考虑存储约束条件
这种深入的理解有助于在生产环境中更好地设计和运维有状态的 Kubernetes 工作负载。
statefulset相关操作实践
以下是 StatefulSet 的常用操作指南,包括扩缩容、更新等实际操作步骤,以及如何观察其有序过程:
一、StatefulSet 核心特性回顾
- 有序性:Pod 按顺序创建/删除(从 0 到 N-1)
- 稳定标识:Pod 名称、主机名、存储卷保持不变
- 滚动更新:逆序更新(从最大序号开始)
二、常用操作指南
1. 扩容(增加副本数)
# 将副本数从 3 扩容到 5
kubectl scale statefulset nginx --replicas=5
# 实时观察扩容过程(新窗口)
kubectl get pods -l app=nginx -w
有序创建过程观察:
# 预期输出:
nginx-0 1/1 Running 0 45m
nginx-1 1/1 Running 0 45m
nginx-2 1/1 Running 0 45m
nginx-3 0/1 Pending 0 0s # 顺序创建
nginx-3 0/1 ContainerCreating 0s
nginx-3 1/1 Running 10s
nginx-4 0/1 Pending 0s # 下一个副本
...
2. 缩容(减少副本数)
# 将副本数从 5 缩容到 2
kubectl scale statefulset nginx --replicas=2
# 观察缩容过程
kubectl get pods -l app=nginx -w
有序删除过程观察:
# 预期输出:
nginx-0 1/1 Running 0 50m
nginx-1 1/1 Running 0 50m
nginx-2 1/1 Running 0 50m
nginx-3 1/1 Running 0 5m
nginx-4 1/1 Running 0 4m
nginx-4 1/1 Terminating 0 # 逆序删除(从最高序号开始)
nginx-4 0/1 Terminating 0
nginx-3 1/1 Terminating 0 # 下一个副本
...
3. 滚动更新(镜像升级)
# 方法1:直接编辑
kubectl edit statefulset nginx
# 修改 spec.template.spec.containers[0].image 为新版本
# 方法2:patch命令
kubectl patch statefulset nginx -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx","image":"nginx:1.25.4"}]}}}}'
# 观察更新过程
kubectl rollout status statefulset nginx
有序更新过程观察:
# 预期输出:
Waiting for partitioned roll out to finish: 2 out of 3 new pods have been updated...
nginx-2 1/1 Terminating 0 # 逆序更新(从最高序号开始)
nginx-2 0/1 Terminating 0
nginx-2 0/1 Pending 0
nginx-2 0/1 ContainerCreating 0
nginx-2 1/1 Running 5s # 新版本运行
nginx-1 1/1 Terminating 0 # 下一个副本
...
4. 查看历史版本和回滚
# 查看发布历史
kubectl rollout history statefulset nginx
# 回滚到上一个版本
kubectl rollout undo statefulset nginx
# 回滚到特定版本
kubectl rollout undo statefulset nginx --to-revision=2
5. 存储卷管理
# 查看关联的PVC
kubectl get pvc -l app=nginx
# 预期输出:
NAME STATUS VOLUME CAPACITY
www-nginx-0 Bound pvc-xxx 1Gi
www-nginx-1 Bound pvc-yyy 1Gi
www-nginx-2 Bound pvc-zzz 1Gi
下面是环境中实操的回显 可以结合上面的预期输出自己分析下
[root@master ~]# time kubectl get pods -l app=nginx -w
NAME READY STATUS RESTARTS AGE
nginx-0 0/1 Pending 0 0s
nginx-0 0/1 Pending 0 1s
nginx-0 0/1 ContainerCreating 0 1s
nginx-0 1/1 Running 0 5s
nginx-1 0/1 Pending 0 0s
nginx-1 0/1 Pending 0 1s
nginx-1 0/1 ContainerCreating 0 1s
nginx-1 1/1 Running 0 2s
nginx-2 0/1 Pending 0 0s
nginx-2 0/1 Pending 0 1s
nginx-2 0/1 ContainerCreating 0 1s
nginx-2 1/1 Running 0 3s
nginx-3 0/1 Pending 0 1s
nginx-3 0/1 Pending 0 2s
nginx-3 0/1 ContainerCreating 0 2s
nginx-3 1/1 Running 0 3s
nginx-4 0/1 Pending 0 0s
nginx-4 0/1 Pending 0 1s
nginx-4 0/1 ContainerCreating 0 1s
nginx-4 1/1 Running 0 3s
nginx-4 1/1 Terminating 0 24s
nginx-4 0/1 Terminating 0 25s
nginx-4 0/1 Terminating 0 25s
nginx-4 0/1 Terminating 0 25s
nginx-3 1/1 Terminating 0 28s
nginx-3 0/1 Terminating 0 28s
nginx-3 0/1 Terminating 0 28s
nginx-3 0/1 Terminating 0 28s
nginx-2 1/1 Terminating 0 49s
nginx-2 0/1 Terminating 0 49s
nginx-2 0/1 Terminating 0 50s
nginx-2 0/1 Terminating 0 50s
nginx-1 1/1 Terminating 0 104s
nginx-1 0/1 Terminating 0 104s
nginx-1 0/1 Terminating 0 104s
nginx-1 0/1 Terminating 0 104s
nginx-1 0/1 Pending 0 0s
nginx-1 0/1 Pending 0 0s
nginx-1 0/1 ContainerCreating 0 0s
nginx-1 1/1 Running 0 1s
nginx-0 1/1 Terminating 0 110s
nginx-0 0/1 Terminating 0 111s
nginx-0 0/1 Terminating 0 111s
nginx-0 0/1 Terminating 0 111s
nginx-0 0/1 Pending 0 0s
nginx-0 0/1 Pending 0 0s
nginx-0 0/1 ContainerCreating 0 0s
nginx-0 1/1 Running 0 1s
nginx-1 1/1 Terminating 0 3m30s
nginx-1 0/1 Terminating 0 3m30s
nginx-1 0/1 Terminating 0 3m31s
nginx-1 0/1 Terminating 0 3m31s
nginx-1 0/1 Pending 0 0s
nginx-1 0/1 Pending 0 0s
nginx-1 0/1 ContainerCreating 0 0s
nginx-1 1/1 Running 0 1s
nginx-0 1/1 Terminating 0 3m30s
nginx-0 0/1 Terminating 0 3m30s
nginx-0 0/1 Terminating 0 3m30s
nginx-0 0/1 Terminating 0 3m30s
nginx-0 0/1 Pending 0 0s
nginx-0 0/1 Pending 0 0s
nginx-0 0/1 ContainerCreating 0 0s
nginx-0 1/1 Running 0 3s
nginx-1 1/1 Terminating 0 34s
nginx-1 0/1 Terminating 0 34s
nginx-1 0/1 Terminating 0 34s
nginx-1 0/1 Terminating 0 34s
nginx-1 0/1 Pending 0 0s
nginx-1 0/1 Pending 0 0s
nginx-1 0/1 ContainerCreating 0 0s
nginx-1 1/1 Running 0 1s
nginx-0 1/1 Terminating 0 34s
nginx-0 0/1 Terminating 0 34s
nginx-0 0/1 Terminating 0 34s
nginx-0 0/1 Terminating 0 34s
nginx-0 0/1 Pending 0 0s
nginx-0 0/1 Pending 0 0s
nginx-0 0/1 ContainerCreating 0 0s
nginx-0 1/1 Running 0 1s
[root@master test]# ll
total 8
-rw-r--r-- 1 root root 2447 Oct 4 06:47 nginx-pvs.yaml
-rw-r--r-- 1 root root 846 Oct 4 02:03 nginx-statefulset.yaml
[root@master test]# kubectl apply -f ./
persistentvolume/nginx-pv-0 created
persistentvolume/nginx-pv-1 created
persistentvolume/nginx-pv-2 created
persistentvolume/nginx-pv-3 created
persistentvolume/nginx-pv-4 created
statefulset.apps/nginx created
[root@master test]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-nginx-0 Bound nginx-pv-0 1Gi RWO local-storage 19s
www-nginx-1 Bound nginx-pv-2 1Gi RWO local-storage 14s
www-nginx-2 Bound nginx-pv-1 1Gi RWO local-storage 12s
[root@master test]# kubectl scale statefulset nginx --replicas=5
statefulset.apps/nginx scaled
[root@master test]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-nginx-0 Bound nginx-pv-0 1Gi RWO local-storage 30s
www-nginx-1 Bound nginx-pv-2 1Gi RWO local-storage 25s
www-nginx-2 Bound nginx-pv-1 1Gi RWO local-storage 23s
www-nginx-3 Bound nginx-pv-4 1Gi RWO local-storage 2s
[root@master test]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-nginx-0 Bound nginx-pv-0 1Gi RWO local-storage 34s
www-nginx-1 Bound nginx-pv-2 1Gi RWO local-storage 29s
www-nginx-2 Bound nginx-pv-1 1Gi RWO local-storage 27s
www-nginx-3 Bound nginx-pv-4 1Gi RWO local-storage 6s
www-nginx-4 Bound nginx-pv-3 1Gi RWO local-storage 3s
[root@master test]# kubectl scale statefulset nginx --replicas=2
statefulset.apps/nginx scaled
[root@master test]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-nginx-0 Bound nginx-pv-0 1Gi RWO local-storage 59s
www-nginx-1 Bound nginx-pv-2 1Gi RWO local-storage 54s
www-nginx-2 Bound nginx-pv-1 1Gi RWO local-storage 52s
www-nginx-3 Bound nginx-pv-4 1Gi RWO local-storage 31s
www-nginx-4 Bound nginx-pv-3 1Gi RWO local-storage 28s
[root@master test]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-nginx-0 Bound nginx-pv-0 1Gi RWO local-storage 64s
www-nginx-1 Bound nginx-pv-2 1Gi RWO local-storage 59s
www-nginx-2 Bound nginx-pv-1 1Gi RWO local-storage 57s
www-nginx-3 Bound nginx-pv-4 1Gi RWO local-storage 36s
www-nginx-4 Bound nginx-pv-3 1Gi RWO local-storage 33s
[root@master test]# kubectl edit statefulset nginx
statefulset.apps/nginx edited
[root@master test]# kubectl rollout status statefulset nginx
partitioned roll out complete: 2 new pods have been updated...
[root@master test]# kubectl rollout history statefulset nginx
statefulset.apps/nginx
REVISION CHANGE-CAUSE
1 <none>
2 <none>
[root@master test]# kubectl describe statefulset nginx
Name: nginx
Namespace: default
CreationTimestamp: Sat, 04 Oct 2025 08:28:40 +0800
Selector: app=nginx
Labels: <none>
Annotations: <none>
Replicas: 2 desired | 2 total
Update Strategy: RollingUpdate
Partition: 0
Pods Status: 2 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/nginx:1.26
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts:
/usr/share/nginx/html from www (rw)
Volumes: <none>
Volume Claims:
Name: www
StorageClass: local-storage
Labels: <none>
Annotations: <none>
Capacity: 1Gi
Access Modes: [ReadWriteOnce]
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 2m53s statefulset-controller create Claim www-nginx-0 Pod nginx-0 in StatefulSet nginx success
Normal SuccessfulCreate 2m48s statefulset-controller create Claim www-nginx-1 Pod nginx-1 in StatefulSet nginx success
Normal SuccessfulCreate 2m46s statefulset-controller create Claim www-nginx-2 Pod nginx-2 in StatefulSet nginx success
Normal SuccessfulCreate 2m46s statefulset-controller create Pod nginx-2 in StatefulSet nginx successful
Normal SuccessfulCreate 2m25s statefulset-controller create Claim www-nginx-3 Pod nginx-3 in StatefulSet nginx success
Normal SuccessfulCreate 2m24s statefulset-controller create Pod nginx-3 in StatefulSet nginx successful
Normal SuccessfulCreate 2m22s statefulset-controller (combined from similar events): create Pod nginx-4 in StatefulSet nginx successful
Normal SuccessfulCreate 2m22s statefulset-controller create Claim www-nginx-4 Pod nginx-4 in StatefulSet nginx success
Normal SuccessfulDelete 118s statefulset-controller delete Pod nginx-4 in StatefulSet nginx successful
Normal SuccessfulDelete 117s statefulset-controller delete Pod nginx-3 in StatefulSet nginx successful
Normal SuccessfulDelete 117s statefulset-controller delete Pod nginx-2 in StatefulSet nginx successful
Normal SuccessfulCreate 64s (x2 over 2m48s) statefulset-controller create Pod nginx-1 in StatefulSet nginx successful
Normal SuccessfulDelete 64s statefulset-controller delete Pod nginx-1 in StatefulSet nginx successful
Normal SuccessfulDelete 63s statefulset-controller delete Pod nginx-0 in StatefulSet nginx successful
Normal SuccessfulCreate 62s (x2 over 2m53s) statefulset-controller create Pod nginx-0 in StatefulSet nginx successful
[root@master test]# kubectl describne pod nginx-1
error: unknown command "describne" for "kubectl"
Did you mean this?
describe
[root@master test]# kubectl describe pod nginx-1
Name: nginx-1
Namespace: default
Priority: 0
Service Account: default
Node: node2/192.168.124.130
Start Time: Sat, 04 Oct 2025 08:30:29 +0800
Labels: app=nginx
apps.kubernetes.io/pod-index=1
controller-revision-hash=nginx-5964c998fd
statefulset.kubernetes.io/pod-name=nginx-1
Annotations: <none>
Status: Running
IP: 10.244.2.38
IPs:
IP: 10.244.2.38
Controlled By: StatefulSet/nginx
Containers:
nginx:
Container ID: containerd://2c033c974bbf86943ba6164982b679a10659286cfcecde9488dfe44e5a53ebd3
Image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/nginx:1.26
Image ID: sha256:f87a52ac001c2a1e4db0aac4ccd0b1e79ee8e33e55245c323008f67de1cbe4c7
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Sat, 04 Oct 2025 08:30:30 +0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/usr/share/nginx/html from www (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-nd4fw (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
www:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: www-nginx-1
ReadOnly: false
kube-api-access-nd4fw:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node-role.kubernetes.io/control-plane:NoSchedule op=Exists
node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m1s default-scheduler Successfully assigned default/nginx-1 to node2
Normal Pulled 2m1s kubelet Container image "swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/nginx:1.26" already present on machine
Normal Created 2m1s kubelet Created container nginx
Normal Started 2m1s kubelet Started container nginx
[root@master test]# kubectl rollout undo statefulset nginx
statefulset.apps/nginx rolled back
[root@master test]# kubectl rollout history statefulset nginx
statefulset.apps/nginx
REVISION CHANGE-CAUSE
2 <none>
3 <none>
[root@master test]# kubectl rollout undo statefulset nginx --to-revision=2
statefulset.apps/nginx rolled back
[root@master test]#
三、高级操作技巧
1. 分区更新(金丝雀发布)
# 只更新索引 >= 2 的Pod(保留nginx-0, nginx-1不变)
kubectl patch statefulset nginx -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'
2. 并行更新(打破有序限制)
# 编辑StatefulSet添加:
spec:
podManagementPolicy: Parallel
3. 手动删除单个Pod(触发重建)
# 删除nginx-1(会自动重建)
kubectl delete pod nginx-1
# 观察重建过程(保持名称和存储不变)
kubectl get pods -l app=nginx -w
四、操作验证命令
-
检查Pod状态:
kubectl get pods -l app=nginx -o wide -
查看事件日志:
kubectl describe statefulset nginx -
验证存储持久性:
# 进入Pod写入测试文件 kubectl exec -it nginx-0 -- bash -c "echo 'test' > /usr/share/nginx/html/test.txt" # 删除Pod后验证文件是否存在 kubectl delete pod nginx-0 kubectl exec -it nginx-0 -- cat /usr/share/nginx/html/test.txt -
检查网络标识:
# 查看稳定的主机名 kubectl exec nginx-0 -- hostname # 输出:nginx-0
五、生产环境注意事项
-
扩缩容前:
- 确保存储资源充足(特别是云环境配额)
- 检查节点资源容量
-
更新时:
- 使用
kubectl rollout pause暂停更新 - 验证新版本正常后再继续
- 使用
-
缩容时:
- 有状态服务需确保数据已安全转移
- 重要数据卷不要自动删除(persistentVolumeReclaimPolicy: Retain)
-
监控指标:
# 关键指标: kubectl get sts -o custom-columns=NAME:.metadata.name,DESIRED:.spec.replicas,CURRENT:.status.currentReplicas,READY:.status.readyReplicas
通过以上操作,您可以充分利用 StatefulSet 的有序特性来管理有状态应用,同时确保数据的一致性和服务的稳定性。
更多推荐


所有评论(0)