kubernetes apiserver源码: filter-chain WithRequestDeadline

138 阅读2分钟

这个部分主要是对请求超时做处理。

// 请求超时设置
handler = genericapifilters.WithRequestDeadline(handler, c.AuditBackend, c.AuditPolicyChecker,
		c.LongRunningFunc, c.Serializer, c.RequestTimeout)
  • k8s.io/apiserver/pkg/endpoints/filters/request_deadline.go

这个函数就是一个透传函数,同时增加一个时钟utilclock.RealClock{},用于获取当前时间。

// WithRequestDeadline determines the timeout duration applicable to the given request and sets a new context
// with the appropriate deadline.
// auditWrapper provides an http.Handler that audits a failed request.
// longRunning returns true if he given request is a long running request.
// requestTimeoutMaximum specifies the default request timeout value.
func WithRequestDeadline(handler http.Handler, sink audit.Sink, policy policy.Checker, longRunning request.LongRunningRequestCheck,
	negotiatedSerializer runtime.NegotiatedSerializer, requestTimeoutMaximum time.Duration) http.Handler {
	return withRequestDeadline(handler, sink, policy, longRunning, negotiatedSerializer, requestTimeoutMaximum, utilclock.RealClock{})
}

主要的逻辑在withRequestDeadline:

func withRequestDeadline(handler http.Handler, sink audit.Sink, policy policy.Checker, longRunning request.LongRunningRequestCheck,
	negotiatedSerializer runtime.NegotiatedSerializer, requestTimeoutMaximum time.Duration, clock utilclock.PassiveClock) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		ctx := req.Context()
                // 从Context获取WithRequestInfo chain放进去的RequestInfo对象
		requestInfo, ok := request.RequestInfoFrom(ctx)
		if !ok {
			handleError(w, req, http.StatusInternalServerError, fmt.Errorf("no RequestInfo found in context, handler chain must be wrong"))
			return
		}
                // 长请求比如watch请求之类的,做超时处理没意义,直接略过交给下一个handler处理
		if longRunning(req, requestInfo) {
			handler.ServeHTTP(w, req)
			return
		}
                // + 查询请求url有没有指定超时时间
		userSpecifiedTimeout, ok, err := parseTimeout(req)
		if err != nil {
			statusErr := apierrors.NewBadRequest(err.Error())

			klog.Errorf("Error - %s: %#v", err.Error(), req.RequestURI)

			failed := failedErrorHandler(negotiatedSerializer, statusErr)
			failWithAudit := withFailedRequestAudit(failed, statusErr, sink, policy)
			failWithAudit.ServeHTTP(w, req)
			return
		}
                // 获取配置文件里apiserver的默认请求超时时间
		timeout := requestTimeoutMaximum
		if ok {
			// we use the default timeout enforced by the apiserver:
			// - if the user has specified a timeout of 0s, this implies no timeout on the user's part.
			// - if the user has specified a timeout that exceeds the maximum deadline allowed by the apiserver.
			if userSpecifiedTimeout > 0 && userSpecifiedTimeout < requestTimeoutMaximum {
                                // 如果request中指定的比apiserver配置中指定超时时间要短,使用request中的
				timeout = userSpecifiedTimeout
			}
		}
                // 获取现在的时间
		started := clock.Now()
                
                // 从请求中获取请求发起时间,如果有则覆盖clock.Now()获取的时间
		if requestStartedTimestamp, ok := request.ReceivedTimestampFrom(ctx); ok {
			started = requestStartedTimestamp
		}
                // 创建一个超时的Context
		ctx, cancel := context.WithDeadline(ctx, started.Add(timeout))
		defer cancel()

		req = req.WithContext(ctx)
                // 移交给下个Handler
		handler.ServeHTTP(w, req)
	})
}
  • userSpecifiedTimeout, ok, err := parseTimeout(req)
// parseTimeout parses the given HTTP request URL and extracts the timeout query parameter
// value if specified by the user.
// If a timeout is not specified the function returns false and err is set to nil
// If the value specified is malformed then the function returns false and err is set
func parseTimeout(req *http.Request) (time.Duration, bool, error) {
	value := req.URL.Query().Get("timeout")
	if value == "" {
		return 0, false, nil
	}

	timeout, err := time.ParseDuration(value)
	if err != nil {
		return 0, false, fmt.Errorf("%s - %s", invalidTimeoutInURL, err.Error())
	}

	return timeout, true, nil
}
  • requestStartedTimestamp, ok := request.ReceivedTimestampFrom(ctx)
// ReceivedTimestampFrom returns the value of the ReceivedTimestamp key from the specified context.
func ReceivedTimestampFrom(ctx context.Context) (time.Time, bool) {
	info, ok := ctx.Value(requestReceivedTimestampKey).(time.Time)
	return info, ok
}

总结

请求超时的核心API是带超时的Context, 这里有个demo:

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
        // 设置一个请求100ms超时
	d := time.Now().Add(100 *time.Microsecond)
	ctx, cancel := context.WithDeadline(context.Background(), d)

	// Even though ctx will be expired, it is good practice to call its
	// cancellation function in any case. Failure to do so may keep the
	// context and its parent alive longer than necessary.
	defer cancel()

	select {
	case <-time.After(1 * time.Second):
		fmt.Println("overslept")
	case <-ctx.Done(): // 如果100ms内,没有完成则打印超时异常
		fmt.Println(ctx.Err())
	}

	//Output:
	//
	//context deadline exceeded
}