Skip to content

Commit 60651eb

Browse files
authored
Merge pull request kubernetes#125577 from richabanker/statusz
Add statusz endpoint for apiserver
2 parents 210deea + 8bf6eec commit 60651eb

File tree

17 files changed

+792
-13
lines changed

17 files changed

+792
-13
lines changed

Diff for: pkg/controlplane/apiserver/server.go

+6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import (
3636
utilfeature "k8s.io/apiserver/pkg/util/feature"
3737
clientgoinformers "k8s.io/client-go/informers"
3838
"k8s.io/client-go/kubernetes"
39+
zpagesfeatures "k8s.io/component-base/zpages/features"
40+
"k8s.io/component-base/zpages/statusz"
3941
"k8s.io/component-helpers/apimachinery/lease"
4042
"k8s.io/klog/v2"
4143
"k8s.io/utils/clock"
@@ -151,6 +153,10 @@ func (c completedConfig) New(name string, delegationTarget genericapiserver.Dele
151153
return nil, fmt.Errorf("failed to get listener address: %w", err)
152154
}
153155

156+
if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentStatusz) {
157+
statusz.Install(s.GenericAPIServer.Handler.NonGoRestfulMux, name, statusz.NewRegistry())
158+
}
159+
154160
if utilfeature.DefaultFeatureGate.Enabled(apiserverfeatures.CoordinatedLeaderElection) {
155161
leaseInformer := s.VersionedInformers.Coordination().V1().Leases()
156162
lcInformer := s.VersionedInformers.Coordination().V1alpha1().LeaseCandidates()

Diff for: pkg/features/kube_features.go

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
utilfeature "k8s.io/apiserver/pkg/util/feature"
2222
clientfeatures "k8s.io/client-go/features"
2323
"k8s.io/component-base/featuregate"
24+
zpagesfeatures "k8s.io/component-base/zpages/features"
2425
)
2526

2627
const (
@@ -840,6 +841,7 @@ const (
840841
func init() {
841842
runtime.Must(utilfeature.DefaultMutableFeatureGate.Add(defaultKubernetesFeatureGates))
842843
runtime.Must(utilfeature.DefaultMutableFeatureGate.AddVersioned(defaultVersionedKubernetesFeatureGates))
844+
runtime.Must(zpagesfeatures.AddFeatureGates(utilfeature.DefaultMutableFeatureGate))
843845

844846
// Register all client-go features with kube's feature gate instance and make all client-go
845847
// feature checks use kube's instance. The effect is that for kube binaries, client-go

Diff for: pkg/features/versioned_kube_features.go

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"k8s.io/apimachinery/pkg/util/version"
2222
genericfeatures "k8s.io/apiserver/pkg/features"
2323
"k8s.io/component-base/featuregate"
24+
zpagesfeatures "k8s.io/component-base/zpages/features"
2425
kcmfeatures "k8s.io/controller-manager/pkg/features"
2526
)
2627

@@ -801,4 +802,8 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
801802
WindowsHostNetwork: {
802803
{Version: version.MustParse("1.26"), Default: true, PreRelease: featuregate.Alpha},
803804
},
805+
806+
zpagesfeatures.ComponentStatusz: {
807+
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha},
808+
},
804809
}

Diff for: plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go

+14-7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"k8s.io/apiserver/pkg/authentication/serviceaccount"
2626
"k8s.io/apiserver/pkg/authentication/user"
2727
utilfeature "k8s.io/apiserver/pkg/util/feature"
28+
zpagesfeatures "k8s.io/component-base/zpages/features"
2829

2930
rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
3031
"k8s.io/kubernetes/pkg/features"
@@ -194,6 +195,18 @@ func NodeRules() []rbacv1.PolicyRule {
194195

195196
// ClusterRoles returns the cluster roles to bootstrap an API server with
196197
func ClusterRoles() []rbacv1.ClusterRole {
198+
monitoringRules := []rbacv1.PolicyRule{
199+
rbacv1helpers.NewRule("get").URLs(
200+
"/metrics", "/metrics/slis",
201+
"/livez", "/readyz", "/healthz",
202+
"/livez/*", "/readyz/*", "/healthz/*",
203+
).RuleOrDie(),
204+
}
205+
206+
if utilfeature.DefaultFeatureGate.Enabled(zpagesfeatures.ComponentStatusz) {
207+
monitoringRules = append(monitoringRules, rbacv1helpers.NewRule("get").URLs("/statusz").RuleOrDie())
208+
}
209+
197210
roles := []rbacv1.ClusterRole{
198211
{
199212
// a "root" role which can do absolutely anything
@@ -223,13 +236,7 @@ func ClusterRoles() []rbacv1.ClusterRole {
223236
// The splatted health check endpoints allow read access to individual health check
224237
// endpoints which may contain more sensitive cluster information information
225238
ObjectMeta: metav1.ObjectMeta{Name: "system:monitoring"},
226-
Rules: []rbacv1.PolicyRule{
227-
rbacv1helpers.NewRule("get").URLs(
228-
"/metrics", "/metrics/slis",
229-
"/livez", "/readyz", "/healthz",
230-
"/livez/*", "/readyz/*", "/healthz/*",
231-
).RuleOrDie(),
232-
},
239+
Rules: monitoringRules,
233240
},
234241
}
235242

Diff for: staging/src/k8s.io/apiserver/pkg/server/config.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,7 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
979979

980980
s.listedPathProvider = routes.ListedPathProviders{s.listedPathProvider, delegationTarget}
981981

982-
installAPI(s, c.Config)
982+
installAPI(name, s, c.Config)
983983

984984
// use the UnprotectedHandler from the delegation target to ensure that we don't attempt to double authenticator, authorize,
985985
// or some other part of the filter chain in delegation cases.
@@ -1076,7 +1076,7 @@ func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
10761076
return handler
10771077
}
10781078

1079-
func installAPI(s *GenericAPIServer, c *Config) {
1079+
func installAPI(name string, s *GenericAPIServer, c *Config) {
10801080
if c.EnableIndex {
10811081
routes.Index{}.Install(s.listedPathProvider, s.Handler.NonGoRestfulMux)
10821082
}

Diff for: staging/src/k8s.io/component-base/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
github.com/go-logr/zapr v1.3.0
1313
github.com/google/go-cmp v0.6.0
1414
github.com/moby/term v0.5.0
15+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
1516
github.com/prometheus/client_golang v1.19.1
1617
github.com/prometheus/client_model v0.6.1
1718
github.com/prometheus/common v0.55.0
@@ -59,7 +60,6 @@ require (
5960
github.com/mailru/easyjson v0.7.7 // indirect
6061
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
6162
github.com/modern-go/reflect2 v1.0.2 // indirect
62-
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
6363
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
6464
github.com/x448/float16 v0.8.4 // indirect
6565
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect

Diff for: staging/src/k8s.io/component-base/metrics/processstarttime.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ var processStartTime = NewGaugeVec(
3535
// a prometheus registry. This metric needs to be included to ensure counter
3636
// data fidelity.
3737
func RegisterProcessStartTime(registrationFunc func(Registerable) error) error {
38-
start, err := getProcessStart()
38+
start, err := GetProcessStart()
3939
if err != nil {
4040
klog.Errorf("Could not get process start time, %v", err)
4141
start = float64(time.Now().Unix())

Diff for: staging/src/k8s.io/component-base/metrics/processstarttime_others.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
"github.com/prometheus/procfs"
2626
)
2727

28-
func getProcessStart() (float64, error) {
28+
func GetProcessStart() (float64, error) {
2929
pid := os.Getpid()
3030
p, err := procfs.NewProc(pid)
3131
if err != nil {

Diff for: staging/src/k8s.io/component-base/metrics/processstarttime_windows.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
"golang.org/x/sys/windows"
2424
)
2525

26-
func getProcessStart() (float64, error) {
26+
func GetProcessStart() (float64, error) {
2727
processHandle := windows.CurrentProcess()
2828

2929
var creationTime, exitTime, kernelTime, userTime windows.Filetime
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package features contains a separate feature set specifically designed for
18+
// managing zpages related features. These feature gates control the
19+
// availability and behavior of various zpages within Kubernetes components.
20+
// New zpages added to Kubernetes components should utilize this feature set
21+
// to ensure proper management of their availability.
22+
package features
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package features
18+
19+
import (
20+
"k8s.io/apimachinery/pkg/util/version"
21+
"k8s.io/component-base/featuregate"
22+
)
23+
24+
const (
25+
// owner: @richabanker
26+
// kep: https://kep.k8s.io/4827
27+
// alpha: v1.32
28+
//
29+
// Enables /statusz endpoint for a component making it accessible to
30+
// users with the system:monitoring cluster role.
31+
ComponentStatusz featuregate.Feature = "ComponentStatusz"
32+
)
33+
34+
func featureGates() map[featuregate.Feature]featuregate.VersionedSpecs {
35+
return map[featuregate.Feature]featuregate.VersionedSpecs{
36+
ComponentStatusz: {
37+
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha},
38+
},
39+
}
40+
}
41+
42+
// AddFeatureGates adds all feature gates used by this package.
43+
func AddFeatureGates(mutableFeatureGate featuregate.MutableVersionedFeatureGate) error {
44+
return mutableFeatureGate.AddVersioned(featureGates())
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package statusz
18+
19+
import (
20+
"time"
21+
22+
"k8s.io/apimachinery/pkg/util/version"
23+
"k8s.io/component-base/featuregate"
24+
"k8s.io/klog/v2"
25+
26+
compbasemetrics "k8s.io/component-base/metrics"
27+
utilversion "k8s.io/component-base/version"
28+
)
29+
30+
type statuszRegistry interface {
31+
processStartTime() time.Time
32+
goVersion() string
33+
binaryVersion() *version.Version
34+
emulationVersion() *version.Version
35+
}
36+
37+
type registry struct{}
38+
39+
func (registry) processStartTime() time.Time {
40+
start, err := compbasemetrics.GetProcessStart()
41+
if err != nil {
42+
klog.Errorf("Could not get process start time, %v", err)
43+
}
44+
45+
return time.Unix(int64(start), 0)
46+
}
47+
48+
func (registry) goVersion() string {
49+
return utilversion.Get().GoVersion
50+
}
51+
52+
func (registry) binaryVersion() *version.Version {
53+
effectiveVer := featuregate.DefaultComponentGlobalsRegistry.EffectiveVersionFor(featuregate.DefaultKubeComponent)
54+
if effectiveVer != nil {
55+
return effectiveVer.BinaryVersion()
56+
}
57+
58+
return utilversion.DefaultKubeEffectiveVersion().BinaryVersion()
59+
}
60+
61+
func (registry) emulationVersion() *version.Version {
62+
effectiveVer := featuregate.DefaultComponentGlobalsRegistry.EffectiveVersionFor(featuregate.DefaultKubeComponent)
63+
if effectiveVer != nil {
64+
return effectiveVer.EmulationVersion()
65+
}
66+
67+
return nil
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package statusz
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
24+
"k8s.io/apimachinery/pkg/util/version"
25+
"k8s.io/component-base/featuregate"
26+
utilversion "k8s.io/component-base/version"
27+
)
28+
29+
func TestBinaryVersion(t *testing.T) {
30+
componentGlobalsRegistry := featuregate.DefaultComponentGlobalsRegistry
31+
tests := []struct {
32+
name string
33+
setFakeEffectiveVersion bool
34+
fakeVersion string
35+
wantBinaryVersion *version.Version
36+
}{
37+
{
38+
name: "binaryVersion with effective version",
39+
wantBinaryVersion: version.MustParseSemantic("v1.2.3"),
40+
setFakeEffectiveVersion: true,
41+
fakeVersion: "1.2.3",
42+
},
43+
{
44+
name: "binaryVersion without effective version",
45+
wantBinaryVersion: utilversion.DefaultKubeEffectiveVersion().BinaryVersion(),
46+
},
47+
}
48+
49+
for _, tt := range tests {
50+
componentGlobalsRegistry.Reset()
51+
t.Run(tt.name, func(t *testing.T) {
52+
if tt.setFakeEffectiveVersion {
53+
verKube := utilversion.NewEffectiveVersion(tt.fakeVersion)
54+
fg := featuregate.NewVersionedFeatureGate(version.MustParse(tt.fakeVersion))
55+
utilruntime.Must(componentGlobalsRegistry.Register(featuregate.DefaultKubeComponent, verKube, fg))
56+
}
57+
58+
registry := &registry{}
59+
got := registry.binaryVersion()
60+
assert.Equal(t, tt.wantBinaryVersion, got)
61+
})
62+
}
63+
}
64+
65+
func TestEmulationVersion(t *testing.T) {
66+
componentGlobalsRegistry := featuregate.DefaultComponentGlobalsRegistry
67+
tests := []struct {
68+
name string
69+
setFakeEffectiveVersion bool
70+
fakeEmulVer string
71+
wantEmul *version.Version
72+
}{
73+
{
74+
name: "emulationVersion with effective version",
75+
fakeEmulVer: "2.3.4",
76+
setFakeEffectiveVersion: true,
77+
wantEmul: version.MustParseSemantic("2.3.4"),
78+
},
79+
{
80+
name: "emulationVersion without effective version",
81+
wantEmul: nil,
82+
},
83+
}
84+
85+
for _, tt := range tests {
86+
componentGlobalsRegistry.Reset()
87+
t.Run(tt.name, func(t *testing.T) {
88+
if tt.setFakeEffectiveVersion {
89+
verKube := utilversion.NewEffectiveVersion("0.0.0")
90+
verKube.SetEmulationVersion(version.MustParse(tt.fakeEmulVer))
91+
fg := featuregate.NewVersionedFeatureGate(version.MustParse(tt.fakeEmulVer))
92+
utilruntime.Must(componentGlobalsRegistry.Register(featuregate.DefaultKubeComponent, verKube, fg))
93+
}
94+
95+
registry := &registry{}
96+
got := registry.emulationVersion()
97+
if tt.wantEmul != nil && got != nil {
98+
assert.Equal(t, tt.wantEmul.Major(), got.Major())
99+
assert.Equal(t, tt.wantEmul.Minor(), got.Minor())
100+
} else {
101+
assert.Equal(t, tt.wantEmul, got)
102+
}
103+
})
104+
}
105+
}

0 commit comments

Comments
 (0)