kubelet: config: add userNamespaces.idsPerPod

IDsPerPod is the mapping length of subids for UserNS.
The length must be multiple of 65536.
Default: 65536

Implements kubernetes/enhancements PR 5020 (addendum to KEP-127)

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
This commit is contained in:
Akihiro Suda 2025-02-07 16:25:52 +09:00
parent 07a275437f
commit 1592bfa4a8
No known key found for this signature in database
GPG Key ID: 49524C6F9F638F1A
16 changed files with 236 additions and 24 deletions

View File

@ -261,6 +261,7 @@ API rule violation: names_match,k8s.io/kube-proxy/config/v1alpha1,KubeProxyConfi
API rule violation: names_match,k8s.io/kubelet/config/v1beta1,KubeletConfiguration,IPTablesDropBit
API rule violation: names_match,k8s.io/kubelet/config/v1beta1,KubeletConfiguration,IPTablesMasqueradeBit
API rule violation: names_match,k8s.io/kubelet/config/v1beta1,KubeletConfiguration,ResolverConfig
API rule violation: names_match,k8s.io/kubelet/config/v1beta1,UserNamespaces,IDsPerPod
API rule violation: names_match,k8s.io/metrics/pkg/apis/custom_metrics/v1beta1,MetricValue,WindowSeconds
API rule violation: names_match,k8s.io/metrics/pkg/apis/external_metrics/v1beta1,ExternalMetricValue,WindowSeconds
API rule violation: streaming_list_type_proto_tags,k8s.io/apimachinery/pkg/apis/meta/v1beta1,PartialObjectMetadataList,Items

View File

@ -1280,6 +1280,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"k8s.io/kubelet/config/v1beta1.MemorySwapConfiguration": schema_k8sio_kubelet_config_v1beta1_MemorySwapConfiguration(ref),
"k8s.io/kubelet/config/v1beta1.SerializedNodeConfigSource": schema_k8sio_kubelet_config_v1beta1_SerializedNodeConfigSource(ref),
"k8s.io/kubelet/config/v1beta1.ShutdownGracePeriodByPodPriority": schema_k8sio_kubelet_config_v1beta1_ShutdownGracePeriodByPodPriority(ref),
"k8s.io/kubelet/config/v1beta1.UserNamespaces": schema_k8sio_kubelet_config_v1beta1_UserNamespaces(ref),
"k8s.io/kubernetes/pkg/apis/abac/v1beta1.Policy": schema_pkg_apis_abac_v1beta1_Policy(ref),
"k8s.io/kubernetes/pkg/apis/abac/v1beta1.PolicySpec": schema_pkg_apis_abac_v1beta1_PolicySpec(ref),
"k8s.io/metrics/pkg/apis/custom_metrics/v1beta1.MetricListOptions": schema_pkg_apis_custom_metrics_v1beta1_MetricListOptions(ref),
@ -66201,12 +66202,18 @@ func schema_k8sio_kubelet_config_v1beta1_KubeletConfiguration(ref common.Referen
Format: "",
},
},
"userNamespaces": {
SchemaProps: spec.SchemaProps{
Description: "UserNamespaces contains User Namespace configurations.",
Ref: ref("k8s.io/kubelet/config/v1beta1.UserNamespaces"),
},
},
},
Required: []string{"containerRuntimeEndpoint"},
},
},
Dependencies: []string{
"k8s.io/api/core/v1.Taint", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration", "k8s.io/component-base/logs/api/v1.LoggingConfiguration", "k8s.io/component-base/tracing/api/v1.TracingConfiguration", "k8s.io/kubelet/config/v1beta1.CrashLoopBackOffConfig", "k8s.io/kubelet/config/v1beta1.KubeletAuthentication", "k8s.io/kubelet/config/v1beta1.KubeletAuthorization", "k8s.io/kubelet/config/v1beta1.MemoryReservation", "k8s.io/kubelet/config/v1beta1.MemorySwapConfiguration", "k8s.io/kubelet/config/v1beta1.ShutdownGracePeriodByPodPriority"},
"k8s.io/api/core/v1.Taint", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration", "k8s.io/component-base/logs/api/v1.LoggingConfiguration", "k8s.io/component-base/tracing/api/v1.TracingConfiguration", "k8s.io/kubelet/config/v1beta1.CrashLoopBackOffConfig", "k8s.io/kubelet/config/v1beta1.KubeletAuthentication", "k8s.io/kubelet/config/v1beta1.KubeletAuthorization", "k8s.io/kubelet/config/v1beta1.MemoryReservation", "k8s.io/kubelet/config/v1beta1.MemorySwapConfiguration", "k8s.io/kubelet/config/v1beta1.ShutdownGracePeriodByPodPriority", "k8s.io/kubelet/config/v1beta1.UserNamespaces"},
}
}
@ -66403,6 +66410,26 @@ func schema_k8sio_kubelet_config_v1beta1_ShutdownGracePeriodByPodPriority(ref co
}
}
func schema_k8sio_kubelet_config_v1beta1_UserNamespaces(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "UserNamespaces contains User Namespace configurations.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"idsPerPod": {
SchemaProps: spec.SchemaProps{
Description: "IDsPerPod is the mapping length of UIDs and GIDs. The length must be a multiple of 65536, and must be less than 1<<32. On non-linux such as windows, only null / absent is allowed.\n\nChanging the value may require recreating all containers on the node.\n\nDefault: 65536",
Type: []string{"integer"},
Format: "int64",
},
},
},
},
},
}
}
func schema_pkg_apis_abac_v1beta1_Policy(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{

View File

@ -306,5 +306,6 @@ var (
"LocalStorageCapacityIsolation",
"FailCgroupV1",
"CrashLoopBackOff.MaxContainerRestartPeriod",
"UserNamespaces.IDsPerPod",
)
)

View File

@ -541,6 +541,11 @@ type KubeletConfiguration struct {
// +featureGate=KubeletCrashLoopBackoffMax
// +optional
CrashLoopBackOff CrashLoopBackOffConfig
// UserNamespaces contains User Namespace configurations.
// +featureGate=UserNamespaceSupport
// +optional
UserNamespaces *UserNamespaces
}
// KubeletAuthorizationMode denotes the authorization mode for the kubelet
@ -878,3 +883,17 @@ type ImagePullSecret struct {
// content of the secret specified by the UID/Namespace/Name coordinates.
CredentialHash string
}
// UserNamespaces contains User Namespace configurations.
type UserNamespaces struct {
// IDsPerPod is the mapping length of UIDs and GIDs.
// The length must be a multiple of 65536, and must be less than 1<<32.
// On non-linux such as windows, only null / absent is allowed.
//
// Changing the value may require recreating all containers on the node.
//
// Default: 65536
// +featureGate=UserNamespaceSupport
// +optional
IDsPerPod *int64
}

View File

@ -185,6 +185,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*configv1beta1.UserNamespaces)(nil), (*config.UserNamespaces)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta1_UserNamespaces_To_config_UserNamespaces(a.(*configv1beta1.UserNamespaces), b.(*config.UserNamespaces), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.UserNamespaces)(nil), (*configv1beta1.UserNamespaces)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_UserNamespaces_To_v1beta1_UserNamespaces(a.(*config.UserNamespaces), b.(*configv1beta1.UserNamespaces), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*config.CredentialProvider)(nil), (*configv1beta1.CredentialProvider)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_CredentialProvider_To_v1beta1_CredentialProvider(a.(*config.CredentialProvider), b.(*configv1beta1.CredentialProvider), scope)
}); err != nil {
@ -584,6 +594,7 @@ func autoConvert_v1beta1_KubeletConfiguration_To_config_KubeletConfiguration(in
if err := v1.Convert_Pointer_bool_To_bool(&in.FailCgroupV1, &out.FailCgroupV1, s); err != nil {
return err
}
out.UserNamespaces = (*config.UserNamespaces)(unsafe.Pointer(in.UserNamespaces))
return nil
}
@ -789,6 +800,7 @@ func autoConvert_config_KubeletConfiguration_To_v1beta1_KubeletConfiguration(in
if err := Convert_config_CrashLoopBackOffConfig_To_v1beta1_CrashLoopBackOffConfig(&in.CrashLoopBackOff, &out.CrashLoopBackOff, s); err != nil {
return err
}
out.UserNamespaces = (*configv1beta1.UserNamespaces)(unsafe.Pointer(in.UserNamespaces))
return nil
}
@ -948,3 +960,23 @@ func autoConvert_config_ShutdownGracePeriodByPodPriority_To_v1beta1_ShutdownGrac
func Convert_config_ShutdownGracePeriodByPodPriority_To_v1beta1_ShutdownGracePeriodByPodPriority(in *config.ShutdownGracePeriodByPodPriority, out *configv1beta1.ShutdownGracePeriodByPodPriority, s conversion.Scope) error {
return autoConvert_config_ShutdownGracePeriodByPodPriority_To_v1beta1_ShutdownGracePeriodByPodPriority(in, out, s)
}
func autoConvert_v1beta1_UserNamespaces_To_config_UserNamespaces(in *configv1beta1.UserNamespaces, out *config.UserNamespaces, s conversion.Scope) error {
out.IDsPerPod = (*int64)(unsafe.Pointer(in.IDsPerPod))
return nil
}
// Convert_v1beta1_UserNamespaces_To_config_UserNamespaces is an autogenerated conversion function.
func Convert_v1beta1_UserNamespaces_To_config_UserNamespaces(in *configv1beta1.UserNamespaces, out *config.UserNamespaces, s conversion.Scope) error {
return autoConvert_v1beta1_UserNamespaces_To_config_UserNamespaces(in, out, s)
}
func autoConvert_config_UserNamespaces_To_v1beta1_UserNamespaces(in *config.UserNamespaces, out *configv1beta1.UserNamespaces, s conversion.Scope) error {
out.IDsPerPod = (*int64)(unsafe.Pointer(in.IDsPerPod))
return nil
}
// Convert_config_UserNamespaces_To_v1beta1_UserNamespaces is an autogenerated conversion function.
func Convert_config_UserNamespaces_To_v1beta1_UserNamespaces(in *config.UserNamespaces, out *configv1beta1.UserNamespaces, s conversion.Scope) error {
return autoConvert_config_UserNamespaces_To_v1beta1_UserNamespaces(in, out, s)
}

View File

@ -21,12 +21,15 @@ package validation
import (
"fmt"
"math"
libcontainercgroups "github.com/opencontainers/cgroups"
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
"k8s.io/utils/ptr"
)
const userNsUnitLength = 65536
// validateKubeletOSConfiguration validates os specific kubelet configuration and returns an error if it is invalid.
func validateKubeletOSConfiguration(kc *kubeletconfig.KubeletConfiguration) error {
isCgroup1 := !libcontainercgroups.IsCgroup2UnifiedMode()
@ -38,5 +41,20 @@ func validateKubeletOSConfiguration(kc *kubeletconfig.KubeletConfiguration) erro
return fmt.Errorf("invalid configuration: singleProcessOOMKill must not be explicitly set to false when using cgroup v1")
}
if userNs := kc.UserNamespaces; userNs != nil {
if idsPerPod := userNs.IDsPerPod; idsPerPod != nil {
if *idsPerPod < userNsUnitLength {
return fmt.Errorf("invalid configuration: userNamespaces.idsPerPod must not be less than %d", userNsUnitLength)
}
if *idsPerPod%userNsUnitLength != 0 {
return fmt.Errorf("invalid configuration: userNamespaces.idsPerPod must be a multiple of %d", userNsUnitLength)
}
if *idsPerPod > math.MaxUint32 {
// int64() is needed for 32-bit targets
return fmt.Errorf("invalid configuration: userNamespaces.idsPerPod must not be more than %d", int64(math.MaxUint32))
}
}
}
return nil
}

View File

@ -31,5 +31,9 @@ func validateKubeletOSConfiguration(kc *kubeletconfig.KubeletConfiguration) erro
return fmt.Errorf("invalid configuration: singleProcessOOMKill is only supported on linux")
}
if kc.UserNamespaces != nil {
return fmt.Errorf("invalid configuration: userNamespaces is only supported on linux")
}
return nil
}

View File

@ -46,5 +46,9 @@ func validateKubeletOSConfiguration(kc *kubeletconfig.KubeletConfiguration) erro
klog.Warningf(message, "EnforceNodeAllocatable", "--enforce-node-allocatable", kc.EnforceNodeAllocatable)
}
if kc.UserNamespaces != nil {
return fmt.Errorf("invalid configuration: userNamespaces is not supported on Windows")
}
return nil
}

View File

@ -459,6 +459,11 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) {
(*in).DeepCopyInto(*out)
}
in.CrashLoopBackOff.DeepCopyInto(&out.CrashLoopBackOff)
if in.UserNamespaces != nil {
in, out := &in.UserNamespaces, &out.UserNamespaces
*out = new(UserNamespaces)
(*in).DeepCopyInto(*out)
}
return
}
@ -642,3 +647,24 @@ func (in *ShutdownGracePeriodByPodPriority) DeepCopy() *ShutdownGracePeriodByPod
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UserNamespaces) DeepCopyInto(out *UserNamespaces) {
*out = *in
if in.IDsPerPod != nil {
in, out := &in.IDsPerPod, &out.IDsPerPod
*out = new(int64)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserNamespaces.
func (in *UserNamespaces) DeepCopy() *UserNamespaces {
if in == nil {
return nil
}
out := new(UserNamespaces)
in.DeepCopyInto(out)
return out
}

View File

@ -30,4 +30,5 @@ const (
KubeletPluginsDirSELinuxLabel = "system_u:object_r:container_file_t:s0"
KubeletContainersSharedSELinuxLabel = "system_u:object_r:container_file_t:s0"
DefaultKubeletCheckpointsDirName = "checkpoints"
DefaultKubeletUserNamespacesIDsPerPod = 65536
)

View File

@ -140,6 +140,20 @@ func (kl *Kubelet) GetMaxPods() int {
return kl.maxPods
}
func (kl *Kubelet) GetUserNamespacesIDsPerPod() uint32 {
userNs := kl.kubeletConfiguration.UserNamespaces
if userNs == nil {
return config.DefaultKubeletUserNamespacesIDsPerPod
}
idsPerPod := userNs.IDsPerPod
if idsPerPod == nil || *idsPerPod == 0 {
return config.DefaultKubeletUserNamespacesIDsPerPod
}
// The value is already validated to be <= MaxUint32,
// so we can safely drop the upper bits.
return uint32(*idsPerPod)
}
// getPodDir returns the full path to the per-pod directory for the pod with
// the given UID.
func (kl *Kubelet) getPodDir(podUID types.UID) string {

View File

@ -26,4 +26,5 @@ type userNsPodsManager interface {
ListPodsFromDisk() ([]types.UID, error)
GetKubeletMappings() (uint32, uint32, error)
GetMaxPods() int
GetUserNamespacesIDsPerPod() uint32
}

View File

@ -39,12 +39,14 @@ import (
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
)
// length for the user namespace to create (65536).
const userNsLength = (1 << 16)
const (
// Create a new map when we removed enough pods to avoid memory leaks
// since Go maps never free memory.
mapReInitializeThreshold = 1000
// Create a new map when we removed enough pods to avoid memory leaks
// since Go maps never free memory.
const mapReInitializeThreshold = 1000
// userNsUnitLength is the unit length of UserNS
userNsUnitLength = 65536
)
type UsernsManager struct {
used *allocator.AllocationBitmap
@ -54,6 +56,8 @@ type UsernsManager struct {
off int
len int
userNsLength uint32
kl userNsPodsManager
// This protects all members except for kl.anager
lock sync.Mutex
@ -130,6 +134,11 @@ func MakeUserNsManager(kl userNsPodsManager) (*UsernsManager, error) {
return nil, fmt.Errorf("kubelet mappings: %w", err)
}
userNsLength := kl.GetUserNamespacesIDsPerPod()
if userNsLength%userNsUnitLength != 0 {
return nil, fmt.Errorf("kubelet user namespace length %v is not a multiple of %d", userNsLength, userNsUnitLength)
}
if kubeletMappingID%userNsLength != 0 {
return nil, fmt.Errorf("kubelet user assigned ID %v is not a multiple of %v", kubeletMappingID, userNsLength)
}
@ -147,11 +156,12 @@ func MakeUserNsManager(kl userNsPodsManager) (*UsernsManager, error) {
len := int(kubeletMappingLen / userNsLength)
m := UsernsManager{
used: allocator.NewAllocationMap(len, "user namespaces"),
usedBy: make(map[types.UID]uint32),
kl: kl,
off: off,
len: len,
used: allocator.NewAllocationMap(len, "user namespaces"),
usedBy: make(map[types.UID]uint32),
kl: kl,
off: off,
len: len,
userNsLength: userNsLength,
}
// do not bother reading the list of pods if user namespaces are not enabled.
@ -196,7 +206,7 @@ func (m *UsernsManager) recordPodMappings(pod types.UID) error {
// isSet checks if the specified index is already set.
func (m *UsernsManager) isSet(v uint32) bool {
index := int(v/userNsLength) - m.off
index := int(v/m.userNsLength) - m.off
if index < 0 || index >= m.len {
return true
}
@ -217,24 +227,24 @@ func (m *UsernsManager) allocateOne(pod types.UID) (firstID uint32, length uint3
klog.V(5).InfoS("new pod user namespace allocation", "podUID", pod)
firstID = uint32((firstZero + m.off) * userNsLength)
firstID = uint32((firstZero + m.off)) * m.userNsLength
m.usedBy[pod] = firstID
return firstID, userNsLength, nil
return firstID, m.userNsLength, nil
}
// record stores the user namespace [from; from+length] to the specified pod.
func (m *UsernsManager) record(pod types.UID, from, length uint32) (err error) {
if length != userNsLength {
if length != m.userNsLength {
return fmt.Errorf("wrong user namespace length %v", length)
}
if from%userNsLength != 0 {
if from%m.userNsLength != 0 {
return fmt.Errorf("wrong user namespace offset specified %v", from)
}
prevFrom, found := m.usedBy[pod]
if found && prevFrom != from {
return fmt.Errorf("different user namespace range already used by pod %q", pod)
}
index := int(from/userNsLength) - m.off
index := int(from/m.userNsLength) - m.off
if index < 0 || index >= m.len {
return fmt.Errorf("id %v is out of range", from)
}
@ -302,7 +312,7 @@ func (m *UsernsManager) releaseWithLock(pod types.UID) {
m.usedBy = n
m.removed = 0
}
_ = m.used.Release(int(v/userNsLength) - m.off)
_ = m.used.Release(int(v/m.userNsLength) - m.off)
}
func (m *UsernsManager) parseUserNsFileAndRecord(pod types.UID, content []byte) (userNs userNamespace, err error) {

View File

@ -38,10 +38,11 @@ import (
)
const (
testUserNsLength = uint32(65536)
// skip the first block
minimumMappingUID = userNsLength
minimumMappingUID = testUserNsLength
// allocate enough space for 2000 user namespaces
mappingLen = userNsLength * 2000
mappingLen = testUserNsLength * 2000
testMaxPods = 110
)
@ -52,6 +53,7 @@ type testUserNsPodsManager struct {
maxPods int
mappingFirstID uint32
mappingLen uint32
userNsLength uint32
}
func (m *testUserNsPodsManager) GetPodDir(podUID types.UID) string {
@ -90,6 +92,13 @@ func (m *testUserNsPodsManager) GetMaxPods() int {
return testMaxPods
}
func (m *testUserNsPodsManager) GetUserNamespacesIDsPerPod() uint32 {
if m.userNsLength != 0 {
return m.userNsLength
}
return testUserNsLength
}
func TestUserNsManagerAllocate(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true)
@ -99,7 +108,7 @@ func TestUserNsManagerAllocate(t *testing.T) {
allocated, length, err := m.allocateOne("one")
assert.NoError(t, err)
assert.Equal(t, userNsLength, int(length), "m.isSet(%d).length=%v", allocated, length)
assert.Equal(t, testUserNsLength, length, "m.isSet(%d).length=%v", allocated, length)
assert.True(t, m.isSet(allocated), "m.isSet(%d)", allocated)
allocated2, length2, err := m.allocateOne("two")
@ -122,11 +131,11 @@ func TestUserNsManagerAllocate(t *testing.T) {
var allocs []uint32
for i := 0; i < 1000; i++ {
allocated, length, err = m.allocateOne(types.UID(fmt.Sprintf("%d", i)))
assert.Equal(t, userNsLength, int(length), "length is not the expected. iter: %v", i)
assert.Equal(t, testUserNsLength, length, "length is not the expected. iter: %v", i)
assert.NoError(t, err)
assert.GreaterOrEqual(t, allocated, uint32(minimumMappingUID))
// The last ID of the userns range (allocated+userNsLength) should be within bounds.
assert.LessOrEqual(t, allocated, uint32(minimumMappingUID+mappingLen-userNsLength))
assert.LessOrEqual(t, allocated, uint32(minimumMappingUID+mappingLen-testUserNsLength))
allocs = append(allocs, allocated)
}
for i, v := range allocs {
@ -134,7 +143,7 @@ func TestUserNsManagerAllocate(t *testing.T) {
m.Release(types.UID(fmt.Sprintf("%d", i)))
assert.False(t, m.isSet(v), "m.isSet(%d) should be false", v)
err = m.record(types.UID(fmt.Sprintf("%d", i)), v, userNsLength)
err = m.record(types.UID(fmt.Sprintf("%d", i)), v, testUserNsLength)
assert.NoError(t, err)
m.Release(types.UID(fmt.Sprintf("%d", i)))
assert.False(t, m.isSet(v), "m.isSet(%d) should be false", v)

View File

@ -924,6 +924,11 @@ type KubeletConfiguration struct {
// Default: false
// +optional
FailCgroupV1 *bool `json:"failCgroupV1,omitempty"`
// UserNamespaces contains User Namespace configurations.
// +featureGate=UserNamespaceSupport
// +optional
UserNamespaces *UserNamespaces `json:"userNamespaces,omitempty"`
}
type KubeletAuthorizationMode string
@ -1119,3 +1124,17 @@ type ExecEnvVar struct {
Name string `json:"name"`
Value string `json:"value"`
}
// UserNamespaces contains User Namespace configurations.
type UserNamespaces struct {
// IDsPerPod is the mapping length of UIDs and GIDs.
// The length must be a multiple of 65536, and must be less than 1<<32.
// On non-linux such as windows, only null / absent is allowed.
//
// Changing the value may require recreating all containers on the node.
//
// Default: 65536
// +featureGate=UserNamespaceSupport
// +optional
IDsPerPod *int64 `json:"idsPerPod,omitempty"`
}

View File

@ -527,6 +527,11 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) {
*out = new(bool)
**out = **in
}
if in.UserNamespaces != nil {
in, out := &in.UserNamespaces, &out.UserNamespaces
*out = new(UserNamespaces)
(*in).DeepCopyInto(*out)
}
return
}
@ -684,3 +689,24 @@ func (in *ShutdownGracePeriodByPodPriority) DeepCopy() *ShutdownGracePeriodByPod
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UserNamespaces) DeepCopyInto(out *UserNamespaces) {
*out = *in
if in.IDsPerPod != nil {
in, out := &in.IDsPerPod, &out.IDsPerPod
*out = new(int64)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserNamespaces.
func (in *UserNamespaces) DeepCopy() *UserNamespaces {
if in == nil {
return nil
}
out := new(UserNamespaces)
in.DeepCopyInto(out)
return out
}