K8S Ingress Controller 健康检查原理剖析

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 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其维护在内存中的后端服务信息是否存在
相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
目录
相关文章
|
2月前
|
Kubernetes 负载均衡 应用服务中间件
k8s学习--ingress详细解释与应用(nginx ingress controller))
k8s学习--ingress详细解释与应用(nginx ingress controller))
358 0
|
3月前
|
Kubernetes 应用服务中间件 nginx
Kubernetes上安装Metallb和Ingress并部署应用程序
Kubernetes上安装Metallb和Ingress并部署nginx应用程序,使用LoadBalancer类型的KubernetesService
210 10
|
2月前
|
缓存 Kubernetes 负载均衡
在K8S中,ingress 有何作用?
在K8S中,ingress 有何作用?
|
4月前
|
Kubernetes 应用服务中间件 API
【Ingress 秘籍】集群进出流量的总管:揭秘 Kubernetes 中 Ingress 的终极奥秘!
【8月更文挑战第25天】Ingress是Kubernetes中用于管理HTTP与HTTPS流量进入集群的核心功能。作为集群内外通信的桥梁,Ingress通过定义规则将外部请求导向内部服务。本文详细介绍了Ingress的基本概念、配置方法及其实现方式。通过使用不同的Ingress控制器(如Nginx、Traefik等),用户可以根据需要选择最适合的方案。文中还提供了示例代码展示如何创建服务、部署应用及配置Ingress规则。
179 6
|
4月前
|
API UED 开发者
超实用技巧大放送:彻底革新你的WinForms应用,从流畅动画到丝滑交互设计,全面解析如何在保证性能的同时大幅提升用户体验,让软件操作变得赏心悦目不再是梦!
【8月更文挑战第31天】在Windows平台上,使用WinForms框架开发应用程序时,如何在保持性能的同时提升用户界面的吸引力和响应性是一个常见挑战。本文探讨了在不牺牲性能的前提下实现流畅动画与交互设计的最佳实践,包括使用BackgroundWorker处理耗时任务、利用Timer控件创建简单动画,以及使用Graphics类绘制自定义图形。通过具体示例代码展示了这些技术的应用,帮助开发者显著改善用户体验,使应用程序更加吸引人和易于使用。
84 0
|
10天前
|
存储 Kubernetes 关系型数据库
阿里云ACK备份中心,K8s集群业务应用数据的一站式灾备方案
本文源自2024云栖大会苏雅诗的演讲,探讨了K8s集群业务为何需要灾备及其重要性。文中强调了集群与业务高可用配置对稳定性的重要性,并指出人为误操作等风险,建议实施周期性和特定情况下的灾备措施。针对容器化业务,提出了灾备的新特性与需求,包括工作负载为核心、云资源信息的备份,以及有状态应用的数据保护。介绍了ACK推出的备份中心解决方案,支持命名空间、标签、资源类型等维度的备份,并具备存储卷数据保护功能,能够满足GitOps流程企业的特定需求。此外,还详细描述了备份中心的使用流程、控制台展示、灾备难点及解决方案等内容,展示了备份中心如何有效应对K8s集群资源和存储卷数据的灾备挑战。
|
1月前
|
Kubernetes 监控 Cloud Native
Kubernetes集群的高可用性与伸缩性实践
Kubernetes集群的高可用性与伸缩性实践
71 1
|
2月前
|
JSON Kubernetes 容灾
ACK One应用分发上线:高效管理多集群应用
ACK One应用分发上线,主要介绍了新能力的使用场景
|
2月前
|
Kubernetes 持续交付 开发工具
ACK One GitOps:ApplicationSet UI简化多集群GitOps应用管理
ACK One GitOps新发布了多集群应用控制台,支持管理Argo CD ApplicationSet,提升大规模应用和集群的多集群GitOps应用分发管理体验。
|
2月前
|
Kubernetes Ubuntu Linux
Centos7 搭建 kubernetes集群
本文介绍了如何搭建一个三节点的Kubernetes集群,包括一个主节点和两个工作节点。各节点运行CentOS 7系统,最低配置为2核CPU、2GB内存和15GB硬盘。详细步骤包括环境配置、安装Docker、关闭防火墙和SELinux、禁用交换分区、安装kubeadm、kubelet、kubectl,以及初始化Kubernetes集群和安装网络插件Calico或Flannel。
203 4

相关产品

  • 容器服务Kubernetes版
  • 下一篇
    DataWorks