Update custom-metrics-apiserver and metrics-server

This commit is contained in:
Johannes Würbach 2020-09-27 22:14:53 +02:00
parent 4c673534f2
commit b480e45a67
No known key found for this signature in database
GPG key ID: 74DB0F4D956CCCE3
915 changed files with 63694 additions and 106514 deletions

View file

@ -60,6 +60,7 @@ import (
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/apiserver/pkg/server/routes"
serverstore "k8s.io/apiserver/pkg/server/storage"
utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol"
"k8s.io/client-go/informers"
restclient "k8s.io/client-go/rest"
"k8s.io/component-base/logs"
@ -107,6 +108,9 @@ type Config struct {
AdmissionControl admission.Interface
CorsAllowedOriginList []string
// FlowControl, if not nil, gives priority and fairness to request handling
FlowControl utilflowcontrol.Interface
EnableIndex bool
EnableProfiling bool
EnableDiscovery bool
@ -193,6 +197,12 @@ type Config struct {
// Predicate which is true for paths of long-running http requests
LongRunningFunc apirequest.LongRunningRequestCheck
// GoawayChance is the probability that send a GOAWAY to HTTP/2 clients. When client received
// GOAWAY, the in-flight requests will not be affected and new requests will use
// a new TCP connection to triggering re-balancing to another server behind the load balance.
// Default to 0, means never send GOAWAY. Max is 0.02 to prevent break the apiserver.
GoawayChance float64
// MergedResourceConfig indicates which groupVersion enabled and its resources enabled/disabled.
// This is composed of genericapiserver defaultAPIResourceConfig and those parsed from flags.
// If not specify any in flags, then genericapiserver will only enable defaultAPIResourceConfig.
@ -606,6 +616,21 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
}
}
const priorityAndFairnessConfigConsumerHookName = "priority-and-fairness-config-consumer"
if s.isPostStartHookRegistered(priorityAndFairnessConfigConsumerHookName) {
} else if c.FlowControl != nil {
err := s.AddPostStartHook(priorityAndFairnessConfigConsumerHookName, func(context PostStartHookContext) error {
go c.FlowControl.Run(context.StopCh)
return nil
})
if err != nil {
return nil, err
}
// TODO(yue9944882): plumb pre-shutdown-hook for request-management system?
} else {
klog.V(3).Infof("Not requested to run hook %s", priorityAndFairnessConfigConsumerHookName)
}
for _, delegateCheck := range delegationTarget.HealthzChecks() {
skip := false
for _, existingCheck := range c.HealthzChecks {
@ -638,7 +663,11 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
handler := genericapifilters.WithAuthorization(apiHandler, c.Authorization.Authorizer, c.Serializer)
handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.LongRunningFunc)
if c.FlowControl != nil {
handler = genericfilters.WithPriorityAndFairness(handler, c.LongRunningFunc, c.FlowControl)
} else {
handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.LongRunningFunc)
}
handler = genericapifilters.WithImpersonation(handler, c.Authorization.Authorizer, c.Serializer)
handler = genericapifilters.WithAudit(handler, c.AuditBackend, c.AuditPolicyChecker, c.LongRunningFunc)
failedHandler := genericapifilters.Unauthorized(c.Serializer, c.Authentication.SupportsBasicAuth)
@ -648,6 +677,10 @@ func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.LongRunningFunc, c.RequestTimeout)
handler = genericfilters.WithWaitGroup(handler, c.LongRunningFunc, c.HandlerChainWaitGroup)
handler = genericapifilters.WithRequestInfo(handler, c.RequestInfoResolver)
if c.SecureServing != nil && !c.SecureServing.DisableHTTP2 && c.GoawayChance > 0 {
handler = genericfilters.WithProbabilisticGoaway(handler, c.GoawayChance)
}
handler = genericapifilters.WithCacheControl(handler)
handler = genericfilters.WithPanicRecovery(handler)
return handler
}

View file

@ -97,10 +97,7 @@ func NewDynamicCAFromConfigMapController(purpose, namespace, name, key string, k
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), fmt.Sprintf("DynamicConfigMapCABundle-%s", purpose)),
preRunCaches: []cache.InformerSynced{uncastConfigmapInformer.HasSynced},
}
if err := c.loadCABundle(); err != nil {
// don't fail, but do print out a message
klog.Warningf("unable to load initial CA bundle for: %q due to: %s", c.name, err)
}
uncastConfigmapInformer.AddEventHandler(cache.FilteringResourceEventHandler{
FilterFunc: func(obj interface{}) bool {
if cast, ok := obj.(*corev1.ConfigMap); ok {
@ -217,7 +214,7 @@ func (c *ConfigMapCAController) Run(workers int, stopCh <-chan struct{}) {
go wait.Until(c.runWorker, time.Second, stopCh)
// start timer that rechecks every minute, just in case. this also serves to prime the controller quickly.
_ = wait.PollImmediateUntil(FileRefreshDuration, func() (bool, error) {
go wait.PollImmediateUntil(FileRefreshDuration, func() (bool, error) {
c.queue.Add(workItemKey)
return false, nil
}, stopCh)

View file

@ -126,6 +126,7 @@ func (c *DynamicFileCAContent) loadCABundle() error {
return err
}
c.caBundle.Store(caBundleAndVerifier)
klog.V(2).Infof("Loaded a new CA Bundle and Verifier for %q", c.Name())
for _, listener := range c.listeners {
listener.Enqueue()
@ -170,7 +171,7 @@ func (c *DynamicFileCAContent) Run(workers int, stopCh <-chan struct{}) {
go wait.Until(c.runWorker, time.Second, stopCh)
// start timer that rechecks every minute, just in case. this also serves to prime the controller quickly.
_ = wait.PollImmediateUntil(FileRefreshDuration, func() (bool, error) {
go wait.PollImmediateUntil(FileRefreshDuration, func() (bool, error) {
c.queue.Add(workItemKey)
return false, nil
}, stopCh)

View file

@ -29,8 +29,8 @@ import (
"k8s.io/klog"
)
// DynamicFileServingContent provides a CertKeyContentProvider that can dynamically react to new file content
type DynamicFileServingContent struct {
// DynamicCertKeyPairContent provides a CertKeyContentProvider that can dynamically react to new file content
type DynamicCertKeyPairContent struct {
name string
// certFile is the name of the certificate file to read.
@ -39,7 +39,7 @@ type DynamicFileServingContent struct {
keyFile string
// servingCert is a certKeyContent that contains the last read, non-zero length content of the key and cert
servingCert atomic.Value
certKeyPair atomic.Value
listeners []Listener
@ -47,24 +47,24 @@ type DynamicFileServingContent struct {
queue workqueue.RateLimitingInterface
}
var _ Notifier = &DynamicFileServingContent{}
var _ CertKeyContentProvider = &DynamicFileServingContent{}
var _ ControllerRunner = &DynamicFileServingContent{}
var _ Notifier = &DynamicCertKeyPairContent{}
var _ CertKeyContentProvider = &DynamicCertKeyPairContent{}
var _ ControllerRunner = &DynamicCertKeyPairContent{}
// NewDynamicServingContentFromFiles returns a dynamic CertKeyContentProvider based on a cert and key filename
func NewDynamicServingContentFromFiles(purpose, certFile, keyFile string) (*DynamicFileServingContent, error) {
func NewDynamicServingContentFromFiles(purpose, certFile, keyFile string) (*DynamicCertKeyPairContent, error) {
if len(certFile) == 0 || len(keyFile) == 0 {
return nil, fmt.Errorf("missing filename for serving cert")
}
name := fmt.Sprintf("%s::%s::%s", purpose, certFile, keyFile)
ret := &DynamicFileServingContent{
ret := &DynamicCertKeyPairContent{
name: name,
certFile: certFile,
keyFile: keyFile,
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), fmt.Sprintf("DynamicCABundle-%s", purpose)),
}
if err := ret.loadServingCert(); err != nil {
if err := ret.loadCertKeyPair(); err != nil {
return nil, err
}
@ -72,12 +72,12 @@ func NewDynamicServingContentFromFiles(purpose, certFile, keyFile string) (*Dyna
}
// AddListener adds a listener to be notified when the serving cert content changes.
func (c *DynamicFileServingContent) AddListener(listener Listener) {
func (c *DynamicCertKeyPairContent) AddListener(listener Listener) {
c.listeners = append(c.listeners, listener)
}
// loadServingCert determines the next set of content for the file.
func (c *DynamicFileServingContent) loadServingCert() error {
func (c *DynamicCertKeyPairContent) loadCertKeyPair() error {
cert, err := ioutil.ReadFile(c.certFile)
if err != nil {
return err
@ -102,12 +102,13 @@ func (c *DynamicFileServingContent) loadServingCert() error {
}
// check to see if we have a change. If the values are the same, do nothing.
existing, ok := c.servingCert.Load().(*certKeyContent)
existing, ok := c.certKeyPair.Load().(*certKeyContent)
if ok && existing != nil && existing.Equal(newCertKey) {
return nil
}
c.servingCert.Store(newCertKey)
c.certKeyPair.Store(newCertKey)
klog.V(2).Infof("Loaded a new cert/key pair for %q", c.Name())
for _, listener := range c.listeners {
listener.Enqueue()
@ -117,12 +118,12 @@ func (c *DynamicFileServingContent) loadServingCert() error {
}
// RunOnce runs a single sync loop
func (c *DynamicFileServingContent) RunOnce() error {
return c.loadServingCert()
func (c *DynamicCertKeyPairContent) RunOnce() error {
return c.loadCertKeyPair()
}
// Run starts the controller and blocks until stopCh is closed.
func (c *DynamicFileServingContent) Run(workers int, stopCh <-chan struct{}) {
func (c *DynamicCertKeyPairContent) Run(workers int, stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
defer c.queue.ShutDown()
@ -133,7 +134,7 @@ func (c *DynamicFileServingContent) Run(workers int, stopCh <-chan struct{}) {
go wait.Until(c.runWorker, time.Second, stopCh)
// start timer that rechecks every minute, just in case. this also serves to prime the controller quickly.
_ = wait.PollImmediateUntil(FileRefreshDuration, func() (bool, error) {
go wait.PollImmediateUntil(FileRefreshDuration, func() (bool, error) {
c.queue.Add(workItemKey)
return false, nil
}, stopCh)
@ -143,19 +144,19 @@ func (c *DynamicFileServingContent) Run(workers int, stopCh <-chan struct{}) {
<-stopCh
}
func (c *DynamicFileServingContent) runWorker() {
func (c *DynamicCertKeyPairContent) runWorker() {
for c.processNextWorkItem() {
}
}
func (c *DynamicFileServingContent) processNextWorkItem() bool {
func (c *DynamicCertKeyPairContent) processNextWorkItem() bool {
dsKey, quit := c.queue.Get()
if quit {
return false
}
defer c.queue.Done(dsKey)
err := c.loadServingCert()
err := c.loadCertKeyPair()
if err == nil {
c.queue.Forget(dsKey)
return true
@ -168,12 +169,12 @@ func (c *DynamicFileServingContent) processNextWorkItem() bool {
}
// Name is just an identifier
func (c *DynamicFileServingContent) Name() string {
func (c *DynamicCertKeyPairContent) Name() string {
return c.name
}
// CurrentCertKeyContent provides serving cert byte content
func (c *DynamicFileServingContent) CurrentCertKeyContent() ([]byte, []byte) {
certKeyContent := c.servingCert.Load().(*certKeyContent)
// CurrentCertKeyContent provides cert and key byte content
func (c *DynamicCertKeyPairContent) CurrentCertKeyContent() ([]byte, []byte) {
certKeyContent := c.certKeyPair.Load().(*certKeyContent)
return certKeyContent.cert, certKeyContent.key
}

View file

@ -18,7 +18,7 @@ package dynamiccertificates
// DynamicFileSNIContent provides a SNICertKeyContentProvider that can dynamically react to new file content
type DynamicFileSNIContent struct {
*DynamicFileServingContent
*DynamicCertKeyPairContent
sniNames []string
}
@ -34,10 +34,10 @@ func NewDynamicSNIContentFromFiles(purpose, certFile, keyFile string, sniNames .
}
ret := &DynamicFileSNIContent{
DynamicFileServingContent: servingContent,
DynamicCertKeyPairContent: servingContent,
sniNames: sniNames,
}
if err := ret.loadServingCert(); err != nil {
if err := ret.loadCertKeyPair(); err != nil {
return nil, err
}

View file

@ -20,9 +20,10 @@ import (
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"strings"
v1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/klog"
)
@ -51,7 +52,7 @@ func (c *DynamicServingCertificateController) BuildNamedCertificates(sniCerts []
klog.V(2).Infof("loaded SNI cert [%d/%q]: %s", i, c.sniCerts[i].Name(), GetHumanCertDetail(x509Cert))
if c.eventRecorder != nil {
c.eventRecorder.Eventf(nil, nil, v1.EventTypeWarning, "TLSConfigChanged", "SNICertificateReload", "loaded SNI cert [%d/%q]: %s with explicit names %v", i, c.sniCerts[i].Name(), GetHumanCertDetail(x509Cert), names)
c.eventRecorder.Eventf(&corev1.ObjectReference{Name: c.sniCerts[i].Name()}, nil, corev1.EventTypeWarning, "TLSConfigChanged", "SNICertificateReload", "loaded SNI cert [%d/%q]: %s with explicit names %v", i, c.sniCerts[i].Name(), GetHumanCertDetail(x509Cert), names)
}
if len(names) == 0 {
@ -76,7 +77,10 @@ func getCertificateNames(cert *x509.Certificate) []string {
var names []string
cn := cert.Subject.CommonName
if cn == "*" || len(validation.IsDNS1123Subdomain(strings.TrimPrefix(cn, "*."))) == 0 {
cnIsIP := net.ParseIP(cn) != nil
cnIsValidDomain := cn == "*" || len(validation.IsDNS1123Subdomain(strings.TrimPrefix(cn, "*."))) == 0
// don't use the CN if it is a valid IP because our IP serving detection may unexpectedly use it to terminate the connection.
if !cnIsIP && cnIsValidDomain {
names = append(names, cn)
}
for _, san := range cert.DNSNames {

View file

@ -19,8 +19,6 @@ package dynamiccertificates
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
)
type staticCAContent struct {
@ -30,19 +28,6 @@ type staticCAContent struct {
var _ CAContentProvider = &staticCAContent{}
// NewStaticCAContentFromFile returns a CAContentProvider based on a filename
func NewStaticCAContentFromFile(filename string) (CAContentProvider, error) {
if len(filename) == 0 {
return nil, fmt.Errorf("missing filename for ca bundle")
}
caBundle, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return NewStaticCAContent(filename, caBundle)
}
// NewStaticCAContent returns a CAContentProvider that always returns the same value
func NewStaticCAContent(name string, caBundle []byte) (CAContentProvider, error) {
caBundleAndVerifier, err := newCABundleAndVerifier(name, caBundle)
@ -81,48 +66,6 @@ type staticSNICertKeyContent struct {
sniNames []string
}
// NewStaticCertKeyContentFromFiles returns a CertKeyContentProvider based on a filename
func NewStaticCertKeyContentFromFiles(certFile, keyFile string) (CertKeyContentProvider, error) {
if len(certFile) == 0 {
return nil, fmt.Errorf("missing filename for certificate")
}
if len(keyFile) == 0 {
return nil, fmt.Errorf("missing filename for key")
}
certPEMBlock, err := ioutil.ReadFile(certFile)
if err != nil {
return nil, err
}
keyPEMBlock, err := ioutil.ReadFile(keyFile)
if err != nil {
return nil, err
}
return NewStaticCertKeyContent(fmt.Sprintf("cert: %s, key: %s", certFile, keyFile), certPEMBlock, keyPEMBlock)
}
// NewStaticSNICertKeyContentFromFiles returns a SNICertKeyContentProvider based on a filename
func NewStaticSNICertKeyContentFromFiles(certFile, keyFile string, sniNames ...string) (SNICertKeyContentProvider, error) {
if len(certFile) == 0 {
return nil, fmt.Errorf("missing filename for certificate")
}
if len(keyFile) == 0 {
return nil, fmt.Errorf("missing filename for key")
}
certPEMBlock, err := ioutil.ReadFile(certFile)
if err != nil {
return nil, err
}
keyPEMBlock, err := ioutil.ReadFile(keyFile)
if err != nil {
return nil, err
}
return NewStaticSNICertKeyContent(fmt.Sprintf("cert: %s, key: %s", certFile, keyFile), certPEMBlock, keyPEMBlock, sniNames...)
}
// NewStaticCertKeyContent returns a CertKeyContentProvider that always returns the same value
func NewStaticCertKeyContent(name string, cert, key []byte) (CertKeyContentProvider, error) {
// Ensure that the key matches the cert and both are valid

View file

@ -21,11 +21,11 @@ import (
"crypto/x509"
"errors"
"fmt"
"net"
"sync/atomic"
"time"
v1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/tools/events"
@ -40,7 +40,7 @@ const workItemKey = "key"
type DynamicServingCertificateController struct {
// baseTLSConfig is the static portion of the tlsConfig for serving to clients. It is copied and the copy is mutated
// based on the dynamic cert state.
baseTLSConfig tls.Config
baseTLSConfig *tls.Config
// clientCA provides the very latest content of the ca bundle
clientCA CAContentProvider
@ -64,7 +64,7 @@ var _ Listener = &DynamicServingCertificateController{}
// NewDynamicServingCertificateController returns a controller that can be used to keep a TLSConfig up to date.
func NewDynamicServingCertificateController(
baseTLSConfig tls.Config,
baseTLSConfig *tls.Config,
clientCA CAContentProvider,
servingCert CertKeyContentProvider,
sniCerts []SNICertKeyContentProvider,
@ -94,7 +94,28 @@ func (c *DynamicServingCertificateController) GetConfigForClient(clientHello *tl
return nil, errors.New("dynamiccertificates: unexpected config type")
}
return tlsConfig.Clone(), nil
tlsConfigCopy := tlsConfig.Clone()
// if the client set SNI information, just use our "normal" SNI flow
if len(clientHello.ServerName) > 0 {
return tlsConfigCopy, nil
}
// if the client didn't set SNI, then we need to inspect the requested IP so that we can choose
// a certificate from our list if we specifically handle that IP. This can happen when an IP is specifically mapped by name.
host, _, err := net.SplitHostPort(clientHello.Conn.LocalAddr().String())
if err != nil {
return tlsConfigCopy, nil
}
ipCert, ok := tlsConfigCopy.NameToCertificate[host]
if !ok {
return tlsConfigCopy, nil
}
tlsConfigCopy.Certificates = []tls.Certificate{*ipCert}
tlsConfigCopy.NameToCertificate = nil
return tlsConfigCopy, nil
}
// newTLSContent determines the next set of content for overriding the baseTLSConfig.
@ -156,7 +177,7 @@ func (c *DynamicServingCertificateController) syncCerts() error {
for i, cert := range newClientCAs {
klog.V(2).Infof("loaded client CA [%d/%q]: %s", i, c.clientCA.Name(), GetHumanCertDetail(cert))
if c.eventRecorder != nil {
c.eventRecorder.Eventf(nil, nil, v1.EventTypeWarning, "TLSConfigChanged", "CACertificateReload", "loaded client CA [%d/%q]: %s", i, c.clientCA.Name(), GetHumanCertDetail(cert))
c.eventRecorder.Eventf(&corev1.ObjectReference{Name: c.clientCA.Name()}, nil, corev1.EventTypeWarning, "TLSConfigChanged", "CACertificateReload", "loaded client CA [%d/%q]: %s", i, c.clientCA.Name(), GetHumanCertDetail(cert))
}
newClientCAPool.AddCert(cert)
@ -178,7 +199,7 @@ func (c *DynamicServingCertificateController) syncCerts() error {
klog.V(2).Infof("loaded serving cert [%q]: %s", c.servingCert.Name(), GetHumanCertDetail(x509Cert))
if c.eventRecorder != nil {
c.eventRecorder.Eventf(nil, nil, v1.EventTypeWarning, "TLSConfigChanged", "ServingCertificateReload", "loaded serving cert [%q]: %s", c.clientCA.Name(), GetHumanCertDetail(x509Cert))
c.eventRecorder.Eventf(&corev1.ObjectReference{Name: c.servingCert.Name()}, nil, corev1.EventTypeWarning, "TLSConfigChanged", "ServingCertificateReload", "loaded serving cert [%q]: %s", c.servingCert.Name(), GetHumanCertDetail(x509Cert))
}
newTLSConfigCopy.Certificates = []tls.Certificate{cert}

View file

@ -25,7 +25,7 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/apis/apiserver"
"k8s.io/apiserver/pkg/apis/apiserver/install"
"k8s.io/apiserver/pkg/apis/apiserver/v1alpha1"
"k8s.io/apiserver/pkg/apis/apiserver/v1beta1"
"k8s.io/utils/path"
"sigs.k8s.io/yaml"
)
@ -51,7 +51,7 @@ func ReadEgressSelectorConfiguration(configFilePath string) (*apiserver.EgressSe
if err != nil {
return nil, fmt.Errorf("unable to read egress selector configuration from %q [%v]", configFilePath, err)
}
var decodedConfig v1alpha1.EgressSelectorConfiguration
var decodedConfig v1beta1.EgressSelectorConfiguration
err = yaml.Unmarshal(data, &decodedConfig)
if err != nil {
// we got an error where the decode wasn't related to a missing type
@ -78,99 +78,155 @@ func ValidateEgressSelectorConfiguration(config *apiserver.EgressSelectorConfigu
return allErrs // Treating a nil configuration as valid
}
for _, service := range config.EgressSelections {
base := field.NewPath("service", "connection")
switch service.Connection.Type {
case "direct":
allErrs = append(allErrs, validateDirectConnection(service.Connection, base)...)
case "http-connect":
allErrs = append(allErrs, validateHTTPConnection(service.Connection, base)...)
fldPath := field.NewPath("service", "connection")
switch service.Connection.ProxyProtocol {
case apiserver.ProtocolDirect:
allErrs = append(allErrs, validateDirectConnection(service.Connection, fldPath)...)
case apiserver.ProtocolHTTPConnect:
allErrs = append(allErrs, validateHTTPConnectTransport(service.Connection.Transport, fldPath)...)
case apiserver.ProtocolGRPC:
allErrs = append(allErrs, validateGRPCTransport(service.Connection.Transport, fldPath)...)
default:
allErrs = append(allErrs, field.NotSupported(
base.Child("type"),
service.Connection.Type,
[]string{"direct", "http-connect"}))
fldPath.Child("protocol"),
service.Connection.ProxyProtocol,
[]string{
string(apiserver.ProtocolDirect),
string(apiserver.ProtocolHTTPConnect),
string(apiserver.ProtocolGRPC),
}))
}
}
return allErrs
}
func validateHTTPConnectTransport(transport *apiserver.Transport, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if transport == nil {
allErrs = append(allErrs, field.Required(
fldPath.Child("transport"),
"transport must be set for HTTPConnect"))
return allErrs
}
if transport.TCP != nil && transport.UDS != nil {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("tcp"),
transport.TCP,
"TCP and UDS cannot both be set"))
} else if transport.TCP == nil && transport.UDS == nil {
allErrs = append(allErrs, field.Required(
fldPath.Child("tcp"),
"One of TCP or UDS must be set"))
} else if transport.TCP != nil {
allErrs = append(allErrs, validateTCPConnection(transport.TCP, fldPath)...)
} else if transport.UDS != nil {
allErrs = append(allErrs, validateUDSConnection(transport.UDS, fldPath)...)
}
return allErrs
}
func validateGRPCTransport(transport *apiserver.Transport, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if transport == nil {
allErrs = append(allErrs, field.Required(
fldPath.Child("transport"),
"transport must be set for GRPC"))
return allErrs
}
if transport.UDS != nil {
allErrs = append(allErrs, validateUDSConnection(transport.UDS, fldPath)...)
} else {
allErrs = append(allErrs, field.Required(
fldPath.Child("uds"),
"UDS must be set with GRPC"))
}
return allErrs
}
func validateDirectConnection(connection apiserver.Connection, fldPath *field.Path) field.ErrorList {
if connection.HTTPConnect != nil {
if connection.Transport != nil {
return field.ErrorList{field.Invalid(
fldPath.Child("httpConnect"),
fldPath.Child("transport"),
"direct",
"httpConnect config should be absent for direct connect"),
"Transport config should be absent for direct connect"),
}
}
return nil
}
func validateHTTPConnection(connection apiserver.Connection, fldPath *field.Path) field.ErrorList {
func validateUDSConnection(udsConfig *apiserver.UDSTransport, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if connection.HTTPConnect == nil {
if udsConfig.UDSName == "" {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("httpConnect"),
fldPath.Child("udsName"),
"nil",
"httpConnect config should be present for http-connect"))
} else if strings.HasPrefix(connection.HTTPConnect.URL, "https://") {
if connection.HTTPConnect.CABundle == "" {
"UDSName should be present for UDS connections"))
}
return allErrs
}
func validateTCPConnection(tcpConfig *apiserver.TCPTransport, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if strings.HasPrefix(tcpConfig.URL, "http://") {
if tcpConfig.TLSConfig != nil {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("httpConnect", "caBundle"),
fldPath.Child("tlsConfig"),
"nil",
"http-connect via https requires caBundle"))
} else if exists, err := path.Exists(path.CheckFollowSymlink, connection.HTTPConnect.CABundle); exists == false || err != nil {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("httpConnect", "caBundle"),
connection.HTTPConnect.CABundle,
"http-connect ca bundle does not exist"))
}
if connection.HTTPConnect.ClientCert == "" {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("httpConnect", "clientCert"),
"nil",
"http-connect via https requires clientCert"))
} else if exists, err := path.Exists(path.CheckFollowSymlink, connection.HTTPConnect.ClientCert); exists == false || err != nil {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("httpConnect", "clientCert"),
connection.HTTPConnect.ClientCert,
"http-connect client cert does not exist"))
}
if connection.HTTPConnect.ClientKey == "" {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("httpConnect", "clientKey"),
"nil",
"http-connect via https requires clientKey"))
} else if exists, err := path.Exists(path.CheckFollowSymlink, connection.HTTPConnect.ClientKey); exists == false || err != nil {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("httpConnect", "clientKey"),
connection.HTTPConnect.ClientKey,
"http-connect client key does not exist"))
}
} else if strings.HasPrefix(connection.HTTPConnect.URL, "http://") {
if connection.HTTPConnect.CABundle != "" {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("httpConnect", "caBundle"),
connection.HTTPConnect.CABundle,
"http-connect via http does not support caBundle"))
}
if connection.HTTPConnect.ClientCert != "" {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("httpConnect", "clientCert"),
connection.HTTPConnect.ClientCert,
"http-connect via http does not support clientCert"))
}
if connection.HTTPConnect.ClientKey != "" {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("httpConnect", "clientKey"),
connection.HTTPConnect.ClientKey,
"http-connect via http does not support clientKey"))
"TLSConfig config should not be present when using HTTP"))
}
} else if strings.HasPrefix(tcpConfig.URL, "https://") {
return validateTLSConfig(tcpConfig.TLSConfig, fldPath)
} else {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("httpConnect", "url"),
connection.HTTPConnect.URL,
fldPath.Child("url"),
tcpConfig.URL,
"supported connection protocols are http:// and https://"))
}
return allErrs
}
func validateTLSConfig(tlsConfig *apiserver.TLSConfig, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if tlsConfig == nil {
allErrs = append(allErrs, field.Required(
fldPath.Child("tlsConfig"),
"TLSConfig must be present when using HTTPS"))
return allErrs
}
if tlsConfig.CABundle != "" {
if exists, err := path.Exists(path.CheckFollowSymlink, tlsConfig.CABundle); exists == false || err != nil {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("tlsConfig", "caBundle"),
tlsConfig.CABundle,
"TLS config ca bundle does not exist"))
}
}
if tlsConfig.ClientCert == "" {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("tlsConfig", "clientCert"),
"nil",
"Using TLS requires clientCert"))
} else if exists, err := path.Exists(path.CheckFollowSymlink, tlsConfig.ClientCert); exists == false || err != nil {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("tlsConfig", "clientCert"),
tlsConfig.ClientCert,
"TLS client cert does not exist"))
}
if tlsConfig.ClientKey == "" {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("tlsConfig", "clientKey"),
"nil",
"Using TLS requires requires clientKey"))
} else if exists, err := path.Exists(path.CheckFollowSymlink, tlsConfig.ClientKey); exists == false || err != nil {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("tlsConfig", "clientKey"),
tlsConfig.ClientKey,
"TLS client key does not exist"))
}
return allErrs
}

View file

@ -23,13 +23,20 @@ import (
"crypto/x509"
"fmt"
"io/ioutil"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apiserver/pkg/apis/apiserver"
"k8s.io/klog"
"net"
"net/http"
"net/url"
"strings"
"time"
"google.golang.org/grpc"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apiserver/pkg/apis/apiserver"
egressmetrics "k8s.io/apiserver/pkg/server/egressselector/metrics"
"k8s.io/klog"
utiltrace "k8s.io/utils/trace"
client "sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/client"
)
var directDialer utilnet.DialFunc = http.DefaultTransport.(*http.Transport).DialContext
@ -94,67 +101,236 @@ func lookupServiceName(name string) (EgressType, error) {
return -1, fmt.Errorf("unrecognized service name %s", name)
}
func createConnectDialer(connectConfig *apiserver.HTTPConnectConfig) (utilnet.DialFunc, error) {
clientCert := connectConfig.ClientCert
clientKey := connectConfig.ClientKey
caCert := connectConfig.CABundle
proxyURL, err := url.Parse(connectConfig.URL)
func tunnelHTTPConnect(proxyConn net.Conn, proxyAddress, addr string) (net.Conn, error) {
fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, "127.0.0.1")
br := bufio.NewReader(proxyConn)
res, err := http.ReadResponse(br, nil)
if err != nil {
return nil, fmt.Errorf("invalid proxy server url %q: %v", connectConfig.URL, err)
proxyConn.Close()
return nil, fmt.Errorf("reading HTTP response from CONNECT to %s via proxy %s failed: %v",
addr, proxyAddress, err)
}
if res.StatusCode != 200 {
proxyConn.Close()
return nil, fmt.Errorf("proxy error from %s while dialing %s, code %d: %v",
proxyAddress, addr, res.StatusCode, res.Status)
}
proxyAddress := proxyURL.Host
// It's safe to discard the bufio.Reader here and return the
// original TCP conn directly because we only use this for
// TLS, and in TLS the client speaks first, so we know there's
// no unbuffered data. But we can double-check.
if br.Buffered() > 0 {
proxyConn.Close()
return nil, fmt.Errorf("unexpected %d bytes of buffered data from CONNECT proxy %q",
br.Buffered(), proxyAddress)
}
return proxyConn, nil
}
type proxier interface {
// proxy returns a connection to addr.
proxy(addr string) (net.Conn, error)
}
var _ proxier = &httpConnectProxier{}
type httpConnectProxier struct {
conn net.Conn
proxyAddress string
}
func (t *httpConnectProxier) proxy(addr string) (net.Conn, error) {
return tunnelHTTPConnect(t.conn, t.proxyAddress, addr)
}
var _ proxier = &grpcProxier{}
type grpcProxier struct {
tunnel client.Tunnel
}
func (g *grpcProxier) proxy(addr string) (net.Conn, error) {
return g.tunnel.Dial("tcp", addr)
}
type proxyServerConnector interface {
// connect establishes connection to the proxy server, and returns a
// proxier based on the connection.
connect() (proxier, error)
}
type tcpHTTPConnectConnector struct {
proxyAddress string
tlsConfig *tls.Config
}
func (t *tcpHTTPConnectConnector) connect() (proxier, error) {
conn, err := tls.Dial("tcp", t.proxyAddress, t.tlsConfig)
if err != nil {
return nil, err
}
return &httpConnectProxier{conn: conn, proxyAddress: t.proxyAddress}, nil
}
type udsHTTPConnectConnector struct {
udsName string
}
func (u *udsHTTPConnectConnector) connect() (proxier, error) {
conn, err := net.Dial("unix", u.udsName)
if err != nil {
return nil, err
}
return &httpConnectProxier{conn: conn, proxyAddress: u.udsName}, nil
}
type udsGRPCConnector struct {
udsName string
}
func (u *udsGRPCConnector) connect() (proxier, error) {
udsName := u.udsName
dialOption := grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
c, err := net.Dial("unix", udsName)
if err != nil {
klog.Errorf("failed to create connection to uds name %s, error: %v", udsName, err)
}
return c, err
})
tunnel, err := client.CreateGrpcTunnel(udsName, dialOption, grpc.WithInsecure())
if err != nil {
return nil, err
}
return &grpcProxier{tunnel: tunnel}, nil
}
type dialerCreator struct {
connector proxyServerConnector
direct bool
options metricsOptions
}
type metricsOptions struct {
transport string
protocol string
}
func (d *dialerCreator) createDialer() utilnet.DialFunc {
if d.direct {
return directDialer
}
return func(ctx context.Context, network, addr string) (net.Conn, error) {
trace := utiltrace.New(fmt.Sprintf("Proxy via HTTP Connect over %s", d.options.transport), utiltrace.Field{Key: "address", Value: addr})
defer trace.LogIfLong(500 * time.Millisecond)
start := egressmetrics.Metrics.Clock().Now()
proxier, err := d.connector.connect()
if err != nil {
egressmetrics.Metrics.ObserveDialFailure(d.options.protocol, d.options.transport, egressmetrics.StageConnect)
return nil, err
}
conn, err := proxier.proxy(addr)
if err != nil {
egressmetrics.Metrics.ObserveDialFailure(d.options.protocol, d.options.transport, egressmetrics.StageProxy)
return nil, err
}
egressmetrics.Metrics.ObserveDialLatency(egressmetrics.Metrics.Clock().Now().Sub(start), d.options.protocol, d.options.transport)
return conn, nil
}
}
func getTLSConfig(t *apiserver.TLSConfig) (*tls.Config, error) {
clientCert := t.ClientCert
clientKey := t.ClientKey
caCert := t.CABundle
clientCerts, err := tls.LoadX509KeyPair(clientCert, clientKey)
if err != nil {
return nil, fmt.Errorf("failed to read key pair %s & %s, got %v", clientCert, clientKey, err)
}
certPool := x509.NewCertPool()
certBytes, err := ioutil.ReadFile(caCert)
if err != nil {
return nil, fmt.Errorf("failed to read cert file %s, got %v", caCert, err)
}
ok := certPool.AppendCertsFromPEM(certBytes)
if !ok {
return nil, fmt.Errorf("failed to append CA cert to the cert pool")
}
contextDialer := func(ctx context.Context, network, addr string) (net.Conn, error) {
klog.V(4).Infof("Sending request to %q.", addr)
proxyConn, err := tls.Dial("tcp", proxyAddress,
&tls.Config{
Certificates: []tls.Certificate{clientCerts},
RootCAs: certPool,
},
)
if caCert != "" {
certBytes, err := ioutil.ReadFile(caCert)
if err != nil {
return nil, fmt.Errorf("dialing proxy %q failed: %v", proxyAddress, err)
return nil, fmt.Errorf("failed to read cert file %s, got %v", caCert, err)
}
fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, "127.0.0.1")
br := bufio.NewReader(proxyConn)
res, err := http.ReadResponse(br, nil)
if err != nil {
proxyConn.Close()
return nil, fmt.Errorf("reading HTTP response from CONNECT to %s via proxy %s failed: %v",
addr, proxyAddress, err)
}
if res.StatusCode != 200 {
proxyConn.Close()
return nil, fmt.Errorf("proxy error from %s while dialing %s, code %d: %v",
proxyAddress, addr, res.StatusCode, res.Status)
ok := certPool.AppendCertsFromPEM(certBytes)
if !ok {
return nil, fmt.Errorf("failed to append CA cert to the cert pool")
}
} else {
// Use host's root CA set instead of providing our own
certPool = nil
}
return &tls.Config{
Certificates: []tls.Certificate{clientCerts},
RootCAs: certPool,
}, nil
}
// It's safe to discard the bufio.Reader here and return the
// original TCP conn directly because we only use this for
// TLS, and in TLS the client speaks first, so we know there's
// no unbuffered data. But we can double-check.
if br.Buffered() > 0 {
proxyConn.Close()
return nil, fmt.Errorf("unexpected %d bytes of buffered data from CONNECT proxy %q",
br.Buffered(), proxyAddress)
}
klog.V(4).Infof("About to proxy request to %s over %s.", addr, proxyAddress)
return proxyConn, nil
func getProxyAddress(urlString string) (string, error) {
proxyURL, err := url.Parse(urlString)
if err != nil {
return "", fmt.Errorf("invalid proxy server url %q: %v", urlString, err)
}
return contextDialer, nil
return proxyURL.Host, nil
}
func connectionToDialerCreator(c apiserver.Connection) (*dialerCreator, error) {
switch c.ProxyProtocol {
case apiserver.ProtocolHTTPConnect:
if c.Transport.UDS != nil {
return &dialerCreator{
connector: &udsHTTPConnectConnector{
udsName: c.Transport.UDS.UDSName,
},
options: metricsOptions{
transport: egressmetrics.TransportUDS,
protocol: egressmetrics.ProtocolHTTPConnect,
},
}, nil
} else if c.Transport.TCP != nil {
tlsConfig, err := getTLSConfig(c.Transport.TCP.TLSConfig)
if err != nil {
return nil, err
}
proxyAddress, err := getProxyAddress(c.Transport.TCP.URL)
if err != nil {
return nil, err
}
return &dialerCreator{
connector: &tcpHTTPConnectConnector{
tlsConfig: tlsConfig,
proxyAddress: proxyAddress,
},
options: metricsOptions{
transport: egressmetrics.TransportTCP,
protocol: egressmetrics.ProtocolHTTPConnect,
},
}, nil
} else {
return nil, fmt.Errorf("Either a TCP or UDS transport must be specified")
}
case apiserver.ProtocolGRPC:
if c.Transport.UDS != nil {
return &dialerCreator{
connector: &udsGRPCConnector{
udsName: c.Transport.UDS.UDSName,
},
options: metricsOptions{
transport: egressmetrics.TransportUDS,
protocol: egressmetrics.ProtocolGRPC,
},
}, nil
}
return nil, fmt.Errorf("UDS transport must be specified for GRPC")
case apiserver.ProtocolDirect:
return &dialerCreator{direct: true}, nil
default:
return nil, fmt.Errorf("unrecognized service connection protocol %q", c.ProxyProtocol)
}
}
// NewEgressSelector configures lookup mechanism for Lookup.
@ -172,18 +348,11 @@ func NewEgressSelector(config *apiserver.EgressSelectorConfiguration) (*EgressSe
if err != nil {
return nil, err
}
switch service.Connection.Type {
case "http-connect":
contextDialer, err := createConnectDialer(service.Connection.HTTPConnect)
if err != nil {
return nil, fmt.Errorf("failed to create http-connect dialer: %v", err)
}
cs.egressToDialer[name] = contextDialer
case "direct":
cs.egressToDialer[name] = directDialer
default:
return nil, fmt.Errorf("unrecognized service connection type %q", service.Connection.Type)
dialerCreator, err := connectionToDialerCreator(service.Connection)
if err != nil {
return nil, fmt.Errorf("failed to create dialer for egressSelection %q: %v", name, err)
}
cs.egressToDialer[name] = dialerCreator.createDialer()
}
return cs, nil
}

View file

@ -0,0 +1,114 @@
/*
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 metrics
import (
"time"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/component-base/metrics"
"k8s.io/component-base/metrics/legacyregistry"
)
const (
namespace = "apiserver"
subsystem = "egress_dialer"
// ProtocolHTTPConnect means that the proxy protocol is http-connect.
ProtocolHTTPConnect = "http_connect"
// ProtocolGRPC means that the proxy protocol is the GRPC protocol.
ProtocolGRPC = "grpc"
// TransportTCP means that the transport is TCP.
TransportTCP = "tcp"
// TransportUDS means that the transport is UDS.
TransportUDS = "uds"
// StageConnect indicates that the dial failed at establishing connection to the proxy server.
StageConnect = "connect"
// StageProxy indicates that the dial failed at requesting the proxy server to proxy.
StageProxy = "proxy"
)
var (
// Use buckets ranging from 5 ms to 12.5 seconds.
latencyBuckets = []float64{0.005, 0.025, 0.1, 0.5, 2.5, 12.5}
// Metrics provides access to all dial metrics.
Metrics = newDialMetrics()
)
// DialMetrics instruments dials to proxy server with prometheus metrics.
type DialMetrics struct {
clock clock.Clock
latencies *metrics.HistogramVec
failures *metrics.CounterVec
}
// newDialMetrics create a new DialMetrics, configured with default metric names.
func newDialMetrics() *DialMetrics {
latencies := metrics.NewHistogramVec(
&metrics.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "dial_duration_seconds",
Help: "Dial latency histogram in seconds, labeled by the protocol (http-connect or grpc), transport (tcp or uds)",
Buckets: latencyBuckets,
StabilityLevel: metrics.ALPHA,
},
[]string{"protocol", "transport"},
)
failures := metrics.NewCounterVec(
&metrics.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "dial_failure_count",
Help: "Dial failure count, labeled by the protocol (http-connect or grpc), transport (tcp or uds), and stage (connect or proxy). The stage indicates at which stage the dial failed",
StabilityLevel: metrics.ALPHA,
},
[]string{"protocol", "transport", "stage"},
)
legacyregistry.MustRegister(latencies)
legacyregistry.MustRegister(failures)
return &DialMetrics{latencies: latencies, failures: failures, clock: clock.RealClock{}}
}
// Clock returns the clock.
func (m *DialMetrics) Clock() clock.Clock {
return m.clock
}
// SetClock sets the clock.
func (m *DialMetrics) SetClock(c clock.Clock) {
m.clock = c
}
// Reset resets the metrics.
func (m *DialMetrics) Reset() {
m.latencies.Reset()
m.failures.Reset()
}
// ObserveDialLatency records the latency of a dial, labeled by protocol, transport.
func (m *DialMetrics) ObserveDialLatency(elapsed time.Duration, protocol, transport string) {
m.latencies.WithLabelValues(protocol, transport).Observe(elapsed.Seconds())
}
// ObserveDialFailure records a failed dial, labeled by protocol, transport, and the stage the dial failed at.
func (m *DialMetrics) ObserveDialFailure(protocol, transport, stage string) {
m.failures.WithLabelValues(protocol, transport, stage).Inc()
}

88
vendor/k8s.io/apiserver/pkg/server/filters/goaway.go generated vendored Normal file
View file

@ -0,0 +1,88 @@
/*
Copyright 2020 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 filters
import (
"math/rand"
"net/http"
"sync"
)
// GoawayDecider decides if server should send a GOAWAY
type GoawayDecider interface {
Goaway(r *http.Request) bool
}
var (
// randPool used to get a rand.Rand and generate a random number thread-safely,
// which improve the performance of using rand.Rand with a locker
randPool = &sync.Pool{
New: func() interface{} {
return rand.New(rand.NewSource(rand.Int63()))
},
}
)
// WithProbabilisticGoaway returns an http.Handler that send GOAWAY probabilistically
// according to the given chance for HTTP2 requests. After client receive GOAWAY,
// the in-flight long-running requests will not be influenced, and the new requests
// will use a new TCP connection to re-balancing to another server behind the load balance.
func WithProbabilisticGoaway(inner http.Handler, chance float64) http.Handler {
return &goaway{
handler: inner,
decider: &probabilisticGoawayDecider{
chance: chance,
next: func() float64 {
rnd := randPool.Get().(*rand.Rand)
ret := rnd.Float64()
randPool.Put(rnd)
return ret
},
},
}
}
// goaway send a GOAWAY to client according to decider for HTTP2 requests
type goaway struct {
handler http.Handler
decider GoawayDecider
}
// ServeHTTP implement HTTP handler
func (h *goaway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Proto == "HTTP/2.0" && h.decider.Goaway(r) {
// Send a GOAWAY and tear down the TCP connection when idle.
w.Header().Set("Connection", "close")
}
h.handler.ServeHTTP(w, r)
}
// probabilisticGoawayDecider send GOAWAY probabilistically according to chance
type probabilisticGoawayDecider struct {
chance float64
next func() float64
}
// Goaway implement GoawayDecider
func (p *probabilisticGoawayDecider) Goaway(r *http.Request) bool {
if p.next() < p.chance {
return true
}
return false
}

View file

@ -160,14 +160,6 @@ func WithMaxInFlightLimit(
handler.ServeHTTP(w, r)
default:
// We need to split this data between buckets used for throttling.
if isMutatingRequest {
metrics.DroppedRequests.WithLabelValues(metrics.MutatingKind).Inc()
metrics.DeprecatedDroppedRequests.WithLabelValues(metrics.MutatingKind).Inc()
} else {
metrics.DroppedRequests.WithLabelValues(metrics.ReadOnlyKind).Inc()
metrics.DeprecatedDroppedRequests.WithLabelValues(metrics.ReadOnlyKind).Inc()
}
// at this point we're about to return a 429, BUT not all actors should be rate limited. A system:master is so powerful
// that they should always get an answer. It's a super-admin or a loopback connection.
if currUser, ok := apirequest.UserFrom(ctx); ok {
@ -178,6 +170,12 @@ func WithMaxInFlightLimit(
}
}
}
// We need to split this data between buckets used for throttling.
if isMutatingRequest {
metrics.DroppedRequests.WithLabelValues(metrics.MutatingKind).Inc()
} else {
metrics.DroppedRequests.WithLabelValues(metrics.ReadOnlyKind).Inc()
}
metrics.RecordRequestTermination(r, requestInfo, metrics.APIServerComponent, http.StatusTooManyRequests)
tooManyRequests(r, w)
}

View file

@ -0,0 +1,132 @@
/*
Copyright 2019 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 filters
import (
"context"
"fmt"
"net/http"
"sync/atomic"
fcv1a1 "k8s.io/api/flowcontrol/v1alpha1"
apitypes "k8s.io/apimachinery/pkg/types"
apirequest "k8s.io/apiserver/pkg/endpoints/request"
utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol"
"k8s.io/klog"
)
type priorityAndFairnessKeyType int
const priorityAndFairnessKey priorityAndFairnessKeyType = iota
const (
responseHeaderMatchedPriorityLevelConfigurationUID = "X-Kubernetes-PF-PriorityLevel-UID"
responseHeaderMatchedFlowSchemaUID = "X-Kubernetes-PF-FlowSchema-UID"
)
// PriorityAndFairnessClassification identifies the results of
// classification for API Priority and Fairness
type PriorityAndFairnessClassification struct {
FlowSchemaName string
FlowSchemaUID apitypes.UID
PriorityLevelName string
PriorityLevelUID apitypes.UID
}
// GetClassification returns the classification associated with the
// given context, if any, otherwise nil
func GetClassification(ctx context.Context) *PriorityAndFairnessClassification {
return ctx.Value(priorityAndFairnessKey).(*PriorityAndFairnessClassification)
}
var atomicMutatingLen, atomicNonMutatingLen int32
// WithPriorityAndFairness limits the number of in-flight
// requests in a fine-grained way.
func WithPriorityAndFairness(
handler http.Handler,
longRunningRequestCheck apirequest.LongRunningRequestCheck,
fcIfc utilflowcontrol.Interface,
) http.Handler {
if fcIfc == nil {
klog.Warningf("priority and fairness support not found, skipping")
return handler
}
startOnce.Do(startRecordingUsage)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
requestInfo, ok := apirequest.RequestInfoFrom(ctx)
if !ok {
handleError(w, r, fmt.Errorf("no RequestInfo found in context"))
return
}
user, ok := apirequest.UserFrom(ctx)
if !ok {
handleError(w, r, fmt.Errorf("no User found in context"))
return
}
// Skip tracking long running requests.
if longRunningRequestCheck != nil && longRunningRequestCheck(r, requestInfo) {
klog.V(6).Infof("Serving RequestInfo=%#+v, user.Info=%#+v as longrunning\n", requestInfo, user)
handler.ServeHTTP(w, r)
return
}
var classification *PriorityAndFairnessClassification
note := func(fs *fcv1a1.FlowSchema, pl *fcv1a1.PriorityLevelConfiguration) {
classification = &PriorityAndFairnessClassification{
FlowSchemaName: fs.Name,
FlowSchemaUID: fs.UID,
PriorityLevelName: pl.Name,
PriorityLevelUID: pl.UID}
}
var served bool
isMutatingRequest := !nonMutatingRequestVerbs.Has(requestInfo.Verb)
execute := func() {
var mutatingLen, readOnlyLen int
if isMutatingRequest {
mutatingLen = int(atomic.AddInt32(&atomicMutatingLen, 1))
} else {
readOnlyLen = int(atomic.AddInt32(&atomicNonMutatingLen, 1))
}
defer func() {
if isMutatingRequest {
atomic.AddInt32(&atomicMutatingLen, -11)
watermark.recordMutating(mutatingLen)
} else {
atomic.AddInt32(&atomicNonMutatingLen, -1)
watermark.recordReadOnly(readOnlyLen)
}
}()
served = true
innerCtx := context.WithValue(ctx, priorityAndFairnessKey, classification)
innerReq := r.Clone(innerCtx)
w.Header().Set(responseHeaderMatchedPriorityLevelConfigurationUID, string(classification.PriorityLevelUID))
w.Header().Set(responseHeaderMatchedFlowSchemaUID, string(classification.FlowSchemaUID))
handler.ServeHTTP(w, innerReq)
}
digest := utilflowcontrol.RequestDigest{requestInfo, user}
fcIfc.Handle(ctx, digest, note, execute)
if !served {
tooManyRequests(r, w)
return
}
})
}

View file

@ -18,11 +18,16 @@ package filters
import (
"errors"
"fmt"
"net/http"
"k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
utilwaitgroup "k8s.io/apimachinery/pkg/util/waitgroup"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
apirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/client-go/kubernetes/scheme"
)
// WithWaitGroup adds all non long-running requests to wait group, which is used for graceful shutdown.
@ -38,7 +43,14 @@ func WithWaitGroup(handler http.Handler, longRunning apirequest.LongRunningReque
if !longRunning(req, requestInfo) {
if err := wg.Add(1); err != nil {
http.Error(w, "apiserver is shutting down.", http.StatusInternalServerError)
// When apiserver is shutting down, signal clients to retry
// There is a good chance the client hit a different server, so a tight retry is good for client responsiveness.
w.Header().Add("Retry-After", "1")
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
w.Header().Set("X-Content-Type-Options", "nosniff")
statusErr := apierrors.NewServiceUnavailable("apiserver is shutting down").Status()
w.WriteHeader(int(statusErr.Code))
fmt.Fprintln(w, runtime.EncodeOrDie(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &statusErr))
return
}
defer wg.Done()

View file

@ -318,8 +318,14 @@ func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) error {
go func() {
defer close(delayedStopCh)
<-stopCh
// As soon as shutdown is initiated, /readyz should start returning failure.
// This gives the load balancer a window defined by ShutdownDelayDuration to detect that /readyz is red
// and stop sending traffic to this server.
close(s.readinessStopCh)
time.Sleep(s.ShutdownDelayDuration)
}()
@ -379,7 +385,6 @@ func (s preparedGenericAPIServer) NonBlockingRun(stopCh <-chan struct{}) error {
// ensure cleanup.
go func() {
<-stopCh
close(s.readinessStopCh)
close(internalStopCh)
if stoppedCh != nil {
<-stoppedCh

View file

@ -27,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/endpoints/metrics"
"k8s.io/apiserver/pkg/server/httplog"
"k8s.io/klog"
)
@ -122,7 +123,15 @@ func InstallPathHandler(mux mux, path string, checks ...HealthChecker) {
klog.V(5).Infof("Installing health checkers for (%v): %v", path, formatQuoted(checkerNames(checks...)...))
mux.Handle(path, handleRootHealthz(checks...))
mux.Handle(path,
metrics.InstrumentHandlerFunc("GET",
/* group = */ "",
/* version = */ "",
/* resource = */ "",
/* subresource = */ path,
/* scope = */ "",
/* component = */ "",
handleRootHealthz(checks...)))
for _, check := range checks {
mux.Handle(fmt.Sprintf("%s/%v", path, check.Name()), adaptCheckToHandler(check.Check))
}

View file

@ -158,9 +158,17 @@ func (rl *respLogger) Log() {
latency := time.Since(rl.startTime)
if klog.V(3) {
if !rl.hijacked {
klog.InfoDepth(1, fmt.Sprintf("%s %s: (%v) %v%v%v [%s %s]", rl.req.Method, rl.req.RequestURI, latency, rl.status, rl.statusStack, rl.addedInfo, rl.req.UserAgent(), rl.req.RemoteAddr))
klog.InfoDepth(1, fmt.Sprintf("verb=%q URI=%q latency=%v resp=%v UserAgent=%q srcIP=%q: %v%v",
rl.req.Method, rl.req.RequestURI,
latency, rl.status,
rl.req.UserAgent(), rl.req.RemoteAddr,
rl.statusStack, rl.addedInfo,
))
} else {
klog.InfoDepth(1, fmt.Sprintf("%s %s: (%v) hijacked [%s %s]", rl.req.Method, rl.req.RequestURI, latency, rl.req.UserAgent(), rl.req.RemoteAddr))
klog.InfoDepth(1, fmt.Sprintf("verb=%q URI=%q latency=%v UserAgent=%q srcIP=%q: hijacked",
rl.req.Method, rl.req.RequestURI,
latency, rl.req.UserAgent(), rl.req.RemoteAddr,
))
}
}
}

View file

@ -112,7 +112,7 @@ func (a *AdmissionOptions) AddFlags(fs *pflag.FlagSet) {
}
// ApplyTo adds the admission chain to the server configuration.
// In case admission plugin names were not provided by a custer-admin they will be prepared from the recommended/default values.
// In case admission plugin names were not provided by a cluster-admin they will be prepared from the recommended/default values.
// In addition the method lazily initializes a generic plugin that is appended to the list of pluginInitializers
// note this method uses:
// genericconfig.Authorizer

View file

@ -29,6 +29,7 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
utilnet "k8s.io/apimachinery/pkg/util/net"
auditinternal "k8s.io/apiserver/pkg/apis/audit"
auditv1 "k8s.io/apiserver/pkg/apis/audit/v1"
auditv1alpha1 "k8s.io/apiserver/pkg/apis/audit/v1alpha1"
@ -37,6 +38,7 @@ import (
"k8s.io/apiserver/pkg/audit/policy"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/egressselector"
utilfeature "k8s.io/apiserver/pkg/util/feature"
pluginbuffered "k8s.io/apiserver/plugin/pkg/audit/buffered"
plugindynamic "k8s.io/apiserver/plugin/pkg/audit/dynamic"
@ -323,7 +325,15 @@ func (o *AuditOptions) ApplyTo(
if checker == nil {
klog.V(2).Info("No audit policy file provided, no events will be recorded for webhook backend")
} else {
webhookBackend, err = o.WebhookOptions.newUntruncatedBackend()
if c.EgressSelector != nil {
egressDialer, err := c.EgressSelector.Lookup(egressselector.Master.AsNetworkContext())
if err != nil {
return err
}
webhookBackend, err = o.WebhookOptions.newUntruncatedBackend(egressDialer)
} else {
webhookBackend, err = o.WebhookOptions.newUntruncatedBackend(nil)
}
if err != nil {
return err
}
@ -590,9 +600,9 @@ func (o *AuditWebhookOptions) enabled() bool {
// newUntruncatedBackend returns a webhook backend without the truncate options applied
// this is done so that the same trucate backend can wrap both the webhook and dynamic backends
func (o *AuditWebhookOptions) newUntruncatedBackend() (audit.Backend, error) {
func (o *AuditWebhookOptions) newUntruncatedBackend(customDial utilnet.DialFunc) (audit.Backend, error) {
groupVersion, _ := schema.ParseGroupVersion(o.GroupVersionString)
webhook, err := pluginwebhook.NewBackend(o.ConfigFile, groupVersion, o.InitialBackoff)
webhook, err := pluginwebhook.NewBackend(o.ConfigFile, groupVersion, o.InitialBackoff, customDial)
if err != nil {
return nil, fmt.Errorf("initializing audit webhook: %v", err)
}

View file

@ -17,6 +17,7 @@ limitations under the License.
package options
import (
"context"
"encoding/json"
"fmt"
"strings"
@ -212,7 +213,7 @@ func (s *DelegatingAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
}
fs.StringVar(&s.RemoteKubeConfigFile, "authentication-kubeconfig", s.RemoteKubeConfigFile, ""+
"kubeconfig file pointing at the 'core' kubernetes server with enough rights to create "+
"tokenaccessreviews.authentication.k8s.io."+optionalKubeConfigSentence)
"tokenreviews.authentication.k8s.io."+optionalKubeConfigSentence)
fs.DurationVar(&s.CacheTTL, "authentication-token-webhook-cache-ttl", s.CacheTTL,
"The duration to cache responses from the webhook token authenticator.")
@ -339,7 +340,7 @@ func (s *DelegatingAuthenticationOptions) createRequestHeaderConfig(client kuber
return nil, fmt.Errorf("unable to create request header authentication config: %v", err)
}
authConfigMap, err := client.CoreV1().ConfigMaps(authenticationConfigMapNamespace).Get(authenticationConfigMapName, metav1.GetOptions{})
authConfigMap, err := client.CoreV1().ConfigMaps(authenticationConfigMapNamespace).Get(context.TODO(), authenticationConfigMapName, metav1.GetOptions{})
switch {
case errors.IsNotFound(err):
// ignore, authConfigMap is nil now

View file

@ -20,6 +20,7 @@ import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
@ -33,6 +34,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/serializer"
apiserverconfig "k8s.io/apiserver/pkg/apis/config"
apiserverconfigv1 "k8s.io/apiserver/pkg/apis/config/v1"
"k8s.io/apiserver/pkg/apis/config/validation"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/apiserver/pkg/storage/value"
aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes"
@ -46,8 +48,8 @@ const (
aesGCMTransformerPrefixV1 = "k8s:enc:aesgcm:v1:"
secretboxTransformerPrefixV1 = "k8s:enc:secretbox:v1:"
kmsTransformerPrefixV1 = "k8s:enc:kms:v1:"
kmsPluginConnectionTimeout = 3 * time.Second
kmsPluginHealthzTTL = 3 * time.Second
kmsPluginHealthzNegativeTTL = 3 * time.Second
kmsPluginHealthzPositiveTTL = 20 * time.Second
)
type kmsPluginHealthzResponse struct {
@ -57,6 +59,7 @@ type kmsPluginHealthzResponse struct {
type kmsPluginProbe struct {
name string
ttl time.Duration
envelope.Service
lastResponse *kmsPluginHealthzResponse
l *sync.Mutex
@ -104,21 +107,14 @@ func getKMSPluginProbes(reader io.Reader) ([]*kmsPluginProbe, error) {
for _, r := range config.Resources {
for _, p := range r.Providers {
if p.KMS != nil {
timeout := kmsPluginConnectionTimeout
if p.KMS.Timeout != nil {
if p.KMS.Timeout.Duration <= 0 {
return nil, fmt.Errorf("could not configure KMS-Plugin's probe %q, timeout should be a positive value", p.KMS.Name)
}
timeout = p.KMS.Timeout.Duration
}
s, err := envelope.NewGRPCService(p.KMS.Endpoint, timeout)
s, err := envelope.NewGRPCService(p.KMS.Endpoint, p.KMS.Timeout.Duration)
if err != nil {
return nil, fmt.Errorf("could not configure KMS-Plugin's probe %q, error: %v", p.KMS.Name, err)
}
result = append(result, &kmsPluginProbe{
name: p.KMS.Name,
ttl: kmsPluginHealthzNegativeTTL,
Service: s,
l: &sync.Mutex{},
lastResponse: &kmsPluginHealthzResponse{},
@ -135,22 +131,25 @@ func (h *kmsPluginProbe) Check() error {
h.l.Lock()
defer h.l.Unlock()
if (time.Since(h.lastResponse.received)) < kmsPluginHealthzTTL {
if (time.Since(h.lastResponse.received)) < h.ttl {
return h.lastResponse.err
}
p, err := h.Service.Encrypt([]byte("ping"))
if err != nil {
h.lastResponse = &kmsPluginHealthzResponse{err: err, received: time.Now()}
h.ttl = kmsPluginHealthzNegativeTTL
return fmt.Errorf("failed to perform encrypt section of the healthz check for KMS Provider %s, error: %v", h.name, err)
}
if _, err := h.Service.Decrypt(p); err != nil {
h.lastResponse = &kmsPluginHealthzResponse{err: err, received: time.Now()}
h.ttl = kmsPluginHealthzNegativeTTL
return fmt.Errorf("failed to perform decrypt section of the healthz check for KMS Provider %s, error: %v", h.name, err)
}
h.lastResponse = &kmsPluginHealthzResponse{err: nil, received: time.Now()}
h.ttl = kmsPluginHealthzPositiveTTL
return nil
}
@ -221,7 +220,8 @@ func loadConfig(data []byte) (*apiserverconfig.EncryptionConfiguration, error) {
if !ok {
return nil, fmt.Errorf("got unexpected config type: %v", gvk)
}
return config, nil
return config, validation.ValidateEncryptionConfiguration(config).ToAggregate()
}
// The factory to create kms service. This is to make writing test easier.
@ -231,82 +231,38 @@ var envelopeServiceFactory = envelope.NewGRPCService
func GetPrefixTransformers(config *apiserverconfig.ResourceConfiguration) ([]value.PrefixTransformer, error) {
var result []value.PrefixTransformer
for _, provider := range config.Providers {
found := false
var (
transformer value.PrefixTransformer
err error
)
var transformer value.PrefixTransformer
var err error
if provider.AESGCM != nil {
switch {
case provider.AESGCM != nil:
transformer, err = GetAESPrefixTransformer(provider.AESGCM, aestransformer.NewGCMTransformer, aesGCMTransformerPrefixV1)
if err != nil {
return result, err
}
found = true
}
if provider.AESCBC != nil {
if found == true {
return result, fmt.Errorf("more than one provider specified in a single element, should split into different list elements")
}
case provider.AESCBC != nil:
transformer, err = GetAESPrefixTransformer(provider.AESCBC, aestransformer.NewCBCTransformer, aesCBCTransformerPrefixV1)
found = true
}
if provider.Secretbox != nil {
if found == true {
return result, fmt.Errorf("more than one provider specified in a single element, should split into different list elements")
}
case provider.Secretbox != nil:
transformer, err = GetSecretboxPrefixTransformer(provider.Secretbox)
found = true
}
if provider.Identity != nil {
if found == true {
return result, fmt.Errorf("more than one provider specified in a single element, should split into different list elements")
}
transformer = value.PrefixTransformer{
Transformer: identity.NewEncryptCheckTransformer(),
Prefix: []byte{},
}
found = true
}
if provider.KMS != nil {
if found == true {
return nil, fmt.Errorf("more than one provider specified in a single element, should split into different list elements")
}
// Ensure the endpoint is provided.
if len(provider.KMS.Endpoint) == 0 {
return nil, fmt.Errorf("remote KMS provider can't use empty string as endpoint")
}
timeout := kmsPluginConnectionTimeout
if provider.KMS.Timeout != nil {
if provider.KMS.Timeout.Duration <= 0 {
return nil, fmt.Errorf("could not configure KMS plugin %q, timeout should be a positive value", provider.KMS.Name)
}
timeout = provider.KMS.Timeout.Duration
}
// Get gRPC client service with endpoint.
envelopeService, err := envelopeServiceFactory(provider.KMS.Endpoint, timeout)
case provider.KMS != nil:
envelopeService, err := envelopeServiceFactory(provider.KMS.Endpoint, provider.KMS.Timeout.Duration)
if err != nil {
return nil, fmt.Errorf("could not configure KMS plugin %q, error: %v", provider.KMS.Name, err)
}
transformer, err = getEnvelopePrefixTransformer(provider.KMS, envelopeService, kmsTransformerPrefixV1)
found = true
case provider.Identity != nil:
transformer = value.PrefixTransformer{
Transformer: identity.NewEncryptCheckTransformer(),
Prefix: []byte{},
}
default:
return nil, errors.New("provider does not contain any of the expected providers: KMS, AESGCM, AESCBC, Secretbox, Identity")
}
if err != nil {
return result, err
}
result = append(result, transformer)
if found == false {
return result, fmt.Errorf("invalid provider configuration: at least one provider must be specified")
}
}
return result, nil
}
@ -417,7 +373,7 @@ func GetSecretboxPrefixTransformer(config *apiserverconfig.SecretboxConfiguratio
// getEnvelopePrefixTransformer returns a prefix transformer from the provided config.
// envelopeService is used as the root of trust.
func getEnvelopePrefixTransformer(config *apiserverconfig.KMSConfiguration, envelopeService envelope.Service, prefix string) (value.PrefixTransformer, error) {
envelopeTransformer, err := envelope.NewEnvelopeTransformer(envelopeService, int(config.CacheSize), aestransformer.NewCBCTransformer)
envelopeTransformer, err := envelope.NewEnvelopeTransformer(envelopeService, int(*config.CacheSize), aestransformer.NewCBCTransformer)
if err != nil {
return value.PrefixTransformer{}, err
}

View file

@ -18,12 +18,15 @@ package options
import (
"github.com/spf13/pflag"
"k8s.io/apiserver/pkg/util/feature"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/storage/storagebackend"
"k8s.io/apiserver/pkg/util/feature"
utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol"
"k8s.io/client-go/kubernetes"
"k8s.io/component-base/featuregate"
)
@ -125,7 +128,14 @@ func (o *RecommendedOptions) ApplyTo(config *server.RecommendedConfig) error {
if err := o.EgressSelector.ApplyTo(&config.Config); err != nil {
return err
}
if feature.DefaultFeatureGate.Enabled(features.APIPriorityAndFairness) {
config.FlowControl = utilflowcontrol.New(
config.SharedInformerFactory,
kubernetes.NewForConfigOrDie(config.ClientConfig).FlowcontrolV1alpha1(),
config.MaxRequestsInFlight+config.MaxMutatingRequestsInFlight,
config.RequestTimeout/4,
)
}
return nil
}

View file

@ -26,9 +26,6 @@ import (
"k8s.io/apiserver/pkg/server"
utilfeature "k8s.io/apiserver/pkg/util/feature"
// add the generic feature gates
"k8s.io/apiserver/pkg/features"
"github.com/spf13/pflag"
)
@ -41,6 +38,7 @@ type ServerRunOptions struct {
MaxRequestsInFlight int
MaxMutatingRequestsInFlight int
RequestTimeout time.Duration
GoawayChance float64
LivezGracePeriod time.Duration
MinRequestTimeout int
ShutdownDelayDuration time.Duration
@ -51,9 +49,9 @@ type ServerRunOptions struct {
// decoded in a write request. 0 means no limit.
// We intentionally did not add a flag for this option. Users of the
// apiserver library can wire it to a flag.
MaxRequestBodyBytes int64
TargetRAMMB int
EnableInflightQuotaHandler bool
MaxRequestBodyBytes int64
TargetRAMMB int
EnablePriorityAndFairness bool
}
func NewServerRunOptions() *ServerRunOptions {
@ -67,6 +65,7 @@ func NewServerRunOptions() *ServerRunOptions {
ShutdownDelayDuration: defaults.ShutdownDelayDuration,
JSONPatchMaxCopyBytes: defaults.JSONPatchMaxCopyBytes,
MaxRequestBodyBytes: defaults.MaxRequestBodyBytes,
EnablePriorityAndFairness: true,
}
}
@ -78,6 +77,7 @@ func (s *ServerRunOptions) ApplyTo(c *server.Config) error {
c.MaxMutatingRequestsInFlight = s.MaxMutatingRequestsInFlight
c.LivezGracePeriod = s.LivezGracePeriod
c.RequestTimeout = s.RequestTimeout
c.GoawayChance = s.GoawayChance
c.MinRequestTimeout = s.MinRequestTimeout
c.ShutdownDelayDuration = s.ShutdownDelayDuration
c.JSONPatchMaxCopyBytes = s.JSONPatchMaxCopyBytes
@ -116,32 +116,21 @@ func (s *ServerRunOptions) Validate() []error {
errors = append(errors, fmt.Errorf("--livez-grace-period can not be a negative value"))
}
if s.EnableInflightQuotaHandler {
if !utilfeature.DefaultFeatureGate.Enabled(features.APIPriorityAndFairness) {
errors = append(errors, fmt.Errorf("--enable-inflight-quota-handler can not be set if feature "+
"gate APIPriorityAndFairness is disabled"))
}
if s.MaxMutatingRequestsInFlight != 0 {
errors = append(errors, fmt.Errorf("--max-mutating-requests-inflight=%v "+
"can not be set if enabled inflight quota handler", s.MaxMutatingRequestsInFlight))
}
if s.MaxRequestsInFlight != 0 {
errors = append(errors, fmt.Errorf("--max-requests-inflight=%v "+
"can not be set if enabled inflight quota handler", s.MaxRequestsInFlight))
}
} else {
if s.MaxRequestsInFlight < 0 {
errors = append(errors, fmt.Errorf("--max-requests-inflight can not be negative value"))
}
if s.MaxMutatingRequestsInFlight < 0 {
errors = append(errors, fmt.Errorf("--max-mutating-requests-inflight can not be negative value"))
}
if s.MaxRequestsInFlight < 0 {
errors = append(errors, fmt.Errorf("--max-requests-inflight can not be negative value"))
}
if s.MaxMutatingRequestsInFlight < 0 {
errors = append(errors, fmt.Errorf("--max-mutating-requests-inflight can not be negative value"))
}
if s.RequestTimeout.Nanoseconds() < 0 {
errors = append(errors, fmt.Errorf("--request-timeout can not be negative value"))
}
if s.GoawayChance < 0 || s.GoawayChance > 0.02 {
errors = append(errors, fmt.Errorf("--goaway-chance can not be less than 0 or greater than 0.02"))
}
if s.MinRequestTimeout < 0 {
errors = append(errors, fmt.Errorf("--min-request-timeout can not be negative value"))
}
@ -180,11 +169,11 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) {
"Memory limit for apiserver in MB (used to configure sizes of caches, etc.)")
fs.StringVar(&s.ExternalHost, "external-hostname", s.ExternalHost,
"The hostname to use when generating externalized URLs for this master (e.g. Swagger API Docs).")
"The hostname to use when generating externalized URLs for this master (e.g. Swagger API Docs or OpenID Discovery).")
deprecatedMasterServiceNamespace := metav1.NamespaceDefault
fs.StringVar(&deprecatedMasterServiceNamespace, "master-service-namespace", deprecatedMasterServiceNamespace, ""+
"DEPRECATED: the namespace from which the kubernetes master services should be injected into pods.")
"DEPRECATED: the namespace from which the Kubernetes master services should be injected into pods.")
fs.IntVar(&s.MaxRequestsInFlight, "max-requests-inflight", s.MaxRequestsInFlight, ""+
"The maximum number of non-mutating requests in flight at a given time. When the server exceeds this, "+
@ -199,6 +188,12 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) {
"it out. This is the default request timeout for requests but may be overridden by flags such as "+
"--min-request-timeout for specific types of requests.")
fs.Float64Var(&s.GoawayChance, "goaway-chance", s.GoawayChance, ""+
"To prevent HTTP/2 clients from getting stuck on a single apiserver, randomly close a connection (GOAWAY). "+
"The client's other in-flight requests won't be affected, and the client will reconnect, likely landing on a different apiserver after going through the load balancer again. "+
"This argument sets the fraction of requests that will be sent a GOAWAY. Clusters with single apiservers, or which don't use a load balancer, should NOT enable this. "+
"Min is 0 (off), Max is .02 (1/50 requests); .001 (1/1000) is a recommended starting point.")
fs.DurationVar(&s.LivezGracePeriod, "livez-grace-period", s.LivezGracePeriod, ""+
"This option represents the maximum amount of time it should take for apiserver to complete its startup sequence "+
"and become live. From apiserver's start time to when this amount of time has elapsed, /livez will assume "+
@ -210,8 +205,8 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) {
"handler, which picks a randomized value above this number as the connection timeout, "+
"to spread out load.")
fs.BoolVar(&s.EnableInflightQuotaHandler, "enable-inflight-quota-handler", s.EnableInflightQuotaHandler, ""+
"If true, replace the max-in-flight handler with an enhanced one that queues and dispatches with priority and fairness")
fs.BoolVar(&s.EnablePriorityAndFairness, "enable-priority-and-fairness", s.EnablePriorityAndFairness, ""+
"If true and the APIPriorityAndFairness feature gate is enabled, replace the max-in-flight handler with an enhanced one that queues and dispatches with priority and fairness")
fs.DurationVar(&s.ShutdownDelayDuration, "shutdown-delay-duration", s.ShutdownDelayDuration, ""+
"Time to delay the termination. During that time the server keeps serving requests normally and /healthz "+

View file

@ -143,13 +143,13 @@ func (s *SecureServingOptions) AddFlags(fs *pflag.FlagSet) {
fs.IPVar(&s.BindAddress, "bind-address", s.BindAddress, ""+
"The IP address on which to listen for the --secure-port port. The "+
"associated interface(s) must be reachable by the rest of the cluster, and by CLI/web "+
"clients. If blank, all interfaces will be used (0.0.0.0 for all IPv4 interfaces and :: for all IPv6 interfaces).")
"clients. If blank or an unspecified address (0.0.0.0 or ::), all interfaces will be used.")
desc := "The port on which to serve HTTPS with authentication and authorization."
if s.Required {
desc += "It cannot be switched off with 0."
desc += " It cannot be switched off with 0."
} else {
desc += "If 0, don't serve HTTPS at all."
desc += " If 0, don't serve HTTPS at all."
}
fs.IntVar(&s.BindPort, "secure-port", s.BindPort, desc)
@ -180,7 +180,9 @@ func (s *SecureServingOptions) AddFlags(fs *pflag.FlagSet) {
fs.Var(cliflag.NewNamedCertKeyArray(&s.SNICertKeys), "tls-sni-cert-key", ""+
"A pair of x509 certificate and private key file paths, optionally suffixed with a list of "+
"domain patterns which are fully qualified domain names, possibly with prefixed wildcard "+
"segments. If no domain patterns are provided, the names of the certificate are "+
"segments. The domain patterns also allow IP addresses, but IPs should only be used if "+
"the apiserver has visibility to the IP address requested by a client. "+
"If no domain patterns are provided, the names of the certificate are "+
"extracted. Non-wildcard matches trump over wildcard matches, explicit domain patterns "+
"trump over extracted names. For multiple key/certificate pairs, use the "+
"--tls-sni-cert-key multiple times. "+
@ -287,8 +289,7 @@ func (s *SecureServingOptions) MaybeDefaultWithSelfSignedCerts(publicAddress str
if !canReadCertAndKey {
// add either the bind address or localhost to the valid alternates
bindIP := s.BindAddress.String()
if bindIP == "0.0.0.0" {
if s.BindAddress.IsUnspecified() {
alternateDNS = append(alternateDNS, "localhost")
} else {
alternateIPs = append(alternateIPs, s.BindAddress)

View file

@ -23,6 +23,7 @@ import (
apimetrics "k8s.io/apiserver/pkg/endpoints/metrics"
"k8s.io/apiserver/pkg/server/mux"
etcd3metrics "k8s.io/apiserver/pkg/storage/etcd3/metrics"
flowcontrolmetrics "k8s.io/apiserver/pkg/util/flowcontrol/metrics"
"k8s.io/component-base/metrics/legacyregistry"
)
@ -58,4 +59,5 @@ func (m MetricsWithReset) Install(c *mux.PathRecorderMux) {
func register() {
apimetrics.Register()
etcd3metrics.Register()
flowcontrolmetrics.Register()
}

View file

@ -66,7 +66,7 @@ func (s *SecureServingInfo) tlsConfig(stopCh <-chan struct{}) (*tls.Config, erro
if s.ClientCA != nil || s.Cert != nil || len(s.SNICerts) > 0 {
dynamicCertificateController := dynamiccertificates.NewDynamicServingCertificateController(
*tlsConfig,
tlsConfig,
s.ClientCA,
s.Cert,
s.SNICerts,