k8s.io/apiserver@v0.31.1/pkg/server/filters/maxinflight.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package filters
    18  
    19  import (
    20  	"fmt"
    21  	"net/http"
    22  	"sync"
    23  	"time"
    24  
    25  	"k8s.io/apimachinery/pkg/util/sets"
    26  	"k8s.io/apimachinery/pkg/util/wait"
    27  	"k8s.io/apiserver/pkg/authentication/user"
    28  	"k8s.io/apiserver/pkg/endpoints/metrics"
    29  	apirequest "k8s.io/apiserver/pkg/endpoints/request"
    30  	fcmetrics "k8s.io/apiserver/pkg/util/flowcontrol/metrics"
    31  
    32  	"k8s.io/klog/v2"
    33  )
    34  
    35  const (
    36  	// Constant for the retry-after interval on rate limiting.
    37  	retryAfter = "1"
    38  
    39  	// How often inflight usage metric should be updated. Because
    40  	// the metrics tracks maximal value over period making this
    41  	// longer will increase the metric value.
    42  	inflightUsageMetricUpdatePeriod = time.Second
    43  )
    44  
    45  var (
    46  	nonMutatingRequestVerbs = sets.NewString("get", "list", "watch")
    47  	watchVerbs              = sets.NewString("watch")
    48  )
    49  
    50  func handleError(w http.ResponseWriter, r *http.Request, err error) {
    51  	errorMsg := fmt.Sprintf("Internal Server Error: %#v", r.RequestURI)
    52  	http.Error(w, errorMsg, http.StatusInternalServerError)
    53  	klog.Errorf(err.Error())
    54  }
    55  
    56  // requestWatermark is used to track maximal numbers of requests in a particular phase of handling
    57  type requestWatermark struct {
    58  	phase                                string
    59  	readOnlyObserver, mutatingObserver   fcmetrics.RatioedGauge
    60  	lock                                 sync.Mutex
    61  	readOnlyWatermark, mutatingWatermark int
    62  }
    63  
    64  func (w *requestWatermark) recordMutating(mutatingVal int) {
    65  	w.mutatingObserver.Set(float64(mutatingVal))
    66  
    67  	w.lock.Lock()
    68  	defer w.lock.Unlock()
    69  
    70  	if w.mutatingWatermark < mutatingVal {
    71  		w.mutatingWatermark = mutatingVal
    72  	}
    73  }
    74  
    75  func (w *requestWatermark) recordReadOnly(readOnlyVal int) {
    76  	w.readOnlyObserver.Set(float64(readOnlyVal))
    77  
    78  	w.lock.Lock()
    79  	defer w.lock.Unlock()
    80  
    81  	if w.readOnlyWatermark < readOnlyVal {
    82  		w.readOnlyWatermark = readOnlyVal
    83  	}
    84  }
    85  
    86  // watermark tracks requests being executed (not waiting in a queue)
    87  var watermark = &requestWatermark{
    88  	phase: metrics.ExecutingPhase,
    89  }
    90  
    91  // startWatermarkMaintenance starts the goroutines to observe and maintain the specified watermark.
    92  func startWatermarkMaintenance(watermark *requestWatermark, stopCh <-chan struct{}) {
    93  	// Periodically update the inflight usage metric.
    94  	go wait.Until(func() {
    95  		watermark.lock.Lock()
    96  		readOnlyWatermark := watermark.readOnlyWatermark
    97  		mutatingWatermark := watermark.mutatingWatermark
    98  		watermark.readOnlyWatermark = 0
    99  		watermark.mutatingWatermark = 0
   100  		watermark.lock.Unlock()
   101  
   102  		metrics.UpdateInflightRequestMetrics(watermark.phase, readOnlyWatermark, mutatingWatermark)
   103  	}, inflightUsageMetricUpdatePeriod, stopCh)
   104  }
   105  
   106  var initMaxInFlightOnce sync.Once
   107  
   108  func initMaxInFlight(nonMutatingLimit, mutatingLimit int) {
   109  	initMaxInFlightOnce.Do(func() {
   110  		// Fetching these gauges is delayed until after their underlying metric has been registered
   111  		// so that this latches onto the efficient implementation.
   112  		watermark.readOnlyObserver = fcmetrics.GetExecutingReadonlyConcurrency()
   113  		watermark.mutatingObserver = fcmetrics.GetExecutingMutatingConcurrency()
   114  		if nonMutatingLimit != 0 {
   115  			watermark.readOnlyObserver.SetDenominator(float64(nonMutatingLimit))
   116  			klog.V(2).InfoS("Set denominator for readonly requests", "limit", nonMutatingLimit)
   117  		}
   118  		if mutatingLimit != 0 {
   119  			watermark.mutatingObserver.SetDenominator(float64(mutatingLimit))
   120  			klog.V(2).InfoS("Set denominator for mutating requests", "limit", mutatingLimit)
   121  		}
   122  	})
   123  }
   124  
   125  // WithMaxInFlightLimit limits the number of in-flight requests to buffer size of the passed in channel.
   126  func WithMaxInFlightLimit(
   127  	handler http.Handler,
   128  	nonMutatingLimit int,
   129  	mutatingLimit int,
   130  	longRunningRequestCheck apirequest.LongRunningRequestCheck,
   131  ) http.Handler {
   132  	if nonMutatingLimit == 0 && mutatingLimit == 0 {
   133  		return handler
   134  	}
   135  	var nonMutatingChan chan bool
   136  	var mutatingChan chan bool
   137  	if nonMutatingLimit != 0 {
   138  		nonMutatingChan = make(chan bool, nonMutatingLimit)
   139  		klog.V(2).InfoS("Initialized nonMutatingChan", "len", nonMutatingLimit)
   140  	} else {
   141  		klog.V(2).InfoS("Running with nil nonMutatingChan")
   142  	}
   143  	if mutatingLimit != 0 {
   144  		mutatingChan = make(chan bool, mutatingLimit)
   145  		klog.V(2).InfoS("Initialized mutatingChan", "len", mutatingLimit)
   146  	} else {
   147  		klog.V(2).InfoS("Running with nil mutatingChan")
   148  	}
   149  	initMaxInFlight(nonMutatingLimit, mutatingLimit)
   150  
   151  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   152  		ctx := r.Context()
   153  		requestInfo, ok := apirequest.RequestInfoFrom(ctx)
   154  		if !ok {
   155  			handleError(w, r, fmt.Errorf("no RequestInfo found in context, handler chain must be wrong"))
   156  			return
   157  		}
   158  
   159  		// Skip tracking long running events.
   160  		if longRunningRequestCheck != nil && longRunningRequestCheck(r, requestInfo) {
   161  			handler.ServeHTTP(w, r)
   162  			return
   163  		}
   164  
   165  		var c chan bool
   166  		isMutatingRequest := !nonMutatingRequestVerbs.Has(requestInfo.Verb)
   167  		if isMutatingRequest {
   168  			c = mutatingChan
   169  		} else {
   170  			c = nonMutatingChan
   171  		}
   172  
   173  		if c == nil {
   174  			handler.ServeHTTP(w, r)
   175  		} else {
   176  
   177  			select {
   178  			case c <- true:
   179  				// We note the concurrency level both while the
   180  				// request is being served and after it is done being
   181  				// served, because both states contribute to the
   182  				// sampled stats on concurrency.
   183  				if isMutatingRequest {
   184  					watermark.recordMutating(len(c))
   185  				} else {
   186  					watermark.recordReadOnly(len(c))
   187  				}
   188  				defer func() {
   189  					<-c
   190  					if isMutatingRequest {
   191  						watermark.recordMutating(len(c))
   192  					} else {
   193  						watermark.recordReadOnly(len(c))
   194  					}
   195  				}()
   196  				handler.ServeHTTP(w, r)
   197  
   198  			default:
   199  				// at this point we're about to return a 429, BUT not all actors should be rate limited.  A system:master is so powerful
   200  				// that they should always get an answer.  It's a super-admin or a loopback connection.
   201  				if currUser, ok := apirequest.UserFrom(ctx); ok {
   202  					for _, group := range currUser.GetGroups() {
   203  						if group == user.SystemPrivilegedGroup {
   204  							handler.ServeHTTP(w, r)
   205  							return
   206  						}
   207  					}
   208  				}
   209  				// We need to split this data between buckets used for throttling.
   210  				metrics.RecordDroppedRequest(r, requestInfo, metrics.APIServerComponent, isMutatingRequest)
   211  				metrics.RecordRequestTermination(r, requestInfo, metrics.APIServerComponent, http.StatusTooManyRequests)
   212  				tooManyRequests(r, w, retryAfter)
   213  			}
   214  		}
   215  	})
   216  }
   217  
   218  // StartMaxInFlightWatermarkMaintenance starts the goroutines to observe and maintain watermarks for max-in-flight
   219  // requests.
   220  func StartMaxInFlightWatermarkMaintenance(stopCh <-chan struct{}) {
   221  	startWatermarkMaintenance(watermark, stopCh)
   222  }