Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/nodeproblemdetector/node_problem_detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
_ "k8s.io/node-problem-detector/cmd/nodeproblemdetector/problemdaemonplugins"
"k8s.io/node-problem-detector/cmd/options"
"k8s.io/node-problem-detector/pkg/exporters"
"k8s.io/node-problem-detector/pkg/exporters/httpexporter"
"k8s.io/node-problem-detector/pkg/exporters/k8sexporter"
"k8s.io/node-problem-detector/pkg/exporters/prometheusexporter"
"k8s.io/node-problem-detector/pkg/problemdaemon"
Expand Down Expand Up @@ -59,6 +60,10 @@ func npdMain(ctx context.Context, npdo *options.NodeProblemDetectorOptions) erro
defaultExporters = append(defaultExporters, pe)
klog.Info("Prometheus exporter started.")
}
if he := httpexporter.NewExporterOrDie(npdo); he != nil {
defaultExporters = append(defaultExporters, he)
klog.Info("HTTP exporter started.")
}

plugableExporters := exporters.NewExporters()

Expand Down
98 changes: 98 additions & 0 deletions pkg/exporters/httpexporter/http_exporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
Copyright 2026 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package httpexporter provides a standalone HTTP server exposing /healthz,
// /conditions and /debug/pprof without requiring a Kubernetes API server.
package httpexporter

import (
"net"
"net/http"
"net/http/pprof"
"strconv"
"sync"

"k8s.io/klog/v2"

"k8s.io/node-problem-detector/cmd/options"
"k8s.io/node-problem-detector/pkg/types"
"k8s.io/node-problem-detector/pkg/util"
)

type httpExporter struct {
mu sync.RWMutex
conditions map[string]types.Condition
}

// NewExporterOrDie creates the standalone HTTP exporter and starts the server.
// Returns nil if --port is 0 (disabled). Panics on bind errors.
func NewExporterOrDie(npdo *options.NodeProblemDetectorOptions) types.Exporter {
if npdo.ServerPort <= 0 {
return nil
}

he := &httpExporter{
conditions: make(map[string]types.Condition),
}

mux := http.NewServeMux()

mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
if _, err := w.Write([]byte("ok")); err != nil {
klog.Errorf("Failed to write response: %v", err)
}
})

mux.HandleFunc("/conditions", func(w http.ResponseWriter, r *http.Request) {
util.ReturnHTTPJson(w, he.getConditions())
})

mux.HandleFunc("/debug/pprof/", 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)

addr := net.JoinHostPort(npdo.ServerAddress, strconv.Itoa(npdo.ServerPort))
go func() {
if err := http.ListenAndServe(addr, mux); err != nil {
klog.Fatalf("Failed to start HTTP server: %v", err)
}
}()

klog.Infof("HTTP exporter started on %s", addr)
return he
}

// ExportProblems updates the in-memory condition store from the incoming status.
func (he *httpExporter) ExportProblems(status *types.Status) {
he.mu.Lock()
defer he.mu.Unlock()
for _, cdt := range status.Conditions {
he.conditions[cdt.Type] = cdt
}
}

func (he *httpExporter) getConditions() []types.Condition {
he.mu.RLock()
defer he.mu.RUnlock()
conditions := make([]types.Condition, 0, len(he.conditions))
for _, c := range he.conditions {
conditions = append(conditions, c)
}
return conditions
}
41 changes: 0 additions & 41 deletions pkg/exporters/k8sexporter/k8s_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ package k8sexporter

import (
"context"
"net"
"net/http"
"net/http/pprof"
"strconv"

"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/klog/v2"
Expand Down Expand Up @@ -65,7 +61,6 @@ func NewExporterOrDie(ctx context.Context, npdo *options.NodeProblemDetectorOpti
updateConditions: npdo.K8sExporterUpdateNodeConditions,
}

ke.startHTTPReporting(npdo)
ke.conditionManager.Start(ctx)

return &ke
Expand All @@ -84,42 +79,6 @@ func (ke *k8sExporter) ExportProblems(status *types.Status) {
}
}

func (ke *k8sExporter) startHTTPReporting(npdo *options.NodeProblemDetectorOptions) {
if npdo.ServerPort <= 0 {
return
}
mux := http.NewServeMux()

// Add healthz http request handler. Always return ok now, add more health check
// logic in the future.
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
if _, err := w.Write([]byte("ok")); err != nil {
klog.Errorf("Failed to write response: %v", err)
}
})

// Add the handler to serve condition http request.
mux.HandleFunc("/conditions", func(w http.ResponseWriter, r *http.Request) {
util.ReturnHTTPJson(w, ke.conditionManager.GetConditions())
})

// register pprof
mux.HandleFunc("/debug/pprof/", 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)

addr := net.JoinHostPort(npdo.ServerAddress, strconv.Itoa(npdo.ServerPort))
go func() {
err := http.ListenAndServe(addr, mux)
if err != nil {
klog.Fatalf("Failed to start server: %v", err)
}
}()
}

func waitForAPIServerReadyWithTimeout(ctx context.Context, c problemclient.Client, npdo *options.NodeProblemDetectorOptions) error {
return wait.PollUntilContextTimeout(ctx, npdo.APIServerWaitInterval, npdo.APIServerWaitTimeout, true, func(ctx context.Context) (done bool, err error) {
// If NPD can get the node object from kube-apiserver, the server is
Expand Down