一、Kubernetes集群不稳定的常见表现

你有没有遇到过这种情况:明明集群资源充足,但Pod就是频繁重启,或者某些节点莫名其妙地负载飙升?这些现象往往和默认调度策略有关。比如:

  1. 节点资源挤兑:多个高负载Pod被调度到同一个节点,导致OOM(内存不足)
  2. 热点节点问题:新Pod总是往少数几个节点上堆
  3. 调度延迟:明明有空闲节点,Pod却卡在Pending状态

举个真实案例:某电商平台大促时,订单服务的Pod在node-1上密集部署,结果该节点CPU飙到95%,而其他节点利用率还不到30%。

二、默认调度器的工作原理

Kubernetes的默认调度器(kube-scheduler)其实是个"老实人",它主要看两个指标:

  1. 资源请求(Requests):比如你声明需要2核CPU,调度器就找至少有2核空闲的节点
  2. 默认谓词(Predicates):包括节点内存是否足够、端口是否冲突等基础检查

但问题在于,它不会主动考虑:

  • 节点现有工作负载的实际情况
  • Pod之间的亲和/反亲和需求
  • 跨节点的资源均衡
# 典型的问题部署示例(技术栈:Kubernetes)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: problem-service
spec:
  replicas: 5
  template:
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        resources:
          requests:
            cpu: "1"  # 每个Pod要1核CPU
            memory: "1Gi" # 和1G内存
          limits:
            memory: "2Gi"
      tolerations:
      - key: "disktype"
        operator: "Exists"  # 这个容忍度会导致Pod可能被调度到特殊节点

三、精准调度解决方案

3.1 给节点打标签定向调度

就像给快递包裹贴"易碎品"标签一样,我们可以给节点分类:

# 给SSD节点打标签
kubectl label nodes node-1 disktype=ssd

# 然后让数据库Pod只跑在这些节点上
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-cache
spec:
  template:
    spec:
      nodeSelector:
        disktype: ssd  # 关键选择器

3.2 使用反亲和性避免扎堆

就像疫情期间要保持社交距离,重要服务也应该分散部署:

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
        matchExpressions:
        - key: app
          operator: In
          values: ["payment-service"]  # 支付服务必须分散
      topologyKey: "kubernetes.io/hostname"  # 按主机名分散

3.3 自定义调度器扩展

当默认调度器不够用时,可以自己写调度插件。比如这个Go语言示例:

// 技术栈:Kubernetes调度器扩展(Go语言)
package main

import (
	"k8s.io/kubernetes/pkg/scheduler/framework"
)

type BalancePlugin struct{}

func (bp *BalancePlugin) Name() string {
	return "BalancePlugin"
}

func (bp *BalancePlugin) Filter(ctx context.Context, 
    state *framework.CycleState, 
    pod *v1.Pod, 
    nodeInfo *framework.NodeInfo) *framework.Status {
    
    // 检查节点现有Pod的CPU使用率
    if nodeInfo.UsedCPU > nodeInfo.AllocatableCPU * 0.7 {
        return framework.NewStatus(framework.Unschedulable, "节点负载过高")
    }
    return framework.NewStatus(framework.Success)
}

四、实战中的进阶技巧

4.1 优先级和抢占配置

就像医院急诊科优先处理危重病人,我们可以设置:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000  # 数值越大优先级越高
description: "用于关键业务服务"

# 在Deployment中引用
spec:
  priorityClassName: high-priority

4.2 基于实际负载的调度

默认调度器只看资源请求,但我们可以通过metrics-server获取真实数据:

# 安装metrics-server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# 查看节点实时负载
kubectl top nodes

4.3 调度器性能调优

在大规模集群中,可以调整这些参数:

apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
profiles:
  - schedulerName: default-scheduler
    percentageOfNodesToScore: 50  # 检查半数节点即可
    nodeResourcesBalancedAllocation: true  # 开启资源均衡

五、避坑指南

  1. 别过度依赖默认值

    • 默认CPU请求是0.5核,可能不符合实际需求
    • 默认没有设置反亲和规则
  2. 注意资源限制的副作用

    resources:
      limits:
        cpu: "2"  # 设置太严格会导致throttling
    
  3. 跨AZ调度要特殊处理

    topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: topology.kubernetes.io/zone
      whenUnsatisfiable: DoNotSchedule
    

六、总结

就像交通指挥系统需要智能红绿灯一样,Kubernetes调度也需要精细化管理。关键要点:

  • 重要服务必须设置反亲和性
  • 利用节点标签实现分类调度
  • 实时监控实际负载比静态配置更可靠
  • 自定义调度器是解决复杂场景的终极方案

下次当你发现集群不稳定时,不妨先从调度策略入手检查,或许就能找到那把隐藏的钥匙。