kubernetes apiserver源码: filter-chain WithWarningRecorder

189 阅读3分钟

filter-chain里的这个handler作用是在HTTP响应头Warning里添加请求处理过程中的一些警告信息。

// WithWarningRecorder attaches a deduplicating k8s.io/apiserver/pkg/warning#WarningRecorder to the request context.
func WithWarningRecorder(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		recorder := &recorder{writer: w}
                
                // 把处理警告信息的recorder 放到请求的Context中.
		req = req.WithContext(warning.WithWarningRecorder(req.Context(), recorder))
                
                // 交给下一个handler继续处理
		handler.ServeHTTP(w, req)
	})
}

// WithWarningRecorder returns a new context that wraps the provided context and contains the provided Recorder implementation.
// The returned context can be passed to AddWarning().
func WithWarningRecorder(ctx context.Context, recorder Recorder) context.Context {
        // note: 不展开,已分析
	return context.WithValue(ctx, warningRecorderKey, recorder)
}

Recorder

Interface Recorder 表示提供警告信息处理的一类结构,struct recorder实现了它。

// Recorder provides a method for recording warnings
type Recorder interface {
	// AddWarning adds the specified warning to the response.
	// agent must be valid UTF-8, and must not contain spaces, quotes, backslashes, or control characters.
	// text must be valid UTF-8, and must not contain control characters.
	AddWarning(agent, text string)
}
  • recorder

这里runes是一个字符的意思,一个汉字或者英文字母都代表一个字符, runes是utf-8的字符,占用4个字节,也就是int32

type recorder struct {
	// lock guards calls to AddWarning from multiple threads
	lock sync.Mutex

	// recorded tracks whether AddWarning was already called with a given text
	recorded map[string]bool  // 警告信息去重(dedupe)

	// ordered tracks warnings added so they can be replayed and truncated if needed
	ordered []recordedWarning // 大部分agent都是空字符串`""`

	// written tracks how many runes of text have been added as warning headers
	written int      // utf-8字符数

	// truncating tracks if we have already exceeded truncateAtTotalRunes and are now truncating warning messages as we add them
	truncating bool  // 信息太长截断

	// writer is the response writer to add warning headers to
	writer http.ResponseWriter
}
// 一条完整的警告信息
type recordedWarning struct {
	agent string
	text  string
}

下面来分析下如何实现AddWarning(agent, text string)方法。

func (r *recorder) AddWarning(agent, text string) {
	if len(text) == 0 {
		return
	}

	r.lock.Lock()
	defer r.lock.Unlock()

        // truncateAtTotalRunes是响应头Warning的总长度限制,超长且已经截断了就不再添加了
	// if we've already exceeded our limit and are already truncating, return early
	if r.written >= truncateAtTotalRunes && r.truncating {
		return
	}

	// init if needed
	if r.recorded == nil {
		r.recorded = map[string]bool{}
	}

        // 警告信息去重
	// dedupe if already warned
	if r.recorded[text] {
		return
	}
	r.recorded[text] = true
        
        // 记录完整警告信息
	r.ordered = append(r.ordered, recordedWarning{agent: agent, text: text})

        //  truncateItemRunes是一条警告信息限制,超长则截断
	// truncate on a rune boundary, if needed
	textRuneLength := utf8.RuneCountInString(text)
	if r.truncating && textRuneLength > truncateItemRunes {
		text = string([]rune(text)[:truncateItemRunes]) // 切片语法截断
		textRuneLength = truncateItemRunes
	}

        // 处理text里的字符转义,如果agent="",则agent="-"
	// compute the header
	header, err := net.NewWarningHeader(299, agent, text)
	if err != nil {
		return
	}


        // 如果不超总长限制,则直接追加返回
	// if this fits within our limit, or we're already truncating, write and return
	if r.written+textRuneLength <= truncateAtTotalRunes || r.truncating {
		r.written += textRuneLength
		r.writer.Header().Add("Warning", header)
		return
	}

        // 否则,需要重新截断处理
	// otherwise, enable truncation, reset, and replay the existing items as truncated warnings
	r.truncating = true
	r.written = 0
	r.writer.Header().Del("Warning")
	utilruntime.HandleError(fmt.Errorf("exceeded max warning header size, truncating"))
	for _, w := range r.ordered {
		agent := w.agent
		text := w.text

                // 每条警告信息超长则截断
		textRuneLength := utf8.RuneCountInString(text)
		if textRuneLength > truncateItemRunes {
			text = string([]rune(text)[:truncateItemRunes])
			textRuneLength = truncateItemRunes
		}
		if header, err := net.NewWarningHeader(299, agent, text); err == nil {
			r.written += textRuneLength
                        // Add adds the key, value pair to the header.
                        // It appends to any existing values associated with key.
                        // The key is case insensitive; it is canonicalized by
                        // CanonicalHeaderKey.
			r.writer.Header().Add("Warning", header)
		}
	}
}

Client Usage

客户端使用的话,直接调用AddWarning

// AddWarning records a warning for the specified agent and text to the Recorder added to the provided context using WithWarningRecorder().
// If no Recorder exists in the provided context, this is a no-op.
// agent must be valid UTF-8, and must not contain spaces, quotes, backslashes, or control characters.
// text must be valid UTF-8, and must not contain control characters.
func AddWarning(ctx context.Context, agent string, text string) {
        // 从上下文中拿出来recorder
	recorder, ok := warningRecorderFrom(ctx)
	if !ok {
		return
	}
        // 刚才分析的recorder.AddWarning
	recorder.AddWarning(agent, text)
}

比如:

  • k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go
        
	result, err := webhookrequest.VerifyAdmissionResponse(uid, true, response)
	if err != nil {
		return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
	}

	for k, v := range result.AuditAnnotations {
		key := h.Name + "/" + k
		if err := attr.Attributes.AddAnnotation(key, v); err != nil {
			klog.Warningf("Failed to set admission audit annotation %s to %s for mutating webhook %s: %v", key, v, h.Name, err)
		}
	}
	for _, w := range result.Warnings {
		warning.AddWarning(ctx, "", w)
	}