通过prometheus实现k8s hpa自定义指标 (三)
在本系列文章的上一节通过prometheus实现k8s hpa自定义指标 (二),我们从开发者角度理解k8s hpa工作的流程。这节我们将通过一个最基础的custom metrics API server介绍开发一个自定义metrics-apiserver需要完成哪些方面的工作,作为后继分析prometheus-adapter的基础。开发Custom Metrics API Server适配器..
在本系列文章的上一节通过prometheus实现k8s hpa自定义指标 (二),我们从开发者角度理解k8s hpa工作的流程。这节我们将通过一个最基础的custom metrics API server介绍开发一个自定义metrics-apiserver需要完成哪些方面的工作,作为后继分析prometheus-adapter的基础。
开发Custom Metrics API Server
适配器有两个部分: setup code和providers。setup code用于初始化API server,providers处理对metrics的API请求。
1. 编写provider
目前provider有两个接口,对应于两个不同的API:custom metrics API(用于描述k8s对象的metrics),另外一个是external metrics API(不用描述k8s对象的metrics,或者不用于附加到特定对象的metrics)。为了简洁起见,本文只展示custom metrics API。
将你的provider放在你项目的pkg/provider目录。
首先得引入以下包
package provider
import (
"fmt"
"time"
"github.com/golang/glog"
apierr "k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/metrics/pkg/apis/custom_metrics"
"github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider"
"k8s.io/metrics/pkg/apis/external_metrics"
)
所要实现的custom metrics provider接口成为CustomMetricsProvider,说下所示:
type CustomMetricsProvider interface {
ListAllMetrics() []CustomMetricInfo
GetMetricByName(name types.NamespacedName, info CustomMetricInfo) (*custom_metrics.MetricValue, error)
GetMetricBySelector(namespace string, selector labels.Selector, info CustomMetricInfo) (*custom_metrics.MetricValueList, error)
}
通过上述接口可以列出所有的可用metrics,用于API中的信息发现,以便客户端可以知道哪些metrics是可以用的。同时不允许失败(它不会返回任何错误),并且请求应该快速返回,因此在代码中最好是要异步更新。
如下是返回几个固定命名的metrics,其中两个metrics是具备命名空间的,其中一个本身就是namespaces,属于root-scoped:
func (p *yourProvider) ListAllMetrics() []provider.CustomMetricInfo {
return []provider.CustomMetricInfo{
// these are mostly arbitrary examples
{
GroupResource: schema.GroupResource{Group: "", Resource: "pods"},
Metric: "packets-per-second",
Namespaced: true,
},
{
GroupResource: schema.GroupResource{Group: "", Resource: "services"},
Metric: "connections-per-second",
Namespaced: true,
},
{
GroupResource: schema.GroupResource{Group: "", Resource: "namespaces"},
Metric: "work-queue-length",
Namespaced: false,
},
}
}
接下来,需要实现获取metrics的实际的方法。这些方法获取的metrics可以描述任意k8s资源,包括root-scoped和namespaced-scoped。当然,这些metrics也可以以单个对象形式获取,也可以通过selector方式获取一组对象的metrics。
这里需要一个RESTMapper(用于映射resources和kinds)和动态客户端来获取集群中的对象列表。
type yourProvider struct {
client dynamic.Interface
mapper apimeta.RESTMapper
// just increment values when they're requested
values map[provider.CustomMetricInfo]int64
}
接着可以实现获取metrics的方法。这里,这些方法只是在获取metrics时递增metric值,在真正的适配器中,这些metrics需要从真正的后端获取。
这里提供假的"获取"操作,并构造一个结果对象返回。
// valueFor fetches a value from the fake list and increments it.
func (p *yourProvider) valueFor(info provider.CustomMetricInfo) (int64, error) {
// normalize the value so that you treat plural resources and singular
// resources the same (e.g. pods vs pod)
info, _, err := info.Normalized(p.mapper)
if err != nil {
return 0, err
}
value := p.values[info]
value += 1
p.values[info] = value
return value, nil
}
// metricFor constructs a result for a single metric value.
func (p *testingProvider) metricFor(value int64, name types.NamespacedName, info provider.CustomMetricInfo) (*custom_metrics.MetricValue, error) {
// construct a reference referring to the described object
objRef, err := helpers.ReferenceFor(p.mapper, name, info)
if err != nil {
return nil, err
}
return &custom_metrics.MetricValue{
DescribedObject: objRef,
MetricName: info.Metric,
// you'll want to use the actual timestamp in a real adapter
Timestamp: metav1.Time{time.Now()},
Value: *resource.NewMilliQuantity(value*100, resource.DecimalSI),
}, nil
}
接着要实现连个主要的方法,第一个是获取一个对象的单个metric值(例如,HorizontalPodAutoScaler中的对象metric类型):
func (p *yourProvider) GetMetricByName(name types.NamespacedName, info provider.CustomMetricInfo) (*custom_metrics.MetricValue, error) {
value, err := p.valueFor(info)
if err != nil {
return nil, err
}
return p.metricFor(value, name, info)
}
第二个是获取多个metric值,用于集合中的每个对象(例如HorizontalPodAutoScaler中的Pods metric类型)。
func (p *yourProvider) GetMetricBySelector(namespace string, selector labels.Selector, info provider.CustomMetricInfo) (*custom_metrics.MetricValueList, error) {
totalValue, err := p.valueFor(info)
if err != nil {
return nil, err
}
names, err := helpers.ListObjectNames(p.mapper, p.client, namespace, selector, info)
if err != nil {
return nil, err
}
res := make([]custom_metrics.MetricValue, len(names))
for i, name := range names {
// in a real adapter, you might want to consider pre-computing the
// object reference created in metricFor, instead of recomputing it
// for each object.
value, err := p.metricFor(100*totalValue/int64(len(res)), types.NamespacedName{Namespace: namespace, Name: name}, info)
if err != nil {
return nil, err
}
res[i] = *value
}
return &custom_metrics.MetricValueList{
Items: res,
}, nil
}
最后,将provider当作API server的插件即可。
2. 编写setup code
首先,引入下列包
package main
import (
"flag"
"os"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/util/logs"
basecmd "github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/cmd"
"github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider"
// make this the path to the provider that you just wrote
yourprov "github.com/user/repo/pkg/provider"
)
接着利用basecmd.AdapterBase结构帮助设置API server:
type YourAdapter struct {
basecmd.AdapterBase
// the message printed on startup
Message string
}
func main() {
logs.InitLogs()
defer logs.FlushLogs()
// initialize the flags, with one custom flag for the message
cmd := &YourAdapter{}
cmd.Flags().StringVar(&cmd.Message, "msg", "starting adapter...", "startup message")
cmd.Flags().AddGoFlagSet(flag.CommandLine) // make sure you get the glog flags
cmd.Flags().Parse(os.Args)
provider := cmd.makeProviderOrDie()
cmd.WithCustomMetrics(provider)
// you could also set up external metrics support,
// if your provider supported it:
// cmd.WithExternalMetrics(provider)
glog.Infof(cmd.Message)
if err := cmd.Run(wait.NeverStop); err != nil {
glog.Fatalf("unable to run custom metrics adapter: %v", err)
}
}
最后,只要给我们的provider添加一点设置代码即可。当然你的provider可能会有其它选项,比如你需要与后端metrics解决方案的连接配置,证书或者其它高级配置。对于上面编写的provider,安装代码如下所示:
func (a *SampleAdapter) makeProviderOrDie() provider.CustomMetricsProvider {
client, err := a.DynamicClient()
if err != nil {
glog.Fatalf("unable to construct dynamic client: %v", err)
}
mapper, err := a.RESTMapper()
if err != nil {
glog.Fatalf("unable to construct discovery REST mapper: %v", err)
}
return yourprov.NewProvider(client, mapper)
}
总结
本节介绍了如何去开发自己的custom Metrics API Server,主要是编写provider和setup code,对于我们后继即将分析的prometheus adapter具有一定的帮助作用,剥离底层实现,我们在第四节将主要关注adapter插件的metric相关逻辑。
更多推荐
所有评论(0)