Kubernetes容器资源请求推荐

2年前 (2022) 程序员胖胖胖虎阿
201 0 0

背景

现在是云原生时代,公司的业务基本都是以容器的方式运行在Kubernetes集群中。但是在业务高峰期,经常有开发反馈,自己业务的pod 被驱逐了,查找一看,原来是同个宿主机的某个pod占用了太多的cpu和内存资源,导致其他Qos 等级较低的pod 被kubelet 驱逐了,如何让pod 更加合理的调度到资源充足的节点也是本文将要探讨的一个问题。

Reqeust对调度的影响

CPU资源

CPU 资源申请包含cpu.request 和cpu.limit。内核对CPU 资源的使用以时间片为单位,因此Pod 对CPU 资源的申请可以以Millicore 为最小单位,一个CPU Core 等于1000 Millicore。

Kubernetes 调度Pod 时,会判断当前节点正在运行的Pod 的CPU Request 的总和,再加上当前调度Pod 的CPU request,计算其是否超过节点的CPU 的可分配资源。如果超出,则该节点应被过滤掉。换言之,调度器会判断当前节点的剩余CPU 资源是否满足Pod 的CPU Request。

CPU 的Cgroup相关参数

Kubernetes容器资源请求推荐

容器内部可以通过查看/sys/fs/cgroup/cpu/cpu.shares 和/sys/fs/cgroup/cpu/cpu.cfs_ quota_us 来获取当前容器CGroup 的CPU 信息。节点资源充裕时,容器可以使用不超过限额的CPU 资源。

而节点资源紧张时,Linux内核进程调度器通过cpu.shares 确保该容器不会占用其他容器或者进程申请的CPU 资源,同时保证该容器能够获取相应的CPU 时间。Kubernetes 通过request 和limit 的组合,极大地提升了节点资源利用率。

内存资源

同CPU 资源一样,Pod Spec 通过定义requests.memory 和limits.memory 的值来为Pod申请内存。Kubernetes 调度Pod 时会判断节点的剩余内存是否满足Pod 的内存请求量,以确定是否可以将Pod 调度到该节点。

同样地,kube-scheudler 只计算内存requests,而不看内存limits。Pod 调度完成后,对应节点的kubelet 会将容器申请的内存资源设置到该容器对应的memory CGroup 中。

内存CGroup 参数设置

Kubernetes容器资源请求推荐

为什么在进行调度的时候计算容器的内存是requests,而设置CGroup 时用的内存却是limits?requests 在节点上有什么意义呢?

在节点内存资源宽松时,容器的内存使用量可以在不超过memory.limits 的前提下使用。而在内存资源紧张的情况下,kubelet 会周期性地按照既定优先级对容器进行驱逐。在kubelet 对容器进行驱逐之前,系统的OOM Killer 可能会采取OOM 的方式来中止某些容器的进程,进行必要的内存回收操作。而系统根据进程的oom_score 来进行优先级排序,选择待终止的进程,且进程的oom_score 越高,越容易被终止。进程的oom_score 是根据当前进程使用的内存占节点总内存的比例值乘以10,再加上oom_score_adj 综合得到的,而容器进程的oom_score_adj 正是kubelet 根据memory.request 进行设置的。

资源请求推荐

用户通常不了解容器的requests 怎么填,每次都让他们去grafana 看容器的资源使用情况来填,这一步增加了用户的使用成本,所以由平台方直接进行推荐是更优解。

公司的每个集群都部署了prometheus 并把采集数据写到远程存储VictoriaMetrics ,所以平台方这边使用VictoriaMetrics 作为数据源来查询最近一周内的容器资源使用情况来进行推荐。

本次用到的query的function:

quantile_over_time(scalar, range-vector): the φ-quantile (0 ≤ φ ≤ 1) of the values in the specified interval.

查询容器cpu的使用情况

quantile_over_time(0.99, irate(container_cpu_usage_seconds_total{cluster="%s", app="%s", namespace="%s", container="%s"}[5m])[1w:5m])

查询容器内存的使用情况

quantile_over_time(0.99, container_memory_working_set_bytes{cluster="%s", app="%s", namespace="%s", container="%s"}[1w:5m])

上面使用的是最近一周资源使用的p99来给出资源推荐,可根据实际情况来修改

代码实例

package main

import (
    "context"
    "fmt"
    "time"

    promapi "github.com/prometheus/client_golang/api"
    promv1 "github.com/prometheus/client_golang/api/prometheus/v1"
    prommodel "github.com/prometheus/common/model"
)

const (
    containerMemoryUsageBytesLastWeek = `quantile_over_time(0.99, container_memory_working_set_bytes{cluster="%s", app="%s", namespace="%s", container="%s"}[1w:5m])`
)

func main() {
    prometheusClient, err := promapi.NewClient(promapi.Config{
        Address: "http://localhost:9090",
    })
    if err != nil {
        panic(err)
    }
    cluster := "k8s_test1"
    app := "web"
    namespace := "default"
    container := "web"
    query := fmt.Sprintf(containerMemoryUsageBytesLastWeek, cluster, app, namespace, container)

    v1api := promv1.NewAPI(prometheusClient)
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    obj, _, err := v1api.Query(ctx, query, time.Now())
    if err != nil {
        panic(err)
    }
    podContainerValues := map[prommodel.LabelValue]prommodel.SampleValue{}
    resp, ok := obj.(prommodel.Vector)
    if !ok {
        panic(err)
    }
    // pod里面的同名容器重启了多次,会有多个sample,所以取最大值
    for _, sample := range resp {
        key := sample.Metric["pod"] + sample.Metric["container"]
        if sample.Value > podContainerValues[key] {
            podContainerValues[key] = sample.Value
        }
    }
    // 一个应用有多个pod,取平均值
    // todo 后续考虑方差大的影响
    podNum := len(podContainerValues)
    if podNum == 0 {
        panic("查询为空")
    }
    var valueTotal prommodel.SampleValue
    for _, v := range podContainerValues {
        valueTotal += v
    }

    suggest := float64(valueTotal) / float64(podNum)
    fmt.Println("推荐内存:", formatMemorySize(int64(suggest)))
}

func formatMemorySize(size int64) string {
    if size < 0 {
        return ""
    }
    // 最低推荐
    if size <= 1024*1024*100 {
        return "100Mi"
    }
    a, b := size/(1024*1024*1024), size%(1024*1024*1024)
    if a > 0 && b == 0 {
        return fmt.Sprintf("%dGi", a)
    }
    return fmt.Sprintf("%dMi", size/(1024*1024))
}

查询结果在一段时间内都是有效的,可适当加上缓存,避免频繁查询数据源。

上面是requests的推荐值,limits的推荐值可以是它的2倍,要结合实际情况。

总结

上面的资源推荐能满足大部分业务场景,但是遇到像离线业务或者突发高峰这些场景不一定适用,此时需要结合Horizontal Pod Autoscaler(HPA)是根据资源利用率或者自定义指标自动调整副本数实现部署的水平自动扩缩容,让部署的规模接近于实际服务的负载。

HPA 功能可实现基于Metric CPU 与 MEM 的使用率来进行动态扩缩容,但是想要根据api请求数或者tcp连接数等Promtheus 数值阀值来自动扩缩容需要借助 KEDA 这个事件驱动K8s资源对象扩缩容的利器。

参考

  • 《Kubernetes生产化实践之路》
版权声明:程序员胖胖胖虎阿 发表于 2022年10月30日 上午2:40。
转载请注明:Kubernetes容器资源请求推荐 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...