kubelet: modify KubeletConfiguration API with image pull policies

Also adds PreloadedImagesVerificationAllowlist to API exceptions list
for missing list type as this is not a part of the REST API.
This commit is contained in:
Stanislav Láznička 2024-10-15 17:04:36 +02:00
parent ad96b3aed5
commit 47827f4d9a
No known key found for this signature in database
GPG Key ID: F8D8054395A1D157
7 changed files with 316 additions and 164 deletions

View File

@ -243,6 +243,7 @@ var (
"ImageGCLowThresholdPercent", "ImageGCLowThresholdPercent",
"ImageMinimumGCAge.Duration", "ImageMinimumGCAge.Duration",
"ImageMaximumGCAge.Duration", "ImageMaximumGCAge.Duration",
"ImagePullCredentialsVerificationPolicy",
"KernelMemcgNotification", "KernelMemcgNotification",
"KubeAPIBurst", "KubeAPIBurst",
"KubeAPIQPS", "KubeAPIQPS",
@ -268,6 +269,7 @@ var (
"PodPidsLimit", "PodPidsLimit",
"PodsPerCore", "PodsPerCore",
"Port", "Port",
"PreloadedImagesVerificationAllowlist[*]",
"ProtectKernelDefaults", "ProtectKernelDefaults",
"ProviderID", "ProviderID",
"ReadOnlyPort", "ReadOnlyPort",

View File

@ -155,6 +155,25 @@ type KubeletConfiguration struct {
// pulls to burst to this number, while still not exceeding registryPullQPS. // pulls to burst to this number, while still not exceeding registryPullQPS.
// Only used if registryPullQPS > 0. // Only used if registryPullQPS > 0.
RegistryBurst int32 RegistryBurst int32
// imagePullCredentialsVerificationPolicy determines how credentials should be
// verified when pod requests an image that is already present on the node:
// - NeverVerify
// - anyone on a node can use any image present on the node
// - NeverVerifyPreloadedImages
// - images that were pulled to the node by something else than the kubelet
// can be used without reverifying pull credentials
// - NeverVerifyAllowlistedImages
// - like "NeverVerifyPreloadedImages" but only node images from
// `preloadedImagesVerificationAllowlist` don't require reverification
// - AlwaysVerify
// - all images require credential reverification
ImagePullCredentialsVerificationPolicy string
// preloadedImagesVerificationAllowlist specifies a list of images that are
// exempted from credential reverification for the "NeverVerifyAllowlistedImages"
// `imagePullCredentialsVerificationPolicy`.
// The list accepts a full path segment wildcard suffix "/*".
// Only use image specs without an image tag or digest.
PreloadedImagesVerificationAllowlist []string
// eventRecordQPS is the maximum event creations per second. If 0, there // eventRecordQPS is the maximum event creations per second. If 0, there
// is no limit enforced. // is no limit enforced.
EventRecordQPS int32 EventRecordQPS int32
@ -770,6 +789,25 @@ type CrashLoopBackOffConfig struct {
MaxContainerRestartPeriod *metav1.Duration MaxContainerRestartPeriod *metav1.Duration
} }
// ImagePullCredentialsVerificationPolicy is an enum for the policy that is enforced
// when pod is requesting an image that appears on the system
type ImagePullCredentialsVerificationPolicy string
const (
// NeverVerify will never require credential verification for images that
// already exist on the node
NeverVerify ImagePullCredentialsVerificationPolicy = "NeverVerify"
// NeverVerifyPreloadedImages does not require credential verification for images
// pulled outside the kubelet process
NeverVerifyPreloadedImages ImagePullCredentialsVerificationPolicy = "NeverVerifyPreloadedImages"
// NeverVerifyAllowlistedImages does not require credential verification for
// a list of images that were pulled outside the kubelet process
NeverVerifyAllowlistedImages ImagePullCredentialsVerificationPolicy = "NeverVerifyAllowlistedImages"
// AlwaysVerify requires credential verification for accessing any image on the
// node irregardless how it was pulled
AlwaysVerify ImagePullCredentialsVerificationPolicy = "AlwaysVerify"
)
// ImagePullIntent is a record of the kubelet attempting to pull an image. // ImagePullIntent is a record of the kubelet attempting to pull an image.
// //
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@ -313,4 +313,10 @@ func SetDefaults_KubeletConfiguration(obj *kubeletconfigv1beta1.KubeletConfigura
obj.CrashLoopBackOff.MaxContainerRestartPeriod = &metav1.Duration{Duration: MaxContainerBackOff} obj.CrashLoopBackOff.MaxContainerRestartPeriod = &metav1.Duration{Duration: MaxContainerBackOff}
} }
} }
if localFeatureGate.Enabled(features.KubeletEnsureSecretPulledImages) {
if obj.ImagePullCredentialsVerificationPolicy == "" {
obj.ImagePullCredentialsVerificationPolicy = kubeletconfigv1beta1.NeverVerifyPreloadedImages
}
}
} }

View File

@ -77,6 +77,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
NodeLeaseDurationSeconds: 40, NodeLeaseDurationSeconds: 40,
ImageMinimumGCAge: metav1.Duration{Duration: 2 * time.Minute}, ImageMinimumGCAge: metav1.Duration{Duration: 2 * time.Minute},
ImageMaximumGCAge: metav1.Duration{}, ImageMaximumGCAge: metav1.Duration{},
ImagePullCredentialsVerificationPolicy: "",
ImageGCHighThresholdPercent: ptr.To[int32](85), ImageGCHighThresholdPercent: ptr.To[int32](85),
ImageGCLowThresholdPercent: ptr.To[int32](80), ImageGCLowThresholdPercent: ptr.To[int32](80),
ContainerRuntimeEndpoint: "unix:///run/containerd/containerd.sock", ContainerRuntimeEndpoint: "unix:///run/containerd/containerd.sock",
@ -168,74 +169,75 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
CacheUnauthorizedTTL: zeroDuration, CacheUnauthorizedTTL: zeroDuration,
}, },
}, },
RegistryPullQPS: ptr.To[int32](0), RegistryPullQPS: ptr.To[int32](0),
RegistryBurst: 0, RegistryBurst: 0,
EventRecordQPS: ptr.To[int32](0), EventRecordQPS: ptr.To[int32](0),
EventBurst: 0, EventBurst: 0,
EnableDebuggingHandlers: ptr.To(false), EnableDebuggingHandlers: ptr.To(false),
EnableContentionProfiling: false, EnableContentionProfiling: false,
HealthzPort: ptr.To[int32](0), HealthzPort: ptr.To[int32](0),
HealthzBindAddress: "", HealthzBindAddress: "",
OOMScoreAdj: ptr.To[int32](0), OOMScoreAdj: ptr.To[int32](0),
ClusterDomain: "", ClusterDomain: "",
ClusterDNS: []string{}, ClusterDNS: []string{},
StreamingConnectionIdleTimeout: zeroDuration, StreamingConnectionIdleTimeout: zeroDuration,
NodeStatusUpdateFrequency: zeroDuration, NodeStatusUpdateFrequency: zeroDuration,
NodeStatusReportFrequency: zeroDuration, NodeStatusReportFrequency: zeroDuration,
NodeLeaseDurationSeconds: 0, NodeLeaseDurationSeconds: 0,
ContainerRuntimeEndpoint: "unix:///run/containerd/containerd.sock", ContainerRuntimeEndpoint: "unix:///run/containerd/containerd.sock",
ImageMinimumGCAge: zeroDuration, ImageMinimumGCAge: zeroDuration,
ImageGCHighThresholdPercent: ptr.To[int32](0), ImagePullCredentialsVerificationPolicy: "",
ImageGCLowThresholdPercent: ptr.To[int32](0), ImageGCHighThresholdPercent: ptr.To[int32](0),
VolumeStatsAggPeriod: zeroDuration, ImageGCLowThresholdPercent: ptr.To[int32](0),
KubeletCgroups: "", VolumeStatsAggPeriod: zeroDuration,
SystemCgroups: "", KubeletCgroups: "",
CgroupRoot: "", SystemCgroups: "",
CgroupsPerQOS: ptr.To(false), CgroupRoot: "",
CgroupDriver: "", CgroupsPerQOS: ptr.To(false),
CPUManagerPolicy: "", CgroupDriver: "",
CPUManagerPolicyOptions: map[string]string{}, CPUManagerPolicy: "",
CPUManagerReconcilePeriod: zeroDuration, CPUManagerPolicyOptions: map[string]string{},
MemoryManagerPolicy: "", CPUManagerReconcilePeriod: zeroDuration,
TopologyManagerPolicy: "", MemoryManagerPolicy: "",
TopologyManagerScope: "", TopologyManagerPolicy: "",
QOSReserved: map[string]string{}, TopologyManagerScope: "",
RuntimeRequestTimeout: zeroDuration, QOSReserved: map[string]string{},
HairpinMode: "", RuntimeRequestTimeout: zeroDuration,
MaxPods: 0, HairpinMode: "",
PodCIDR: "", MaxPods: 0,
PodPidsLimit: ptr.To[int64](0), PodCIDR: "",
ResolverConfig: ptr.To(""), PodPidsLimit: ptr.To[int64](0),
RunOnce: false, ResolverConfig: ptr.To(""),
CPUCFSQuota: ptr.To(false), RunOnce: false,
CPUCFSQuotaPeriod: &zeroDuration, CPUCFSQuota: ptr.To(false),
NodeStatusMaxImages: ptr.To[int32](0), CPUCFSQuotaPeriod: &zeroDuration,
MaxOpenFiles: 0, NodeStatusMaxImages: ptr.To[int32](0),
ContentType: "", MaxOpenFiles: 0,
KubeAPIQPS: ptr.To[int32](0), ContentType: "",
KubeAPIBurst: 0, KubeAPIQPS: ptr.To[int32](0),
SerializeImagePulls: ptr.To(false), KubeAPIBurst: 0,
MaxParallelImagePulls: nil, SerializeImagePulls: ptr.To(false),
EvictionHard: map[string]string{}, MaxParallelImagePulls: nil,
EvictionSoft: map[string]string{}, EvictionHard: map[string]string{},
EvictionSoftGracePeriod: map[string]string{}, EvictionSoft: map[string]string{},
EvictionPressureTransitionPeriod: zeroDuration, EvictionSoftGracePeriod: map[string]string{},
EvictionMaxPodGracePeriod: 0, EvictionPressureTransitionPeriod: zeroDuration,
EvictionMinimumReclaim: map[string]string{}, EvictionMaxPodGracePeriod: 0,
MergeDefaultEvictionSettings: ptr.To(false), EvictionMinimumReclaim: map[string]string{},
PodsPerCore: 0, MergeDefaultEvictionSettings: ptr.To(false),
EnableControllerAttachDetach: ptr.To(false), PodsPerCore: 0,
ProtectKernelDefaults: false, EnableControllerAttachDetach: ptr.To(false),
MakeIPTablesUtilChains: ptr.To(false), ProtectKernelDefaults: false,
IPTablesMasqueradeBit: ptr.To[int32](0), MakeIPTablesUtilChains: ptr.To(false),
IPTablesDropBit: ptr.To[int32](0), IPTablesMasqueradeBit: ptr.To[int32](0),
FeatureGates: map[string]bool{}, IPTablesDropBit: ptr.To[int32](0),
FailSwapOn: ptr.To(false), FeatureGates: map[string]bool{},
MemorySwap: v1beta1.MemorySwapConfiguration{SwapBehavior: ""}, FailSwapOn: ptr.To(false),
ContainerLogMaxSize: "", MemorySwap: v1beta1.MemorySwapConfiguration{SwapBehavior: ""},
ContainerLogMaxFiles: ptr.To[int32](0), ContainerLogMaxSize: "",
ContainerLogMaxWorkers: ptr.To[int32](1), ContainerLogMaxFiles: ptr.To[int32](0),
ContainerLogMonitorInterval: &metav1.Duration{Duration: 10 * time.Second}, ContainerLogMaxWorkers: ptr.To[int32](1),
ContainerLogMonitorInterval: &metav1.Duration{Duration: 10 * time.Second},
ConfigMapAndSecretChangeDetectionStrategy: v1beta1.WatchChangeDetectionStrategy, ConfigMapAndSecretChangeDetectionStrategy: v1beta1.WatchChangeDetectionStrategy,
SystemReserved: map[string]string{}, SystemReserved: map[string]string{},
KubeReserved: map[string]string{}, KubeReserved: map[string]string{},
@ -307,6 +309,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
ImageMinimumGCAge: metav1.Duration{Duration: 2 * time.Minute}, ImageMinimumGCAge: metav1.Duration{Duration: 2 * time.Minute},
ImageGCHighThresholdPercent: ptr.To[int32](0), ImageGCHighThresholdPercent: ptr.To[int32](0),
ImageGCLowThresholdPercent: ptr.To[int32](0), ImageGCLowThresholdPercent: ptr.To[int32](0),
ImagePullCredentialsVerificationPolicy: "",
VolumeStatsAggPeriod: metav1.Duration{Duration: time.Minute}, VolumeStatsAggPeriod: metav1.Duration{Duration: time.Minute},
CgroupsPerQOS: ptr.To(false), CgroupsPerQOS: ptr.To(false),
CgroupDriver: "cgroupfs", CgroupDriver: "cgroupfs",
@ -407,54 +410,55 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
CacheUnauthorizedTTL: metav1.Duration{Duration: 60 * time.Second}, CacheUnauthorizedTTL: metav1.Duration{Duration: 60 * time.Second},
}, },
}, },
RegistryPullQPS: ptr.To[int32](1), RegistryPullQPS: ptr.To[int32](1),
RegistryBurst: 1, RegistryBurst: 1,
EventRecordQPS: ptr.To[int32](1), EventRecordQPS: ptr.To[int32](1),
EventBurst: 1, EventBurst: 1,
EnableDebuggingHandlers: ptr.To(true), EnableDebuggingHandlers: ptr.To(true),
EnableContentionProfiling: true, EnableContentionProfiling: true,
HealthzPort: ptr.To[int32](1), HealthzPort: ptr.To[int32](1),
HealthzBindAddress: "127.0.0.2", HealthzBindAddress: "127.0.0.2",
OOMScoreAdj: ptr.To[int32](1), OOMScoreAdj: ptr.To[int32](1),
ClusterDomain: "cluster-domain", ClusterDomain: "cluster-domain",
ClusterDNS: []string{"192.168.1.3"}, ClusterDNS: []string{"192.168.1.3"},
StreamingConnectionIdleTimeout: metav1.Duration{Duration: 60 * time.Second}, StreamingConnectionIdleTimeout: metav1.Duration{Duration: 60 * time.Second},
NodeStatusUpdateFrequency: metav1.Duration{Duration: 60 * time.Second}, NodeStatusUpdateFrequency: metav1.Duration{Duration: 60 * time.Second},
NodeStatusReportFrequency: metav1.Duration{Duration: 60 * time.Second}, NodeStatusReportFrequency: metav1.Duration{Duration: 60 * time.Second},
NodeLeaseDurationSeconds: 1, NodeLeaseDurationSeconds: 1,
ContainerRuntimeEndpoint: "unix:///run/containerd/containerd.sock", ContainerRuntimeEndpoint: "unix:///run/containerd/containerd.sock",
ImageMinimumGCAge: metav1.Duration{Duration: 60 * time.Second}, ImageMinimumGCAge: metav1.Duration{Duration: 60 * time.Second},
ImageGCHighThresholdPercent: ptr.To[int32](1), ImageGCHighThresholdPercent: ptr.To[int32](1),
ImageGCLowThresholdPercent: ptr.To[int32](1), ImageGCLowThresholdPercent: ptr.To[int32](1),
VolumeStatsAggPeriod: metav1.Duration{Duration: 60 * time.Second}, PreloadedImagesVerificationAllowlist: []string{"test.test/repo/image"},
KubeletCgroups: "kubelet-cgroup", VolumeStatsAggPeriod: metav1.Duration{Duration: 60 * time.Second},
SystemCgroups: "system-cgroup", KubeletCgroups: "kubelet-cgroup",
CgroupRoot: "root-cgroup", SystemCgroups: "system-cgroup",
CgroupsPerQOS: ptr.To(true), CgroupRoot: "root-cgroup",
CgroupDriver: "systemd", CgroupsPerQOS: ptr.To(true),
CPUManagerPolicy: "cpu-manager-policy", CgroupDriver: "systemd",
CPUManagerPolicyOptions: map[string]string{"key": "value"}, CPUManagerPolicy: "cpu-manager-policy",
CPUManagerReconcilePeriod: metav1.Duration{Duration: 60 * time.Second}, CPUManagerPolicyOptions: map[string]string{"key": "value"},
MemoryManagerPolicy: v1beta1.StaticMemoryManagerPolicy, CPUManagerReconcilePeriod: metav1.Duration{Duration: 60 * time.Second},
TopologyManagerPolicy: v1beta1.RestrictedTopologyManagerPolicy, MemoryManagerPolicy: v1beta1.StaticMemoryManagerPolicy,
TopologyManagerScope: v1beta1.PodTopologyManagerScope, TopologyManagerPolicy: v1beta1.RestrictedTopologyManagerPolicy,
QOSReserved: map[string]string{"memory": "10%"}, TopologyManagerScope: v1beta1.PodTopologyManagerScope,
RuntimeRequestTimeout: metav1.Duration{Duration: 60 * time.Second}, QOSReserved: map[string]string{"memory": "10%"},
HairpinMode: v1beta1.HairpinVeth, RuntimeRequestTimeout: metav1.Duration{Duration: 60 * time.Second},
MaxPods: 1, HairpinMode: v1beta1.HairpinVeth,
PodCIDR: "192.168.1.0/24", MaxPods: 1,
PodPidsLimit: ptr.To[int64](1), PodCIDR: "192.168.1.0/24",
ResolverConfig: ptr.To("resolver-config"), PodPidsLimit: ptr.To[int64](1),
RunOnce: true, ResolverConfig: ptr.To("resolver-config"),
CPUCFSQuota: ptr.To(true), RunOnce: true,
CPUCFSQuotaPeriod: &metav1.Duration{Duration: 60 * time.Second}, CPUCFSQuota: ptr.To(true),
NodeStatusMaxImages: ptr.To[int32](1), CPUCFSQuotaPeriod: &metav1.Duration{Duration: 60 * time.Second},
MaxOpenFiles: 1, NodeStatusMaxImages: ptr.To[int32](1),
ContentType: "application/protobuf", MaxOpenFiles: 1,
KubeAPIQPS: ptr.To[int32](1), ContentType: "application/protobuf",
KubeAPIBurst: 1, KubeAPIQPS: ptr.To[int32](1),
SerializeImagePulls: ptr.To(true), KubeAPIBurst: 1,
MaxParallelImagePulls: ptr.To[int32](5), SerializeImagePulls: ptr.To(true),
MaxParallelImagePulls: ptr.To[int32](5),
EvictionHard: map[string]string{ EvictionHard: map[string]string{
"memory.available": "1Mi", "memory.available": "1Mi",
"nodefs.available": "1%", "nodefs.available": "1%",
@ -563,54 +567,55 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
CacheUnauthorizedTTL: metav1.Duration{Duration: 60 * time.Second}, CacheUnauthorizedTTL: metav1.Duration{Duration: 60 * time.Second},
}, },
}, },
RegistryPullQPS: ptr.To[int32](1), RegistryPullQPS: ptr.To[int32](1),
RegistryBurst: 1, RegistryBurst: 1,
EventRecordQPS: ptr.To[int32](1), EventRecordQPS: ptr.To[int32](1),
EventBurst: 1, EventBurst: 1,
EnableDebuggingHandlers: ptr.To(true), EnableDebuggingHandlers: ptr.To(true),
EnableContentionProfiling: true, EnableContentionProfiling: true,
HealthzPort: ptr.To[int32](1), HealthzPort: ptr.To[int32](1),
HealthzBindAddress: "127.0.0.2", HealthzBindAddress: "127.0.0.2",
OOMScoreAdj: ptr.To[int32](1), OOMScoreAdj: ptr.To[int32](1),
ClusterDomain: "cluster-domain", ClusterDomain: "cluster-domain",
ClusterDNS: []string{"192.168.1.3"}, ClusterDNS: []string{"192.168.1.3"},
StreamingConnectionIdleTimeout: metav1.Duration{Duration: 60 * time.Second}, StreamingConnectionIdleTimeout: metav1.Duration{Duration: 60 * time.Second},
NodeStatusUpdateFrequency: metav1.Duration{Duration: 60 * time.Second}, NodeStatusUpdateFrequency: metav1.Duration{Duration: 60 * time.Second},
NodeStatusReportFrequency: metav1.Duration{Duration: 60 * time.Second}, NodeStatusReportFrequency: metav1.Duration{Duration: 60 * time.Second},
NodeLeaseDurationSeconds: 1, NodeLeaseDurationSeconds: 1,
ContainerRuntimeEndpoint: "unix:///run/containerd/containerd.sock", ContainerRuntimeEndpoint: "unix:///run/containerd/containerd.sock",
ImageMinimumGCAge: metav1.Duration{Duration: 60 * time.Second}, ImageMinimumGCAge: metav1.Duration{Duration: 60 * time.Second},
ImageGCHighThresholdPercent: ptr.To[int32](1), ImageGCHighThresholdPercent: ptr.To[int32](1),
ImageGCLowThresholdPercent: ptr.To[int32](1), ImageGCLowThresholdPercent: ptr.To[int32](1),
VolumeStatsAggPeriod: metav1.Duration{Duration: 60 * time.Second}, PreloadedImagesVerificationAllowlist: []string{"test.test/repo/image"},
KubeletCgroups: "kubelet-cgroup", VolumeStatsAggPeriod: metav1.Duration{Duration: 60 * time.Second},
SystemCgroups: "system-cgroup", KubeletCgroups: "kubelet-cgroup",
CgroupRoot: "root-cgroup", SystemCgroups: "system-cgroup",
CgroupsPerQOS: ptr.To(true), CgroupRoot: "root-cgroup",
CgroupDriver: "systemd", CgroupsPerQOS: ptr.To(true),
CPUManagerPolicy: "cpu-manager-policy", CgroupDriver: "systemd",
CPUManagerPolicyOptions: map[string]string{"key": "value"}, CPUManagerPolicy: "cpu-manager-policy",
CPUManagerReconcilePeriod: metav1.Duration{Duration: 60 * time.Second}, CPUManagerPolicyOptions: map[string]string{"key": "value"},
MemoryManagerPolicy: v1beta1.StaticMemoryManagerPolicy, CPUManagerReconcilePeriod: metav1.Duration{Duration: 60 * time.Second},
TopologyManagerPolicy: v1beta1.RestrictedTopologyManagerPolicy, MemoryManagerPolicy: v1beta1.StaticMemoryManagerPolicy,
TopologyManagerScope: v1beta1.PodTopologyManagerScope, TopologyManagerPolicy: v1beta1.RestrictedTopologyManagerPolicy,
QOSReserved: map[string]string{"memory": "10%"}, TopologyManagerScope: v1beta1.PodTopologyManagerScope,
RuntimeRequestTimeout: metav1.Duration{Duration: 60 * time.Second}, QOSReserved: map[string]string{"memory": "10%"},
HairpinMode: v1beta1.HairpinVeth, RuntimeRequestTimeout: metav1.Duration{Duration: 60 * time.Second},
MaxPods: 1, HairpinMode: v1beta1.HairpinVeth,
PodCIDR: "192.168.1.0/24", MaxPods: 1,
PodPidsLimit: ptr.To[int64](1), PodCIDR: "192.168.1.0/24",
ResolverConfig: ptr.To("resolver-config"), PodPidsLimit: ptr.To[int64](1),
RunOnce: true, ResolverConfig: ptr.To("resolver-config"),
CPUCFSQuota: ptr.To(true), RunOnce: true,
CPUCFSQuotaPeriod: &metav1.Duration{Duration: 60 * time.Second}, CPUCFSQuota: ptr.To(true),
NodeStatusMaxImages: ptr.To[int32](1), CPUCFSQuotaPeriod: &metav1.Duration{Duration: 60 * time.Second},
MaxOpenFiles: 1, NodeStatusMaxImages: ptr.To[int32](1),
ContentType: "application/protobuf", MaxOpenFiles: 1,
KubeAPIQPS: ptr.To[int32](1), ContentType: "application/protobuf",
KubeAPIBurst: 1, KubeAPIQPS: ptr.To[int32](1),
SerializeImagePulls: ptr.To(true), KubeAPIBurst: 1,
MaxParallelImagePulls: ptr.To[int32](5), SerializeImagePulls: ptr.To(true),
MaxParallelImagePulls: ptr.To[int32](5),
EvictionHard: map[string]string{ EvictionHard: map[string]string{
"memory.available": "1Mi", "memory.available": "1Mi",
"nodefs.available": "1%", "nodefs.available": "1%",

View File

@ -32,6 +32,7 @@ import (
tracingapi "k8s.io/component-base/tracing/api/v1" tracingapi "k8s.io/component-base/tracing/api/v1"
"k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/features"
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
"k8s.io/kubernetes/pkg/kubelet/images"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types" kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
utilfs "k8s.io/kubernetes/pkg/util/filesystem" utilfs "k8s.io/kubernetes/pkg/util/filesystem"
utiltaints "k8s.io/kubernetes/pkg/util/taints" utiltaints "k8s.io/kubernetes/pkg/util/taints"
@ -286,6 +287,32 @@ func ValidateKubeletConfiguration(kc *kubeletconfig.KubeletConfiguration, featur
allErrors = append(allErrors, fmt.Errorf("invalid configuration: option %q specified for hairpinMode (--hairpin-mode). Valid options are %q, %q or %q", allErrors = append(allErrors, fmt.Errorf("invalid configuration: option %q specified for hairpinMode (--hairpin-mode). Valid options are %q, %q or %q",
kc.HairpinMode, kubeletconfig.HairpinNone, kubeletconfig.HairpinVeth, kubeletconfig.PromiscuousBridge)) kc.HairpinMode, kubeletconfig.HairpinNone, kubeletconfig.HairpinVeth, kubeletconfig.PromiscuousBridge))
} }
if localFeatureGate.Enabled(features.KubeletEnsureSecretPulledImages) {
switch kc.ImagePullCredentialsVerificationPolicy {
case string(kubeletconfig.NeverVerify),
string(kubeletconfig.NeverVerifyPreloadedImages),
string(kubeletconfig.NeverVerifyAllowlistedImages),
string(kubeletconfig.AlwaysVerify):
default:
allErrors = append(allErrors, fmt.Errorf("invalid configuration: option %q specified for imagePullCredentialsVerificationPolicy. Valid options are %q, %q, %q or %q",
kc.ImagePullCredentialsVerificationPolicy, kubeletconfig.NeverVerify, kubeletconfig.NeverVerifyPreloadedImages, kubeletconfig.NeverVerifyAllowlistedImages, kubeletconfig.AlwaysVerify))
}
if len(kc.PreloadedImagesVerificationAllowlist) > 0 && kc.ImagePullCredentialsVerificationPolicy != string(kubeletconfig.NeverVerifyAllowlistedImages) {
allErrors = append(allErrors, fmt.Errorf("invalid configuration: can't set `preloadedImagesVerificationAllowlist` if `imagePullCredentialsVertificationPolicy` is not \"NeverVerifyAllowlistedImages\""))
} else if err := images.ValidateAllowlistImagesPatterns(kc.PreloadedImagesVerificationAllowlist); err != nil {
allErrors = append(allErrors, fmt.Errorf("invalid configuration: invalid image pattern in `preloadedImagesVerificationAllowlist`: %w", err))
}
} else {
if len(kc.ImagePullCredentialsVerificationPolicy) > 0 {
allErrors = append(allErrors, fmt.Errorf("invalid configuration: `imagePullCredentialsVerificationPolicy` must not be set if KubeletEnsureSecretPulledImages feature gate is not enabled"))
}
if len(kc.PreloadedImagesVerificationAllowlist) > 0 {
allErrors = append(allErrors, fmt.Errorf("invalid configuration: `preloadedImagesVerificationAllowlist` must not be set if KubeletEnsureSecretPulledImages feature gate is not enabled"))
}
}
if kc.ReservedSystemCPUs != "" { if kc.ReservedSystemCPUs != "" {
// --reserved-cpus does not support --system-reserved-cgroup or --kube-reserved-cgroup // --reserved-cpus does not support --system-reserved-cgroup or --kube-reserved-cgroup
if kc.SystemReservedCgroup != "" || kc.KubeReservedCgroup != "" { if kc.SystemReservedCgroup != "" || kc.KubeReservedCgroup != "" {

View File

@ -728,6 +728,39 @@ func TestValidateKubeletConfiguration(t *testing.T) {
return conf return conf
}, },
errMsg: "logging.format: Invalid value: \"invalid\": Unsupported log format", errMsg: "logging.format: Invalid value: \"invalid\": Unsupported log format",
}, {
name: "invalid imagePullCredentialsVerificationPolicy configuration",
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
conf.FeatureGates = map[string]bool{"KubeletEnsureSecretPulledImages": true}
conf.ImagePullCredentialsVerificationPolicy = "invalid"
return conf
},
errMsg: `option "invalid" specified for imagePullCredentialsVerificationPolicy. Valid options are "NeverVerify", "NeverVerifyPreloadedImages", "NeverVerifyAllowlistedImages" or "AlwaysVerify"]`,
}, {
name: "invalid PreloadedImagesVerificationAllowlist configuration - featuregate enabled",
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
conf.FeatureGates = map[string]bool{"KubeletEnsureSecretPulledImages": true}
conf.ImagePullCredentialsVerificationPolicy = string(kubeletconfig.NeverVerify)
conf.PreloadedImagesVerificationAllowlist = []string{"test.test/repo"}
return conf
},
errMsg: "can't set `preloadedImagesVerificationAllowlist` if `imagePullCredentialsVertificationPolicy` is not \"NeverVerifyAllowlistedImages\"]",
}, {
name: "invalid PreloadedImagesVerificationAllowlist configuration - featuregate disabled",
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
conf.FeatureGates = map[string]bool{"KubeletEnsureSecretPulledImages": false}
conf.ImagePullCredentialsVerificationPolicy = string(kubeletconfig.NeverVerify)
return conf
},
errMsg: "invalid configuration: `imagePullCredentialsVerificationPolicy` must not be set if KubeletEnsureSecretPulledImages feature gate is not enabled",
}, {
name: "invalid PreloadedImagesVerificationAllowlist configuration",
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
conf.FeatureGates = map[string]bool{"KubeletEnsureSecretPulledImages": false}
conf.PreloadedImagesVerificationAllowlist = []string{"test.test/repo"}
return conf
},
errMsg: "invalid configuration: `preloadedImagesVerificationAllowlist` must not be set if KubeletEnsureSecretPulledImages feature gate is not enabled",
}, { }, {
name: "invalid FeatureGate", name: "invalid FeatureGate",
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {

View File

@ -83,6 +83,25 @@ const (
StaticMemoryManagerPolicy = "Static" StaticMemoryManagerPolicy = "Static"
) )
// ImagePullCredentialsVerificationPolicy is an enum for the policy that is enforced
// when pod is requesting an image that appears on the system
type ImagePullCredentialsVerificationPolicy string
const (
// NeverVerify will never require credential verification for images that
// already exist on the node
NeverVerify ImagePullCredentialsVerificationPolicy = "NeverVerify"
// NeverVerifyPreloadedImages does not require credential verification for images
// pulled outside the kubelet process
NeverVerifyPreloadedImages ImagePullCredentialsVerificationPolicy = "NeverVerifyPreloadedImages"
// NeverVerifyAllowlistedImages does not require credential verification for
// a list of images that were pulled outside the kubelet process
NeverVerifyAllowlistedImages ImagePullCredentialsVerificationPolicy = "NeverVerifyAllowlistedImages"
// AlwaysVerify requires credential verification for accessing any image on the
// node irregardless how it was pulled
AlwaysVerify ImagePullCredentialsVerificationPolicy = "AlwaysVerify"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// KubeletConfiguration contains the configuration for the Kubelet // KubeletConfiguration contains the configuration for the Kubelet
@ -210,6 +229,28 @@ type KubeletConfiguration struct {
// Default: 10 // Default: 10
// +optional // +optional
RegistryBurst int32 `json:"registryBurst,omitempty"` RegistryBurst int32 `json:"registryBurst,omitempty"`
// imagePullCredentialsVerificationPolicy determines how credentials should be
// verified when pod requests an image that is already present on the node:
// - NeverVerify
// - anyone on a node can use any image present on the node
// - NeverVerifyPreloadedImages
// - images that were pulled to the node by something else than the kubelet
// can be used without reverifying pull credentials
// - NeverVerifyAllowlistedImages
// - like "NeverVerifyPreloadedImages" but only node images from
// `preloadedImagesVerificationAllowlist` don't require reverification
// - AlwaysVerify
// - all images require credential reverification
// +optional
ImagePullCredentialsVerificationPolicy ImagePullCredentialsVerificationPolicy `json:"imagePullCredentialsVerificationPolicy,omitempty"`
// preloadedImagesVerificationAllowlist specifies a list of images that are
// exempted from credential reverification for the "NeverVerifyAllowlistedImages"
// `imagePullCredentialsVerificationPolicy`.
// The list accepts a full path segment wildcard suffix "/*".
// Only use image specs without an image tag or digest.
// +optional
// +listType=set
PreloadedImagesVerificationAllowlist []string `json:"preloadedImagesVerificationAllowlist,omitempty"`
// eventRecordQPS is the maximum event creations per second. If 0, there // eventRecordQPS is the maximum event creations per second. If 0, there
// is no limit enforced. The value cannot be a negative number. // is no limit enforced. The value cannot be a negative number.
// Default: 50 // Default: 50