Merge pull request #113629 from andrewsykim/apiserver-identity-beta

Promote APIServerIdentity to Beta
This commit is contained in:
Kubernetes Prow Robot 2022-11-08 12:43:10 -08:00 committed by GitHub
commit 3a99a5954d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 205 additions and 45 deletions

View File

@ -82,9 +82,6 @@ type ServerRunOptions struct {
MasterCount int
EndpointReconcilerType string
IdentityLeaseDurationSeconds int
IdentityLeaseRenewIntervalSeconds int
ServiceAccountSigningKeyFile string
ServiceAccountIssuer serviceaccount.TokenGenerator
ServiceAccountTokenMaxExpiration time.Duration
@ -114,8 +111,6 @@ func NewServerRunOptions() *ServerRunOptions {
EventTTL: 1 * time.Hour,
MasterCount: 1,
EndpointReconcilerType: string(reconcilers.LeaseEndpointReconcilerType),
IdentityLeaseDurationSeconds: 3600,
IdentityLeaseRenewIntervalSeconds: 10,
KubeletConfig: kubeletclient.KubeletClientConfig{
Port: ports.KubeletPort,
ReadOnlyPort: ports.KubeletReadOnlyPort,
@ -185,12 +180,6 @@ func (s *ServerRunOptions) Flags() (fss cliflag.NamedFlagSets) {
fs.StringVar(&s.EndpointReconcilerType, "endpoint-reconciler-type", s.EndpointReconcilerType,
"Use an endpoint reconciler ("+strings.Join(reconcilers.AllTypes.Names(), ", ")+") master-count is deprecated, and will be removed in a future version.")
fs.IntVar(&s.IdentityLeaseDurationSeconds, "identity-lease-duration-seconds", s.IdentityLeaseDurationSeconds,
"The duration of kube-apiserver lease in seconds, must be a positive number. (In use when the APIServerIdentity feature gate is enabled.)")
fs.IntVar(&s.IdentityLeaseRenewIntervalSeconds, "identity-lease-renew-interval-seconds", s.IdentityLeaseRenewIntervalSeconds,
"The interval of kube-apiserver renewing its lease in seconds, must be a positive number. (In use when the APIServerIdentity feature gate is enabled.)")
// See #14282 for details on how to test/try this option out.
// TODO: remove this comment once this option is tested in CI.
fs.IntVar(&s.KubernetesServiceNodePort, "kubernetes-service-node-port", s.KubernetesServiceNodePort, ""+

View File

@ -318,8 +318,6 @@ func TestAddFlags(t *testing.T) {
Traces: &apiserveroptions.TracingOptions{
ConfigFile: "/var/run/kubernetes/tracing_config.yaml",
},
IdentityLeaseDurationSeconds: 3600,
IdentityLeaseRenewIntervalSeconds: 10,
AggregatorRejectForwardingRedirects: true,
}

View File

@ -142,17 +142,6 @@ func validateAPIPriorityAndFairness(options *ServerRunOptions) []error {
return nil
}
func validateAPIServerIdentity(options *ServerRunOptions) []error {
var errs []error
if options.IdentityLeaseDurationSeconds <= 0 {
errs = append(errs, fmt.Errorf("--identity-lease-duration-seconds should be a positive number, but value '%d' provided", options.IdentityLeaseDurationSeconds))
}
if options.IdentityLeaseRenewIntervalSeconds <= 0 {
errs = append(errs, fmt.Errorf("--identity-lease-renew-interval-seconds should be a positive number, but value '%d' provided", options.IdentityLeaseRenewIntervalSeconds))
}
return errs
}
// Validate checks ServerRunOptions and return a slice of found errs.
func (s *ServerRunOptions) Validate() []error {
var errs []error
@ -171,7 +160,6 @@ func (s *ServerRunOptions) Validate() []error {
errs = append(errs, s.APIEnablement.Validate(legacyscheme.Scheme, apiextensionsapiserver.Scheme, aggregatorscheme.Scheme)...)
errs = append(errs, validateTokenRequest(s)...)
errs = append(errs, s.Metrics.Validate()...)
errs = append(errs, validateAPIServerIdentity(s)...)
return errs
}

View File

@ -283,9 +283,6 @@ func CreateKubeAPIServerConfig(s completedServerRunOptions) (
ExtendExpiration: s.Authentication.ServiceAccounts.ExtendExpiration,
VersionedInformers: versionedInformers,
IdentityLeaseDurationSeconds: s.IdentityLeaseDurationSeconds,
IdentityLeaseRenewIntervalSeconds: s.IdentityLeaseRenewIntervalSeconds,
},
}

View File

@ -130,6 +130,18 @@ const (
repairLoopInterval = 3 * time.Minute
)
var (
// IdentityLeaseGCPeriod is the interval which the lease GC controller checks for expired leases
// IdentityLeaseGCPeriod is exposed so integration tests can tune this value.
IdentityLeaseGCPeriod = 3600 * time.Second
// IdentityLeaseDurationSeconds is the duration of kube-apiserver lease in seconds
// IdentityLeaseDurationSeconds is exposed so integration tests can tune this value.
IdentityLeaseDurationSeconds = 3600
// IdentityLeaseRenewIntervalSeconds is the interval of kube-apiserver renewing its lease in seconds
// IdentityLeaseRenewIntervalSeconds is exposed so integration tests can tune this value.
IdentityLeaseRenewIntervalPeriod = 10 * time.Second
)
// ExtraConfig defines extra configuration for the master
type ExtraConfig struct {
ClusterAuthenticationInfo clusterauthenticationtrust.ClusterAuthenticationInfo
@ -194,9 +206,6 @@ type ExtraConfig struct {
VersionedInformers informers.SharedInformerFactory
IdentityLeaseDurationSeconds int
IdentityLeaseRenewIntervalSeconds int
// RepairServicesInterval interval used by the repair loops for
// the Services NodePort and ClusterIP resources
RepairServicesInterval time.Duration
@ -489,9 +498,9 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
clock.RealClock{},
kubeClient,
holderIdentity,
int32(c.ExtraConfig.IdentityLeaseDurationSeconds),
int32(IdentityLeaseDurationSeconds),
nil,
time.Duration(c.ExtraConfig.IdentityLeaseRenewIntervalSeconds)*time.Second,
IdentityLeaseRenewIntervalPeriod,
leaseName,
metav1.NamespaceSystem,
labelAPIServerHeartbeat)
@ -505,7 +514,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
}
go apiserverleasegc.NewAPIServerLeaseGC(
kubeClient,
time.Duration(c.ExtraConfig.IdentityLeaseDurationSeconds)*time.Second,
IdentityLeaseGCPeriod,
metav1.NamespaceSystem,
KubeAPIServerIdentityLeaseLabelSelector,
).Run(hookContext.StopCh)

View File

@ -216,7 +216,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
APIResponseCompression: {Default: true, PreRelease: featuregate.Beta},
APIServerIdentity: {Default: false, PreRelease: featuregate.Alpha},
APIServerIdentity: {Default: true, PreRelease: featuregate.Beta},
APIServerTracing: {Default: false, PreRelease: featuregate.Alpha},

View File

@ -0,0 +1,169 @@
/*
Copyright 2022 The Kubernetes Authors.
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 apimachinery
import (
"context"
"crypto/sha256"
"encoding/base32"
"errors"
"fmt"
"net"
"strings"
"time"
"github.com/onsi/ginkgo/v2"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/test/e2e/framework"
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
admissionapi "k8s.io/pod-security-admission/api"
)
func getControlPlaneHostname(node *v1.Node) (string, error) {
nodeAddresses := e2enode.GetAddresses(node, v1.NodeExternalIP)
if len(nodeAddresses) == 0 {
return "", errors.New("no valid addresses to use for SSH")
}
controlPlaneAddress := nodeAddresses[0]
host := controlPlaneAddress + ":" + e2essh.SSHPort
result, err := e2essh.SSH("hostname", host, framework.TestContext.Provider)
if err != nil {
return "", err
}
if result.Code != 0 {
return "", fmt.Errorf("encountered non-zero exit code when running hostname command: %d", result.Code)
}
return strings.TrimSpace(result.Stdout), nil
}
// restartAPIServer attempts to restart the kube-apiserver on a node
func restartAPIServer(node *v1.Node) error {
nodeAddresses := e2enode.GetAddresses(node, v1.NodeExternalIP)
if len(nodeAddresses) == 0 {
return errors.New("no valid addresses to use for SSH")
}
controlPlaneAddress := nodeAddresses[0]
cmd := "pidof kube-apiserver | xargs sudo kill"
framework.Logf("Restarting kube-apiserver via ssh, running: %v", cmd)
result, err := e2essh.SSH(cmd, net.JoinHostPort(controlPlaneAddress, e2essh.SSHPort), framework.TestContext.Provider)
if err != nil || result.Code != 0 {
e2essh.LogResult(result)
return fmt.Errorf("couldn't restart kube-apiserver: %v", err)
}
return nil
}
// This test requires that --feature-gates=APIServerIdentity=true be set on the apiserver
var _ = SIGDescribe("kube-apiserver identity [Feature:APIServerIdentity]", func() {
f := framework.NewDefaultFramework("kube-apiserver-identity")
f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged
ginkgo.It("kube-apiserver identity should persist after restart [Disruptive]", func() {
e2eskipper.SkipUnlessProviderIs("gce")
client := f.ClientSet
var controlPlaneNodes []v1.Node
nodes, err := client.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
framework.ExpectNoError(err)
for _, node := range nodes.Items {
if _, ok := node.Labels["node-role.kubernetes.io/control-plane"]; ok {
controlPlaneNodes = append(controlPlaneNodes, node)
continue
}
if _, ok := node.Labels["node-role.kubernetes.io/master"]; ok {
controlPlaneNodes = append(controlPlaneNodes, node)
continue
}
for _, taint := range node.Spec.Taints {
if taint.Key == "node-role.kubernetes.io/master" {
controlPlaneNodes = append(controlPlaneNodes, node)
break
}
if taint.Key == "node-role.kubernetes.io/control-plane" {
controlPlaneNodes = append(controlPlaneNodes, node)
break
}
}
}
leases, err := client.CoordinationV1().Leases(metav1.NamespaceSystem).List(context.TODO(), metav1.ListOptions{
LabelSelector: "k8s.io/component=kube-apiserver",
})
framework.ExpectNoError(err)
framework.ExpectEqual(len(leases.Items), len(controlPlaneNodes), "unexpected number of leases")
for _, node := range controlPlaneNodes {
hostname, err := getControlPlaneHostname(&node)
framework.ExpectNoError(err)
hash := sha256.Sum256([]byte(hostname))
leaseName := "kube-apiserver-" + strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash[:16]))
lease, err := client.CoordinationV1().Leases(metav1.NamespaceSystem).Get(context.TODO(), leaseName, metav1.GetOptions{})
framework.ExpectNoError(err)
oldHolderIdentity := lease.Spec.HolderIdentity
lastRenewedTime := lease.Spec.RenewTime
err = restartAPIServer(&node)
framework.ExpectNoError(err)
err = wait.PollImmediate(time.Second, wait.ForeverTestTimeout, func() (bool, error) {
lease, err = client.CoordinationV1().Leases(metav1.NamespaceSystem).Get(context.TODO(), leaseName, metav1.GetOptions{})
if err != nil {
return false, nil
}
// expect only the holder identity to change after a restart
newHolderIdentity := lease.Spec.HolderIdentity
if newHolderIdentity == oldHolderIdentity {
return false, nil
}
// wait for at least one lease heart beat after the holder identity changes
if !lease.Spec.RenewTime.After(lastRenewedTime.Time) {
return false, nil
}
return true, nil
})
framework.ExpectNoError(err, "holder identity did not change after a restart")
}
// As long as the hostname of kube-apiserver is unchanged, a restart should not result in new Lease objects.
// Check that the number of lease objects remains the same after restarting kube-apiserver.
leases, err = client.CoordinationV1().Leases(metav1.NamespaceSystem).List(context.TODO(), metav1.ListOptions{
LabelSelector: "k8s.io/component=kube-apiserver",
})
framework.ExpectNoError(err)
framework.ExpectEqual(len(leases.Items), len(controlPlaneNodes), "unexpected number of leases")
})
})

View File

@ -99,13 +99,23 @@ func TestCreateLeaseOnStart(t *testing.T) {
}
func TestLeaseGarbageCollection(t *testing.T) {
oldIdentityLeaseDurationSeconds := controlplane.IdentityLeaseDurationSeconds
oldIdentityLeaseGCPeriod := controlplane.IdentityLeaseGCPeriod
oldIdentityLeaseRenewIntervalPeriod := controlplane.IdentityLeaseRenewIntervalPeriod
defer func() {
// reset the default values for leases after this test
controlplane.IdentityLeaseDurationSeconds = oldIdentityLeaseDurationSeconds
controlplane.IdentityLeaseGCPeriod = oldIdentityLeaseGCPeriod
controlplane.IdentityLeaseRenewIntervalPeriod = oldIdentityLeaseRenewIntervalPeriod
}()
// Shorten lease parameters so GC behavior can be exercised in integration tests
controlplane.IdentityLeaseDurationSeconds = 1
controlplane.IdentityLeaseGCPeriod = time.Second
controlplane.IdentityLeaseRenewIntervalPeriod = time.Second
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APIServerIdentity, true)()
result := kubeapiservertesting.StartTestServerOrDie(t, nil,
// This shorten the GC check period to make the test run faster.
// Since we are testing GC behavior on leases we create, what happens to
// the real apiserver lease doesn't matter.
[]string{"--identity-lease-duration-seconds=1"},
framework.SharedEtcd())
result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
defer result.TearDownFn()
kubeclient, err := kubernetes.NewForConfig(result.ClientConfig)