vendor: revendor

This commit is contained in:
Sergiusz Urbaniak 2020-12-14 12:43:28 +01:00
parent 269295a414
commit 9f0440be0f
No known key found for this signature in database
GPG key ID: 44E6612519E13C39
669 changed files with 58447 additions and 20021 deletions

View file

@ -50,6 +50,7 @@ import (
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
authorizerunion "k8s.io/apiserver/pkg/authorization/union"
"k8s.io/apiserver/pkg/endpoints/discovery"
"k8s.io/apiserver/pkg/endpoints/filterlatency"
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
apiopenapi "k8s.io/apiserver/pkg/endpoints/openapi"
apirequest "k8s.io/apiserver/pkg/endpoints/request"
@ -61,6 +62,7 @@ import (
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/apiserver/pkg/server/routes"
serverstore "k8s.io/apiserver/pkg/server/storage"
"k8s.io/apiserver/pkg/storageversion"
"k8s.io/apiserver/pkg/util/feature"
utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol"
"k8s.io/client-go/informers"
@ -223,6 +225,12 @@ type Config struct {
// EquivalentResourceRegistry provides information about resources equivalent to a given resource,
// and the kind associated with a given resource. As resources are installed, they are registered here.
EquivalentResourceRegistry runtime.EquivalentResourceRegistry
// APIServerID is the ID of this API server
APIServerID string
// StorageVersionManager holds the storage versions of the API resources installed by this server.
StorageVersionManager storageversion.Manager
}
type RecommendedConfig struct {
@ -286,6 +294,10 @@ type AuthorizationInfo struct {
// NewConfig returns a Config struct with the default values
func NewConfig(codecs serializer.CodecFactory) *Config {
defaultHealthChecks := []healthz.HealthChecker{healthz.PingHealthz, healthz.LogHealthz}
var id string
if feature.DefaultFeatureGate.Enabled(features.APIServerIdentity) {
id = "kube-apiserver-" + uuid.New().String()
}
return &Config{
Serializer: codecs,
BuildHandlerChainFunc: DefaultBuildHandlerChain,
@ -323,7 +335,9 @@ func NewConfig(codecs serializer.CodecFactory) *Config {
// Default to treating watch as a long-running operation
// Generic API servers have no inherent long-running subresources
LongRunningFunc: genericfilters.BasicLongRunningRequestCheck(sets.NewString("watch"), sets.NewString()),
LongRunningFunc: genericfilters.BasicLongRunningRequestCheck(sets.NewString("watch"), sets.NewString()),
APIServerID: id,
StorageVersionManager: storageversion.NewDefaultManager(),
}
}
@ -573,6 +587,9 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
maxRequestBodyBytes: c.MaxRequestBodyBytes,
livezClock: clock.RealClock{},
APIServerID: c.APIServerID,
StorageVersionManager: c.StorageVersionManager,
}
for {
@ -638,6 +655,31 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
klog.V(3).Infof("Not requested to run hook %s", priorityAndFairnessConfigConsumerHookName)
}
// Add PostStartHooks for maintaining the watermarks for the Priority-and-Fairness and the Max-in-Flight filters.
if c.FlowControl != nil {
const priorityAndFairnessFilterHookName = "priority-and-fairness-filter"
if !s.isPostStartHookRegistered(priorityAndFairnessFilterHookName) {
err := s.AddPostStartHook(priorityAndFairnessFilterHookName, func(context PostStartHookContext) error {
genericfilters.StartPriorityAndFairnessWatermarkMaintenance(context.StopCh)
return nil
})
if err != nil {
return nil, err
}
}
} else {
const maxInFlightFilterHookName = "max-in-flight-filter"
if !s.isPostStartHookRegistered(maxInFlightFilterHookName) {
err := s.AddPostStartHook(maxInFlightFilterHookName, func(context PostStartHookContext) error {
genericfilters.StartMaxInFlightWatermarkMaintenance(context.StopCh)
return nil
})
if err != nil {
return nil, err
}
}
}
for _, delegateCheck := range delegationTarget.HealthzChecks() {
skip := false
for _, existingCheck := range c.HealthzChecks {
@ -668,18 +710,41 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
return s, nil
}
func BuildHandlerChainWithStorageVersionPrecondition(apiHandler http.Handler, c *Config) http.Handler {
// WithStorageVersionPrecondition needs the WithRequestInfo to run first
handler := genericapifilters.WithStorageVersionPrecondition(apiHandler, c.StorageVersionManager, c.Serializer)
return DefaultBuildHandlerChain(handler, c)
}
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
handler := genericapifilters.WithAuthorization(apiHandler, c.Authorization.Authorizer, c.Serializer)
handler := filterlatency.TrackCompleted(apiHandler)
handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer, c.Serializer)
handler = filterlatency.TrackStarted(handler, "authorization")
if c.FlowControl != nil {
handler = filterlatency.TrackCompleted(handler)
handler = genericfilters.WithPriorityAndFairness(handler, c.LongRunningFunc, c.FlowControl)
handler = filterlatency.TrackStarted(handler, "priorityandfairness")
} else {
handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.LongRunningFunc)
}
handler = filterlatency.TrackCompleted(handler)
handler = genericapifilters.WithImpersonation(handler, c.Authorization.Authorizer, c.Serializer)
handler = filterlatency.TrackStarted(handler, "impersonation")
handler = filterlatency.TrackCompleted(handler)
handler = genericapifilters.WithAudit(handler, c.AuditBackend, c.AuditPolicyChecker, c.LongRunningFunc)
handler = filterlatency.TrackStarted(handler, "audit")
failedHandler := genericapifilters.Unauthorized(c.Serializer)
failedHandler = genericapifilters.WithFailedAuthenticationAudit(failedHandler, c.AuditBackend, c.AuditPolicyChecker)
failedHandler = filterlatency.TrackCompleted(failedHandler)
handler = filterlatency.TrackCompleted(handler)
handler = genericapifilters.WithAuthentication(handler, c.Authentication.Authenticator, failedHandler, c.Authentication.APIAudiences)
handler = filterlatency.TrackStarted(handler, "authentication")
handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true")
handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.LongRunningFunc, c.RequestTimeout)
handler = genericfilters.WithWaitGroup(handler, c.LongRunningFunc, c.HandlerChainWaitGroup)
@ -690,7 +755,8 @@ func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
handler = genericapifilters.WithAuditAnnotations(handler, c.AuditBackend, c.AuditPolicyChecker)
handler = genericapifilters.WithWarningRecorder(handler)
handler = genericapifilters.WithCacheControl(handler)
handler = genericfilters.WithPanicRecovery(handler)
handler = genericapifilters.WithRequestReceivedTimestamp(handler)
handler = genericfilters.WithPanicRecovery(handler, c.RequestInfoResolver)
return handler
}
@ -719,7 +785,7 @@ func installAPI(s *GenericAPIServer, c *Config) {
if c.EnableDiscovery {
s.Handler.GoRestfulContainer.Add(s.DiscoveryGroupManager.WebService())
}
if feature.DefaultFeatureGate.Enabled(features.APIPriorityAndFairness) {
if c.FlowControl != nil && feature.DefaultFeatureGate.Enabled(features.APIPriorityAndFairness) {
c.FlowControl.Install(s.Handler.NonGoRestfulMux)
}
}

View file

@ -22,6 +22,7 @@ import (
"strings"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/apis/apiserver"
"k8s.io/apiserver/pkg/apis/apiserver/install"
@ -32,6 +33,10 @@ import (
var cfgScheme = runtime.NewScheme()
// validEgressSelectorNames contains the set of valid egress selctor names.
// 'master' is deprecated in favor of 'controlplane' and will be removed in v1.22.
var validEgressSelectorNames = sets.NewString("master", "controlplane", "cluster", "etcd")
func init() {
install.Install(cfgScheme)
}
@ -97,6 +102,30 @@ func ValidateEgressSelectorConfiguration(config *apiserver.EgressSelectorConfigu
}))
}
}
var foundControlPlane, foundMaster bool
for _, service := range config.EgressSelections {
canonicalName := strings.ToLower(service.Name)
if !validEgressSelectorNames.Has(canonicalName) {
allErrs = append(allErrs, field.NotSupported(field.NewPath("egressSelection", "name"), canonicalName, validEgressSelectorNames.List()))
continue
}
if canonicalName == "master" {
foundMaster = true
}
if canonicalName == "controlplane" {
foundControlPlane = true
}
}
// error if both master and controlplane egress selectors are set
if foundMaster && foundControlPlane {
allErrs = append(allErrs, field.Forbidden(field.NewPath("egressSelection", "name"), "both egressSelection names 'master' and 'controlplane' are specified, only one is allowed"))
}
return allErrs
}
@ -199,7 +228,7 @@ func validateTLSConfig(tlsConfig *apiserver.TLSConfig, fldPath *field.Path) fiel
return allErrs
}
if tlsConfig.CABundle != "" {
if exists, err := path.Exists(path.CheckFollowSymlink, tlsConfig.CABundle); exists == false || err != nil {
if exists, err := path.Exists(path.CheckFollowSymlink, tlsConfig.CABundle); !exists || err != nil {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("tlsConfig", "caBundle"),
tlsConfig.CABundle,
@ -211,7 +240,7 @@ func validateTLSConfig(tlsConfig *apiserver.TLSConfig, fldPath *field.Path) fiel
fldPath.Child("tlsConfig", "clientCert"),
"nil",
"Using TLS requires clientCert"))
} else if exists, err := path.Exists(path.CheckFollowSymlink, tlsConfig.ClientCert); exists == false || err != nil {
} else if exists, err := path.Exists(path.CheckFollowSymlink, tlsConfig.ClientCert); !exists || err != nil {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("tlsConfig", "clientCert"),
tlsConfig.ClientCert,
@ -222,7 +251,7 @@ func validateTLSConfig(tlsConfig *apiserver.TLSConfig, fldPath *field.Path) fiel
fldPath.Child("tlsConfig", "clientKey"),
"nil",
"Using TLS requires requires clientKey"))
} else if exists, err := path.Exists(path.CheckFollowSymlink, tlsConfig.ClientKey); exists == false || err != nil {
} else if exists, err := path.Exists(path.CheckFollowSymlink, tlsConfig.ClientKey); !exists || err != nil {
allErrs = append(allErrs, field.Invalid(
fldPath.Child("tlsConfig", "clientKey"),
tlsConfig.ClientKey,

View file

@ -51,8 +51,8 @@ type EgressSelector struct {
type EgressType int
const (
// Master is the EgressType for traffic intended to go to the control plane.
Master EgressType = iota
// ControlPlane is the EgressType for traffic intended to go to the control plane.
ControlPlane EgressType = iota
// Etcd is the EgressType for traffic intended to go to Kubernetes persistence store.
Etcd
// Cluster is the EgressType for traffic intended to go to the system being managed by Kubernetes.
@ -73,8 +73,8 @@ type Lookup func(networkContext NetworkContext) (utilnet.DialFunc, error)
// String returns the canonical string representation of the egress type
func (s EgressType) String() string {
switch s {
case Master:
return "master"
case ControlPlane:
return "controlplane"
case Etcd:
return "etcd"
case Cluster:
@ -91,8 +91,12 @@ func (s EgressType) AsNetworkContext() NetworkContext {
func lookupServiceName(name string) (EgressType, error) {
switch strings.ToLower(name) {
// 'master' is deprecated, interpret "master" as controlplane internally until removed in v1.22.
case "master":
return Master, nil
klog.Warning("EgressSelection name 'master' is deprecated, use 'controlplane' instead")
return ControlPlane, nil
case "controlplane":
return ControlPlane, nil
case "etcd":
return Etcd, nil
case "cluster":
@ -364,5 +368,6 @@ func (cs *EgressSelector) Lookup(networkContext NetworkContext) (utilnet.DialFun
// The round trip wrapper will over-ride the dialContext method appropriately
return nil, nil
}
return cs.egressToDialer[networkContext.EgressSelectionName], nil
}

View file

@ -80,9 +80,5 @@ type probabilisticGoawayDecider struct {
// Goaway implement GoawayDecider
func (p *probabilisticGoawayDecider) Goaway(r *http.Request) bool {
if p.next() < p.chance {
return true
}
return false
return p.next() < p.chance
}

View file

@ -41,6 +41,10 @@ const (
// the metrics tracks maximal value over period making this
// longer will increase the metric value.
inflightUsageMetricUpdatePeriod = time.Second
// How often to run maintenance on observations to ensure
// that they do not fall too far behind.
observationMaintenancePeriod = 10 * time.Second
)
var nonMutatingRequestVerbs = sets.NewString("get", "list", "watch")
@ -88,23 +92,29 @@ var watermark = &requestWatermark{
mutatingObserver: fcmetrics.ReadWriteConcurrencyObserverPairGenerator.Generate(1, 1, []string{metrics.MutatingKind}).RequestsExecuting,
}
func startRecordingUsage(watermark *requestWatermark) {
go func() {
wait.Forever(func() {
watermark.lock.Lock()
readOnlyWatermark := watermark.readOnlyWatermark
mutatingWatermark := watermark.mutatingWatermark
watermark.readOnlyWatermark = 0
watermark.mutatingWatermark = 0
watermark.lock.Unlock()
// startWatermarkMaintenance starts the goroutines to observe and maintain the specified watermark.
func startWatermarkMaintenance(watermark *requestWatermark, stopCh <-chan struct{}) {
// Periodically update the inflight usage metric.
go wait.Until(func() {
watermark.lock.Lock()
readOnlyWatermark := watermark.readOnlyWatermark
mutatingWatermark := watermark.mutatingWatermark
watermark.readOnlyWatermark = 0
watermark.mutatingWatermark = 0
watermark.lock.Unlock()
metrics.UpdateInflightRequestMetrics(watermark.phase, readOnlyWatermark, mutatingWatermark)
}, inflightUsageMetricUpdatePeriod)
}()
metrics.UpdateInflightRequestMetrics(watermark.phase, readOnlyWatermark, mutatingWatermark)
}, inflightUsageMetricUpdatePeriod, stopCh)
// Periodically observe the watermarks. This is done to ensure that they do not fall too far behind. When they do
// fall too far behind, then there is a long delay in responding to the next request received while the observer
// catches back up.
go wait.Until(func() {
watermark.readOnlyObserver.Add(0)
watermark.mutatingObserver.Add(0)
}, observationMaintenancePeriod, stopCh)
}
var startOnce sync.Once
// WithMaxInFlightLimit limits the number of in-flight requests to buffer size of the passed in channel.
func WithMaxInFlightLimit(
handler http.Handler,
@ -112,7 +122,6 @@ func WithMaxInFlightLimit(
mutatingLimit int,
longRunningRequestCheck apirequest.LongRunningRequestCheck,
) http.Handler {
startOnce.Do(func() { startRecordingUsage(watermark) })
if nonMutatingLimit == 0 && mutatingLimit == 0 {
return handler
}
@ -198,6 +207,12 @@ func WithMaxInFlightLimit(
})
}
// StartMaxInFlightWatermarkMaintenance starts the goroutines to observe and maintain watermarks for max-in-flight
// requests.
func StartMaxInFlightWatermarkMaintenance(stopCh <-chan struct{}) {
startWatermarkMaintenance(watermark, stopCh)
}
func tooManyRequests(req *http.Request, w http.ResponseWriter) {
// Return a 429 status indicating "Too Many Requests"
w.Header().Set("Retry-After", retryAfter)

View file

@ -22,7 +22,7 @@ import (
"net/http"
"sync/atomic"
fcv1a1 "k8s.io/api/flowcontrol/v1alpha1"
flowcontrol "k8s.io/api/flowcontrol/v1beta1"
apitypes "k8s.io/apimachinery/pkg/types"
epmetrics "k8s.io/apiserver/pkg/endpoints/metrics"
apirequest "k8s.io/apiserver/pkg/endpoints/request"
@ -35,11 +35,6 @@ 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 {
@ -76,10 +71,6 @@ func WithPriorityAndFairness(
klog.Warningf("priority and fairness support not found, skipping")
return handler
}
startOnce.Do(func() {
startRecordingUsage(watermark)
startRecordingUsage(waitingMark)
})
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
requestInfo, ok := apirequest.RequestInfoFrom(ctx)
@ -101,7 +92,7 @@ func WithPriorityAndFairness(
}
var classification *PriorityAndFairnessClassification
note := func(fs *fcv1a1.FlowSchema, pl *fcv1a1.PriorityLevelConfiguration) {
note := func(fs *flowcontrol.FlowSchema, pl *flowcontrol.PriorityLevelConfiguration) {
classification = &PriorityAndFairnessClassification{
FlowSchemaName: fs.Name,
FlowSchemaUID: fs.UID,
@ -131,11 +122,11 @@ func WithPriorityAndFairness(
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))
w.Header().Set(flowcontrol.ResponseHeaderMatchedPriorityLevelConfigurationUID, string(classification.PriorityLevelUID))
w.Header().Set(flowcontrol.ResponseHeaderMatchedFlowSchemaUID, string(classification.FlowSchemaUID))
handler.ServeHTTP(w, innerReq)
}
digest := utilflowcontrol.RequestDigest{requestInfo, user}
digest := utilflowcontrol.RequestDigest{RequestInfo: requestInfo, User: user}
fcIfc.Handle(ctx, digest, note, func(inQueue bool) {
if inQueue {
noteWaitingDelta(1)
@ -144,9 +135,21 @@ func WithPriorityAndFairness(
}
}, execute)
if !served {
if isMutatingRequest {
epmetrics.DroppedRequests.WithLabelValues(epmetrics.MutatingKind).Inc()
} else {
epmetrics.DroppedRequests.WithLabelValues(epmetrics.ReadOnlyKind).Inc()
}
epmetrics.RecordRequestTermination(r, requestInfo, epmetrics.APIServerComponent, http.StatusTooManyRequests)
tooManyRequests(r, w)
}
})
}
// StartPriorityAndFairnessWatermarkMaintenance starts the goroutines to observe and maintain watermarks for
// priority-and-fairness requests.
func StartPriorityAndFairnessWatermarkMaintenance(stopCh <-chan struct{}) {
startWatermarkMaintenance(watermark, stopCh)
startWatermarkMaintenance(waitingMark, stopCh)
}

View file

@ -33,8 +33,6 @@ import (
apirequest "k8s.io/apiserver/pkg/endpoints/request"
)
var errConnKilled = fmt.Errorf("killing connection/stream because serving request timed out and response had been started")
// WithTimeoutForNonLongRunningRequests times out non-long-running requests after the time given by timeout.
func WithTimeoutForNonLongRunningRequests(handler http.Handler, longRunning apirequest.LongRunningRequestCheck, timeout time.Duration) http.Handler {
if longRunning == nil {
@ -246,15 +244,17 @@ func (tw *baseTimeoutWriter) timeout(err *apierrors.StatusError) {
// no way to timeout the HTTP request at the point. We have to shutdown
// the connection for HTTP1 or reset stream for HTTP2.
//
// Note from: Brad Fitzpatrick
// if the ServeHTTP goroutine panics, that will do the best possible thing for both
// HTTP/1 and HTTP/2. In HTTP/1, assuming you're replying with at least HTTP/1.1 and
// you've already flushed the headers so it's using HTTP chunking, it'll kill the TCP
// connection immediately without a proper 0-byte EOF chunk, so the peer will recognize
// the response as bogus. In HTTP/2 the server will just RST_STREAM the stream, leaving
// the TCP connection open, but resetting the stream to the peer so it'll have an error,
// like the HTTP/1 case.
panic(errConnKilled)
// Note from the golang's docs:
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and either closes the network connection or sends an HTTP/2
// RST_STREAM, depending on the HTTP protocol. To abort a handler so
// the client sees an interrupted response but the server doesn't log
// an error, panic with the value ErrAbortHandler.
//
// We are throwing http.ErrAbortHandler deliberately so that a client is notified and to suppress a not helpful stacktrace in the logs
panic(http.ErrAbortHandler)
}
}

View file

@ -17,22 +17,41 @@ limitations under the License.
package filters
import (
"fmt"
"net/http"
"k8s.io/klog/v2"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/endpoints/metrics"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/server/httplog"
"k8s.io/klog/v2"
)
// WithPanicRecovery wraps an http Handler to recover and log panics (except in the special case of http.ErrAbortHandler panics, which suppress logging).
func WithPanicRecovery(handler http.Handler) http.Handler {
func WithPanicRecovery(handler http.Handler, resolver request.RequestInfoResolver) http.Handler {
return withPanicRecovery(handler, func(w http.ResponseWriter, req *http.Request, err interface{}) {
if err == http.ErrAbortHandler {
// honor the http.ErrAbortHandler sentinel panic value:
// ErrAbortHandler is a sentinel panic value to abort a handler.
// While any panic from ServeHTTP aborts the response to the client,
// panicking with ErrAbortHandler also suppresses logging of a stack trace to the server's error log.
// Honor the http.ErrAbortHandler sentinel panic value
//
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and either closes the network connection or sends an HTTP/2
// RST_STREAM, depending on the HTTP protocol. To abort a handler so
// the client sees an interrupted response but the server doesn't log
// an error, panic with the value ErrAbortHandler.
//
// Note that the ReallyCrash variable controls the behaviour of the HandleCrash function
// So it might actually crash, after calling the handlers
if info, err := resolver.NewRequestInfo(req); err != nil {
metrics.RecordRequestAbort(req, nil)
} else {
metrics.RecordRequestAbort(req, info)
}
// This call can have different handlers, but the default chain rate limits. Call it after the metrics are updated
// in case the rate limit delays it. If you outrun the rate for this one timed out requests, something has gone
// seriously wrong with your server, but generally having a logging signal for timeouts is useful.
runtime.HandleError(fmt.Errorf("timeout or abort while handling: %v %q", req.Method, req.URL.Path))
return
}
http.Error(w, "This request caused apiserver to panic. Look in the logs for details.", http.StatusInternalServerError)

View file

@ -40,9 +40,13 @@ import (
"k8s.io/apiserver/pkg/authorization/authorizer"
genericapi "k8s.io/apiserver/pkg/endpoints"
"k8s.io/apiserver/pkg/endpoints/discovery"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/apiserver/pkg/server/routes"
"k8s.io/apiserver/pkg/storageversion"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilopenapi "k8s.io/apiserver/pkg/util/openapi"
restclient "k8s.io/client-go/rest"
"k8s.io/klog/v2"
@ -179,10 +183,6 @@ type GenericAPIServer struct {
// and the kind associated with a given resource. As resources are installed, they are registered here.
EquivalentResourceRegistry runtime.EquivalentResourceRegistry
// enableAPIResponseCompression indicates whether API Responses should support compression
// if the client requests it via Accept-Encoding
enableAPIResponseCompression bool
// delegationTarget is the next delegate in the chain. This is never nil.
delegationTarget DelegationTarget
@ -197,6 +197,12 @@ type GenericAPIServer struct {
// The limit on the request body size that would be accepted and decoded in a write request.
// 0 means no limit.
maxRequestBodyBytes int64
// APIServerID is the ID of this API server
APIServerID string
// StorageVersionManager holds the storage versions of the API resources installed by this server.
StorageVersionManager storageversion.Manager
}
// DelegationTarget is an interface which allows for composition of API servers with top level handling that works
@ -407,6 +413,7 @@ func (s preparedGenericAPIServer) NonBlockingRun(stopCh <-chan struct{}) (<-chan
// installAPIResources is a private method for installing the REST storage backing each api groupversionresource
func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo, openAPIModels openapiproto.Models) error {
var resourceInfos []*storageversion.ResourceInfo
for _, groupVersion := range apiGroupInfo.PrioritizedVersions {
if len(apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version]) == 0 {
klog.Warningf("Skipping API %v because it has no resources.", groupVersion)
@ -418,11 +425,30 @@ func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *A
apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion
}
apiGroupVersion.OpenAPIModels = openAPIModels
if openAPIModels != nil && utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
typeConverter, err := fieldmanager.NewTypeConverter(openAPIModels, false)
if err != nil {
return err
}
apiGroupVersion.TypeConverter = typeConverter
}
apiGroupVersion.MaxRequestBodyBytes = s.maxRequestBodyBytes
if err := apiGroupVersion.InstallREST(s.Handler.GoRestfulContainer); err != nil {
r, err := apiGroupVersion.InstallREST(s.Handler.GoRestfulContainer)
if err != nil {
return fmt.Errorf("unable to setup API %v: %v", apiGroupInfo, err)
}
resourceInfos = append(resourceInfos, r...)
}
if utilfeature.DefaultFeatureGate.Enabled(features.StorageVersionAPI) &&
utilfeature.DefaultFeatureGate.Enabled(features.APIServerIdentity) {
// API installation happens before we start listening on the handlers,
// therefore it is safe to register ResourceInfos here. The handler will block
// write requests until the storage versions of the targeting resources are updated.
s.StorageVersionManager.AddResourceInfo(resourceInfos...)
}
return nil

View file

@ -161,6 +161,7 @@ func InstallPathHandler(mux mux, path string, checks ...HealthChecker) {
klog.V(5).Infof("Installing health checkers for (%v): %v", path, formatQuoted(checkerNames(checks...)...))
name := strings.Split(strings.TrimPrefix(path, "/"), "/")[0]
mux.Handle(path,
metrics.InstrumentHandlerFunc("GET",
/* group = */ "",
@ -171,7 +172,7 @@ func InstallPathHandler(mux mux, path string, checks ...HealthChecker) {
/* component = */ "",
/* deprecated */ false,
/* removedRelease */ "",
handleRootHealthz(checks...)))
handleRootHealth(name, checks...)))
for _, check := range checks {
mux.Handle(fmt.Sprintf("%s/%v", path, check.Name()), adaptCheckToHandler(check.Check))
}
@ -207,8 +208,8 @@ func getExcludedChecks(r *http.Request) sets.String {
return sets.NewString()
}
// handleRootHealthz returns an http.HandlerFunc that serves the provided checks.
func handleRootHealthz(checks ...HealthChecker) http.HandlerFunc {
// handleRootHealth returns an http.HandlerFunc that serves the provided checks.
func handleRootHealth(name string, checks ...HealthChecker) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
excluded := getExcludedChecks(r)
// failedVerboseLogOutput is for output to the log. It indicates detailed failed output information for the log.
@ -240,8 +241,8 @@ func handleRootHealthz(checks ...HealthChecker) http.HandlerFunc {
}
// always be verbose on failure
if len(failedChecks) > 0 {
klog.V(2).Infof("healthz check failed: %s\n%v", strings.Join(failedChecks, ","), failedVerboseLogOutput.String())
http.Error(httplog.Unlogged(r, w), fmt.Sprintf("%shealthz check failed", individualCheckOutput.String()), http.StatusInternalServerError)
klog.V(2).Infof("%s check failed: %s\n%v", strings.Join(failedChecks, ","), name, failedVerboseLogOutput.String())
http.Error(httplog.Unlogged(r, w), fmt.Sprintf("%s%s check failed", individualCheckOutput.String(), name), http.StatusInternalServerError)
return
}
@ -253,7 +254,7 @@ func handleRootHealthz(checks ...HealthChecker) http.HandlerFunc {
}
individualCheckOutput.WriteTo(w)
fmt.Fprint(w, "healthz check passed\n")
fmt.Fprintf(w, "%s check passed\n", name)
})
}

View file

@ -37,6 +37,7 @@ import (
"k8s.io/apiserver/pkg/audit/policy"
"k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/egressselector"
"k8s.io/apiserver/pkg/util/webhook"
pluginbuffered "k8s.io/apiserver/plugin/pkg/audit/buffered"
pluginlog "k8s.io/apiserver/plugin/pkg/audit/log"
plugintruncate "k8s.io/apiserver/plugin/pkg/audit/truncate"
@ -120,6 +121,7 @@ type AuditLogOptions struct {
MaxBackups int
MaxSize int
Format string
Compress bool
BatchOptions AuditBatchOptions
TruncateOptions AuditTruncateOptions
@ -153,7 +155,7 @@ type AuditDynamicOptions struct {
func NewAuditOptions() *AuditOptions {
return &AuditOptions{
WebhookOptions: AuditWebhookOptions{
InitialBackoff: pluginwebhook.DefaultInitialBackoff,
InitialBackoff: pluginwebhook.DefaultInitialBackoffDelay,
BatchOptions: AuditBatchOptions{
Mode: ModeBatch,
BatchConfig: defaultWebhookBatchConfig(),
@ -306,7 +308,7 @@ func (o *AuditOptions) ApplyTo(
klog.V(2).Info("No audit policy file provided, no events will be recorded for webhook backend")
} else {
if c.EgressSelector != nil {
egressDialer, err := c.EgressSelector.Lookup(egressselector.Master.AsNetworkContext())
egressDialer, err := c.EgressSelector.Lookup(egressselector.ControlPlane.AsNetworkContext())
if err != nil {
return err
}
@ -449,6 +451,7 @@ func (o *AuditLogOptions) AddFlags(fs *pflag.FlagSet) {
strings.Join(pluginlog.AllowedFormats, ",")+".")
fs.StringVar(&o.GroupVersionString, "audit-log-version", o.GroupVersionString,
"API group and version used for serializing audit events written to log.")
fs.BoolVar(&o.Compress, "audit-log-compress", o.Compress, "If set, the rotated log files will be compressed using gzip.")
}
func (o *AuditLogOptions) Validate() []error {
@ -513,6 +516,7 @@ func (o *AuditLogOptions) getWriter() io.Writer {
MaxAge: o.MaxAge,
MaxBackups: o.MaxBackups,
MaxSize: o.MaxSize,
Compress: o.Compress,
}
}
return w
@ -566,7 +570,7 @@ func (o *AuditWebhookOptions) enabled() bool {
// this is done so that the same trucate backend can wrap both the webhook and dynamic backends
func (o *AuditWebhookOptions) newUntruncatedBackend(customDial utilnet.DialFunc) (audit.Backend, error) {
groupVersion, _ := schema.ParseGroupVersion(o.GroupVersionString)
webhook, err := pluginwebhook.NewBackend(o.ConfigFile, groupVersion, o.InitialBackoff, customDial)
webhook, err := pluginwebhook.NewBackend(o.ConfigFile, groupVersion, webhook.DefaultRetryBackoffWithInitialDelay(o.InitialBackoff), customDial)
if err != nil {
return nil, fmt.Errorf("initializing audit webhook: %v", err)
}

View file

@ -26,6 +26,7 @@ import (
"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
"k8s.io/apiserver/pkg/server"
@ -36,6 +37,17 @@ import (
openapicommon "k8s.io/kube-openapi/pkg/common"
)
// DefaultAuthWebhookRetryBackoff is the default backoff parameters for
// both authentication and authorization webhook used by the apiserver.
func DefaultAuthWebhookRetryBackoff() *wait.Backoff {
return &wait.Backoff{
Duration: 500 * time.Millisecond,
Factor: 1.5,
Jitter: 0.2,
Steps: 5,
}
}
type RequestHeaderAuthenticationOptions struct {
// ClientCAFile is the root certificate bundle to verify client certificates on incoming requests
// before trusting usernames in headers.
@ -177,6 +189,15 @@ type DelegatingAuthenticationOptions struct {
// TolerateInClusterLookupFailure indicates failures to look up authentication configuration from the cluster configmap should not be fatal.
// Setting this can result in an authenticator that will reject all requests.
TolerateInClusterLookupFailure bool
// WebhookRetryBackoff specifies the backoff parameters for the authentication webhook retry logic.
// This allows us to configure the sleep time at each iteration and the maximum number of retries allowed
// before we fail the webhook call in order to limit the fan out that ensues when the system is degraded.
WebhookRetryBackoff *wait.Backoff
// ClientTimeout specifies a time limit for requests made by the authorization webhook client.
// The default value is set to 10 seconds.
ClientTimeout time.Duration
}
func NewDelegatingAuthenticationOptions() *DelegatingAuthenticationOptions {
@ -189,13 +210,29 @@ func NewDelegatingAuthenticationOptions() *DelegatingAuthenticationOptions {
GroupHeaders: []string{"x-remote-group"},
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
},
WebhookRetryBackoff: DefaultAuthWebhookRetryBackoff(),
ClientTimeout: 10 * time.Second,
}
}
// WithCustomRetryBackoff sets the custom backoff parameters for the authentication webhook retry logic.
func (s *DelegatingAuthenticationOptions) WithCustomRetryBackoff(backoff wait.Backoff) {
s.WebhookRetryBackoff = &backoff
}
// WithClientTimeout sets the given timeout for the authentication webhook client.
func (s *DelegatingAuthenticationOptions) WithClientTimeout(timeout time.Duration) {
s.ClientTimeout = timeout
}
func (s *DelegatingAuthenticationOptions) Validate() []error {
allErrors := []error{}
allErrors = append(allErrors, s.RequestHeader.Validate()...)
if s.WebhookRetryBackoff != nil && s.WebhookRetryBackoff.Steps <= 0 {
allErrors = append(allErrors, fmt.Errorf("number of webhook retry attempts must be greater than 1, but is: %d", s.WebhookRetryBackoff.Steps))
}
return allErrors
}
@ -233,8 +270,9 @@ func (s *DelegatingAuthenticationOptions) ApplyTo(authenticationInfo *server.Aut
}
cfg := authenticatorfactory.DelegatingAuthenticatorConfig{
Anonymous: true,
CacheTTL: s.CacheTTL,
Anonymous: true,
CacheTTL: s.CacheTTL,
WebhookRetryBackoff: s.WebhookRetryBackoff,
}
client, err := s.getClient()
@ -377,6 +415,7 @@ func (s *DelegatingAuthenticationOptions) getClient() (kubernetes.Interface, err
// set high qps/burst limits since this will effectively limit API server responsiveness
clientConfig.QPS = 200
clientConfig.Burst = 400
clientConfig.Timeout = s.ClientTimeout
return kubernetes.NewForConfig(clientConfig)
}

View file

@ -23,6 +23,7 @@ import (
"github.com/spf13/pflag"
"k8s.io/klog/v2"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
"k8s.io/apiserver/pkg/authorization/path"
@ -59,13 +60,24 @@ type DelegatingAuthorizationOptions struct {
// AlwaysAllowGroups are groups which are allowed to take any actions. In kube, this is system:masters.
AlwaysAllowGroups []string
// ClientTimeout specifies a time limit for requests made by SubjectAccessReviews client.
// The default value is set to 10 seconds.
ClientTimeout time.Duration
// WebhookRetryBackoff specifies the backoff parameters for the authorization webhook retry logic.
// This allows us to configure the sleep time at each iteration and the maximum number of retries allowed
// before we fail the webhook call in order to limit the fan out that ensues when the system is degraded.
WebhookRetryBackoff *wait.Backoff
}
func NewDelegatingAuthorizationOptions() *DelegatingAuthorizationOptions {
return &DelegatingAuthorizationOptions{
// very low for responsiveness, but high enough to handle storms
AllowCacheTTL: 10 * time.Second,
DenyCacheTTL: 10 * time.Second,
AllowCacheTTL: 10 * time.Second,
DenyCacheTTL: 10 * time.Second,
ClientTimeout: 10 * time.Second,
WebhookRetryBackoff: DefaultAuthWebhookRetryBackoff(),
}
}
@ -81,8 +93,23 @@ func (s *DelegatingAuthorizationOptions) WithAlwaysAllowPaths(paths ...string) *
return s
}
// WithClientTimeout sets the given timeout for SAR client used by this authorizer
func (s *DelegatingAuthorizationOptions) WithClientTimeout(timeout time.Duration) {
s.ClientTimeout = timeout
}
// WithCustomRetryBackoff sets the custom backoff parameters for the authorization webhook retry logic.
func (s *DelegatingAuthorizationOptions) WithCustomRetryBackoff(backoff wait.Backoff) {
s.WebhookRetryBackoff = &backoff
}
func (s *DelegatingAuthorizationOptions) Validate() []error {
allErrors := []error{}
if s.WebhookRetryBackoff != nil && s.WebhookRetryBackoff.Steps <= 0 {
allErrors = append(allErrors, fmt.Errorf("number of webhook retry attempts must be greater than 1, but is: %d", s.WebhookRetryBackoff.Steps))
}
return allErrors
}
@ -149,6 +176,7 @@ func (s *DelegatingAuthorizationOptions) toAuthorizer(client kubernetes.Interfac
SubjectAccessReviewClient: client.AuthorizationV1().SubjectAccessReviews(),
AllowCacheTTL: s.AllowCacheTTL,
DenyCacheTTL: s.DenyCacheTTL,
WebhookRetryBackoff: s.WebhookRetryBackoff,
}
delegatedAuthorizer, err := cfg.New()
if err != nil {
@ -186,6 +214,7 @@ func (s *DelegatingAuthorizationOptions) getClient() (kubernetes.Interface, erro
// set high qps/burst limits since this will effectively limit API server responsiveness
clientConfig.QPS = 200
clientConfig.Burst = 400
clientConfig.Timeout = s.ClientTimeout
return kubernetes.NewForConfig(clientConfig)
}

View file

@ -18,6 +18,7 @@ package options
import (
"fmt"
"github.com/spf13/pflag"
"k8s.io/utils/path"
@ -84,7 +85,7 @@ func (o *EgressSelectorOptions) Validate() []error {
errs := []error{}
if exists, err := path.Exists(path.CheckFollowSymlink, o.ConfigFile); exists == false || err != nil {
if exists, err := path.Exists(path.CheckFollowSymlink, o.ConfigFile); !exists || err != nil {
errs = append(errs, fmt.Errorf("egress-selector-config-file %s does not exist", o.ConfigFile))
}

View file

@ -180,6 +180,9 @@ func (s *EtcdOptions) AddFlags(fs *pflag.FlagSet) {
fs.DurationVar(&s.StorageConfig.DBMetricPollInterval, "etcd-db-metric-poll-interval", s.StorageConfig.DBMetricPollInterval,
"The interval of requests to poll etcd and update metric. 0 disables the metric collection")
fs.DurationVar(&s.StorageConfig.HealthcheckTimeout, "etcd-healthcheck-timeout", s.StorageConfig.HealthcheckTimeout,
"The timeout to use when checking etcd health.")
}
func (s *EtcdOptions) ApplyTo(c *server.Config) error {

View file

@ -126,7 +126,7 @@ func (o *RecommendedOptions) ApplyTo(config *server.RecommendedConfig) error {
if feature.DefaultFeatureGate.Enabled(features.APIPriorityAndFairness) {
config.FlowControl = utilflowcontrol.New(
config.SharedInformerFactory,
kubernetes.NewForConfigOrDie(config.ClientConfig).FlowcontrolV1alpha1(),
kubernetes.NewForConfigOrDie(config.ClientConfig).FlowcontrolV1beta1(),
config.MaxRequestsInFlight+config.MaxMutatingRequestsInFlight,
config.RequestTimeout/4,
)

View file

@ -38,7 +38,7 @@ func (oa OpenAPI) Install(c *restful.Container, mux *mux.PathRecorderMux) (*hand
if err != nil {
klog.Fatalf("Failed to build open api spec for root: %v", err)
}
spec.Definitions = handler.PruneDefaults(spec.Definitions)
openAPIVersionedService, err := handler.NewOpenAPIService(spec)
if err != nil {
klog.Fatalf("Failed to create OpenAPIService: %v", err)

View file

@ -85,11 +85,7 @@ func (o *ResourceConfig) EnableVersions(versions ...schema.GroupVersion) {
func (o *ResourceConfig) VersionEnabled(version schema.GroupVersion) bool {
enabled, _ := o.GroupVersionConfigs[version]
if enabled {
return true
}
return false
return enabled
}
func (o *ResourceConfig) DisableResources(resources ...schema.GroupVersionResource) {