mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-06-09 08:55:55 -04:00
Add alpha 2 phase implementation for UserNamespacesHostNetworkSupport
This commit is contained in:
parent
1132a4e4ef
commit
0ffc845789
16 changed files with 969 additions and 817 deletions
|
|
@ -2667,7 +2667,7 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature
|
|||
|
||||
TranslateStreamCloseWebsocketRequests: {},
|
||||
|
||||
UserNamespacesHostNetworkSupport: {UserNamespacesSupport},
|
||||
UserNamespacesHostNetworkSupport: {UserNamespacesSupport, NodeDeclaredFeatures},
|
||||
|
||||
UserNamespacesSupport: {},
|
||||
|
||||
|
|
|
|||
|
|
@ -666,7 +666,8 @@ func (c *RuntimeCondition) String() string {
|
|||
|
||||
// RuntimeFeatures contains the set of features implemented by the runtime
|
||||
type RuntimeFeatures struct {
|
||||
SupplementalGroupsPolicy bool
|
||||
SupplementalGroupsPolicy bool
|
||||
UserNamespacesHostNetwork bool
|
||||
}
|
||||
|
||||
// String formats the runtime condition into a human readable string.
|
||||
|
|
@ -674,7 +675,7 @@ func (f *RuntimeFeatures) String() string {
|
|||
if f == nil {
|
||||
return "nil"
|
||||
}
|
||||
return fmt.Sprintf("SupplementalGroupsPolicy: %v", f.SupplementalGroupsPolicy)
|
||||
return fmt.Sprintf("SupplementalGroupsPolicy: %v UserNamespacesHostNetwork: %v", f.SupplementalGroupsPolicy, f.UserNamespacesHostNetwork)
|
||||
}
|
||||
|
||||
// Pods represents the list of pods
|
||||
|
|
|
|||
|
|
@ -603,11 +603,11 @@ func TestRuntimeStatusString(t *testing.T) {
|
|||
{Name: "handler1", SupportsRecursiveReadOnlyMounts: true, SupportsUserNamespaces: false},
|
||||
{Name: "handler2", SupportsRecursiveReadOnlyMounts: false, SupportsUserNamespaces: true},
|
||||
},
|
||||
Features: &RuntimeFeatures{SupplementalGroupsPolicy: true},
|
||||
Features: &RuntimeFeatures{SupplementalGroupsPolicy: true, UserNamespacesHostNetwork: true},
|
||||
}
|
||||
|
||||
result := status.String()
|
||||
expected := "Runtime Conditions: RuntimeReady=true reason:ready message:runtime is ready, NetworkReady=false reason:not ready message:network is not ready; Handlers: Name=handler1 SupportsRecursiveReadOnlyMounts: true SupportsUserNamespaces: false, Name=handler2 SupportsRecursiveReadOnlyMounts: false SupportsUserNamespaces: true, Features: SupplementalGroupsPolicy: true"
|
||||
expected := "Runtime Conditions: RuntimeReady=true reason:ready message:runtime is ready, NetworkReady=false reason:not ready message:network is not ready; Handlers: Name=handler1 SupportsRecursiveReadOnlyMounts: true SupportsUserNamespaces: false, Name=handler2 SupportsRecursiveReadOnlyMounts: false SupportsUserNamespaces: true, Features: SupplementalGroupsPolicy: true UserNamespacesHostNetwork: true"
|
||||
assert.Equal(t, expected, result, "String()")
|
||||
}
|
||||
|
||||
|
|
@ -688,18 +688,20 @@ func TestRuntimeFeaturesString(t *testing.T) {
|
|||
expected string
|
||||
}{
|
||||
{
|
||||
name: "features with SupplementalGroupsPolicy true",
|
||||
name: "features with both flags true",
|
||||
features: &RuntimeFeatures{
|
||||
SupplementalGroupsPolicy: true,
|
||||
SupplementalGroupsPolicy: true,
|
||||
UserNamespacesHostNetwork: true,
|
||||
},
|
||||
expected: "SupplementalGroupsPolicy: true",
|
||||
expected: "SupplementalGroupsPolicy: true UserNamespacesHostNetwork: true",
|
||||
},
|
||||
{
|
||||
name: "features with SupplementalGroupsPolicy false",
|
||||
name: "features with both flags false",
|
||||
features: &RuntimeFeatures{
|
||||
SupplementalGroupsPolicy: false,
|
||||
SupplementalGroupsPolicy: false,
|
||||
UserNamespacesHostNetwork: false,
|
||||
},
|
||||
expected: "SupplementalGroupsPolicy: false",
|
||||
expected: "SupplementalGroupsPolicy: false UserNamespacesHostNetwork: false",
|
||||
},
|
||||
{
|
||||
name: "nil features",
|
||||
|
|
|
|||
|
|
@ -1079,6 +1079,11 @@ func NewMainKubelet(ctx context.Context,
|
|||
handlers = append(handlers, evictionAdmitHandler)
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.NodeDeclaredFeatures) {
|
||||
if status, err := klet.containerRuntime.Status(ctx); err == nil && status != nil {
|
||||
klet.runtimeState.setRuntimeFeatures(status.Features)
|
||||
} else if err != nil {
|
||||
logger.V(4).Info("Unable to prefetch container runtime features for node declared features", "err", err)
|
||||
}
|
||||
v, err := versionutil.Parse(version.Get().String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse version: %w", err)
|
||||
|
|
|
|||
|
|
@ -37,9 +37,16 @@ func (a FeatureGateAdapter) Enabled(key string) bool {
|
|||
// discoverNodeDeclaredFeatures determines the final set of node features to be declared by using the discovery library.
|
||||
func (kl *Kubelet) discoverNodeDeclaredFeatures() []string {
|
||||
adaptedFG := FeatureGateAdapter{FeatureGate: utilfeature.DefaultFeatureGate}
|
||||
|
||||
runtimeFeatures := nodedeclaredfeatures.RuntimeFeatures{}
|
||||
if features := kl.runtimeState.runtimeFeatures(); features != nil {
|
||||
runtimeFeatures.UserNamespacesHostNetwork = features.UserNamespacesHostNetwork
|
||||
}
|
||||
|
||||
cfg := &nodedeclaredfeatures.NodeConfiguration{
|
||||
FeatureGates: adaptedFG,
|
||||
Version: kl.version,
|
||||
FeatureGates: adaptedFG,
|
||||
Version: kl.version,
|
||||
RuntimeFeatures: runtimeFeatures,
|
||||
}
|
||||
return kl.nodeDeclaredFeaturesFramework.DiscoverNodeFeatures(cfg)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -267,7 +267,8 @@ func toKubeRuntimeStatus(status *runtimeapi.RuntimeStatus, handlers []*runtimeap
|
|||
var retFeatures *kubecontainer.RuntimeFeatures
|
||||
if features != nil {
|
||||
retFeatures = &kubecontainer.RuntimeFeatures{
|
||||
SupplementalGroupsPolicy: features.SupplementalGroupsPolicy,
|
||||
SupplementalGroupsPolicy: features.SupplementalGroupsPolicy,
|
||||
UserNamespacesHostNetwork: features.UserNamespacesHostNetwork,
|
||||
}
|
||||
}
|
||||
return &kubecontainer.RuntimeStatus{Conditions: conditions, Handlers: retHandlers, Features: retFeatures}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"k8s.io/component-helpers/nodedeclaredfeatures/features/extendwebsocketstokubelet"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/features/inplacepodresize"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/features/restartallcontainers"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/features/usernamespaceshostnetwork"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/types"
|
||||
)
|
||||
|
||||
|
|
@ -31,4 +32,5 @@ var AllFeatures = []types.Feature{
|
|||
inplacepodresize.PodLevelResourcesResizeFeature,
|
||||
extendwebsocketstokubelet.Feature,
|
||||
inplacepodresize.NonSidecarInitContainerResizeFeature,
|
||||
usernamespaceshostnetwork.Feature,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ func TestFeatureRequirementsConsistency(t *testing.T) {
|
|||
FeatureGates: mockFG,
|
||||
Version: version.MustParse("1.36.0"),
|
||||
}
|
||||
if reqs.RequiredRuntimeFeatures != nil {
|
||||
discoverCfg.RuntimeFeatures = *reqs.RequiredRuntimeFeatures
|
||||
}
|
||||
|
||||
featureEnabled := registeredFeature.Discover(discoverCfg)
|
||||
if !featureEnabled {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
Copyright 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 usernamespaceshostnetwork
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/types"
|
||||
)
|
||||
|
||||
// Ensure the feature struct implements the unified Feature interface.
|
||||
var _ types.Feature = &userNamespacesHostNetworkFeature{}
|
||||
|
||||
const (
|
||||
// UserNamespacesHostNetworkSupportFeatureGate is the feature gate name.
|
||||
UserNamespacesHostNetworkSupportFeatureGate = "UserNamespacesHostNetworkSupport"
|
||||
// UserNamespacesHostNetworkSupport is the declared feature name.
|
||||
UserNamespacesHostNetworkSupport = "UserNamespacesHostNetworkSupport"
|
||||
)
|
||||
|
||||
// Feature is the implementation of the `UserNamespacesHostNetworkSupport` feature.
|
||||
var Feature = &userNamespacesHostNetworkFeature{}
|
||||
|
||||
type userNamespacesHostNetworkFeature struct{}
|
||||
|
||||
func (f *userNamespacesHostNetworkFeature) Requirements() *types.FeatureRequirements {
|
||||
return &types.FeatureRequirements{
|
||||
EnabledFeatureGates: []string{UserNamespacesHostNetworkSupportFeatureGate},
|
||||
RequiredRuntimeFeatures: &types.RuntimeFeatures{
|
||||
UserNamespacesHostNetwork: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *userNamespacesHostNetworkFeature) Name() string {
|
||||
return UserNamespacesHostNetworkSupport
|
||||
}
|
||||
|
||||
func (f *userNamespacesHostNetworkFeature) Discover(cfg *types.NodeConfiguration) bool {
|
||||
// This feature requires both the feature gate to be enabled AND
|
||||
// runtime-level support for user namespaces with host network.
|
||||
if !cfg.FeatureGates.Enabled(UserNamespacesHostNetworkSupportFeatureGate) {
|
||||
return false
|
||||
}
|
||||
|
||||
return cfg.RuntimeFeatures.UserNamespacesHostNetwork
|
||||
}
|
||||
|
||||
func (f *userNamespacesHostNetworkFeature) InferForScheduling(podInfo *types.PodInfo) bool {
|
||||
// A pod needs this feature if it uses both host network AND user namespaces.
|
||||
if podInfo.Spec.HostNetwork && podInfo.Spec.HostUsers != nil && !*podInfo.Spec.HostUsers {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *userNamespacesHostNetworkFeature) InferForUpdate(oldPodInfo, newPodInfo *types.PodInfo) bool {
|
||||
// HostNetwork and HostUsers fields are immutable, so no update inference is needed.
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *userNamespacesHostNetworkFeature) MaxVersion() *version.Version {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
Copyright 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 usernamespaceshostnetwork
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/types"
|
||||
)
|
||||
|
||||
type fakeFeatureGate struct {
|
||||
features map[string]bool
|
||||
}
|
||||
|
||||
func (m *fakeFeatureGate) Enabled(key string) bool {
|
||||
return m.features[key]
|
||||
}
|
||||
|
||||
func TestDiscoverFeature(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
featureGate bool
|
||||
runtimeFeatures types.RuntimeFeatures
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "feature gate disabled",
|
||||
featureGate: false,
|
||||
runtimeFeatures: types.RuntimeFeatures{
|
||||
UserNamespacesHostNetwork: true,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "feature gate enabled but no runtime support",
|
||||
featureGate: true,
|
||||
runtimeFeatures: types.RuntimeFeatures{
|
||||
UserNamespacesHostNetwork: false,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "feature gate enabled and runtime supports it",
|
||||
featureGate: true,
|
||||
runtimeFeatures: types.RuntimeFeatures{
|
||||
UserNamespacesHostNetwork: true,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "runtime support is on",
|
||||
featureGate: true,
|
||||
runtimeFeatures: types.RuntimeFeatures{
|
||||
UserNamespacesHostNetwork: true,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "runtime support is off",
|
||||
featureGate: true,
|
||||
runtimeFeatures: types.RuntimeFeatures{
|
||||
UserNamespacesHostNetwork: false,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockFG := &fakeFeatureGate{
|
||||
features: map[string]bool{
|
||||
UserNamespacesHostNetworkSupportFeatureGate: tt.featureGate,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &types.NodeConfiguration{
|
||||
FeatureGates: mockFG,
|
||||
RuntimeFeatures: tt.runtimeFeatures,
|
||||
}
|
||||
|
||||
result := Feature.Discover(cfg)
|
||||
if result != tt.expected {
|
||||
t.Fatalf("Feature.Discover() = %v, want %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import (
|
|||
type PodInfo = types.PodInfo
|
||||
type Feature = types.Feature
|
||||
type FeatureRequirements = types.FeatureRequirements
|
||||
type RuntimeFeatures = types.RuntimeFeatures
|
||||
type FeatureGate = types.FeatureGate
|
||||
type StaticConfiguration = types.StaticConfiguration
|
||||
type NodeConfiguration = types.NodeConfiguration
|
||||
|
|
|
|||
|
|
@ -63,6 +63,9 @@ type Feature interface {
|
|||
type FeatureRequirements struct {
|
||||
// EnabledFeatureGates lists feature gate strings that the feature depends on.
|
||||
EnabledFeatureGates []string
|
||||
// RequiredRuntimeFeatures lists runtime capabilities that must be true for the
|
||||
// feature to be declared. Nil means the feature has no runtime requirements.
|
||||
RequiredRuntimeFeatures *RuntimeFeatures
|
||||
}
|
||||
|
||||
// FeatureGate is an interface that abstracts feature gate checking.
|
||||
|
|
@ -71,6 +74,12 @@ type FeatureGate interface {
|
|||
Enabled(key string) bool
|
||||
}
|
||||
|
||||
// RuntimeFeatures provides information about CRI runtime-level capabilities.
|
||||
type RuntimeFeatures struct {
|
||||
// UserNamespacesHostNetwork indicates if the runtime supports user namespaces with host network.
|
||||
UserNamespacesHostNetwork bool
|
||||
}
|
||||
|
||||
// StaticConfiguration provides a view of a node's static configuration required for feature discovery.
|
||||
type StaticConfiguration struct {
|
||||
// Add configuration fields here as required by registered features.
|
||||
|
|
@ -85,6 +94,8 @@ type NodeConfiguration struct {
|
|||
// Version holds the current node version. This is used for full semantic version comparisons
|
||||
// with Feature.MaxVersion() to determine if a feature needs to be reported.
|
||||
Version *version.Version
|
||||
// RuntimeFeatures holds runtime-level capabilities discovered from CRI.
|
||||
RuntimeFeatures RuntimeFeatures
|
||||
}
|
||||
|
||||
// FeatureGateMap is provided as a convenience implementation of FeatureGate
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1872,6 +1872,9 @@ message RuntimeHandler {
|
|||
message RuntimeFeatures {
|
||||
// supplemental_groups_policy is set to true if the runtime supports SupplementalGroupsPolicy and ContainerUser.
|
||||
bool supplemental_groups_policy = 1;
|
||||
// user_namespaces_host_network is set to true if the runtime supports containers using both
|
||||
// host network and user namespace simultaneously.
|
||||
bool user_namespaces_host_network = 2;
|
||||
}
|
||||
|
||||
message StatusResponse {
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@
|
|||
| UnauthenticatedHTTP2DOSMitigation | :ballot_box_with_check: 1.29+ | | | 1.25– | | | | [code](https://cs.k8s.io/?q=%5CbUnauthenticatedHTTP2DOSMitigation%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbUnauthenticatedHTTP2DOSMitigation%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
| UnknownVersionInteroperabilityProxy | :ballot_box_with_check: 1.36+ | | 1.28–1.35 | 1.36– | | | APIServerIdentity | [code](https://cs.k8s.io/?q=%5CbUnknownVersionInteroperabilityProxy%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbUnknownVersionInteroperabilityProxy%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
| UnlockWhileProcessingFIFO | :ballot_box_with_check: 1.36+ | | | 1.36– | | | | [code](https://cs.k8s.io/?q=%5CbUnlockWhileProcessingFIFO%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbUnlockWhileProcessingFIFO%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
| UserNamespacesHostNetworkSupport | | | 1.35– | | | | UserNamespacesSupport | [code](https://cs.k8s.io/?q=%5CbUserNamespacesHostNetworkSupport%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbUserNamespacesHostNetworkSupport%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
| UserNamespacesHostNetworkSupport | | | 1.35– | | | | NodeDeclaredFeatures<br>UserNamespacesSupport | [code](https://cs.k8s.io/?q=%5CbUserNamespacesHostNetworkSupport%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbUserNamespacesHostNetworkSupport%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
| UserNamespacesSupport | :ballot_box_with_check: 1.33+ | :closed_lock_with_key: 1.36+ | 1.25–1.29 | 1.30–1.35 | 1.36– | | | [code](https://cs.k8s.io/?q=%5CbUserNamespacesSupport%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbUserNamespacesSupport%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
| VolumeAttributesClass | :ballot_box_with_check: 1.34+ | :closed_lock_with_key: 1.36+ | 1.29–1.30 | 1.31–1.33 | 1.34– | | | [code](https://cs.k8s.io/?q=%5CbVolumeAttributesClass%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbVolumeAttributesClass%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
| VolumeLimitScaling | | | 1.35– | | | | | [code](https://cs.k8s.io/?q=%5CbVolumeLimitScaling%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbVolumeLimitScaling%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import (
|
|||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/kubelet/events"
|
||||
"k8s.io/kubernetes/pkg/kubelet/metrics"
|
||||
"k8s.io/kubernetes/test/e2e/feature"
|
||||
|
|
@ -126,81 +125,6 @@ var _ = SIGDescribe("Security Context", func() {
|
|||
}
|
||||
})
|
||||
|
||||
f.It("must create a user namespace and use host network when hostUsers is false and hostNetwork is true [LinuxOnly]", feature.UserNamespacesHostNetworkSupport, framework.WithFeatureGate(features.UserNamespacesHostNetworkSupport),
|
||||
feature.UserNamespacesSupport, func(ctx context.Context) {
|
||||
// with hostUsers=false the pod must use a new user namespace.
|
||||
// with hostNetwork=true the pod must use the host network namespace.
|
||||
podClient := e2epod.PodClientNS(f, f.Namespace.Name)
|
||||
|
||||
// Schedule pods on the same node to ensure they share the same host network namespace.
|
||||
targetNode, err := findLinuxNode(ctx, f)
|
||||
framework.ExpectNoError(err, "Error finding Linux node")
|
||||
|
||||
makePodForHostNetTest := func(nodeName string) *v1.Pod {
|
||||
return &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "userns-hostnet-" + string(uuid.NewUUID()),
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: nodeName,
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
Image: imageutils.GetE2EImage(imageutils.BusyBox),
|
||||
Command: []string{"sh", "-c", "cat /proc/self/uid_map && readlink /proc/self/ns/net"},
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyNever,
|
||||
HostUsers: ptr.To(false),
|
||||
HostNetwork: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
createdPod1 := podClient.Create(ctx, makePodForHostNetTest(targetNode.Name))
|
||||
createdPod2 := podClient.Create(ctx, makePodForHostNetTest(targetNode.Name))
|
||||
ginkgo.DeferCleanup(func(ctx context.Context) {
|
||||
ginkgo.By("delete the pods")
|
||||
podClient.DeleteSync(ctx, createdPod1.Name, metav1.DeleteOptions{}, f.Timeouts.PodDelete)
|
||||
podClient.DeleteSync(ctx, createdPod2.Name, metav1.DeleteOptions{}, f.Timeouts.PodDelete)
|
||||
})
|
||||
getLogs := func(pod *v1.Pod) (string, string, error) {
|
||||
err := e2epod.WaitForPodSuccessInNamespaceTimeout(ctx, f.ClientSet, pod.Name, f.Namespace.Name, f.Timeouts.PodStart)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
podStatus, err := podClient.Get(ctx, pod.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, podStatus.Name, containerName)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
parts := strings.Split(strings.TrimSpace(logs), "\n")
|
||||
if len(parts) != 2 {
|
||||
return "", "", fmt.Errorf("expected 2 lines of logs, got %d: %q", len(parts), logs)
|
||||
}
|
||||
return parts[0], parts[1], nil
|
||||
}
|
||||
|
||||
uidMap1, netNs1, err := getLogs(createdPod1)
|
||||
framework.ExpectNoError(err)
|
||||
uidMap2, netNs2, err := getLogs(createdPod2)
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
// 65536 is the size used for a user namespace. Verify that the value is present
|
||||
// in the /proc/self/uid_map file.
|
||||
gomega.Expect(uidMap1).To(gomega.ContainSubstring("65536"), "user namespace not created for pod1")
|
||||
gomega.Expect(uidMap2).To(gomega.ContainSubstring("65536"), "user namespace not created for pod2")
|
||||
|
||||
// Check they are in different user namespaces.
|
||||
gomega.Expect(uidMap1).NotTo(gomega.Equal(uidMap2), "two different pods are running with the same user namespace configuration")
|
||||
|
||||
// Check they are in the same network namespace (the host's one).
|
||||
gomega.Expect(netNs1).To(gomega.Equal(netNs2), "two different pods with hostNetwork=true should be in the same network namespace, but they are not. NetNS1: %s, NetNS2: %s", netNs1, netNs2)
|
||||
})
|
||||
|
||||
f.It("must create the user namespace in the configured hostUID/hostGID range [LinuxOnly]", feature.UserNamespacesSupport, func(ctx context.Context) {
|
||||
// We need to check with the binary "getsubuids" the mappings for the kubelet.
|
||||
// If something is not present, we skip the test as the node wasn't configured to run this test.
|
||||
|
|
|
|||
Loading…
Reference in a new issue