Add admission metrics

This commit is contained in:
Joe Betz 2017-11-06 14:14:33 -08:00
parent b983cee8b8
commit 3940e4f053
5 changed files with 210 additions and 8 deletions

View File

@ -32,12 +32,15 @@ go_library(
"errors.go", "errors.go",
"handler.go", "handler.go",
"interfaces.go", "interfaces.go",
"metrics.go",
"plugins.go", "plugins.go",
], ],
importpath = "k8s.io/apiserver/pkg/admission", importpath = "k8s.io/apiserver/pkg/admission",
deps = [ deps = [
"//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
"//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",

View File

@ -16,22 +16,38 @@ limitations under the License.
package admission package admission
// chainAdmissionHandler is an instance of admission.Interface that performs admission control using a chain of admission handlers import "time"
type chainAdmissionHandler []Interface
// chainAdmissionHandler is an instance of admission.Interface that performs admission control using
// a chain of admission handlers
type chainAdmissionHandler []NamedHandler
// NewChainHandler creates a new chain handler from an array of handlers. Used for testing. // NewChainHandler creates a new chain handler from an array of handlers. Used for testing.
func NewChainHandler(handlers ...Interface) chainAdmissionHandler { func NewChainHandler(handlers ...NamedHandler) chainAdmissionHandler {
return chainAdmissionHandler(handlers) return chainAdmissionHandler(handlers)
} }
const (
stepValidating = "validating"
stepMutating = "mutating"
)
// Admit performs an admission control check using a chain of handlers, and returns immediately on first error // Admit performs an admission control check using a chain of handlers, and returns immediately on first error
func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error { func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error {
var err error
start := time.Now()
defer func() {
ObserveAdmissionStep(time.Since(start), err != nil, a, stepMutating)
}()
for _, handler := range admissionHandler { for _, handler := range admissionHandler {
if !handler.Handles(a.GetOperation()) { if !handler.Handles(a.GetOperation()) {
continue continue
} }
if mutator, ok := handler.(MutationInterface); ok { if mutator, ok := handler.(MutationInterface); ok {
err := mutator.Admit(a) t := time.Now()
err = mutator.Admit(a)
ObserveAdmissionController(time.Since(t), err != nil, handler, a)
if err != nil { if err != nil {
return err return err
} }
@ -42,12 +58,20 @@ func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error {
// Validate performs an admission control check using a chain of handlers, and returns immediately on first error // Validate performs an admission control check using a chain of handlers, and returns immediately on first error
func (admissionHandler chainAdmissionHandler) Validate(a Attributes) error { func (admissionHandler chainAdmissionHandler) Validate(a Attributes) error {
var err error
start := time.Now()
defer func() {
ObserveAdmissionStep(time.Since(start), err != nil, a, stepValidating)
}()
for _, handler := range admissionHandler { for _, handler := range admissionHandler {
if !handler.Handles(a.GetOperation()) { if !handler.Handles(a.GetOperation()) {
continue continue
} }
if validator, ok := handler.(ValidationInterface); ok { if validator, ok := handler.(ValidationInterface); ok {
err := validator.Validate(a) t := time.Now()
err = validator.Validate(a)
ObserveAdmissionController(time.Since(t), err != nil, handler, a)
if err != nil { if err != nil {
return err return err
} }

View File

@ -0,0 +1,161 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package admission
import (
"fmt"
"time"
"k8s.io/api/admissionregistration/v1alpha1"
"github.com/prometheus/client_golang/prometheus"
)
const (
namespace = "apiserver"
subsystem = "admission"
)
var (
latencyBuckets = prometheus.ExponentialBuckets(10000, 2.0, 8)
latencySummaryMaxAge = 5 * time.Hour
// Admission step metrics. Each step is identified by a distinct type label value.
stepMetrics = newAdmissionMetrics("step_",
[]string{"operation", "group", "version", "resource", "subresource", "type"},
"Admission sub-step %s, broken out for each operation and API resource and step type (validating or mutating).")
// Build-in admission controller metrics. Each admission controller is identified by name.
controllerMetrics = newAdmissionMetrics("controller_",
[]string{"name", "type", "operation", "group", "version", "resource", "subresource"},
"Admission controller %s, identified by name and broken out for each operation and API resource and type (validating or mutating).")
// External admission webhook metrics. Each webhook is identified by name.
externalWebhookMetrics = newAdmissionMetrics("external_webhook_",
[]string{"name", "type", "operation", "group", "version", "resource", "subresource"},
"External admission webhook %s, identified by name and broken out for each operation and API resource and type (validating or mutating).")
)
func init() {
stepMetrics.mustRegister()
controllerMetrics.mustRegister()
externalWebhookMetrics.mustRegister()
}
// namedHandler requires each admission.Interface be named, primarly for metrics tracking purposes.
type NamedHandler interface {
Interface
GetName() string
}
// ObserveAdmissionStep records admission related metrics for a admission step, identified by step type.
func ObserveAdmissionStep(elapsed time.Duration, rejected bool, attr Attributes, stepType string) {
gvr := attr.GetResource()
stepMetrics.observe(elapsed, rejected, string(attr.GetOperation()), gvr.Group, gvr.Version, gvr.Resource, attr.GetSubresource(), stepType)
}
// ObserveAdmissionController records admission related metrics for a build-in admission controller, identified by it's plugin handler name.
func ObserveAdmissionController(elapsed time.Duration, rejected bool, handler NamedHandler, attr Attributes) {
t := typeToLabel(handler)
gvr := attr.GetResource()
controllerMetrics.observe(elapsed, rejected, handler.GetName(), t, string(attr.GetOperation()), gvr.Group, gvr.Version, gvr.Resource, attr.GetSubresource())
}
// ObserveExternalWebhook records admission related metrics for a external admission webhook.
func ObserveExternalWebhook(elapsed time.Duration, rejected bool, hook *v1alpha1.ExternalAdmissionHook, attr Attributes) {
t := "validating" // TODO: pass in type (validating|mutating) once mutating webhook functionality has been implemented
gvr := attr.GetResource()
externalWebhookMetrics.observe(elapsed, rejected, hook.Name, t, string(attr.GetOperation()), gvr.Group, gvr.Version, gvr.Resource, attr.GetSubresource())
}
func typeToLabel(i Interface) string {
switch i.(type) {
case ValidationInterface:
return "validating"
case MutationInterface:
return "mutating"
default:
return "UNRECOGNIZED_ADMISSION_TYPE"
}
}
type admissionMetrics struct {
total *prometheus.CounterVec
rejectedTotal *prometheus.CounterVec
latencies *prometheus.HistogramVec
latenciesSummary *prometheus.SummaryVec
}
func (m *admissionMetrics) mustRegister() {
prometheus.MustRegister(m.total)
prometheus.MustRegister(m.rejectedTotal)
prometheus.MustRegister(m.latencies)
prometheus.MustRegister(m.latenciesSummary)
}
func (m *admissionMetrics) observe(elapsed time.Duration, rejected bool, labels ...string) {
elapsedMicroseconds := float64(elapsed / time.Microsecond)
m.total.WithLabelValues(labels...).Inc()
if rejected {
m.rejectedTotal.WithLabelValues(labels...).Inc()
}
m.latencies.WithLabelValues(labels...).Observe(elapsedMicroseconds)
m.latenciesSummary.WithLabelValues(labels...).Observe(elapsedMicroseconds)
}
func newAdmissionMetrics(name string, labels []string, helpTemplate string) *admissionMetrics {
return &admissionMetrics{
total: prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: fmt.Sprintf("%stotal", name),
Help: fmt.Sprintf(helpTemplate, "count"),
},
labels,
),
rejectedTotal: prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: fmt.Sprintf("%srejected_total", name),
Help: fmt.Sprintf(helpTemplate, "rejected count"),
},
labels,
),
latencies: prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: fmt.Sprintf("%slatencies", name),
Help: fmt.Sprintf(helpTemplate, "latency histogram"),
Buckets: latencyBuckets,
},
labels,
),
latenciesSummary: prometheus.NewSummaryVec(
prometheus.SummaryOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: fmt.Sprintf("%slatencies_summary", name),
Help: fmt.Sprintf(helpTemplate, "latency summary"),
MaxAge: latencySummaryMaxAge,
},
labels,
),
}
}

View File

@ -26,6 +26,7 @@ import (
"net" "net"
"net/url" "net/url"
"sync" "sync"
"time"
"github.com/golang/glog" "github.com/golang/glog"
lru "github.com/hashicorp/golang-lru" lru "github.com/hashicorp/golang-lru"
@ -306,7 +307,9 @@ func (a *GenericAdmissionWebhook) Admit(attr admission.Attributes) error {
go func(hook *v1alpha1.Webhook) { go func(hook *v1alpha1.Webhook) {
defer wg.Done() defer wg.Done()
t := time.Now()
err := a.callHook(ctx, hook, versionedAttr) err := a.callHook(ctx, hook, versionedAttr)
admission.ObserveExternalWebhook(time.Since(t), err != nil, hook, attr)
if err == nil { if err == nil {
return return
} }

View File

@ -39,6 +39,16 @@ type Plugins struct {
registry map[string]Factory registry map[string]Factory
} }
// pluginHandler associates name with a admission.Interface handler.
type pluginHandler struct {
Interface
name string
}
func (h *pluginHandler) GetName() string {
return h.name
}
// All registered admission options. // All registered admission options.
var ( var (
// PluginEnabledFn checks whether a plugin is enabled. By default, if you ask about it, it's enabled. // PluginEnabledFn checks whether a plugin is enabled. By default, if you ask about it, it's enabled.
@ -121,7 +131,7 @@ func splitStream(config io.Reader) (io.Reader, io.Reader, error) {
// NewFromPlugins returns an admission.Interface that will enforce admission control decisions of all // NewFromPlugins returns an admission.Interface that will enforce admission control decisions of all
// the given plugins. // the given plugins.
func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigProvider, pluginInitializer PluginInitializer) (Interface, error) { func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigProvider, pluginInitializer PluginInitializer) (Interface, error) {
plugins := []Interface{} handlers := []NamedHandler{}
for _, pluginName := range pluginNames { for _, pluginName := range pluginNames {
pluginConfig, err := configProvider.ConfigFor(pluginName) pluginConfig, err := configProvider.ConfigFor(pluginName)
if err != nil { if err != nil {
@ -133,10 +143,11 @@ func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigPro
return nil, err return nil, err
} }
if plugin != nil { if plugin != nil {
plugins = append(plugins, plugin) handler := &pluginHandler{Interface: plugin, name: pluginName}
handlers = append(handlers, handler)
} }
} }
return chainAdmissionHandler(plugins), nil return chainAdmissionHandler(handlers), nil
} }
// InitPlugin creates an instance of the named interface. // InitPlugin creates an instance of the named interface.