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 }