K8S Ingress Controller 健康检查原理剖析

简介: K8S本身提供了Liveness和Readiness机制对Pod进行健康监控,同样我们在部署K8S Ingress Controller时也配置了LivenessProbe和ReadinessProbe来对其进行健康检查,本文旨在剖析Nginx Ingress Controller内部的健康检查逻辑,以便于更好地监控Nginx Ingress Controller。

健康检查配置

nginx_ingress_controller_health_check

我们知道K8S本身提供了LivenessReadiness机制来对Pod进行健康监控,同样我们在部署K8S Ingress Controller时也配置了LivenessProbe和ReadinessProbe对其进行健康检查,具体配置如下所示:

livenessProbe:
  failureThreshold: 3
  httpGet:
    path: /healthz
    port: 10254
    scheme: HTTP
  initialDelaySeconds: 10
  periodSeconds: 10
  successThreshold: 1
  timeoutSeconds: 1
readinessProbe:
  failureThreshold: 3
  httpGet:
    path: /healthz
    port: 10254
    scheme: HTTP
  periodSeconds: 10
  successThreshold: 1
  timeoutSeconds: 1

那么Kubelet在对Nginx Ingress Controller Pod进行定期健康检查时,就会通过HTTP协议发送GET请求,类似于如下请求:

curl -XGET http://<NGINX_INGRESS_CONTROLLER_POD_ID>:10254/healthz

健康检查成功则会返回ok,检查失败则返回失败信息。

原理剖析

那么当Kubelet发起对Ingress Controller Pod的健康检查时,Nginx Ingress Controller内部到底做了什么,以及为什么是10254端口和/healthz路径,今天我们简单剖析下K8S Ingress Controller内部的健康检查逻辑。

1、10254 和 /healthz

首先,Nginx Ingress Controller在启动时会通过goroutine启动一个HTTP Server:

// 初始化一个 HTTP Request Handler
mux := http.NewServeMux()
go registerHandlers(conf.EnableProfiling, conf.ListenPorts.Health, ngx, mux)

其中registerHandlers方法实现如下:

func registerHandlers(enableProfiling bool, port int, ic *controller.NGINXController, mux *http.ServeMux) {
    // 注册健康检查Handler
    healthz.InstallHandler(mux,
        healthz.PingHealthz,
        ic,
    )

    // 用于Prometheus抓取metrics信息
    mux.Handle("/metrics", promhttp.Handler())
    // 获取当前Ingress Controller版本信息
    mux.HandleFunc("/build", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        b, _ := json.Marshal(version.String())
        w.Write(b)
    })
    // 主动停止Ingress Controller Pod
    mux.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
        err := syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
        if err != nil {
            glog.Errorf("Unexpected error: %v", err)
        }
    })

    // 获取性能监控信息
    if enableProfiling {
        mux.HandleFunc("/debug/pprof/", pprof.Index)
        mux.HandleFunc("/debug/pprof/heap", pprof.Index)
        mux.HandleFunc("/debug/pprof/mutex", pprof.Index)
        mux.HandleFunc("/debug/pprof/goroutine", pprof.Index)
        mux.HandleFunc("/debug/pprof/threadcreate", pprof.Index)
        mux.HandleFunc("/debug/pprof/block", pprof.Index)
        mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
        mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
        mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
        mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
    }
    // 启动 HTTP Server
    server := &http.Server{
        Addr:              fmt.Sprintf(":%v", port), // 指定监听Port
        Handler:           mux,
        ReadTimeout:       10 * time.Second,
        ReadHeaderTimeout: 10 * time.Second,
        WriteTimeout:      300 * time.Second,
        IdleTimeout:       120 * time.Second,
    }
    glog.Fatal(server.ListenAndServe())
}

从方法实现中,我们看到启动的HTTP Server监听的是conf.ListenPorts.Health端口;而该端口值是在Nginx Ingress Controller启动时通过如下启动参数解析而来:

httpPort      = flags.Int("http-port", 80, `Port to use for servicing HTTP traffic.`)
httpsPort     = flags.Int("https-port", 443, `Port to use for servicing HTTPS traffic.`)
statusPort    = flags.Int("status-port", 18080, `Port to use for exposing NGINX status pages.`)
sslProxyPort  = flags.Int("ssl-passthrough-proxy-port", 442, `Port to use internally for SSL Passthrough.`)
defServerPort = flags.Int("default-server-port", 8181, `Port to use for exposing the default server (catch-all).`)
healthzPort   = flags.Int("healthz-port", 10254, "Port to use for the healthz endpoint.")

因此,当我们在启动Nginx Ingress Controller时没有明确指定healthz-port参数,那么它的默认值就是10254端口。

另外,从上述方法中我们看到也注册了一个健康检查的Request Handler,其通过healthz.InstallHandler方法来完成注册:

func InstallHandler(mux mux, checks ...HealthzChecker) {
    // 如果没指定任何健康检查实现,那么默认仅注册PingHealthz实现
    if len(checks) == 0 {
        glog.V(5).Info("No default health checks specified. Installing the ping handler.")
        checks = []HealthzChecker{PingHealthz}
    }

    glog.V(5).Info("Installing healthz checkers:", strings.Join(checkerNames(checks...), ", "))
    // 注册健康检查根Handler,其内部会依次调用各个具体Handler实现
    mux.Handle("/healthz", handleRootHealthz(checks...))
    for _, check := range checks {
        // 注册各个具体的健康检查Handler实现
        mux.Handle(fmt.Sprintf("/healthz/%v", check.Name()), adaptCheckToHandler(check.Check))
    }
}

从这里我们看到注册的健康检查的请求根路径就是/healthz;当然我们也可以基于HealthzChecker接口来扩展更多的健康检查实现。

2、健康检查机制

通过前面章节我们看到当Kubelet对Nginx Ingress Controller Pod进行健康检查时,其最终会触发其内部handleRootHealthz 方法的执行:

func handleRootHealthz(checks ...HealthzChecker) http.HandlerFunc {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        failed := false
        var verboseOut bytes.Buffer
        for _, check := range checks {
            if err := check.Check(r); err != nil {
                // don't include the error since this endpoint is public.  If someone wants more detail
                // they should have explicit permission to the detailed checks.
                glog.V(6).Infof("healthz check %v failed: %v", check.Name(), err)
                fmt.Fprintf(&verboseOut, "[-]%v failed: reason withheld\n", check.Name())
                failed = true
            } else {
                fmt.Fprintf(&verboseOut, "[+]%v ok\n", check.Name())
            }
        }
        // always be verbose on failure
        if failed {
            http.Error(w, fmt.Sprintf("%vhealthz check failed", verboseOut.String()), http.StatusInternalServerError)
            return
        }

        if _, found := r.URL.Query()["verbose"]; !found {
            fmt.Fprint(w, "ok")
            return
        }

        verboseOut.WriteTo(w)
        fmt.Fprint(w, "healthz check passed\n")
    })
}

该方法内部会依次调用前面我们注册的各个具体的健康检查Handler实现,全部检查成功则会返回ok,任一检查失败则返回失败信息。

另外从前面代码中我们看到Nginx Ingress Controller在启动时会注册两个健康检查Handler:

  • healthz.PingHealthz

其是HealthzChecker接口的默认实现,实现逻辑很简单:

// PingHealthz returns true automatically when checked
var PingHealthz HealthzChecker = ping{}

// ping implements the simplest possible healthz checker.
type ping struct{}

func (ping) Name() string {
    return "ping"
}

// PingHealthz is a health check that returns true.
func (ping) Check(_ *http.Request) error {
    return nil
}
  • controller.NGINXController

其是Nginx Ingress Controller的具体代码实现,但同时其又实现了HealthzChecker接口来完成其所管理的资源必要的健康检查:

const (
    ngxHealthPath = "/healthz"
    nginxPID = "/tmp/nginx.pid"
)

func (n NGINXController) Name() string {
    return "nginx-ingress-controller"
}

func (n *NGINXController) Check(_ *http.Request) error {
    // 1.对Nginx进行健康检查,具体访问URL:http://0.0.0.0:18080/healthz
    res, err := http.Get(fmt.Sprintf("http://0.0.0.0:%v%v", n.cfg.ListenPorts.Status, ngxHealthPath))
    if err != nil {
        return err
    }
    defer res.Body.Close()
    if res.StatusCode != 200 {
        return fmt.Errorf("ingress controller is not healthy")
    }

    // 2. 若开启dynamic-configuration则检查Nginx维护在内存中的后端服务信息,访问URL:http://0.0.0.0:18080/is-dynamic-lb-initialized
    if n.cfg.DynamicConfigurationEnabled {
        res, err := http.Get(fmt.Sprintf("http://0.0.0.0:%v/is-dynamic-lb-initialized", n.cfg.ListenPorts.Status))
        if err != nil {
            return err
        }
        defer res.Body.Close()
        if res.StatusCode != 200 {
            return fmt.Errorf("dynamic load balancer not started")
        }
    }

    // 3. 检查Nginx主进程是否正常运行中
    fs, err := proc.NewFS("/proc")
    if err != nil {
        return errors.Wrap(err, "unexpected error reading /proc directory")
    }
    f, err := n.fileSystem.ReadFile(nginxPID)
    if err != nil {
        return errors.Wrapf(err, "unexpected error reading %v", nginxPID)
    }
    pid, err := strconv.Atoi(strings.TrimRight(string(f), "\r\n"))
    if err != nil {
        return errors.Wrapf(err, "unexpected error reading the nginx PID from %v", nginxPID)
    }
    _, err = fs.NewProc(pid)

    return err
}

我们看到访问Nginx的端口是n.cfg.ListenPorts.Status,其值同样来自于Nginx Ingress Controller的启动参数status-port,默认值为18080

最后通过Nginx配置文件我们可以看到Nginx在启动时同时会监听18080端口,如此我们便可通过该端口对其进行健康检查:

# used for NGINX healthcheck and access to nginx stats
server {
    listen 18080 default_server  backlog=511;
    listen [::]:18080 default_server  backlog=511;
    set $proxy_upstream_name "-";

    # 访问该路径直接返回200以说明Nginx能正常接收到请求
    location /healthz {
        access_log off;
        return 200;
    }

    # 校验当前内存中是否正常有后端服务信息
    location /is-dynamic-lb-initialized {
        access_log off;
        content_by_lua_block {
            local configuration = require("configuration")
            local backend_data = configuration.get_backends_data()
            if not backend_data then
            ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
            return
            end

            ngx.say("OK")
            ngx.exit(ngx.HTTP_OK)
        }
    }

    # 获取基本的监控统计信息
    location /nginx_status {
        set $proxy_upstream_name "internal";
        access_log off;
        stub_status on;
    }

    # 默认转发到404服务
    location / {
        set $proxy_upstream_name "upstream-default-backend";
        proxy_pass          http://upstream-default-backend;
    }
}

至此,Nginx Ingress Controller健康检查机制分析完毕,总结下来其主要校验两方面:

  1. Nginx进程是否正常运行
  2. 若开启dynamic-configuration其维护在内存中的后端服务信息是否存在
相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。 &nbsp; &nbsp; 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
目录
相关文章
|
Kubernetes 应用服务中间件 nginx
Kubernetes上安装Metallb和Ingress并部署应用程序
Kubernetes上安装Metallb和Ingress并部署nginx应用程序,使用LoadBalancer类型的KubernetesService
1219 113
|
Kubernetes 网络协议 应用服务中间件
Kubernetes Ingress:灵活的集群外部网络访问的利器
《Kubernetes Ingress:集群外部访问的利器-打造灵活的集群网络》介绍了如何通过Ingress实现Kubernetes集群的外部访问。前提条件是已拥有Kubernetes集群并安装了kubectl工具。文章详细讲解了Ingress的基本组成(Ingress Controller和资源对象),选择合适的版本,以及具体的安装步骤,如下载配置文件、部署Nginx Ingress Controller等。此外,还提供了常见问题的解决方案,例如镜像下载失败的应对措施。最后,通过部署示例应用展示了Ingress的实际使用方法。
907 2
|
缓存 Kubernetes 负载均衡
在K8S中,ingress 有何作用?
在K8S中,ingress 有何作用?
|
Kubernetes 负载均衡 应用服务中间件
k8s学习--ingress详细解释与应用(nginx ingress controller))
k8s学习--ingress详细解释与应用(nginx ingress controller))
2814 0
|
API UED 开发者
超实用技巧大放送:彻底革新你的WinForms应用,从流畅动画到丝滑交互设计,全面解析如何在保证性能的同时大幅提升用户体验,让软件操作变得赏心悦目不再是梦!
【8月更文挑战第31天】在Windows平台上,使用WinForms框架开发应用程序时,如何在保持性能的同时提升用户界面的吸引力和响应性是一个常见挑战。本文探讨了在不牺牲性能的前提下实现流畅动画与交互设计的最佳实践,包括使用BackgroundWorker处理耗时任务、利用Timer控件创建简单动画,以及使用Graphics类绘制自定义图形。通过具体示例代码展示了这些技术的应用,帮助开发者显著改善用户体验,使应用程序更加吸引人和易于使用。
324 0
|
6月前
|
人工智能 算法 调度
阿里云ACK托管集群Pro版共享GPU调度操作指南
本文介绍在阿里云ACK托管集群Pro版中,如何通过共享GPU调度实现显存与算力的精细化分配,涵盖前提条件、使用限制、节点池配置及任务部署全流程,提升GPU资源利用率,适用于AI训练与推理场景。
584 1
|
6月前
|
弹性计算 监控 调度
ACK One 注册集群云端节点池升级:IDC 集群一键接入云端 GPU 算力,接入效率提升 80%
ACK One注册集群节点池实现“一键接入”,免去手动编写脚本与GPU驱动安装,支持自动扩缩容与多场景调度,大幅提升K8s集群管理效率。
399 89
|
11月前
|
资源调度 Kubernetes 调度
从单集群到多集群的快速无损转型:ACK One 多集群应用分发
ACK One 的多集群应用分发,可以最小成本地结合您已有的单集群 CD 系统,无需对原先应用资源 YAML 进行修改,即可快速构建成多集群的 CD 系统,并同时获得强大的多集群资源调度和分发的能力。
792 9
|
11月前
|
资源调度 Kubernetes 调度
从单集群到多集群的快速无损转型:ACK One 多集群应用分发
本文介绍如何利用阿里云的分布式云容器平台ACK One的多集群应用分发功能,结合云效CD能力,快速将单集群CD系统升级为多集群CD系统。通过增加分发策略(PropagationPolicy)和差异化策略(OverridePolicy),并修改单集群kubeconfig为舰队kubeconfig,可实现无损改造。该方案具备多地域多集群智能资源调度、重调度及故障迁移等能力,帮助用户提升业务效率与可靠性。
|
存储 Kubernetes 监控
K8s集群实战:使用kubeadm和kuboard部署Kubernetes集群
总之,使用kubeadm和kuboard部署K8s集群就像回归童年一样,简单又有趣。不要忘记,技术是为人服务的,用K8s集群操控云端资源,我们不过是想在复杂的世界找寻简单。尽管部署过程可能遇到困难,但朝着简化复杂的目标,我们就能找到意义和乐趣。希望你也能利用这些工具,找到你的乐趣,满足你的需求。
1132 33

相关产品

  • 容器服务Kubernetes版
  • 推荐镜像

    更多
    下一篇
    开通oss服务