diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 2548fe6f2b2..4c3e5479457 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -684,6 +684,7 @@ const ( // owner: @wzshiming // kep: http://kep.k8s.io/2681 // alpha: v1.28 + // beta: v1.29 // // Adds pod.status.hostIPs and downward API PodHostIPs featuregate.Feature = "PodHostIPs" @@ -1134,7 +1135,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS PodReadyToStartContainersCondition: {Default: false, PreRelease: featuregate.Alpha}, - PodHostIPs: {Default: false, PreRelease: featuregate.Alpha}, + PodHostIPs: {Default: true, PreRelease: featuregate.Beta}, PodSchedulingReadiness: {Default: true, PreRelease: featuregate.Beta}, diff --git a/test/e2e_node/pod_host_ips.go b/test/e2e_node/pod_host_ips.go index 0377f1726dc..05d81d62a6d 100644 --- a/test/e2e_node/pod_host_ips.go +++ b/test/e2e_node/pod_host_ips.go @@ -26,9 +26,8 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/uuid" - netutils "k8s.io/utils/net" - kubefeatures "k8s.io/kubernetes/pkg/features" kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" "k8s.io/kubernetes/test/e2e/framework" @@ -38,9 +37,11 @@ import ( e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output" "k8s.io/kubernetes/test/e2e/network/common" imageutils "k8s.io/kubernetes/test/utils/image" + netutils "k8s.io/utils/net" + "k8s.io/utils/ptr" ) -var _ = common.SIGDescribe("Dual Stack Host IP [Feature:PodHostIPs]", func() { +var _ = common.SIGDescribe("DualStack Host IP [NodeFeature:PodHostIPs] [Feature:PodHostIPs]", func() { f := framework.NewDefaultFramework("dualstack") ginkgo.Context("when creating a Pod, it has no PodHostIPs feature", func() { @@ -53,21 +54,7 @@ var _ = common.SIGDescribe("Dual Stack Host IP [Feature:PodHostIPs]", func() { podName := "pod-dualstack-host-ips" - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: podName, - Labels: map[string]string{"test": "dualstack-host-ips"}, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "dualstack-host-ips", - Image: imageutils.GetE2EImage(imageutils.Agnhost), - }, - }, - }, - } - + pod := genPodHostIPs(podName+string(uuid.NewUUID()), false) ginkgo.By("submitting the pod to kubernetes") podClient := e2epod.NewPodClient(f) p := podClient.CreateSync(ctx, pod) @@ -76,7 +63,7 @@ var _ = common.SIGDescribe("Dual Stack Host IP [Feature:PodHostIPs]", func() { gomega.Expect(p.Status.HostIPs).Should(gomega.BeNil()) ginkgo.By("deleting the pod") - err := podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(30)) + err := podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(1)) framework.ExpectNoError(err, "failed to delete pod") }) @@ -84,22 +71,7 @@ var _ = common.SIGDescribe("Dual Stack Host IP [Feature:PodHostIPs]", func() { podName := "pod-dualstack-host-ips" - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: podName, - Labels: map[string]string{"test": "dualstack-host-ips"}, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "dualstack-host-ips", - Image: imageutils.GetE2EImage(imageutils.Agnhost), - }, - }, - HostNetwork: true, - }, - } - + pod := genPodHostIPs(podName+string(uuid.NewUUID()), true) ginkgo.By("submitting the pod to kubernetes") podClient := e2epod.NewPodClient(f) p := podClient.CreateSync(ctx, pod) @@ -108,7 +80,7 @@ var _ = common.SIGDescribe("Dual Stack Host IP [Feature:PodHostIPs]", func() { gomega.Expect(p.Status.HostIPs).Should(gomega.BeNil()) ginkgo.By("deleting the pod") - err := podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(30)) + err := podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(1)) framework.ExpectNoError(err, "failed to delete pod") }) }) @@ -123,20 +95,7 @@ var _ = common.SIGDescribe("Dual Stack Host IP [Feature:PodHostIPs]", func() { podName := "pod-dualstack-host-ips" - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: podName, - Labels: map[string]string{"test": "dualstack-host-ips"}, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "dualstack-host-ips", - Image: imageutils.GetE2EImage(imageutils.Agnhost), - }, - }, - }, - } + pod := genPodHostIPs(podName+string(uuid.NewUUID()), false) ginkgo.By("submitting the pod to kubernetes") podClient := e2epod.NewPodClient(f) @@ -158,19 +117,26 @@ var _ = common.SIGDescribe("Dual Stack Host IP [Feature:PodHostIPs]", func() { framework.ExpectNoError(err) for _, node := range nodeList.Items { if node.Name == p.Spec.NodeName { - nodeIPs := []string{} + got := sets.New[string]() + for _, hostIP := range p.Status.HostIPs { + got.Insert(hostIP.IP) + } + + want := sets.New[string]() for _, address := range node.Status.Addresses { if address.Type == v1.NodeInternalIP { - nodeIPs = append(nodeIPs, address.Address) + want.Insert(address.Address) } } - gomega.Expect(p.Status.HostIPs).Should(gomega.Equal(nodeIPs)) + if !got.Equal(want) { + framework.Failf("got %v, want %v", got, want) + } break } } ginkgo.By("deleting the pod") - err = podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(30)) + err = podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(1)) framework.ExpectNoError(err, "failed to delete pod") }) @@ -178,21 +144,7 @@ var _ = common.SIGDescribe("Dual Stack Host IP [Feature:PodHostIPs]", func() { podName := "pod-dualstack-host-ips" - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: podName, - Labels: map[string]string{"test": "dualstack-host-ips"}, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "dualstack-host-ips", - Image: imageutils.GetE2EImage(imageutils.Agnhost), - }, - }, - HostNetwork: true, - }, - } + pod := genPodHostIPs(podName+string(uuid.NewUUID()), true) ginkgo.By("submitting the pod to kubernetes") podClient := e2epod.NewPodClient(f) @@ -214,19 +166,26 @@ var _ = common.SIGDescribe("Dual Stack Host IP [Feature:PodHostIPs]", func() { framework.ExpectNoError(err) for _, node := range nodeList.Items { if node.Name == p.Spec.NodeName { - nodeIPs := []string{} + got := sets.New[string]() + for _, hostIP := range p.Status.HostIPs { + got.Insert(hostIP.IP) + } + + want := sets.New[string]() for _, address := range node.Status.Addresses { if address.Type == v1.NodeInternalIP { - nodeIPs = append(nodeIPs, address.Address) + want.Insert(address.Address) } } - gomega.Expect(p.Status.HostIPs).Should(gomega.Equal(nodeIPs)) + if !got.Equal(want) { + framework.Failf("got %v, want %v", got, want) + } break } } ginkgo.By("deleting the pod") - err = podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(30)) + err = podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(1)) framework.ExpectNoError(err, "failed to delete pod") }) @@ -250,9 +209,90 @@ var _ = common.SIGDescribe("Dual Stack Host IP [Feature:PodHostIPs]", func() { testDownwardAPI(ctx, f, podName, env, expectations) }) + + ginkgo.It("should able upgrade and rollback", func(ctx context.Context) { + podName := "pod-dualstack-host-ips" + + pod := genPodHostIPs(podName+string(uuid.NewUUID()), false) + + ginkgo.By("submitting the pod to kubernetes") + podClient := e2epod.NewPodClient(f) + p := podClient.CreateSync(ctx, pod) + + gomega.Expect(p.Status.HostIPs).ShouldNot(gomega.BeNil()) + + ginkgo.By("Disable PodHostIPs feature") + cfg, err := getCurrentKubeletConfig(ctx) + framework.ExpectNoError(err) + + newCfg := cfg.DeepCopy() + newCfg.FeatureGates = map[string]bool{ + string(kubefeatures.PodHostIPs): false, + } + + updateKubeletConfig(ctx, f, newCfg, true) + + gomega.Expect(p.Status.HostIPs).ShouldNot(gomega.BeNil()) + + ginkgo.By("deleting the pod") + err = podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(1)) + framework.ExpectNoError(err, "failed to delete pod") + + ginkgo.By("recreate pod") + pod = genPodHostIPs(podName+string(uuid.NewUUID()), false) + p = podClient.CreateSync(ctx, pod) + // Feature PodHostIPs is disabled, HostIPs should be nil + gomega.Expect(p.Status.HostIPs).Should(gomega.BeNil()) + + newCfg.FeatureGates = map[string]bool{ + string(kubefeatures.PodHostIPs): true, + } + + updateKubeletConfig(ctx, f, newCfg, true) + + p, err = podClient.Get(ctx, pod.Name, metav1.GetOptions{}) + framework.ExpectNoError(err) + // Feature PodHostIPs is enabled, HostIPs should not be nil + gomega.Expect(p.Status.HostIPs).ShouldNot(gomega.BeNil()) + + ginkgo.By("deleting the pod") + err = podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(1)) + framework.ExpectNoError(err, "failed to delete pod") + }) + }) }) +func genPodHostIPs(podName string, hostNetwork bool) *v1.Pod { + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Labels: map[string]string{"test": "dualstack-host-ips"}, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "test-container", + Image: imageutils.GetE2EImage(imageutils.Agnhost), + SecurityContext: &v1.SecurityContext{ + AllowPrivilegeEscalation: ptr.To(false), + Capabilities: &v1.Capabilities{ + Drop: []v1.Capability{"ALL"}, + }, + RunAsNonRoot: ptr.To(true), + RunAsUser: ptr.To[int64](1000), + SeccompProfile: &v1.SeccompProfile{ + Type: v1.SeccompProfileTypeRuntimeDefault, + }, + }, + }, + }, + RestartPolicy: v1.RestartPolicyNever, + HostNetwork: hostNetwork, + }, + } +} + func testDownwardAPI(ctx context.Context, f *framework.Framework, podName string, env []v1.EnvVar, expectations []string) { pod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -276,6 +316,17 @@ func testDownwardAPI(ctx context.Context, f *framework.Framework, podName string }, }, Env: env, + SecurityContext: &v1.SecurityContext{ + AllowPrivilegeEscalation: ptr.To(false), + Capabilities: &v1.Capabilities{ + Drop: []v1.Capability{"ALL"}, + }, + RunAsNonRoot: ptr.To(true), + RunAsUser: ptr.To[int64](1000), + SeccompProfile: &v1.SeccompProfile{ + Type: v1.SeccompProfileTypeRuntimeDefault, + }, + }, }, }, RestartPolicy: v1.RestartPolicyNever,