sigs.k8s.io controller-runtime系列之十二 health分析

64 阅读3分钟

简介

之前介绍过sigs.k8s.io controller-runtime系列之十一 finalizer分析sigs.k8s.io controller-runtime-finalizer 。 本文主要介绍pkg/health的源码分析。

目录结构

  1. health.go
    • 函数
    // getExcludedChecks 提取要从查询参数中排除的健康检查名称。
    func getExcludedChecks(r *http.Request) sets.String {
    	checks, found := r.URL.Query()["exclude"]
    	if found {
    		return sets.NewString(checks...)
    	}
    	return sets.NewString()
    }
    
    // formatQuoted 返回健康检查名称的格式化字符串并且保留传入的顺序。
    func formatQuoted(names ...string) string {
    	quoted := make([]string, 0, len(names))
    	for _, name := range names {
    		quoted = append(quoted, fmt.Sprintf("%q", name))
    	}
    	return strings.Join(quoted, ",")
    }
    // writeStatusAsText 以我们从 Kubernetes 复制的某种半任意定制的文本格式写出给定的检查状态。 
    // writeStatusAsText 在失败时总是详细的,并且可以使用给定的参数强制在成功时变得详细。
    func writeStatusesAsText(resp http.ResponseWriter, parts []checkStatus, unknownExcludes sets.String, failed, forceVerbose bool) {
        resp.Header().Set("Content-Type", "text/plain; charset=utf-8")
        resp.Header().Set("X-Content-Type-Options", "nosniff")
        
        // 先写状态码
        if failed {
        	resp.WriteHeader(http.StatusInternalServerError)
        } else {
        	resp.WriteHeader(http.StatusOK)
        }
        
        // forceVerbose=false且成功,resp简单的响应
        if !failed && !forceVerbose {
        	fmt.Fprint(resp, "ok")
        	return
        }
        
        // 我们总是在失败时详细,所以从现在开始,我们保证响应时详细的
        for _, checkOut := range parts {
        	switch {
        	case checkOut.excluded:
        		fmt.Fprintf(resp, "[+]%s excluded: ok\n", checkOut.name)
        	case checkOut.healthy:
        		fmt.Fprintf(resp, "[+]%s ok\n", checkOut.name)
        	default:
        		fmt.Fprintf(resp, "[-]%s failed: reason withheld\n", checkOut.name)
        	}
        }
        // req参数中设置的排除check项目在该Handler总不匹配的项目
        if unknownExcludes.Len() > 0 {
        	fmt.Fprintf(resp, "warn: some health checks cannot be excluded: no matches for %s\n", formatQuoted(unknownExcludes.List()...))
        }
        
        if failed {
        	log.Info("healthz check failed", "statuses", parts)
        	fmt.Fprintf(resp, "healthz check failed\n")
       	} else {
       		fmt.Fprint(resp, "healthz check passed\n")
       	}
    }
    
    • Handler结构体
    // Handler 是一个 http.Handler,它将给定Checker的结果聚合到根路径,并支持在Checker名称的子路径上调用单个检查器。
    // 即时添加Checker不是线程安全的——使用包装器。
    type Handler struct {
    	Checks map[string]Checker
    }
    
    // 聚合Handler中所有Checker的checkStatus
    func (h *Handler) serveAggregated(resp http.ResponseWriter, req *http.Request) {
    	failed := false
        // 获取req请求参数中不需要check的Checker名称
    	excluded := getExcludedChecks(req)
    
    	parts := make([]checkStatus, 0, len(h.Checks))
    
    	// 计算结果,遍历h.Checks
    	for checkName, check := range h.Checks {
    		// 检查是否存在于我们已经指定要排除检查名称
    		if excluded.Has(checkName) {
                // 从排除检查名称中删除(较少遍历项)
    			excluded.Delete(checkName)
                // 追加check结果healthy: true, excluded: true
    			parts = append(parts, checkStatus{name: checkName, healthy: true, excluded: true})
    			continue
    		}
            // check出错
    		if err := check(req); err != nil {
    			log.V(1).Info("healthz check failed", "checker", checkName, "error", err)
                // 追加check结果healthy: false
    			parts = append(parts, checkStatus{name: checkName, healthy: false})
                // 总的检查结果failed置为true
    			failed = true
    		} else {
                // 追加check结果healthy: true
    			parts = append(parts, checkStatus{name: checkName, healthy: true})
    		}
    	}
    
    	// 如果h.Checks长度为0,则追加检查结果name: "ping", healthy: true
    	if len(h.Checks) == 0 {
    		parts = append(parts, checkStatus{name: "ping", healthy: true})
    	}
     
    	for _, c := range excluded.List() {
    		log.V(1).Info("cannot exclude health check, no matches for it", "checker", c)
    	}
    
    	// 以检查结果中每项的name长度升序排列
    	sort.Slice(parts, func(i, j int) bool { return parts[i].name < parts[j].name })
    
    	// 根据req的参数verbose来响应结果
    	_, forceVerbose := req.URL.Query()["verbose"]
        //  以特定的文本格式响应
    	writeStatusesAsText(resp, parts, excluded, failed, forceVerbose)
    }
    
    // 实现了http.Handler
    func (h *Handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
    	// 修正req的urlpath 
    	reqPath := req.URL.Path
    	if reqPath == "" || reqPath[0] != '/' {
    		reqPath = "/" + reqPath
    	}
    	// 1. 用一个斜线代替多个斜线。 
        // 2. 消除每个 .路径名元素(当前目录)。 
        // 3. 消除每个内部 .. 路径名元素(父目录) // 以及它之前的非 .. 元素。 
        // 4. 消除以根路径开头的 .. 元素: // 即,将路径开头的“/..”替换为“/”。
    	reqPath = path.Clean(reqPath)
    
    	// 如果reqPath="/",根端点聚合除了Excluded所有的Check
    	if reqPath == "/" {
    		h.serveAggregated(resp, req)
    		return
    	}
    
    	// 默认Ping检查(如果没有其他内容)
    	if len(h.Checks) == 0 && reqPath[1:] == "ping" {
    		CheckHandler{Checker: Ping}.ServeHTTP(resp, req)
    		return
    	}
    
    	// 判断reqPath(忽略/)在map中是否存在
    	checkName := reqPath[1:]
    	checker, known := h.Checks[checkName]
    	if !known {
    		http.NotFoundHandler().ServeHTTP(resp, req)
    		return
    	}
    
        // 单一的checker检查
    	CheckHandler{Checker: checker}.ServeHTTP(resp, req)
    }
    
    • checkStatus结构体 保存特定检查的输出
    type checkStatus struct {
    	name     string
    	healthy  bool
    	excluded bool
    }
    
    • CheckHandler结构体及其方法
    // CheckHandler 是一个 http.Handler,它基于其检查器在根路径提供健康检查端点 。
    type CheckHandler struct {
    	Checker
    }
    
    // Checker 知道如何执行健康检查。
    type Checker func(req *http.Request) error
    
    // 默认的Ping, 会自动返回 true。
    var Ping Checker = func(_ *http.Request) error { return nil }
    
    func (h CheckHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
        // 自定义实现的Checker
    	if err := h.Checker(req); err != nil {
    		http.Error(resp, fmt.Sprintf("internal server error: %v", err), http.StatusInternalServerError)
    	} else {
    		fmt.Fprint(resp, "ok")
    	}
    }