阅读视图

发现新文章,点击刷新页面。
🔲 ☆

Kubernetes PV数据卷缩容方案及统计PV容量

在 Kubernetes 中,直接对 Persistent Volume (PV) 进行容量缩容(减小容量)通常是不被支持的。这主要是出于数据安全的考虑,因为贸然缩小底层存储设备可能会破坏数据。

理解 PV 缩容的限制

Kubernetes 及其存储生态系统在设计上就偏向于扩容,对缩容则有严格限制,主要原因如下:

  • 数据安全风险:缩容操作可能导致数据被截断或丢失。如果新的容量小于已存储的数据量,后果不堪设想。

  • 底层存储限制:许多底层存储系统(如 AWS EBS、Longhorn 等)并不支持在线缩小卷容量。例如,LVM 的 pvresize 命令也会拒绝缩小已分配空间的物理卷。

  • Kubernetes 机制限制:Kubernetes 的 allowVolumeExpansion 配置项仅控制扩容,并不会开启缩容功能。

替代方案与变通方法

虽然不能直接缩容,但是可以考虑以下替代方案来管理存储资源:

方案核心步骤优点注意事项
重建 PV 和 PVC1. 备份数据
2. 创建新的、容量更小的 PV 和 PVC。
3. 将数据从旧 PV 迁移到新 PV。
4. 更新 Pod 配置以使用新 PVC,并删除旧资源。
可靠且通用,适用于大多数不支持直接缩容的场景。过程繁琐,涉及业务中断数据迁移
调整存储配额通过 Kubernetes 的 ResourceQuota 来限制命名空间未来的存储请求总量,而不是缩小已有卷。无数据风险,有效预防未来存储资源的过度使用。无法释放已分配的存储空间
利用存储供应商特性某些云厂商的存储服务(如 NetApp Trident)可能通过精简配置(Thin Provisioning)等方式,在逻辑上限制容量使用,而无需物理缩容高效利用存储资源依赖特定存储后端,并非普适性解决方案。

总而言之,在 Kubernetes 中直接对 PV 进行缩容操作是不可行的。当需要减少 PV 容量时,数据迁移和PV/PVC重建是目前最主流和安全的做法。通过创建一个容量更小的新PV,然后迁移数据,可以实现存储空间的"缩容"。这个过程的核心步骤包括:准备新PV、迁移数据、更新应用指向新存储,以及清理旧资源。

为了更安全地进行操作,请务必先留意以下关键风险点和前提:

关键项目重要说明
数据备份务必先备份原始数据,以防迁移过程中发生意外丢失。
业务中断数据迁移通常需要停止相关Pod,请规划在业务低峰期进行。
存储类型确保Kubernetes集群和存储系统支持动态供应或静态PV创建。
应用兼容性确保应用能够适应存储卷的切换。

详细操作步骤

1、确认当前PV/PVC状态并备份数据
执行以下命令,记录当前PV(Persistent Volume)和PVC(Persistent Volume Claim)的详细信息,特别是当前的存储容量和访问模式。

kubectl get pv <old-pv-name> -o yaml
kubectl get pvc <old-pvc-name> -n <namespace> -o yaml

务必确保已经对重要数据进行了可靠备份

2、停止使用旧存储的Pod
为了避免数据不一致,需要先停止所有正在使用该PVC的Pod。具体的操作方法取决于工作负载类型:

    • 如果是Deployment,可以将其副本数缩容到0:kubectl scale deployment <deployment-name> --replicas=0 -n <namespace>

    • 如果是StatefulSet,同样缩容到0:kubectl scale statefulset <statefulset-name> --replicas=0 -n <namespace>

    • 如果是直接创建的Pod,则直接删除:kubectl delete pod <pod-name> -n <namespace>

    3、创建新的、容量更小的PV
    根据存储供应方式,选择静态或者动态方式创建新PV。

    • 静态供应:编写新的PV YAML文件,注意capacity.storage字段设置为目标缩容容量。

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: new-small-pv  # 给新PV起个名字
    spec:
      capacity:
        storage: 5Gi  # 这里设置你想要的、更小的容量
      accessModes:
        - ReadWriteOnce  # 根据你的需求设置访问模式
      persistentVolumeReclaimPolicy: Retain  # 建议先设置为Retain
      storageClassName: manual  # 指定存储类
      # ... 其他必要字段根据你的存储系统来定

    然后应用这个YAML文件:kubectl apply -f new-pv.yaml

    • 动态供应:创建一个新的PVC,其spec.resources.requests.storage字段指定为缩容后的容量,并确保其绑定的StorageClass能正确工作。Kubernetes会自动为其创建并绑定一个符合要求的PV。

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: new-small-pvc
      namespace: your-namespace
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 5Gi  # 指定缩容后的容量
      storageClassName: standard  # 指定能动态创建PV的StorageClass

    应用这个YAML文件:kubectl apply -f new-pvc.yaml

    4、将数据从旧PV迁移到新PV
    这是最关键的一步。常见的方法有:

      • 使用kubectl cp命令:如果PV支持文件系统模式,可以启动一个临时的Pod挂载旧PVC,将数据拷贝到本地,然后另一个临时Pod挂载新PVC,再将数据从本地拷贝过去。

      • 使用rsync工具:对于大量数据,rsync效率更高。可能需要启动一个包含rsync的临时Pod来完成。

      • 存储系统快照/克隆:部分高级存储系统(如Longhorn, Portworx)自身支持卷的快照和克隆功能,可以利用这些功能来加速数据迁移。

      5、修改Pod配置,指向新的PV/PVC
      数据迁移并验证无误后,更新Pod配置模板(例如Deployment或StatefulSet的YAML文件),将其volumes部分指向新的PVC名称

      volumes:
      - name: my-storage
        persistentVolumeClaim:
          claimName: new-small-pvc  # 这里改为新PVC的名字

      然后应用这个更新:kubectl apply -f your-app.yaml

      6、重启应用Pod
      根据第二步中停止Pod的方式,重新启动应用。

        • 如果是Deployment,将副本数扩展回目标数量:kubectl scale deployment <deployment-name> --replicas=<target-replicas> -n <namespace>

        • 如果是StatefulSet,同样操作:kubectl scale statefulset <statefulset-name> --replicas=<target-replicas> -n <namespace>

        • 如果是直接创建的Pod,则重新创建。

        7、验证应用和数据
        密切观察新Pod的启动日志和行为,确认应用能够正常访问新PV上的数据,并且功能符合预期。
        可以再次使用kubectl get pvkubectl get pvc命令,确认新的PVC new-small-pvc已经处于Bound状态,并且绑定到了正确的PV上。

        8、谨慎清理旧PV/PVC
        在确认新环境稳定运行一段时间(例如24小时)后,并且确定不再需要旧数据时,再考虑清理旧的PV和PVC。
        注意:清理操作是不可逆的,请再次确认数据安全。

        kubectl delete pvc <old-pvc-name> -n <namespace>
        kubectl delete pv <old-pv-name>

        补充建议

        • 预防胜于治疗:在Kubernetes命名空间中,可以通过创建ResourceQuotaLimitRange资源来预防单个PVC请求过大的存储空间,从源头控制存储使用。

        • 理解回收策略:在删除旧PV时,务必了解其persistentVolumeReclaimPolicy(回收策略)。设置为Retain时,删除PVC后PV及相关后端存储资源会被保留;设置为Delete时,则可能会被自动清理。


        K8s统计PV实际使用量

        在PV卷缩容替代方案实施前,我们通常会统计Kubernetes集群中PV的总使用量,需要明白一点:kubectl get pv 命令本身无法直接提供PV的实际使用量,它主要显示的是PV的配置容量和状态信息。要获取PV的实际使用量,还需要借助其他方法。下面梳理几种可行的方案。

        快速查看PV使用情况

        如果想快速查看单个PV或PVC的实时使用情况,可以尝试以下方法:

        方法操作说明
        进入Pod查看1. 找到挂载目标PV的Pod:kubectl get pods -o wide --all-namespaces
        2. 进入Pod并查看挂载点使用情况:kubectl exec -it <pod_name> -- df -h 或 `kubectl exec -it <pod_name> -- df -h
        grep <mount_point>`优点:简单直接,无需额外工具。
        局限:需要Pod处于运行状态,且只能查看单个Pod挂载的PV情况。
        使用第三方工具安装 pvcpie 工具:pip install pvcpie,然后执行 pvcpie 命令一个专门用于获取集群中PVC使用信息的Python工具。

        搭建监控系统获取全面数据

        对于生产环境或需要长期、全面监控的场景,建议搭建监控系统:

        1.部署Metrics Server与监控工具

          • 确保集群已安装 Metrics Server

          • 部署 Prometheus 来收集和存储指标数据

          • 部署 Grafana 进行数据可视化

          2.利用Prometheus指标查询
          Prometheus可以收集PV的实际使用量指标,例如 kubelet_volume_stats_used_bytes (卷中已使用的字节数) 和 kubelet_volume_stats_capacity_bytes (卷的总容量字节数)。可以使用PromQL查询语言进行统计。例如,查询所有PV的已用空间和容量:

          kubelet_volume_stats_used_bytes
          kubelet_volume_stats_capacity_bytes

          3.配置Grafana仪表板

          在Grafana中,可以基于Prometheus的数据源创建仪表板,通过图表直观展示PV的总使用量、使用率趋势等。

          云平台或特定发行版的集成监控

          如果使用的是特定云平台的Kubernetes服务(如Azure AKS)或特定发行版(如Red Hat OpenShift),它们通常提供了集成的监控方案:

          • Azure AKS:Azure Monitor的容器见解功能可以自动收集PV使用情况指标(如 pvUsedBytes),并提供了预配置的工作簿进行查看。

          • Red Hat OpenShift:集成了Prometheus,可以直接使用相关的PV监控指标

          最佳实践与建议

          • 明确需求:根据需求(临时检查 vs. 长期监控)选择合适的方法。

          • 数据准确性:请注意,某些监控工具(如 kubectl top)提供的指标可能专为Kubernetes自动扩缩容决策优化,与操作系统工具(如 top)的结果可能不完全一致。

          • 配置告警:在监控系统基础上,为PV使用率配置告警,以便在空间不足时及时收到通知。

          统计所有PV的配置容量总量,用kubectl get pv命令结合一些简单的文本处理工具就能做到。下面是一个汇总表格,列出了几种常用的方法:

          方法命令特点
          JSONPath提取kubectl get pv -o jsonpath='{.items[*].spec.capacity.storage}'直接提取容量值,但结果在同一行,需后续处理
          JSONPath循环kubectl get pv -o jsonpath='{range .items[*]}{.spec.capacity.storage}{"\n"}{end}'每个PV容量单独一行,方便后续处理
          自定义列kubectl get pv -o custom-columns=NAME:.metadata.name,CAPACITY:.spec.capacity.storage输出表格,直观显示每个PV的容量
          自定义列(仅容量)kubectl get pv -o custom-columns=CAPACITY:.spec.capacity.storage --no-headers仅输出容量列,无表头,适合直接处理

          重要提醒

          • 配置容量 vs 实际使用量kubectl get pv命令显示的是PV的配置容量(申请或初始设置的大小),而不是当前的实际数据使用量。要查看实际使用量,通常需要进入Pod内部使用df -h等命令,或借助集群监控系统(如Prometheus)。

          • 单位一致性:确保所有PV的容量单位一致(例如,都是Gi或都是Mi),或者在处理时进行了适当的单位转换,否则求和结果可能不准确。

          • 动态供应PV:对于通过StorageClass动态供应的PV,其配置容量通常由对应的PersistentVolumeClaim (PVC) 指定。

          K8s统计PV配置容量

          当PV的容量单位不一致时(比如同时存在Gi、Ki、Mi等),需要先统一单位再累加。以下是几种处理这种情况的方法:

          方法一:使用awk进行单位转换和累加

          kubectl get pv -o custom-columns=CAPACITY:.spec.capacity.storage --no-headers | awk '
          {
              # 提取数字和单位
              if ($1 ~ /Gi$/) {
                  gsub(/Gi/, "", $1)
                  sum += $1 * 1024 * 1024  # Gi → Ki
              }
              else if ($1 ~ /Mi$/) {
                  gsub(/Mi/, "", $1)
                  sum += $1 * 1024  # Mi → Ki
              }
              else if ($1 ~ /Ki$/) {
                  gsub(/Ki/, "", $1)
                  sum += $1  # Ki保持不变
              }
              else if ($1 ~ /G$/) {
                  gsub(/G/, "", $1)
                  sum += $1 * 1000 * 1000  # G → K (十进制)
              }
              else if ($1 ~ /M$/) {
                  gsub(/M/, "", $1)
                  sum += $1 * 1000  # M → K (十进制)
              }
              else if ($1 ~ /K$/) {
                  gsub(/K/, "", $1)
                  sum += $1  # K保持不变
              }
              else {
                  # 没有单位,假设是字节
                  sum += $1 / 1024  # 字节 → Ki
              }
          }
          END {
              # 输出结果,可以选择合适的单位
              if (sum >= 1024*1024*1024) {
                  printf "总容量: %.2f Ti\n", sum / (1024*1024*1024)
              } else if (sum >= 1024*1024) {
                  printf "总容量: %.2f Gi\n", sum / (1024*1024)
              } else if (sum >= 1024) {
                  printf "总容量: %.2f Mi\n", sum / 1024
              } else {
                  printf "总容量: %.2f Ki\n", sum
              }
          }'

          方法二:使用jq进行更精确的处理

          kubectl get pv -o json | jq '
          [.items[].spec.capacity.storage | 
           capture("(?<value>[0-9.]+)(?<unit>[A-Za-z]+)") |
           {
             value: (.value | tonumber),
             factor: (if .unit == "Ti" then 1024*1024*1024*1024
                     elif .unit == "Gi" then 1024*1024*1024
                     elif .unit == "Mi" then 1024*1024
                     elif .unit == "Ki" then 1024
                     elif .unit == "T" then 1000*1000*1000*1000
                     elif .unit == "G" then 1000*1000*1000
                     elif .unit == "M" then 1000*1000
                     elif .unit == "K" then 1000
                     else 1 end)
           } | .value * .factor] | add' | awk '
          {
              total_ki = $1 / 1024  # 转换为KiB基数
              if (total_ki >= 1024*1024*1024) {
                  printf "总容量: %.2f Ti\n", total_ki / (1024*1024*1024)
              } else if (total_ki >= 1024*1024) {
                  printf "总容量: %.2f Gi\n", total_ki / (1024*1024)
              } else if (total_ki >= 1024) {
                  printf "总容量: %.2f Mi\n", total_ki / 1024
              } else {
                  printf "总容量: %.2f Ki\n", total_ki
              }
          }'

          方法三:简化的单位转换脚本

          如果经常需要这个功能,可以创建一个可重用的脚本:

          #!/bin/bash
          # pv-total-capacity.sh
          
          calculate_pv_capacity() {
              kubectl get pv -o custom-columns=CAPACITY:.spec.capacity.storage --no-headers | awk '
              function to_kib(value, unit) {
                  switch(unit) {
                      case "Ti": return value * 1024 * 1024 * 1024 * 1024
                      case "Gi": return value * 1024 * 1024 * 1024
                      case "Mi": return value * 1024 * 1024
                      case "Ki": return value * 1024
                      case "T": return value * 1000 * 1000 * 1000 * 1000
                      case "G": return value * 1000 * 1000 * 1000
                      case "M": return value * 1000 * 1000
                      case "K": return value * 1000
                      default: return value  # 假设已经是字节
                  }
              }
              
              {
                  if (match($0, /([0-9.]+)([A-Za-z]*)/, parts)) {
                      value = parts[1]
                      unit = parts[2]
                      sum_kib += to_kib(value, unit) / 1024
                  }
              }
              END {
                  # 输出各种单位的表示
                  printf "总容量统计:\n"
                  printf "  %d Bytes\n", sum_kib * 1024
                  printf "  %.2f KiB\n", sum_kib
                  printf "  %.2f MiB\n", sum_kib / 1024
                  printf "  %.2f GiB\n", sum_kib / (1024*1024)
                  printf "  %.2f TiB\n", sum_kib / (1024*1024*1024)
              }'
          }
          
          calculate_pv_capacity

          使用建议

          1. 推荐使用方法一的awk脚本,它简单且功能完备

          2. 如果环境中有jq工具,方法二可以提供更精确的解析

          3. 对于生产环境,建议将方法三保存为脚本文件,方便重复使用

          这些方法都能正确处理混合单位的情况,并输出易于理解的总容量统计结果。可以根据实际需求选择最适合的方法。

          参考:Kubernetes 环境下华为云盘与 PV 数据目录缩容方案分析与实施

          🔲 ☆

          K8s HPA原理及最佳实践

          在Kubernetes整个体系中,弹性伸缩是至关重要的功能,其分为两种:水平弹性伸缩(Horizontal Pod Autoscaling,简称HPA)垂直弹性伸缩(Vertical Pod Autoscaler,简称VPA)。HPA可以根据观察到的资源实际使用情况(如CPU/内存)或其它自定义指标负载自动调整Pod副本数,VPA可以根据资源实际使用情况为其Pod中的容器设置最新的资源配额requests。这两者合理使用既可以满足业务对实例数的要求保证系统稳定性,同时又能充分提升集群资源的利用率。

          1758179126855162.png

          一、HPA 原理

          HPA (Horizontal Pod Autoscaler) 的核心原理可以概括为:定期查询监控指标,然后根据其目标值(Target Value) 和当前值(Current Value),通过特定的算法计算出所需的 Pod 副本数量,自动控制 Deployment、StatefulSet 等资源的副本数量,以使应用的平均资源利用率(如 CPU、内存)维持在你设定的目标值

          1758176659278527.png

          下面是图中涉及的核心组件及其作用的文字说明:

          • HPA Controller:HPA 的“决策大脑”,是 kube-controller-manager 的一部分。它负责[定期循环](默认每隔 15秒)通过 API Server 查询其管理的所有 HPA 对象及其对应的监控指标,并根据指标数据计算是否需要扩缩容以及需要多少个 Pod 副本

          • API Server:Kubernetes 的“信息枢纽”。HPA Controller 通过它查询 HPA 配置、获取监控指标,也是通过它来更新工作负载的副本数量。

          • Metrics API指标数据的标准化接口。HPA Controller 只通过统一的 Metrics API 来获取指标数据,而不关心数据的具体来源。这层抽象使得 HPA 可以灵活地使用不同种类的指标。

            • resource.metrics.k8s.io:提供资源指标,如 Pod 的 CPU 和内存使用率,通常由 Metrics Server 实现

            • custom.metrics.k8s.io:提供自定义指标,这些指标通常与特定应用或 Kubernetes 对象(如 Pod 的 QPS)相关。常用 Prometheus Adapter 来从 Prometheus 中获取并暴露这些指标

            • external.metrics.k8s.io:提供外部指标,这些指标完全来自于 Kubernetes 集群之外的系统,如消息队列的长度、云服务的监控指标等。

          • Metrics Server:一个集群范围内的资源使用数据聚合器。它从每个节点上的 kubelet 收集核心资源指标(如 CPU 和内存),并通过 resource.metrics.k8s.io API 暴露它们,供 HPA 等组件使用。

          • Prometheus + Prometheus Adapter:一个常见的自定义指标方案Prometheus 负责采集和存储丰富的监控数据。Prometheus Adapter 则作为一个桥梁,查询 Prometheus 中的数据,并将其转换为 Kubernetes 能够理解的格式,通过 custom.metrics.k8s.io API 暴露给 HPA Controller

          HPA 的工作流程是一个持续的控制循环,上图的数字编号也大致对应了这些步骤:

          1. 查询 HPA 配置:HPA Controller 定期(默认 15s,可通过 --horizontal-pod-autoscaler-sync-period 参数调整)通过 API Server 查询所有 HPA 对象的配置定义,包括目标工作负载、目标指标类型及其目标值、最小/最大副本数等。

          2. 获取监控指标:对于每个 HPA,Controller 通过对应的 Metrics API(资源、自定义或外部)来获取当前的监控指标值。

          • 源指标(如 CPU、内存)由 Metrics Server 提供。Metrics Server 通过 Kubelet 从每个节点的 cAdvisor 中采集所有 Pod 的资源使用数据,并聚合到集群层面。

            1. metrics.k8s.io: 主要提供Pod和Node的CPU和Memory相关的监控指标。

            2. custom.metrics.k8s.io: 主要提供Kubernetes Object相关的自定义监控指标。

            3. external.metrics.k8s.io:指标来源外部,与任何的Kubernetes资源的指标无关。

          • 自定义指标(如 QPS、应用内部指标)由 Kubernetes Custom Metrics API 提供,这通常需要安装像 Prometheus Adapter 这样的组件来将 Prometheus 等监控系统的指标转换为 Kubernetes API 可以理解的格式。

        1. 计算所需副本数:Controller 使用获取到的当前指标值和 HPA 中定义的目标指标值,通过算法计算出期望的 Pod 副本数量。

          • 计算公式期望副本数 = ceil[当前副本数 * (当前指标值 / 目标指标值)]

            例如,当前有 2 个 Pod,CPU 使用率为 80%,HPA 目标 CPU 使用率为 40%,则期望副本数 = ceil[2 * (80 / 40)] = ceil[4] = 4。HPA 会考虑多种指标(选择计算结果中最大的副本数)并内置了容忍度(默认 0.1,即比率变化不超过 10% 则忽略)和冷却窗口等机制来防止副本数抖动

        2. 调整副本数:如果计算出的期望副本数与当前副本数不同,HPA Controller 会通过 API Server更新目标工作负载(如 Deployment)的 replicas 字段,为了避免副本数量因指标瞬时波动而剧烈变化(“抖动”),HPA 引入了冷却机制

          • 扩容:如果计算出的 期望副本数 > 当前副本数,HPA 就会增加副本数。

            扩容冷却 (默认 3 分钟):扩容操作没有全局等待期,但快速连续扩容时,每次扩容后等待时间会逐渐增加。

          • 缩容:如果计算出的 期望副本数 < 当前副本数,HPA 就会减少副本数。

            缩容冷却 (--horizontal-pod-autoscaler-downscale-stabilization,默认 5 分钟):在一次缩容操作后,会等待一段时间(默认5分钟),再执行下一次缩容操作。

        3. 工作负载控制器响应:相应的工作负载控制器(如 Deployment Controller)检测到 replicas 字段变化,会开始创建删除 Pod,以使实际运行的 Pod 数量等于期望值。

        4. 调度新 Pod:新创建的 Pod 会被 Scheduler 调度到合适的节点上运行。如果节点资源不足,这些 Pod 会处于 Pending 状态。

        5. 节点扩缩容(可选):如果集群中使用了 Cluster Autoscaler (CA),它会检测到因资源不足而无法调度的 Pod(Pending状态),然后自动扩容集群节点来提供更多资源。Pod 被成功调度和运行后,才能开始处理业务流量,此时指标数据也会随之变化,HPA 的下一个循环周期又会开始。

        6. 在实际应用中,基于原生HPA系统架构实现适配业务自定义特性的整体设计架构如下:

          171a62d0e6002878~tplv-t2oaga2asx-jj-mark_3024_0_0_0_q75.png

          二、基于 CPU 和内存的最佳实践示例

          前提条件

          1. 安装 Metrics Server

          • HPA 需要 Metrics Server 来获取 CPU/内存指标。

          • 安装命令(对于大多数集群):

          kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
          • 验证安装:kubectl top nodes

        7. 为 Pod 设置资源请求

          • 这是最关键的一步! HPA 计算使用率的公式是:(Pod 当前实际使用量 / Pod 的资源请求值) * 100%

          • 如果 Pod 没有设置 spec.containers[].resources.requests,HPA 将无法进行有意义的计算。

          示例:为 Nginx Deployment 配置同时基于 CPU 和内存的 HPA

          1. 创建 Deployment (nginx-deployment.yaml)

          这个 Deployment 明确设置了 CPU 和内存的 requests

          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: nginx-deployment
            labels:
              app: nginx
          spec:
            replicas: 3 # 初始副本数
            selector:
              matchLabels:
                app: nginx
            template:
              metadata:
                labels:
                  app: nginx
              spec:
                containers:
                - name: nginx
                  image: nginx:1.25
                  ports:
                  - containerPort: 80
                  resources:
                    requests:
                      cpu: "250m"   # 每个 Pod 请求 0.25 核 CPU
                      memory: "256Mi" # 每个 Pod 请求 256MiB 内存
                    limits:
                      cpu: "500m"
                      memory: "512Mi"

          应用它:kubectl apply -f nginx-deployment.yaml

          2. 创建 HPA (nginx-hpa.yaml)

          我们使用 autoscaling/v2 API,它支持多指标和更丰富的配置。

          apiVersion: autoscaling/v2
          kind: HorizontalPodAutoscaler
          metadata:
            name: nginx-hpa
          spec:
            scaleTargetRef:
              apiVersion: apps/v1
              kind: Deployment
              name: nginx-deployment
            minReplicas: 2   # 最小副本数
            maxReplicas: 10  # 最大副本数
            metrics:
            - type: Resource
              resource:
                name: cpu
                target:
                  type: Utilization # 目标类型为利用率
                  averageUtilization: 50 # CPU 平均使用率目标为 50%
            - type: Resource
              resource:
                name: memory
                target:
                  type: Utilization # 目标类型为利用率
                  averageUtilization: 70 # 内存平均使用率目标为 70%
            behavior: # (可选) 高级伸缩行为控制
              scaleDown:
                stabilizationWindowSeconds: 300 # 缩容冷却期 300 秒 (5分钟)
                policies:
                - type: Pods
                  value: 2
                  periodSeconds: 60
              scaleUp:
                stabilizationWindowSeconds: 0
                policies:
                - type: Pods
                  value: 2
                  periodSeconds: 60

          应用它:kubectl apply -f nginx-hpa.yaml

          HPA 行为解释

          • CPU 规则:HPA 会努力使所有 Pod 的 CPU 平均使用率 维持在 50%

            • 如果平均使用率是 75%,期望副本数 = ceil[3 * (75 / 50)] = ceil[3 * 1.5] = ceil[4.5] = 5。HPA 会将副本数扩容到 5。

          • 内存规则:HPA 会努力使所有 Pod 的 内存平均使用率 维持在 70%

            • 如果平均使用率是 85%,期望副本数 = ceil[3 * (85 / 70)] = ceil[3 * 1.21] = ceil[3.63] = 4

          • 多指标决策:当同时配置了多个指标时,HPA 会分别计算每个指标所需的副本数,然后选择其中最大的那个值

            • 在上面的例子中,CPU 算出来需要 5 个,内存算出来需要 4 个,那么 HPA 最终会将副本数扩容到 5。

          验证与监控

          1.查看 HPA 状态

          kubectl get hpa nginx-hpa -w

          输出会显示当前的指标值、目标值、最小/最大副本数和当前的副本数。

          NAME         REFERENCE                       TARGETS             MINPODS   MAXPODS   REPLICAS   AGE
          nginx-hpa    Deployment/nginx-deployment    50%/70%, 45%/65%    2         10        3          5m
          # TARGETS 列显示为 CPU目标%/内存目标%,当前CPU%/当前内存%

          2.查看 HPA 详细信息

          kubectl describe hpa nginx-hpa

          这个命令会输出非常详细的信息,包括事件日志,帮助你诊断 HPA 为什么不伸缩。

          三、最佳实践总结

          1. 必须设置 resources.requests:没有它,HPA 无法工作。

          2. 使用 autoscaling/v2 APIv2 比 v2beta2 更稳定,且功能强大。

          3. 谨慎设置目标值

          • 目标值设置过高(如 CPU 90%)可能导致 Pod 在触发扩容前就已过载。

          • 目标值设置过低(如 CPU 10%)可能导致资源浪费和过多的副本。

          • 建议:从保守值开始(如 CPU 50-70%),根据实际生产监控数据进行调整。

        8. 合理配置 minReplicas 和 maxReplicas

          • minReplicas 应保证应用的基本可用性。

          • maxReplicas 应避免因意外流量或程序 Bug 导致资源耗尽。

        9. 利用 behavior 字段控制伸缩速率和冷却:根据应用的启动和冷却特性,调整 scaleUp 和 scaleDown 策略,避免抖动。

        10. 结合就绪探针和存活探针:确保新扩容的 Pod 真正准备好接收流量后,才被纳入服务的负载均衡。

        11. 结合日志与事件:注意容忍度、冷却窗口对扩缩容的影响,关注 ScalingActive、ScalingLimited 等状态变化。

        12. 考虑使用自定义指标:对于 Web 服务,基于 HTTP QPS(每秒请求数)进行扩缩容通常比基于 CPU 更直接、更灵敏。这需要安装 Prometheus 和 Prometheus Adapter。

        13. 让应用支持平滑启动和终止:确保新 Pod 启动后能快速就绪(利用就绪探针),并在终止时能优雅处理完已有请求(处理 SIGTERM 信号),避免服务中断。

        14. 四、关于 HPA 的进阶思考

          1. HPA 的局限性及与其他自动伸缩方案的搭配

          HPA 主要负责 Pod 水平扩缩容,但它依赖于集群有足够的资源来调度新创建的 Pod。如果集群资源不足,即使 HPA 创建了新的 Pod,这些 Pod 也会因无法调度而处于 Pending 状态,无法提供服务。

          因此,在生产环境中,HPA 通常需要与 Cluster Autoscaler (CA) 或 Karpenter 搭配使用

          • HPA: 负责应用层伸缩,根据业务负载调整 Pod 数量。

          • CA/Karpenter: 负责基础设施层伸缩,根据 Pod 的资源请求调整集群节点数量,为 HPA 创建的 Pod 提供运行资源

          2. 克服“弹性滞后”:AHPA 与预测性伸缩

          传统 HPA 基于当前的监控指标进行反应式扩缩容,这存在一定的“弹性滞后”(Reactive Scaling)。即从流量增加到 HPA 完成扩容需要一定时间(指标采集、计算、Pod 启动、应用预热等),可能导致流量高峰时应用响应变慢

          为解决这个问题,出现了预测性弹性伸缩(Predictive Scaling)方案,如 Advanced Horizontal Pod Autoscaler (AHPA)。AHPA 能够分析历史指标数据(如过去几天的 CPU 负载、QPS 变化规律),预测未来的负载波动,并提前进行扩容。例如,对于每天早高峰流量明显增大的应用,AHPA 可以在高峰来临前就提前扩容,避免流量激增时的响应延迟。在业务低谷时,它也会定时回收资源,节约成本


          参考:

          🔲 ☆

          K8s集群etcd磁盘更换

          在 Kubernetes 生产环境中更换 etcd 节点的磁盘是一个高风险操作,需谨慎执行。什么情况需要更换磁盘?

          • 磁盘性能不足,如机械盘升级到 SSD

          • 磁盘硬件存在问题,影响稳定性

          ETCD 对磁盘性能要求非常敏感,强烈建议使用 SSD 且独立挂盘。如果有更高性能要求,可以选择分离 snapshot(快照文件)/wal(预写日志)的目录,使用两块盘分离 IO 读写。

          以下是详细步骤和注意事项,以三节点 etcd 集群 (ETCD 使用 static pod 方式部署)为例:

          核心原则

          1. 一次只操作一个节点:确保集群始终满足 N/2 + 1 的存活节点(3 节点集群需至少 2 节点在线)。

          2. 完整备份:操作前必须备份 etcd 数据,并制定好灾备恢复计划。

          3. 业务低峰期操作:减少对集群的影响,并制定好验证方案。

          4. 演练及回滚计划:生产环境操作前务必在测试环境演练!确保团队熟悉流程并备有回滚计划。

          操作步骤

          1. 前置准备

          • 备份 etcd 数据(在任一 etcd 节点执行):

            ETCDCTL_API=3 etcdctl \
              --endpoints=https://127.0.0.1:2379 \
              --cacert=/etc/kubernetes/pki/etcd/ca.crt \
              --cert=/etc/kubernetes/pki/etcd/server.crt \
              --key=/etc/kubernetes/pki/etcd/server.key \
              snapshot save /tmp/etcd-snapshot-$(date +%Y%m%d).db

            验证备份完整性:

            etcdutl snapshot status /tmp/etcd-snapshot-*.db
          • 检查集群健康状态

            ETCDCTL_API=3 etcdctl \
              --endpoints=10.0.1.6:2379,10.0.1.7:2379,10.0.1.8:2379 \
              --cacert=/etc/kubernetes/pki/etcd/ca.crt \
              --cert=/etc/kubernetes/pki/etcd/server.crt \
              --key=/etc/kubernetes/pki/etcd/server.key \
              endpoint status -w table
            
            ETCDCTL_API=3 etcdctl \
              --endpoints=10.0.1.6:2379,10.0.1.7:2379,10.0.1.8:2379 \
              --cacert=/etc/kubernetes/pki/etcd/ca.crt \
              --cert=/etc/kubernetes/pki/etcd/server.crt \
              --key=/etc/kubernetes/pki/etcd/server.key \
              endpoint health -w table

          找到 leader 节点,换盘先从 follower 节点开始

          2. 更换第一个 etcd 节点的磁盘(以 etcd-node1 为例)

          • 步骤 2.1:停止 etcd 服务,备份 etcd 目录数据

            mv /etc/kubernetes/manifests/etcd.yaml /etc/kubernetes/
            # 检查 etcd 进程是否已经退出
            ps -ef | grep etcd 
            kubectl get po -n kube-system | grep etcd
            # 备份 etcd 目录数据
            cp -rp {etcd 数据目录} {备份目录}
          • 步骤 2.2:更换物理磁盘

          1. 卸载旧磁盘(如 umount -l dev/sdb)。

          2. 安装新磁盘并格式化(例如 mkfs.xfs /dev/sdc)。

          3. 挂载到原目录(如 /var/lib/etcd):

            # 可将新盘做成lvm挂载,方便后续的运维和扩容等
            pvcreate /dev/sdc
            vgcreate vg_etcd /dev/sdc
            lvcreate -l +100%FREE -n lv_etcd vg_etcd
            mkfs.xfs /dev/vg_etcd/lv_etcd
            # 配置挂载信息,防止机器重启后丢失
            echo "/dev/vg_etcd/lv_etcd /var/lib/etcd xfs defaults 0 0" >> /etc/fstab 
            mount -a
        15. 步骤 2.3:恢复数据(可选)

          通常新节点会从集群同步数据,但若需快速恢复,可从备份还原:

          cp -r {备份目录}  {etcd 新盘挂载的数据目录}
        16. 步骤 2.4:重启 etcd 服务

          mv /etc/kubernetes/etcd.yaml /etc/kubernetes/manifests/
          # 确认etcd启动
          kubectl get po -n kube-system | grep etcd
        17. 步骤 2.5:验证节点恢复

          ETCDCTL_API=3 etcdctl \
            --endpoints=10.0.1.6:2379,10.0.1.7:2379,10.0.1.8:2379 \
            --cacert=/etc/kubernetes/pki/etcd/ca.crt \
            --cert=/etc/kubernetes/pki/etcd/server.crt \
            --key=/etc/kubernetes/pki/etcd/server.key \
            endpoint status -w table
          
          ETCDCTL_API=3 etcdctl \
            --endpoints=10.0.1.6:2379,10.0.1.7:2379,10.0.1.8:2379 \
            --cacert=/etc/kubernetes/pki/etcd/ca.crt \
            --cert=/etc/kubernetes/pki/etcd/server.crt \
            --key=/etc/kubernetes/pki/etcd/server.key \
            endpoint health -w table
          
          watch "etcdctl member list"  # 观察节点状态变为 started
        18. 3. 重复操作其他节点

          • 按相同流程操作 etcd-node2 → etcd-node3

          • 每次间隔至少 10 分钟,确保集群完全稳定,中间间隔可做集群验证。

          4. 最终验证

          • 集群状态

            ETCDCTL_API=3 etcdctl \
              --endpoints=10.0.1.6:2379,10.0.1.7:2379,10.0.1.8:2379 \
              --cacert=/etc/kubernetes/pki/etcd/ca.crt \
              --cert=/etc/kubernetes/pki/etcd/server.crt \
              --key=/etc/kubernetes/pki/etcd/server.key \
              endpoint status --cluster -w table
          • Kubernetes 功能测试

            kubectl get nodes --v=7
            kubectl create deployment test --image=nginx && kubectl delete deployment test

          关键注意事项

          1. 磁盘挂载配置

          • 更新 /etc/fstab 确保重启后自动挂载:

            echo "/dev/sdc /var/lib/etcd xfs defaults 0 0" >> /etc/fstab
          • 使用 lsblk 确认磁盘路径。

        19. etcd 参数检查

          • 确保 etcd.yaml 中的 data-dir 指向新磁盘路径(如 /var/lib/etcd)。

          • 如果选择分离 snapshot/wal 以达到提升性能的目的, 需在启动前,需修改 etcd.yaml文件,增加 wal-dir 配置:

          --data-dir=/var/lib/etcd/data
          --wal-dir=/var/lib/etcd/wal
          • 验证证书路径和参数正确。

        20. 灾难恢复预案

          • 若操作中集群崩溃(如意外停机两个节点):

            • 从备份恢复整个集群:etcdutl snapshot restore ...

            • 重置 Kubernetes 控制平面组件。

        21. 性能优化建议

          • 使用 SSD 磁盘(etcd 对 I/O 延迟敏感)。

          • 分离 etcd 数据目录与操作系统磁盘

          • 调整 etcd 的 --quota-backend-bytes 限制磁盘用量(默认 2GB)。

          • 如果选择分离 snapshot/wal 以达到提升性能的目的,可以使用使用两块新盘挂载到不同的目录,如 /var/lib/etcd/data 与 /var/lib/etcd/wal 

          常见问题处理

          • 节点无法加入集群

            • 检查网络防火墙(端口 2379/2380)。

            • 确保证书未过期且 CN/SAN 匹配。

            • 查看 etcd 日志:journalctl -u etcd 或 kubectl logs -n kube-system etcd-node1

          • 数据不一致

            • 使用 etcdctl check perf 测试性能。

            • 若数据损坏,从备份恢复受影响节点。


          参考:


          🔲 ⭐

          简化kubectl常用命令

          在k8s集群使用和运维中,每天都需要输入大量 kubectl 常用命令,其实我们可以通过定义 alias 别名和函数简化 kubectl 命令,比如使用k代替kubectl,po代替pods,svc代替services,kgpon代替kubectl get po -n [命名空间+资源名](以参数传入),并且添加kubectl命令自动补全,将这些别名和函数添加到 Shell 配置文件(如 ~/.bashrc、~/.zshrc)中,方便日常使用。以下脚本可以登陆到linux shell环境执行,结合这些别名和函数,可以大幅提高操作 Kubernetes 的效率,以后就可以轻松愉快地使用 kubectl 命令啦

          # 在 bash 中设置当前 shell 的自动补全
          source <(kubectl completion bash)
          echo "source <(kubectl completion bash)" >> ~/.bashrc
          
          # 在 zsh 中设置当前 shell 的自动补全
          # source <(kubectl completion zsh)  
          # echo '[[ $commands[kubectl] ]] && source <(kubectl completion zsh)' >> ~/.zshrc
          # zshrc添加将 .bashrc 改为 .zshrc
          
          # 添加简化命令,单引号包裹 EOF,禁止所有变量解析
          cat >> ~/.bashrc << 'EOF' 
          
          # 基础别名
          alias k='kubectl'                     # 用 k 代替 kubectl
          alias kg='kubectl get'                # 快速 get 资源
          alias kd='kubectl describe'           # 快速 describe 资源
          alias kdel='kubectl delete'           # 快速 delete 资源
          alias ka='kubectl apply -f'           # 快速 apply 文件
          alias ke='kubectl edit'               # 快速 edit 资源
          alias kex='kubectl explain'           # 快速 explain 资源
          alias kl='kubectl logs'               # 快速查看日志
          alias klf='kubectl logs -f'           # 实时查看 Pod 日志
          alias kx='kubectl exec -it'           # 快速进入 Pod 的 Shell
          alias kw='kubectl get --watch'        # 实时监控资源变化
          alias kt='kubectl top'                # 查看资源使用情况
          alias kr='kubectl rollout'            # 更新资源
          alias ks='kubectl scale'              # 资源扩缩容
          alias kcp='kubectl cp'                # 快速 cp 文件
          alias kco='kubectl config'            # 快速 config 资源
          alias kcd='kubectl cordon'            # 节点不可调度
          alias kucd='kubectl uncordon'         # 节点可调度
          alias kapi='kubectl api-resources'    # 列出支持的资源类型
          alias kcl='kubectl cluster-info'      # 显示主控节点和服务的地址
          alias kh='kubectl get --v=7'          # 显示资源请求的HTTP信息
          
          
          # 资源类型别名
          alias kgpo='kubectl get pods'         # pods
          alias kgno='kubectl get nodes'        # nodes
          alias kgns='kubectl get namespaces'   # namespaces
          alias kgcs='kubectl get cs'           # ComponentStatus
          alias kgep='kubectl get ep'           # endpoints
          alias kgsvc='kubectl get services'    # services
          alias kgdep='kubectl get deployments' # deployments
          alias kgrs='kubectl get replicasets'  # replicasets
          alias kging='kubectl get ingress'     # ingress
          alias kgcm='kubectl get configmaps'   # configmaps
          alias kgsec='kubectl get secrets'     # secrets
          alias kgsa='kubectl get sa'           # serviceaccounts
          alias kgpv='kubectl get pv'           # persistentvolumes
          alias kgpvc='kubectl get pvc'         # persistentvolumeclaims
          alias kgsc='kubectl get sc'           # storageclasses
          alias kgsts='kubectl get sts'         # StatefulSet
          alias kgds='kubectl get ds'           # DaemonSet
          alias kgev='kubectl get events'       # events
          alias kgjo='kubectl get jobs'         # jobs
          
          
          # 查看资源描述(需替换 <resource-name>)
          alias kdpo='kubectl describe pod'
          alias kdno='kubectl describe nodes'
          alias kdde='kubectl describe deployment'
          alias kdst='kubectl describe sts'
          alias kdds='kubectl describe ds'
          alias kdsv='kubectl describe svc'
          alias kdcm='kubectl describe cm'
          alias kdse='kubectl describe secrets'
          alias kdsa='kubectl describe sa'
          alias kdpv='kubectl describe pv'
          alias kdpvc='kubectl describe pvc'
          alias kdsc='kubectl describe sc'
          alias kding='kubectl describe ingress'
          alias kdjo='kubectl describe jobs'
          
          
          # 常用组合别名
          
          # 查看所有命名空间的 Pods
          alias kgpoa='kubectl get pods -A'
          
          # 查看 Pods 并显示附加信息(如 IP、节点)
          alias kgpow='kubectl get pods -owide'
          
          # 查看 Nodes 并显示附加信息(如 IP、节点)
          alias kgnow='kubectl get nodes -owide'
          
          # 资源操作:$1对应命名空间、$2对应资源名称;y → yaml,n → namespace,默认输出格式为yaml
          OUTPUT="-oyaml"
          
          # kubectl get resources
          kgpon() { kubectl get po -n "$@"; }
          kgpoy() { kgpon "$@" $OUTPUT; }
          
          kgden() { kubectl get deploy -n "$@"; }
          kgdey() { kgden "$@" $OUTPUT; }
          
          kgstn() { kubectl get sts -n "$@"; }
          kgsty() { kgstn "$@" $OUTPUT; }
          
          kgsvn() { kubectl get svc -n "$@"; }
          kgsvy() { kgsvn "$@" $OUTPUT; }
          
          kgdsn() { kubectl get ds -n "$@"; }
          kgdsy() { kgdsn "$@" $OUTPUT; }
          
          kgcmn() { kubectl get ds -n "$@"; }
          kgcmy() { kgcmn "$@" $OUTPUT; }
          
          kgsen() { kubectl get secrets -n "$@"; }
          kgsey() { kgsen "$@" $OUTPUT; }
          
          kgsan() { kubectl get sa -n "$@"; }
          kgsay() { kgsan "$@" $OUTPUT; }
          
          kgpvn() { kubectl get pv -n "$@"; }
          kgpvy() { kgpvn "$@" $OUTPUT; }
          
          kgpvcn() { kubectl get pvc -n "$@"; }
          kgpvcy() { kgpvcn "$@" $OUTPUT; }
          
          kgscn() { kubectl get sc -n "$@"; }
          kgscy() { kgscn "$@" $OUTPUT; }
          
          kgingn() { kubectl get ingress -n "$@"; }
          kgingy() { kgingn "$@" $OUTPUT; }
          
          kgevn() { kubectl get events -n "$@"; }
          kgevy() { kgevn "$@" $OUTPUT; }
          
          kgjon() { kubectl get jobs -n "$@"; }
          kgjoy() { kgjon "$@" $OUTPUT; }
          
          # kubectl describe resources
          kdpon() { kubectl describe po -n "$@"; }
          kdden() { kubectl describe deploy -n "$@"; }
          kdstn() { kubectl describe sts -n "$@"; }
          kdsvn() { kubectl describe svc -n "$@"; }
          kddsn() { kubectl describe ds -n "$@"; }
          kdcmn() { kubectl describe cm -n "$@"; }
          kdsen() { kubectl describe secrets -n "$@"; }
          kdsan() { kubectl describe sa -n "$@"; }
          kdpvn() { kubectl describe pv -n "$@"; }
          kdpvcn() { kubectl describe pvc -n "$@"; }
          kdscn() { kubectl describe sc -n "$@"; }
          kdingn() { kubectl describe ingress -n "$@"; }
          kdjon() { kubectl describe jobs -n "$@"; }
          
          # kubectl edit resources
          kepon() { kubectl edit po -n "$@"; }
          keden() { kubectl edit deploy -n "$@"; }
          kestn() { kubectl edit sts -n "$@"; }
          kesvn() { kubectl edit svc -n "$@"; }
          kedsn() { kubectl edit ds -n "$@"; }
          kecmn() { kubectl edit cm -n "$@"; }
          kesen() { kubectl edit secrets -n "$@"; }
          kesan() { kubectl edit sa -n "$@"; }
          kepvn() { kubectl edit pv -n "$@"; }
          kepvcn() { kubectl edit pvc -n "$@"; }
          kescn() { kubectl edit sc -n "$@"; }
          keingn() { kubectl edit ingress -n "$@"; }
          kejon() { kubectl edit jobs -n "$@"; }
          
          # 进入 Pod 的 Shell
          kxsh() { kubectl exec -it -n "$@" -- /bin/sh; }
          kxbs() { kubectl exec -it -n "$@" -- /bin/bash; }
          
          # 查看 Pod 的日志
          klpn() { kubectl logs -n "$@"; }
          klfpn() { klpn "$@" -f; }
          klppn() { klpn "$@" -p; }
          
          # 删除 Pod
          kdelp() { kubectl delete po -n "$@"; }
          
          # 强制删除 Pod
          krmfp() { kubectl delete po -n "$@" --force --grace-period=0; }
          
          # 上下文和命名空间切换
          alias kuse='kubectl config use-context'      # 切换集群
          alias kns='kubectl config set-context --current --namespace' # 切换命名空间
          
          # 生成 Pod 的临时调试副本(类似 docker exec)
          alias kdebug='kubectl debug -it --image=busybox'
          
          # 转发本地端口到 Pod(需替换参数)
          alias kpf='kubectl port-forward pod/<pod-name>'
          
          
          complete -o default -F __start_kubectl k
          EOF
          
          source ~/.bashrc

          使用示例:

          # 输入参数:$@对应整个输入参数,$1对应命名空间、$2对应资源名称,其余缩写命令使用方法与示例(Pods)类似
          
          # 查看所有命名空间的 Pods
          kgpoa                   → kubectl get pods -A
          kgpow -A                → kubectl get pods -owide -A
          
          # 查看指定命名空间的 Pods
          k get po -n namespace   → kubectl get pods -n namespace
          kg po -n namespace
          kgpo -n namespace
          kgpon namespace
          kgpon namespace -owide  → kubectl get pods -n namespace -owide
          
          # 查看指定命名空间的 Pod
          kgpon namespace podname → kubectl get pods -n namespace podname
          kgpoy namespace podname → kubectl get pods -n namespace podname -oyaml
          kdpon namespace podname → kubectl describe pods -n namespace podname
          kepon namespace podname → kubectl edit pods -n namespace podname
          
          # 进入 Pod 的 Shell
          kxsh namespace podname  → kubectl exec -it -n namespace podname -- /bin/sh
          kxbs namespace podname  → kubectl exec -it -n namespace podname -- /bin/bash
          
          # 查看 Pod 的日志
          klpn namespace podname  → kubectl logs -n namespace podname
          klfpn namespace podname → kubectl logs -n namespace podname -f
          klppn namespace podname → kubectl logs -n namespace podname --previous
          
          # 删除 Pod
          kdelp namespace podname → kubectl delete pods -n namespace podname
          krmfp namespace podname → kubectl delete pods -n namespace podname --force --grace-period=0

          注意事项

          • 如果别名冲突(如 k 被其他工具占用),可替换为其他名称(如 kb)。

          • 删除资源时务必确认资源名称,避免误操作。

          • 编辑系统级配置文件前建议先备份:cp ~/.bashrc{,.bak}

          • 检察语法错误:bash -n ~/.bashrc

          • 检查别名或函数是否成功定义:type kg、type kgpon


          参考:

          ❌