diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index cfcf6e7d5cd..888cc17fa0a 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -132,14 +132,9 @@ func init() { otel.SetMeterProvider(noop.NewMeterProvider()) } -const ( - // Kubelet component name - componentKubelet = "kubelet" -) - // NewKubeletCommand creates a *cobra.Command object with default parameters func NewKubeletCommand() *cobra.Command { - cleanFlagSet := pflag.NewFlagSet(componentKubelet, pflag.ContinueOnError) + cleanFlagSet := pflag.NewFlagSet(server.ComponentKubelet, pflag.ContinueOnError) cleanFlagSet.SetNormalizeFunc(cliflag.WordSepNormalizeFunc) kubeletFlags := options.NewKubeletFlags() @@ -151,7 +146,7 @@ func NewKubeletCommand() *cobra.Command { } cmd := &cobra.Command{ - Use: componentKubelet, + Use: server.ComponentKubelet, Long: `The kubelet is the primary "node agent" that runs on each node. It can register the node with the apiserver using one of: the hostname; a flag to override the hostname; or specific logic for a cloud provider. @@ -562,7 +557,7 @@ func makeEventRecorder(ctx context.Context, kubeDeps *kubelet.Dependencies, node return } eventBroadcaster := record.NewBroadcaster(record.WithContext(ctx)) - kubeDeps.Recorder = eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: componentKubelet, Host: string(nodeName)}) + kubeDeps.Recorder = eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: server.ComponentKubelet, Host: string(nodeName)}) eventBroadcaster.StartStructuredLogging(3) if kubeDeps.EventClient != nil { klog.V(4).InfoS("Sending events to api server") @@ -1386,7 +1381,7 @@ func newTracerProvider(s *options.KubeletServer) (oteltrace.TracerProvider, erro } resourceOpts := []otelsdkresource.Option{ otelsdkresource.WithAttributes( - semconv.ServiceNameKey.String(componentKubelet), + semconv.ServiceNameKey.String(server.ComponentKubelet), semconv.HostNameKey.String(hostname), ), } diff --git a/pkg/kubelet/server/auth.go b/pkg/kubelet/server/auth.go index 568f78b0484..7e7fdd9643a 100644 --- a/pkg/kubelet/server/auth.go +++ b/pkg/kubelet/server/auth.go @@ -27,6 +27,7 @@ import ( "k8s.io/apiserver/pkg/server/healthz" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/component-base/configz" + "k8s.io/component-base/zpages/statusz" "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/features" ) @@ -68,6 +69,7 @@ func isSubpath(subpath, path string) bool { // /metrics/* => verb=, resource=nodes, name=, subresource(s)=metrics // /logs/* => verb=, resource=nodes, name=, subresource(s)=log // /checkpoint/* => verb=, resource=nodes, name=, subresource(s)=checkpoint +// /statusz => verb=, resource=nodes, name=, subresource(s)=statusz // /pods/* => verb=, resource=nodes, name=, subresource(s)=pods,proxy // /runningPods/* => verb=, resource=nodes, name=, subresource(s)=pods,proxy // /healthz/* => verb=, resource=nodes, name=, subresource(s)=healthz,proxy @@ -116,6 +118,8 @@ func (n nodeAuthorizerAttributesGetter) GetRequestAttributes(u user.Info, r *htt subresources = append(subresources, "log") case isSubpath(requestPath, checkpointPath): subresources = append(subresources, "checkpoint") + case isSubpath(requestPath, statusz.DefaultStatuszPath): + subresources = append(subresources, "statusz") default: subresources = append(subresources, "proxy") } diff --git a/pkg/kubelet/server/auth_test.go b/pkg/kubelet/server/auth_test.go index fd0256059d2..e3d561d8f63 100644 --- a/pkg/kubelet/server/auth_test.go +++ b/pkg/kubelet/server/auth_test.go @@ -125,6 +125,7 @@ func AuthzTestCases(fineGrained bool) []AuthzTestCase { "/attach/{podNamespace}/{podID}/{uid}/{containerName}": {"proxy"}, "/checkpoint/{podNamespace}/{podID}/{containerName}": {"checkpoint"}, "/configz": {"proxy"}, + "/statusz": {"statusz"}, "/containerLogs/{podNamespace}/{podID}/{containerName}": {"proxy"}, "/debug/flags/v": {"proxy"}, "/debug/pprof/{subpath:*}": {"proxy"}, diff --git a/pkg/kubelet/server/server.go b/pkg/kubelet/server/server.go index 46ac2403d1d..08a9ca5bb03 100644 --- a/pkg/kubelet/server/server.go +++ b/pkg/kubelet/server/server.go @@ -67,6 +67,8 @@ import ( metricsfeatures "k8s.io/component-base/metrics/features" "k8s.io/component-base/metrics/legacyregistry" "k8s.io/component-base/metrics/prometheus/slis" + zpagesfeatures "k8s.io/component-base/zpages/features" + "k8s.io/component-base/zpages/statusz" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" "k8s.io/cri-client/pkg/util" podresourcesapi "k8s.io/kubelet/pkg/apis/podresources/v1" @@ -107,6 +109,11 @@ const ( runningPodsPath = "/runningpods/" ) +const ( + // Kubelet component name + ComponentKubelet = "kubelet" +) + // Server is a http.Handler which exposes kubelet functionality over HTTP. type Server struct { auth AuthInterface @@ -567,6 +574,11 @@ func (s *Server) InstallDebuggingHandlers() { s.addMetricsBucketMatcher("configz") configz.InstallHandler(s.restfulCont) + if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentStatusz) { + s.addMetricsBucketMatcher("statusz") + statusz.Install(s.restfulCont, ComponentKubelet, statusz.NewRegistry()) + } + // The /runningpods endpoint is used for testing only. s.addMetricsBucketMatcher("runningpods") ws = new(restful.WebService) diff --git a/pkg/kubelet/server/server_test.go b/pkg/kubelet/server/server_test.go index a79e71742bf..7540a14fbde 100644 --- a/pkg/kubelet/server/server_test.go +++ b/pkg/kubelet/server/server_test.go @@ -57,6 +57,7 @@ import ( "k8s.io/apiserver/pkg/server/healthz" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" + zpagesfeatures "k8s.io/component-base/zpages/features" "k8s.io/kubelet/pkg/cri/streaming" "k8s.io/kubelet/pkg/cri/streaming/portforward" remotecommandserver "k8s.io/kubelet/pkg/cri/streaming/remotecommand" @@ -572,6 +573,7 @@ func TestAuthzCoverage(t *testing.T) { func TestAuthFilters(t *testing.T) { // Enable features.ContainerCheckpoint during test featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ContainerCheckpoint, true) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, zpagesfeatures.ComponentStatusz, true) fw := newServerTest() defer fw.testHTTPServer.Close() @@ -1617,6 +1619,8 @@ func TestServePortForward(t *testing.T) { } func TestMetricBuckets(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, zpagesfeatures.ComponentStatusz, true) + tests := map[string]struct { url string bucket string @@ -1648,6 +1652,7 @@ func TestMetricBuckets(t *testing.T) { "runningpods": {url: "/runningpods/", bucket: "runningpods"}, "stats": {url: "/stats/", bucket: "stats"}, "stats summary sub": {url: "/stats/summary", bucket: "stats"}, + "statusz": {url: "/statusz", bucket: "statusz"}, "invalid path": {url: "/junk", bucket: "other"}, "invalid path starting with good": {url: "/healthzjunk", bucket: "other"}, } diff --git a/staging/src/k8s.io/component-base/zpages/statusz/statusz.go b/staging/src/k8s.io/component-base/zpages/statusz/statusz.go index 7d07d5ddbb6..2c1923cb33f 100644 --- a/staging/src/k8s.io/component-base/zpages/statusz/statusz.go +++ b/staging/src/k8s.io/component-base/zpages/statusz/statusz.go @@ -35,6 +35,8 @@ var ( errUnsupportedMediaType = fmt.Errorf("media type not acceptable, must be: text/plain") ) +const DefaultStatuszPath = "/statusz" + const ( headerFmt = ` %s statusz @@ -73,7 +75,7 @@ func Install(m mux, componentName string, reg statuszRegistry) { klog.Errorf("error while parsing gotemplates: %v", err) return } - m.Handle("/statusz", handleStatusz(componentName, dataTmpl, reg)) + m.Handle(DefaultStatuszPath, handleStatusz(componentName, dataTmpl, reg)) } func initializeTemplates() (*template.Template, error) {