kubernetes+ prometheus自动伸缩的设计与实现(一)

释放双眼,带上耳机,听听看~!

这篇blog名字起的搞得和我写论文一样,xxxx的设计与实现。其实这个东西原理很简单,kubernetes的hpa使用的是heapster,heapster是k8s那帮家伙在搞的,所以k8s还是喜欢自己搞的东西,所以k8s的hpa默认使用的heapster,但在业内,还有一个比heapster更好的监控方案,那就是prometheus。如果按照写论文的方式,我这边应该分别介绍一下k8s和prometheus,但真的没有那个闲功夫,在此略过,我之前blog也做过它们的源码分析。
上面的图片展示了整个体系结构
下面就分析一下adapter的具体实现,这里需要结合上一篇blog关于api聚合的功能,这个adapter就是通过api聚合的方式注册到apiserver上面。
先看一个hpa的例子


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1kind: HorizontalPodAutoscaler
2apiVersion: autoscaling/v2alpha1
3metadata:
4  name: wordpress
5  namespace: default
6spec:
7  scaleTargetRef:
8    apiVersion: apps/v1beta1
9    kind: Deployment
10    name: wordpress
11  minReplicas: 1
12  maxReplicas: 3
13  metrics:
14  - type: Pods
15    pods:
16      metricName: memory_usage_bytes
17      targetAverageValue: 100000
18

上面的代码添加一个内存使用量的hpa例子,通过targetAverageValue去设定阈值还有最大最小副本数,以及管理的deployment。在此额外说一下,hpa支持三种指标,分别是Object:描述k8s对象的某种指标,譬如ingress的hits-per-second。Pods:pod的平均指标,譬如transactions-processed-per-second,描述每个pod的事务每秒事务数,Resource是描述pod资源用量,譬如CPU或者内存。下面举一个object的例子,pod的http请求数。


1
2
3
4
5
6
7
8
1  - type: Object
2    object:
3      target:
4        kind: Service
5        name: sample-metrics-app
6      metricName: http_requests
7      targetValue: 100
8

先从hpa代码开始pkg/controller/podautoscaler/horizontal.go,里面的computeReplicasForMetrics方法,它是负责获取监控指标并且计算副本数的方法。针对不同资源调用不同的方法:


1
2
3
4
5
6
7
8
9
10
1case autoscalingv2.ObjectMetricSourceType:
2GetObjectMetricReplicas
3...
4
5case autoscalingv2.PodsMetricSourceType:
6GetMetricReplicas
7
8case autoscalingv2.ResourceMetricSourceType:
9GetRawResourceReplicas
10

先看看GetObjectMetricReplicas这个方法
pkg/controller/podautoscaler/replica_calculator.go


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1func (c *ReplicaCalculator) GetObjectMetricReplicas(currentReplicas int32, targetUtilization int64, metricName string, namespace string, objectRef *autoscaling.CrossVersionObjectReference) (replicaCount int32, utilization int64, timestamp time.Time, err error) {
2    utilization, timestamp, err = c.metricsClient.GetObjectMetric(metricName, namespace, objectRef)
3    if err != nil {
4        return 0, 0, time.Time{}, fmt.Errorf("unable to get metric %s: %v on %s %s/%s", metricName, objectRef.Kind, namespace, objectRef.Name, err)
5    }
6
7    usageRatio := float64(utilization) / float64(targetUtilization)
8    if math.Abs(1.0-usageRatio) <= c.tolerance {
9        // return the current replicas if the change would be too small
10        return currentReplicas, utilization, timestamp, nil
11    }
12
13    return int32(math.Ceil(usageRatio * float64(currentReplicas))), utilization, timestamp, nil
14}
15

GetObjectMetric是一个接口,有两个方法,就是上面图所示的heapster和自定义custom接口。heapster这个就是调用heapster接口去获取性能指标,本blog着重介绍自定义性能指标,在启动controller-manager时候指定–horizontal-pod-autoscaler-use-rest-clients就可以使用自定义的性能指标了
pkg/controller/podautoscaler/metrics/rest_metrics_client.go


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1func (c *customMetricsClient) GetObjectMetric(metricName string, namespace string, objectRef *autoscaling.CrossVersionObjectReference) (int64, time.Time, error) {
2    gvk := schema.FromAPIVersionAndKind(objectRef.APIVersion, objectRef.Kind)
3    var metricValue *customapi.MetricValue
4    var err error
5    if gvk.Kind == "Namespace" && gvk.Group == "" {
6        // handle namespace separately
7        // NB: we ignore namespace name here, since CrossVersionObjectReference isn't
8        // supposed to allow you to escape your namespace
9        metricValue, err = c.client.RootScopedMetrics().GetForObject(gvk.GroupKind(), namespace, metricName)
10    } else {
11        metricValue, err = c.client.NamespacedMetrics(namespace).GetForObject(gvk.GroupKind(), objectRef.Name, metricName)
12    }
13
14    if err != nil {
15        return 0, time.Time{}, fmt.Errorf("unable to fetch metrics from API: %v", err)
16    }
17
18    return metricValue.Value.MilliValue(), metricValue.Timestamp.Time, nil
19}
20

上面的objectRef针对本blog只为
{Kind:Service,Name:wordpress,APIVersion:,},就是我们在yaml文件里面metrics里面定义。

上面通过vendor/k8s.io/metrics/pkg/client/custom_metrics/client.go


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1func (m *rootScopedMetrics) GetForObject(groupKind schema.GroupKind, name string, metricName string) (*v1alpha1.MetricValue, error) {
2    // handle namespace separately
3    if groupKind.Kind == "Namespace" && groupKind.Group == "" {
4        return m.getForNamespace(name, metricName)
5    }
6
7    resourceName, err := m.client.qualResourceForKind(groupKind)
8    if err != nil {
9        return nil, err
10    }
11
12    res := &v1alpha1.MetricValueList{}
13    err = m.client.client.Get().
14        Resource(resourceName).
15        Name(name).
16        SubResource(metricName).
17        Do().
18        Into(res)
19
20    if err != nil {
21        return nil, err
22    }
23
24    if len(res.Items) != 1 {
25        return nil, fmt.Errorf("the custom metrics API server returned %v results when we asked for exactly one", len(res.Items))
26    }
27
28    return &res.Items[0], nil
29}
30

通过client发送https请求获取metrics。具体发送如下所示object:


1
2
1https://localhost:6443/apis/custom-metrics.metrics.k8s.io/v1alpha1/namespaces/default/services/wordpress/requests-per-second
2

如果是pod则发送的请求是


1
2
1https://localhost:6443/apis/custom-metrics.metrics.k8s.io/v1alpha1/namespaces/default/pods/%2A/memory_usage_bytes?labelSelector=app%3Dwordpress%2Ctier%3Dfrontend
2

至于group为啥是custom-metrics.metrics.k8s.io这个不是别的,是代码里面写死的,vendor/k8s.io/metrics/pkg/apis/custom_metrics/v1alpha1/register.go


1
2
3
4
5
6
1// GroupName is the group name use in this package
2const GroupName = "custom-metrics.metrics.k8s.io"
3
4// SchemeGroupVersion is group version used to register these objects
5var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
6

这样k8s的部分已经讲解完毕。下面就是adapter的部分了。

给TA打赏
共{{data.count}}人
人已打赏
安全运维

故障复盘的简洁框架-黄金三问

2021-9-30 19:18:23

安全运维

OpenSSH-8.7p1离线升级修复安全漏洞

2021-10-23 10:13:25

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索