深入理解 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 调度器使用的关键插件主要有四种方法:

  1. 查看调度器 ConfigMap - 最直接的方法,显示完整配置
  2. 检查调度器启动参数 - 适用于命令行参数配置
  3. 分析调度器日志 - 需要提高日志级别
  4. 使用调度器 API - 通过 /configz 端点查询

关键插件包括:

  • 预选插件:NodeResourcesFit, VolumeBinding
  • 优选插件:NodeResourcesBalancedAllocation, NodeResourcesLeastAllocated
  • 绑定插件:DefaultBinder

了解这些插件及其权重可以帮助您:

  1. 优化集群资源利用率
  2. 排查 Pod 调度失败问题
  3. 定制调度策略以满足特定需求
  4. 理解调度决策背后的逻辑

但是目前我们的环境启动参数没有相关配置 也没使用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 调度器配置中 profilespluginConfig 等关键字段的详细解释:

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表示自动计算需评估的节点比例

调度流程示意

NodeResourcesFit
VolumeBinding
TaintToleration *3
NodeAffinity *2
PodTopologySpread *2
Pod入队
过滤阶段
资源检查
存储可用性
打分阶段
污点容忍
节点亲和
拓扑分布
综合得分
绑定节点

总结设计思想

  1. 模块化插件:每个调度功能独立可配置(如亲和性/卷绑定)
  2. 策略可定制:通过权重系统调整调度优先级
  3. 资源优化parallelism=16 和自动节点评估比例提升调度吞吐量
  4. 生产就绪: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]#

关键发现

  1. 有序绑定特性:StatefulSet 按顺序创建 Pod(nginx-0 → nginx-1 → nginx-2)
  2. 存储感知调度:调度器优先考虑有匹配 PV 的节点
  3. 资源优化:当多个节点满足条件时,选择资源最空闲的节点
  4. 网络稳定性:每个 Pod 获得稳定的网络标识(nginx-0.nginx, nginx-1.nginx 等)

故障排查技巧

常见问题解决

  1. PV 无法绑定

    # 检查 PV 节点亲和性
    kubectl get pv nginx-pv-0 -o jsonpath='{.spec.nodeAffinity}'
    
    # 检查节点标签
    kubectl get node --show-labels
    
  2. Pod 调度失败

    # 查看 Pod 事件
    kubectl describe pod nginx-0
    
    # 检查节点资源
    kubectl describe node node1
    kubectl top nodes
    
  3. 存储类问题

    # 验证存储类配置
    kubectl get storageclass local-storage -o yaml
    

最佳实践建议

  1. 存储规划:提前规划 PV 与节点的对应关系
  2. 资源预留:为系统组件预留足够的 CPU 和内存资源
  3. 监控告警:设置 PVC 绑定失败和 Pod 调度失败的告警
  4. 备份策略:本地 PV 需要额外的数据备份方案
  5. 扩展性考虑:预留 PV 容量以支持 StatefulSet 扩容

结论

通过本次实践,我们深入理解了 Kubernetes StatefulSet 与本地 PV 的协同工作机制。关键收获包括:

  1. StatefulSet 提供了有状态应用所需的所有特性:有序部署、稳定网络标识、持久化存储
  2. 本地 PV 需要精细的节点规划:通过节点亲和性确保数据本地性
  3. 调度器日志是理解调度决策的宝贵工具:通过调整日志级别可以观察完整的调度过程
  4. 存储感知调度确保数据一致性:调度器会优先考虑存储约束条件

这种深入的理解有助于在生产环境中更好地设计和运维有状态的 Kubernetes 工作负载。

statefulset相关操作实践

以下是 StatefulSet 的常用操作指南,包括扩缩容、更新等实际操作步骤,以及如何观察其有序过程:

一、StatefulSet 核心特性回顾

  1. 有序性:Pod 按顺序创建/删除(从 0 到 N-1)
  2. 稳定标识:Pod 名称、主机名、存储卷保持不变
  3. 滚动更新:逆序更新(从最大序号开始)

二、常用操作指南

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

四、操作验证命令

  1. 检查Pod状态

    kubectl get pods -l app=nginx -o wide
    
  2. 查看事件日志

    kubectl describe statefulset nginx
    
  3. 验证存储持久性

    # 进入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
    
  4. 检查网络标识

    # 查看稳定的主机名
    kubectl exec nginx-0 -- hostname
    # 输出:nginx-0
    

五、生产环境注意事项

  1. 扩缩容前

    • 确保存储资源充足(特别是云环境配额)
    • 检查节点资源容量
  2. 更新时

    • 使用 kubectl rollout pause 暂停更新
    • 验证新版本正常后再继续
  3. 缩容时

    • 有状态服务需确保数据已安全转移
    • 重要数据卷不要自动删除(persistentVolumeReclaimPolicy: Retain)
  4. 监控指标

    # 关键指标:
    kubectl get sts -o custom-columns=NAME:.metadata.name,DESIRED:.spec.replicas,CURRENT:.status.currentReplicas,READY:.status.readyReplicas
    

通过以上操作,您可以充分利用 StatefulSet 的有序特性来管理有状态应用,同时确保数据的一致性和服务的稳定性。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐