Skip to content

Commit 22f25ef

Browse files
authored
Merge pull request kubernetes#128991 from Henrywu573/cm-statuz
Add statusz endpoint for kube-controller-manager
2 parents 72d7486 + 8bd4e1b commit 22f25ef

File tree

2 files changed

+158
-4
lines changed

2 files changed

+158
-4
lines changed

cmd/kube-controller-manager/app/controllermanager.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ import (
6464
"k8s.io/component-base/term"
6565
utilversion "k8s.io/component-base/version"
6666
"k8s.io/component-base/version/verflag"
67+
zpagesfeatures "k8s.io/component-base/zpages/features"
68+
"k8s.io/component-base/zpages/statusz"
6769
genericcontrollermanager "k8s.io/controller-manager/app"
6870
"k8s.io/controller-manager/controller"
6971
"k8s.io/controller-manager/pkg/clientbuilder"
@@ -91,6 +93,8 @@ const (
9193
ControllerStartJitter = 1.0
9294
// ConfigzName is the name used for register kube-controller manager /configz, same with GroupName.
9395
ConfigzName = "kubecontrollermanager.config.k8s.io"
96+
// kubeControllerManager defines variable used internally when referring to cloud-controller-manager component
97+
kubeControllerManager = "kube-controller-manager"
9498
)
9599

96100
// NewControllerManagerCommand creates a *cobra.Command object with default parameters
@@ -105,7 +109,7 @@ func NewControllerManagerCommand() *cobra.Command {
105109
}
106110

107111
cmd := &cobra.Command{
108-
Use: "kube-controller-manager",
112+
Use: kubeControllerManager,
109113
Long: `The Kubernetes controller manager is a daemon that embeds
110114
the core control loops shipped with Kubernetes. In applications of robotics and
111115
automation, a control loop is a non-terminating loop that regulates the state of
@@ -213,6 +217,10 @@ func Run(ctx context.Context, c *config.CompletedConfig) error {
213217
unsecuredMux = genericcontrollermanager.NewBaseHandler(&c.ComponentConfig.Generic.Debugging, healthzHandler)
214218
slis.SLIMetricsWithReset{}.Install(unsecuredMux)
215219

220+
if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentStatusz) {
221+
statusz.Install(unsecuredMux, kubeControllerManager, statusz.NewRegistry())
222+
}
223+
216224
handler := genericcontrollermanager.BuildHandlerChain(unsecuredMux, &c.Authorization, &c.Authentication)
217225
// TODO: handle stoppedCh and listenerStoppedCh returned by c.SecureServing.Serve
218226
if _, _, err := c.SecureServing.Serve(handler, 0, stopCh); err != nil {
@@ -267,7 +275,7 @@ func Run(ctx context.Context, c *config.CompletedConfig) error {
267275
logger.Info("starting leader migration")
268276

269277
leaderMigrator = leadermigration.NewLeaderMigrator(&c.ComponentConfig.Generic.LeaderMigration,
270-
"kube-controller-manager")
278+
kubeControllerManager)
271279

272280
// startSATokenControllerInit is the original InitFunc.
273281
startSATokenControllerInit := saTokenControllerDescriptor.GetInitFunc()
@@ -295,7 +303,7 @@ func Run(ctx context.Context, c *config.CompletedConfig) error {
295303
c.Client,
296304
"kube-system",
297305
id,
298-
"kube-controller-manager",
306+
kubeControllerManager,
299307
binaryVersion.FinalizeVersion(),
300308
emulationVersion.FinalizeVersion(),
301309
coordinationv1.OldestEmulationVersion,
@@ -634,7 +642,7 @@ func CreateControllerContext(ctx context.Context, s *config.CompletedConfig, roo
634642
RESTMapper: restMapper,
635643
InformersStarted: make(chan struct{}),
636644
ResyncPeriod: ResyncPeriod(s),
637-
ControllerManagerMetrics: controllersmetrics.NewControllerManagerMetrics("kube-controller-manager"),
645+
ControllerManagerMetrics: controllersmetrics.NewControllerManagerMetrics(kubeControllerManager),
638646
}
639647

640648
if controllerContext.ComponentConfig.GarbageCollectorController.EnableGarbageCollector &&

test/integration/serving/serving_test.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"crypto/x509"
2323
"fmt"
2424
"io"
25+
"net"
2526
"net/http"
2627
"os"
2728
"path"
@@ -30,9 +31,12 @@ import (
3031

3132
"k8s.io/apiserver/pkg/server"
3233
"k8s.io/apiserver/pkg/server/options"
34+
utilfeature "k8s.io/apiserver/pkg/util/feature"
3335
cloudprovider "k8s.io/cloud-provider"
3436
cloudctrlmgrtesting "k8s.io/cloud-provider/app/testing"
3537
"k8s.io/cloud-provider/fake"
38+
featuregatetesting "k8s.io/component-base/featuregate/testing"
39+
zpagesfeatures "k8s.io/component-base/zpages/features"
3640
"k8s.io/klog/v2/ktesting"
3741
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
3842
kubectrlmgrtesting "k8s.io/kubernetes/cmd/kube-controller-manager/app/testing"
@@ -289,3 +293,145 @@ func fakeCloudProviderFactory(io.Reader) (cloudprovider.Interface, error) {
289293
DisableRoutes: true, // disable routes for server tests, otherwise --cluster-cidr is required
290294
}, nil
291295
}
296+
297+
func TestKubeControllerManagerServingStatusz(t *testing.T) {
298+
299+
// authenticate to apiserver via bearer token
300+
token := "flwqkenfjasasdfmwerasd" // Fake token for testing.
301+
tokenFile, err := os.CreateTemp("", "kubeconfig")
302+
if err != nil {
303+
t.Fatal(err)
304+
}
305+
if _, err = tokenFile.WriteString(fmt.Sprintf(`
306+
%s,system:kube-controller-manager,system:kube-controller-manager,""
307+
`, token)); err != nil {
308+
t.Fatal(err)
309+
}
310+
if err = tokenFile.Close(); err != nil {
311+
t.Fatal(err)
312+
}
313+
314+
// start apiserver
315+
server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{
316+
"--token-auth-file", tokenFile.Name(),
317+
"--authorization-mode", "RBAC",
318+
}, framework.SharedEtcd())
319+
defer server.TearDownFn()
320+
321+
// create kubeconfig for the apiserver
322+
apiserverConfig, err := os.CreateTemp("", "kubeconfig")
323+
if err != nil {
324+
t.Fatal(err)
325+
}
326+
if _, err = apiserverConfig.WriteString(fmt.Sprintf(`
327+
apiVersion: v1
328+
kind: Config
329+
clusters:
330+
- cluster:
331+
server: %s
332+
certificate-authority: %s
333+
name: integration
334+
contexts:
335+
- context:
336+
cluster: integration
337+
user: controller-manager
338+
name: default-context
339+
current-context: default-context
340+
users:
341+
- name: controller-manager
342+
user:
343+
token: %s
344+
`, server.ClientConfig.Host, server.ServerOpts.SecureServing.ServerCert.CertKey.CertFile, token)); err != nil {
345+
t.Fatal(err)
346+
}
347+
if err = apiserverConfig.Close(); err != nil {
348+
t.Fatal(err)
349+
}
350+
351+
tests := []struct {
352+
name string
353+
flags []string
354+
path string
355+
anonymous bool // to use the token or not
356+
wantErr bool
357+
wantSecureCode *int
358+
}{
359+
{"serving /statusz", []string{
360+
"--authentication-skip-lookup", // to survive inaccessible extensions-apiserver-authentication configmap
361+
"--authentication-kubeconfig", apiserverConfig.Name(),
362+
"--authorization-kubeconfig", apiserverConfig.Name(),
363+
"--authorization-always-allow-paths", "/statusz",
364+
"--kubeconfig", apiserverConfig.Name(),
365+
"--leader-elect=false",
366+
}, "/statusz", false, false, intPtr(http.StatusOK)},
367+
}
368+
for _, tt := range tests {
369+
t.Run(tt.name, func(t *testing.T) {
370+
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, zpagesfeatures.ComponentStatusz, true)
371+
_, ctx := ktesting.NewTestContext(t)
372+
secureOptions, secureInfo, tearDownFn, err := kubeControllerManagerTester{}.StartTestServer(ctx, append(append([]string{}, tt.flags...), []string{}...))
373+
if tearDownFn != nil {
374+
defer tearDownFn()
375+
}
376+
if (err != nil) != tt.wantErr {
377+
t.Fatalf("StartTestServer() error = %v, wantErr %v", err, tt.wantErr)
378+
}
379+
if err != nil {
380+
return
381+
}
382+
383+
if want, got := tt.wantSecureCode != nil, secureInfo != nil; want != got {
384+
t.Errorf("SecureServing enabled: expected=%v got=%v", want, got)
385+
} else if want {
386+
// only interested on the port, because we are using always localhost
387+
_, port, err := net.SplitHostPort(secureInfo.Listener.Addr().String())
388+
if err != nil {
389+
t.Fatalf("could not get host and port from %s : %v", secureInfo.Listener.Addr().String(), err)
390+
}
391+
// use IPv4 because the self-signed cert does not support [::]
392+
url := fmt.Sprintf("https://127.0.0.1:%s%s", port, tt.path)
393+
394+
// read self-signed server cert disk
395+
pool := x509.NewCertPool()
396+
serverCertPath := path.Join(secureOptions.ServerCert.CertDirectory, secureOptions.ServerCert.PairName+".crt")
397+
serverCert, err := os.ReadFile(serverCertPath)
398+
if err != nil {
399+
t.Fatalf("Failed to read component server cert %q: %v", serverCertPath, err)
400+
}
401+
pool.AppendCertsFromPEM(serverCert)
402+
tr := &http.Transport{
403+
TLSClientConfig: &tls.Config{
404+
RootCAs: pool,
405+
},
406+
}
407+
408+
client := &http.Client{Transport: tr}
409+
req, err := http.NewRequest(http.MethodGet, url, nil)
410+
req.Header.Set("Accept", "text/plain")
411+
if err != nil {
412+
t.Fatal(err)
413+
}
414+
if !tt.anonymous {
415+
req.Header.Add("Authorization", fmt.Sprintf("Token %s", token))
416+
}
417+
r, err := client.Do(req)
418+
if err != nil {
419+
t.Fatalf("failed to GET %s from component: %v", tt.path, err)
420+
}
421+
422+
if _, err = io.ReadAll(r.Body); err != nil {
423+
t.Fatalf("failed to read response body: %v", err)
424+
}
425+
defer func() {
426+
if err := r.Body.Close(); err != nil {
427+
t.Fatalf("Error closing response body: %v", err)
428+
}
429+
}()
430+
431+
if got, expected := r.StatusCode, *tt.wantSecureCode; got != expected {
432+
t.Fatalf("expected http %d at %s of component, got: %d", expected, tt.path, got)
433+
}
434+
}
435+
})
436+
}
437+
}

0 commit comments

Comments
 (0)