mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
Merge pull request #115220 from ruiwen-zhao/limit
Add MaxParallelImagePulls support
This commit is contained in:
commit
af9f7a4d90
7
pkg/generated/openapi/zz_generated.openapi.go
generated
7
pkg/generated/openapi/zz_generated.openapi.go
generated
@ -57699,6 +57699,13 @@ func schema_k8sio_kubelet_config_v1beta1_KubeletConfiguration(ref common.Referen
|
|||||||
Format: "",
|
Format: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"maxParallelImagePulls": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "MaxParallelImagePulls sets the maximum number of image pulls in parallel. This field cannot be set if SerializeImagePulls is true. Setting it to nil means no limit. Default: nil",
|
||||||
|
Type: []string{"integer"},
|
||||||
|
Format: "int32",
|
||||||
|
},
|
||||||
|
},
|
||||||
"evictionHard": {
|
"evictionHard": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Description: "evictionHard is a map of signal names to quantities that defines hard eviction thresholds. For example: `{\"memory.available\": \"300Mi\"}`. To explicitly disable, pass a 0% or 100% threshold on an arbitrary resource. Default:\n memory.available: \"100Mi\"\n nodefs.available: \"10%\"\n nodefs.inodesFree: \"5%\"\n imagefs.available: \"15%\"",
|
Description: "evictionHard is a map of signal names to quantities that defines hard eviction thresholds. For example: `{\"memory.available\": \"300Mi\"}`. To explicitly disable, pass a 0% or 100% threshold on an arbitrary resource. Default:\n memory.available: \"100Mi\"\n nodefs.available: \"10%\"\n nodefs.inodesFree: \"5%\"\n imagefs.available: \"15%\"",
|
||||||
|
@ -266,6 +266,7 @@ var (
|
|||||||
"RunOnce",
|
"RunOnce",
|
||||||
"SeccompDefault",
|
"SeccompDefault",
|
||||||
"SerializeImagePulls",
|
"SerializeImagePulls",
|
||||||
|
"MaxParallelImagePulls",
|
||||||
"ShowHiddenMetricsForVersion",
|
"ShowHiddenMetricsForVersion",
|
||||||
"ShutdownGracePeriodByPodPriority[*].Priority",
|
"ShutdownGracePeriodByPodPriority[*].Priority",
|
||||||
"ShutdownGracePeriodByPodPriority[*].ShutdownGracePeriodSeconds",
|
"ShutdownGracePeriodByPodPriority[*].ShutdownGracePeriodSeconds",
|
||||||
|
@ -292,6 +292,8 @@ type KubeletConfiguration struct {
|
|||||||
KubeAPIBurst int32
|
KubeAPIBurst int32
|
||||||
// serializeImagePulls when enabled, tells the Kubelet to pull images one at a time.
|
// serializeImagePulls when enabled, tells the Kubelet to pull images one at a time.
|
||||||
SerializeImagePulls bool
|
SerializeImagePulls bool
|
||||||
|
// MaxParallelImagePulls sets the maximum number of image pulls in parallel.
|
||||||
|
MaxParallelImagePulls *int32
|
||||||
// Map of signal names to quantities that defines hard eviction thresholds. For example: {"memory.available": "300Mi"}.
|
// Map of signal names to quantities that defines hard eviction thresholds. For example: {"memory.available": "300Mi"}.
|
||||||
// Some default signals are Linux only: nodefs.inodesFree
|
// Some default signals are Linux only: nodefs.inodesFree
|
||||||
EvictionHard map[string]string
|
EvictionHard map[string]string
|
||||||
|
@ -206,7 +206,14 @@ func SetDefaults_KubeletConfiguration(obj *kubeletconfigv1beta1.KubeletConfigura
|
|||||||
obj.KubeAPIBurst = 10
|
obj.KubeAPIBurst = 10
|
||||||
}
|
}
|
||||||
if obj.SerializeImagePulls == nil {
|
if obj.SerializeImagePulls == nil {
|
||||||
obj.SerializeImagePulls = utilpointer.Bool(true)
|
// SerializeImagePulls is default to true when MaxParallelImagePulls
|
||||||
|
// is not set, and false when MaxParallelImagePulls is set.
|
||||||
|
// This is to save users from having to set both configs.
|
||||||
|
if obj.MaxParallelImagePulls == nil || *obj.MaxParallelImagePulls < 2 {
|
||||||
|
obj.SerializeImagePulls = utilpointer.Bool(true)
|
||||||
|
} else {
|
||||||
|
obj.SerializeImagePulls = utilpointer.Bool(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if obj.EvictionPressureTransitionPeriod == zeroDuration {
|
if obj.EvictionPressureTransitionPeriod == zeroDuration {
|
||||||
obj.EvictionPressureTransitionPeriod = metav1.Duration{Duration: 5 * time.Minute}
|
obj.EvictionPressureTransitionPeriod = metav1.Duration{Duration: 5 * time.Minute}
|
||||||
|
@ -100,6 +100,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
|
|||||||
KubeAPIQPS: utilpointer.Int32(5),
|
KubeAPIQPS: utilpointer.Int32(5),
|
||||||
KubeAPIBurst: 10,
|
KubeAPIBurst: 10,
|
||||||
SerializeImagePulls: utilpointer.Bool(true),
|
SerializeImagePulls: utilpointer.Bool(true),
|
||||||
|
MaxParallelImagePulls: nil,
|
||||||
EvictionHard: nil,
|
EvictionHard: nil,
|
||||||
EvictionPressureTransitionPeriod: metav1.Duration{Duration: 5 * time.Minute},
|
EvictionPressureTransitionPeriod: metav1.Duration{Duration: 5 * time.Minute},
|
||||||
EnableControllerAttachDetach: utilpointer.Bool(true),
|
EnableControllerAttachDetach: utilpointer.Bool(true),
|
||||||
@ -206,6 +207,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
|
|||||||
KubeAPIQPS: utilpointer.Int32(0),
|
KubeAPIQPS: utilpointer.Int32(0),
|
||||||
KubeAPIBurst: 0,
|
KubeAPIBurst: 0,
|
||||||
SerializeImagePulls: utilpointer.Bool(false),
|
SerializeImagePulls: utilpointer.Bool(false),
|
||||||
|
MaxParallelImagePulls: nil,
|
||||||
EvictionHard: map[string]string{},
|
EvictionHard: map[string]string{},
|
||||||
EvictionSoft: map[string]string{},
|
EvictionSoft: map[string]string{},
|
||||||
EvictionSoftGracePeriod: map[string]string{},
|
EvictionSoftGracePeriod: map[string]string{},
|
||||||
@ -314,6 +316,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
|
|||||||
KubeAPIQPS: utilpointer.Int32(0),
|
KubeAPIQPS: utilpointer.Int32(0),
|
||||||
KubeAPIBurst: 10,
|
KubeAPIBurst: 10,
|
||||||
SerializeImagePulls: utilpointer.Bool(false),
|
SerializeImagePulls: utilpointer.Bool(false),
|
||||||
|
MaxParallelImagePulls: nil,
|
||||||
EvictionHard: map[string]string{},
|
EvictionHard: map[string]string{},
|
||||||
EvictionSoft: map[string]string{},
|
EvictionSoft: map[string]string{},
|
||||||
EvictionSoftGracePeriod: map[string]string{},
|
EvictionSoftGracePeriod: map[string]string{},
|
||||||
@ -429,6 +432,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
|
|||||||
KubeAPIQPS: utilpointer.Int32(1),
|
KubeAPIQPS: utilpointer.Int32(1),
|
||||||
KubeAPIBurst: 1,
|
KubeAPIBurst: 1,
|
||||||
SerializeImagePulls: utilpointer.Bool(true),
|
SerializeImagePulls: utilpointer.Bool(true),
|
||||||
|
MaxParallelImagePulls: utilpointer.Int32(5),
|
||||||
EvictionHard: map[string]string{
|
EvictionHard: map[string]string{
|
||||||
"memory.available": "1Mi",
|
"memory.available": "1Mi",
|
||||||
"nodefs.available": "1%",
|
"nodefs.available": "1%",
|
||||||
@ -574,6 +578,7 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
|
|||||||
KubeAPIQPS: utilpointer.Int32(1),
|
KubeAPIQPS: utilpointer.Int32(1),
|
||||||
KubeAPIBurst: 1,
|
KubeAPIBurst: 1,
|
||||||
SerializeImagePulls: utilpointer.Bool(true),
|
SerializeImagePulls: utilpointer.Bool(true),
|
||||||
|
MaxParallelImagePulls: utilpointer.Int32Ptr(5),
|
||||||
EvictionHard: map[string]string{
|
EvictionHard: map[string]string{
|
||||||
"memory.available": "1Mi",
|
"memory.available": "1Mi",
|
||||||
"nodefs.available": "1%",
|
"nodefs.available": "1%",
|
||||||
@ -704,6 +709,185 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
|
|||||||
KubeAPIQPS: utilpointer.Int32(5),
|
KubeAPIQPS: utilpointer.Int32(5),
|
||||||
KubeAPIBurst: 10,
|
KubeAPIBurst: 10,
|
||||||
SerializeImagePulls: utilpointer.Bool(true),
|
SerializeImagePulls: utilpointer.Bool(true),
|
||||||
|
MaxParallelImagePulls: nil,
|
||||||
|
EvictionHard: nil,
|
||||||
|
EvictionPressureTransitionPeriod: metav1.Duration{Duration: 5 * time.Minute},
|
||||||
|
EnableControllerAttachDetach: utilpointer.Bool(true),
|
||||||
|
MakeIPTablesUtilChains: utilpointer.Bool(true),
|
||||||
|
IPTablesMasqueradeBit: utilpointer.Int32Ptr(DefaultIPTablesMasqueradeBit),
|
||||||
|
IPTablesDropBit: utilpointer.Int32Ptr(DefaultIPTablesDropBit),
|
||||||
|
FailSwapOn: utilpointer.Bool(true),
|
||||||
|
ContainerLogMaxSize: "10Mi",
|
||||||
|
ContainerLogMaxFiles: utilpointer.Int32Ptr(5),
|
||||||
|
ConfigMapAndSecretChangeDetectionStrategy: v1beta1.WatchChangeDetectionStrategy,
|
||||||
|
EnforceNodeAllocatable: DefaultNodeAllocatableEnforcement,
|
||||||
|
VolumePluginDir: DefaultVolumePluginDir,
|
||||||
|
Logging: logsapi.LoggingConfiguration{
|
||||||
|
Format: "text",
|
||||||
|
FlushFrequency: 5 * time.Second,
|
||||||
|
},
|
||||||
|
EnableSystemLogHandler: utilpointer.Bool(true),
|
||||||
|
EnableProfilingHandler: utilpointer.Bool(true),
|
||||||
|
EnableDebugFlagsHandler: utilpointer.Bool(true),
|
||||||
|
SeccompDefault: utilpointer.Bool(false),
|
||||||
|
MemoryThrottlingFactor: utilpointer.Float64Ptr(DefaultMemoryThrottlingFactor),
|
||||||
|
RegisterNode: utilpointer.Bool(true),
|
||||||
|
LocalStorageCapacityIsolation: utilpointer.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SerializeImagePull defaults to false when MaxParallelImagePulls is larger than 1",
|
||||||
|
&v1beta1.KubeletConfiguration{
|
||||||
|
MaxParallelImagePulls: utilpointer.Int32(5),
|
||||||
|
},
|
||||||
|
&v1beta1.KubeletConfiguration{
|
||||||
|
EnableServer: utilpointer.Bool(true),
|
||||||
|
SyncFrequency: metav1.Duration{Duration: 1 * time.Minute},
|
||||||
|
FileCheckFrequency: metav1.Duration{Duration: 20 * time.Second},
|
||||||
|
HTTPCheckFrequency: metav1.Duration{Duration: 20 * time.Second},
|
||||||
|
Address: "0.0.0.0",
|
||||||
|
Port: ports.KubeletPort,
|
||||||
|
Authentication: v1beta1.KubeletAuthentication{
|
||||||
|
Anonymous: v1beta1.KubeletAnonymousAuthentication{Enabled: utilpointer.Bool(false)},
|
||||||
|
Webhook: v1beta1.KubeletWebhookAuthentication{
|
||||||
|
Enabled: utilpointer.Bool(true),
|
||||||
|
CacheTTL: metav1.Duration{Duration: 2 * time.Minute},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Authorization: v1beta1.KubeletAuthorization{
|
||||||
|
Mode: v1beta1.KubeletAuthorizationModeWebhook,
|
||||||
|
Webhook: v1beta1.KubeletWebhookAuthorization{
|
||||||
|
CacheAuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute},
|
||||||
|
CacheUnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RegistryPullQPS: utilpointer.Int32Ptr(5),
|
||||||
|
RegistryBurst: 10,
|
||||||
|
EventRecordQPS: utilpointer.Int32Ptr(5),
|
||||||
|
EventBurst: 10,
|
||||||
|
EnableDebuggingHandlers: utilpointer.Bool(true),
|
||||||
|
HealthzPort: utilpointer.Int32Ptr(10248),
|
||||||
|
HealthzBindAddress: "127.0.0.1",
|
||||||
|
OOMScoreAdj: utilpointer.Int32Ptr(int32(qos.KubeletOOMScoreAdj)),
|
||||||
|
StreamingConnectionIdleTimeout: metav1.Duration{Duration: 4 * time.Hour},
|
||||||
|
NodeStatusUpdateFrequency: metav1.Duration{Duration: 10 * time.Second},
|
||||||
|
NodeStatusReportFrequency: metav1.Duration{Duration: 5 * time.Minute},
|
||||||
|
NodeLeaseDurationSeconds: 40,
|
||||||
|
ContainerRuntimeEndpoint: "unix:///run/containerd/containerd.sock",
|
||||||
|
ImageMinimumGCAge: metav1.Duration{Duration: 2 * time.Minute},
|
||||||
|
ImageGCHighThresholdPercent: utilpointer.Int32Ptr(85),
|
||||||
|
ImageGCLowThresholdPercent: utilpointer.Int32Ptr(80),
|
||||||
|
VolumeStatsAggPeriod: metav1.Duration{Duration: time.Minute},
|
||||||
|
CgroupsPerQOS: utilpointer.Bool(true),
|
||||||
|
CgroupDriver: "cgroupfs",
|
||||||
|
CPUManagerPolicy: "none",
|
||||||
|
CPUManagerReconcilePeriod: metav1.Duration{Duration: 10 * time.Second},
|
||||||
|
MemoryManagerPolicy: v1beta1.NoneMemoryManagerPolicy,
|
||||||
|
TopologyManagerPolicy: v1beta1.NoneTopologyManagerPolicy,
|
||||||
|
TopologyManagerScope: v1beta1.ContainerTopologyManagerScope,
|
||||||
|
RuntimeRequestTimeout: metav1.Duration{Duration: 2 * time.Minute},
|
||||||
|
HairpinMode: v1beta1.PromiscuousBridge,
|
||||||
|
MaxPods: 110,
|
||||||
|
PodPidsLimit: utilpointer.Int64(-1),
|
||||||
|
ResolverConfig: utilpointer.String(kubetypes.ResolvConfDefault),
|
||||||
|
CPUCFSQuota: utilpointer.Bool(true),
|
||||||
|
CPUCFSQuotaPeriod: &metav1.Duration{Duration: 100 * time.Millisecond},
|
||||||
|
NodeStatusMaxImages: utilpointer.Int32Ptr(50),
|
||||||
|
MaxOpenFiles: 1000000,
|
||||||
|
ContentType: "application/vnd.kubernetes.protobuf",
|
||||||
|
KubeAPIQPS: utilpointer.Int32Ptr(5),
|
||||||
|
KubeAPIBurst: 10,
|
||||||
|
SerializeImagePulls: utilpointer.Bool(false),
|
||||||
|
MaxParallelImagePulls: utilpointer.Int32(5),
|
||||||
|
EvictionHard: nil,
|
||||||
|
EvictionPressureTransitionPeriod: metav1.Duration{Duration: 5 * time.Minute},
|
||||||
|
EnableControllerAttachDetach: utilpointer.Bool(true),
|
||||||
|
MakeIPTablesUtilChains: utilpointer.Bool(true),
|
||||||
|
IPTablesMasqueradeBit: utilpointer.Int32Ptr(DefaultIPTablesMasqueradeBit),
|
||||||
|
IPTablesDropBit: utilpointer.Int32Ptr(DefaultIPTablesDropBit),
|
||||||
|
FailSwapOn: utilpointer.Bool(true),
|
||||||
|
ContainerLogMaxSize: "10Mi",
|
||||||
|
ContainerLogMaxFiles: utilpointer.Int32Ptr(5),
|
||||||
|
ConfigMapAndSecretChangeDetectionStrategy: v1beta1.WatchChangeDetectionStrategy,
|
||||||
|
EnforceNodeAllocatable: DefaultNodeAllocatableEnforcement,
|
||||||
|
VolumePluginDir: DefaultVolumePluginDir,
|
||||||
|
Logging: logsapi.LoggingConfiguration{
|
||||||
|
Format: "text",
|
||||||
|
FlushFrequency: 5 * time.Second,
|
||||||
|
},
|
||||||
|
EnableSystemLogHandler: utilpointer.Bool(true),
|
||||||
|
EnableProfilingHandler: utilpointer.Bool(true),
|
||||||
|
EnableDebugFlagsHandler: utilpointer.Bool(true),
|
||||||
|
SeccompDefault: utilpointer.Bool(false),
|
||||||
|
MemoryThrottlingFactor: utilpointer.Float64Ptr(DefaultMemoryThrottlingFactor),
|
||||||
|
RegisterNode: utilpointer.Bool(true),
|
||||||
|
LocalStorageCapacityIsolation: utilpointer.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SerializeImagePull defaults to true when MaxParallelImagePulls is set to 1",
|
||||||
|
&v1beta1.KubeletConfiguration{
|
||||||
|
MaxParallelImagePulls: utilpointer.Int32(1),
|
||||||
|
},
|
||||||
|
&v1beta1.KubeletConfiguration{
|
||||||
|
EnableServer: utilpointer.Bool(true),
|
||||||
|
SyncFrequency: metav1.Duration{Duration: 1 * time.Minute},
|
||||||
|
FileCheckFrequency: metav1.Duration{Duration: 20 * time.Second},
|
||||||
|
HTTPCheckFrequency: metav1.Duration{Duration: 20 * time.Second},
|
||||||
|
Address: "0.0.0.0",
|
||||||
|
Port: ports.KubeletPort,
|
||||||
|
Authentication: v1beta1.KubeletAuthentication{
|
||||||
|
Anonymous: v1beta1.KubeletAnonymousAuthentication{Enabled: utilpointer.Bool(false)},
|
||||||
|
Webhook: v1beta1.KubeletWebhookAuthentication{
|
||||||
|
Enabled: utilpointer.Bool(true),
|
||||||
|
CacheTTL: metav1.Duration{Duration: 2 * time.Minute},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Authorization: v1beta1.KubeletAuthorization{
|
||||||
|
Mode: v1beta1.KubeletAuthorizationModeWebhook,
|
||||||
|
Webhook: v1beta1.KubeletWebhookAuthorization{
|
||||||
|
CacheAuthorizedTTL: metav1.Duration{Duration: 5 * time.Minute},
|
||||||
|
CacheUnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RegistryPullQPS: utilpointer.Int32Ptr(5),
|
||||||
|
RegistryBurst: 10,
|
||||||
|
EventRecordQPS: utilpointer.Int32Ptr(5),
|
||||||
|
EventBurst: 10,
|
||||||
|
EnableDebuggingHandlers: utilpointer.Bool(true),
|
||||||
|
HealthzPort: utilpointer.Int32Ptr(10248),
|
||||||
|
HealthzBindAddress: "127.0.0.1",
|
||||||
|
OOMScoreAdj: utilpointer.Int32Ptr(int32(qos.KubeletOOMScoreAdj)),
|
||||||
|
StreamingConnectionIdleTimeout: metav1.Duration{Duration: 4 * time.Hour},
|
||||||
|
NodeStatusUpdateFrequency: metav1.Duration{Duration: 10 * time.Second},
|
||||||
|
NodeStatusReportFrequency: metav1.Duration{Duration: 5 * time.Minute},
|
||||||
|
NodeLeaseDurationSeconds: 40,
|
||||||
|
ContainerRuntimeEndpoint: "unix:///run/containerd/containerd.sock",
|
||||||
|
ImageMinimumGCAge: metav1.Duration{Duration: 2 * time.Minute},
|
||||||
|
ImageGCHighThresholdPercent: utilpointer.Int32Ptr(85),
|
||||||
|
ImageGCLowThresholdPercent: utilpointer.Int32Ptr(80),
|
||||||
|
VolumeStatsAggPeriod: metav1.Duration{Duration: time.Minute},
|
||||||
|
CgroupsPerQOS: utilpointer.Bool(true),
|
||||||
|
CgroupDriver: "cgroupfs",
|
||||||
|
CPUManagerPolicy: "none",
|
||||||
|
CPUManagerReconcilePeriod: metav1.Duration{Duration: 10 * time.Second},
|
||||||
|
MemoryManagerPolicy: v1beta1.NoneMemoryManagerPolicy,
|
||||||
|
TopologyManagerPolicy: v1beta1.NoneTopologyManagerPolicy,
|
||||||
|
TopologyManagerScope: v1beta1.ContainerTopologyManagerScope,
|
||||||
|
RuntimeRequestTimeout: metav1.Duration{Duration: 2 * time.Minute},
|
||||||
|
HairpinMode: v1beta1.PromiscuousBridge,
|
||||||
|
MaxPods: 110,
|
||||||
|
PodPidsLimit: utilpointer.Int64(-1),
|
||||||
|
ResolverConfig: utilpointer.String(kubetypes.ResolvConfDefault),
|
||||||
|
CPUCFSQuota: utilpointer.Bool(true),
|
||||||
|
CPUCFSQuotaPeriod: &metav1.Duration{Duration: 100 * time.Millisecond},
|
||||||
|
NodeStatusMaxImages: utilpointer.Int32Ptr(50),
|
||||||
|
MaxOpenFiles: 1000000,
|
||||||
|
ContentType: "application/vnd.kubernetes.protobuf",
|
||||||
|
KubeAPIQPS: utilpointer.Int32Ptr(5),
|
||||||
|
KubeAPIBurst: 10,
|
||||||
|
SerializeImagePulls: utilpointer.Bool(true),
|
||||||
|
MaxParallelImagePulls: utilpointer.Int32(1),
|
||||||
EvictionHard: nil,
|
EvictionHard: nil,
|
||||||
EvictionPressureTransitionPeriod: metav1.Duration{Duration: 5 * time.Minute},
|
EvictionPressureTransitionPeriod: metav1.Duration{Duration: 5 * time.Minute},
|
||||||
EnableControllerAttachDetach: utilpointer.Bool(true),
|
EnableControllerAttachDetach: utilpointer.Bool(true),
|
||||||
|
@ -443,6 +443,7 @@ func autoConvert_v1beta1_KubeletConfiguration_To_config_KubeletConfiguration(in
|
|||||||
if err := v1.Convert_Pointer_bool_To_bool(&in.SerializeImagePulls, &out.SerializeImagePulls, s); err != nil {
|
if err := v1.Convert_Pointer_bool_To_bool(&in.SerializeImagePulls, &out.SerializeImagePulls, s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
out.MaxParallelImagePulls = (*int32)(unsafe.Pointer(in.MaxParallelImagePulls))
|
||||||
out.EvictionHard = *(*map[string]string)(unsafe.Pointer(&in.EvictionHard))
|
out.EvictionHard = *(*map[string]string)(unsafe.Pointer(&in.EvictionHard))
|
||||||
out.EvictionSoft = *(*map[string]string)(unsafe.Pointer(&in.EvictionSoft))
|
out.EvictionSoft = *(*map[string]string)(unsafe.Pointer(&in.EvictionSoft))
|
||||||
out.EvictionSoftGracePeriod = *(*map[string]string)(unsafe.Pointer(&in.EvictionSoftGracePeriod))
|
out.EvictionSoftGracePeriod = *(*map[string]string)(unsafe.Pointer(&in.EvictionSoftGracePeriod))
|
||||||
@ -626,6 +627,7 @@ func autoConvert_config_KubeletConfiguration_To_v1beta1_KubeletConfiguration(in
|
|||||||
if err := v1.Convert_bool_To_Pointer_bool(&in.SerializeImagePulls, &out.SerializeImagePulls, s); err != nil {
|
if err := v1.Convert_bool_To_Pointer_bool(&in.SerializeImagePulls, &out.SerializeImagePulls, s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
out.MaxParallelImagePulls = (*int32)(unsafe.Pointer(in.MaxParallelImagePulls))
|
||||||
out.EvictionHard = *(*map[string]string)(unsafe.Pointer(&in.EvictionHard))
|
out.EvictionHard = *(*map[string]string)(unsafe.Pointer(&in.EvictionHard))
|
||||||
out.EvictionSoft = *(*map[string]string)(unsafe.Pointer(&in.EvictionSoft))
|
out.EvictionSoft = *(*map[string]string)(unsafe.Pointer(&in.EvictionSoft))
|
||||||
out.EvictionSoftGracePeriod = *(*map[string]string)(unsafe.Pointer(&in.EvictionSoftGracePeriod))
|
out.EvictionSoftGracePeriod = *(*map[string]string)(unsafe.Pointer(&in.EvictionSoftGracePeriod))
|
||||||
|
@ -122,6 +122,12 @@ func ValidateKubeletConfiguration(kc *kubeletconfig.KubeletConfiguration, featur
|
|||||||
if kc.RegistryPullQPS < 0 {
|
if kc.RegistryPullQPS < 0 {
|
||||||
allErrors = append(allErrors, fmt.Errorf("invalid configuration: registryPullQPS (--registry-qps) %v must not be a negative number", kc.RegistryPullQPS))
|
allErrors = append(allErrors, fmt.Errorf("invalid configuration: registryPullQPS (--registry-qps) %v must not be a negative number", kc.RegistryPullQPS))
|
||||||
}
|
}
|
||||||
|
if kc.MaxParallelImagePulls != nil && *kc.MaxParallelImagePulls < 1 {
|
||||||
|
allErrors = append(allErrors, fmt.Errorf("invalid configuration: maxParallelImagePulls %v must be a positive number", *kc.MaxParallelImagePulls))
|
||||||
|
}
|
||||||
|
if kc.SerializeImagePulls && kc.MaxParallelImagePulls != nil && *kc.MaxParallelImagePulls > 1 {
|
||||||
|
allErrors = append(allErrors, fmt.Errorf("invalid configuration: maxParallelImagePulls cannot be larger than 1 unless SerializeImagePulls (--serialize-image-pulls) is set to false"))
|
||||||
|
}
|
||||||
if kc.ServerTLSBootstrap && !localFeatureGate.Enabled(features.RotateKubeletServerCertificate) {
|
if kc.ServerTLSBootstrap && !localFeatureGate.Enabled(features.RotateKubeletServerCertificate) {
|
||||||
allErrors = append(allErrors, fmt.Errorf("invalid configuration: serverTLSBootstrap %v requires feature gate RotateKubeletServerCertificate", kc.ServerTLSBootstrap))
|
allErrors = append(allErrors, fmt.Errorf("invalid configuration: serverTLSBootstrap %v requires feature gate RotateKubeletServerCertificate", kc.ServerTLSBootstrap))
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,7 @@ var (
|
|||||||
ReadOnlyPort: 0,
|
ReadOnlyPort: 0,
|
||||||
RegistryBurst: 10,
|
RegistryBurst: 10,
|
||||||
RegistryPullQPS: 5,
|
RegistryPullQPS: 5,
|
||||||
|
MaxParallelImagePulls: nil,
|
||||||
HairpinMode: kubeletconfig.PromiscuousBridge,
|
HairpinMode: kubeletconfig.PromiscuousBridge,
|
||||||
NodeLeaseDurationSeconds: 1,
|
NodeLeaseDurationSeconds: 1,
|
||||||
CPUCFSQuotaPeriod: metav1.Duration{Duration: 25 * time.Millisecond},
|
CPUCFSQuotaPeriod: metav1.Duration{Duration: 25 * time.Millisecond},
|
||||||
@ -298,6 +299,31 @@ func TestValidateKubeletConfiguration(t *testing.T) {
|
|||||||
},
|
},
|
||||||
errMsg: "invalid configuration: registryPullQPS (--registry-qps) -1 must not be a negative number",
|
errMsg: "invalid configuration: registryPullQPS (--registry-qps) -1 must not be a negative number",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "invalid MaxParallelImagePulls",
|
||||||
|
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
|
||||||
|
conf.MaxParallelImagePulls = utilpointer.Int32(0)
|
||||||
|
return conf
|
||||||
|
},
|
||||||
|
errMsg: "invalid configuration: maxParallelImagePulls 0 must be a positive number",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid MaxParallelImagePulls and SerializeImagePulls combination",
|
||||||
|
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
|
||||||
|
conf.MaxParallelImagePulls = utilpointer.Int32(3)
|
||||||
|
conf.SerializeImagePulls = true
|
||||||
|
return conf
|
||||||
|
},
|
||||||
|
errMsg: "invalid configuration: maxParallelImagePulls cannot be larger than 1 unless SerializeImagePulls (--serialize-image-pulls) is set to false",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid MaxParallelImagePulls and SerializeImagePulls combination",
|
||||||
|
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
|
||||||
|
conf.MaxParallelImagePulls = utilpointer.Int32(1)
|
||||||
|
conf.SerializeImagePulls = true
|
||||||
|
return conf
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "specify ServerTLSBootstrap without enabling RotateKubeletServerCertificate",
|
name: "specify ServerTLSBootstrap without enabling RotateKubeletServerCertificate",
|
||||||
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
|
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
|
||||||
|
5
pkg/kubelet/apis/config/zz_generated.deepcopy.go
generated
5
pkg/kubelet/apis/config/zz_generated.deepcopy.go
generated
@ -227,6 +227,11 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) {
|
|||||||
}
|
}
|
||||||
out.RuntimeRequestTimeout = in.RuntimeRequestTimeout
|
out.RuntimeRequestTimeout = in.RuntimeRequestTimeout
|
||||||
out.CPUCFSQuotaPeriod = in.CPUCFSQuotaPeriod
|
out.CPUCFSQuotaPeriod = in.CPUCFSQuotaPeriod
|
||||||
|
if in.MaxParallelImagePulls != nil {
|
||||||
|
in, out := &in.MaxParallelImagePulls, &out.MaxParallelImagePulls
|
||||||
|
*out = new(int32)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.EvictionHard != nil {
|
if in.EvictionHard != nil {
|
||||||
in, out := &in.EvictionHard, &out.EvictionHard
|
in, out := &in.EvictionHard, &out.EvictionHard
|
||||||
*out = make(map[string]string, len(*in))
|
*out = make(map[string]string, len(*in))
|
||||||
|
@ -58,7 +58,12 @@ type FakeRuntime struct {
|
|||||||
Err error
|
Err error
|
||||||
InspectErr error
|
InspectErr error
|
||||||
StatusErr error
|
StatusErr error
|
||||||
T *testing.T
|
// If BlockImagePulls is true, then all PullImage() calls will be blocked until
|
||||||
|
// UnblockImagePulls() is called. This is used to simulate image pull latency
|
||||||
|
// from container runtime.
|
||||||
|
BlockImagePulls bool
|
||||||
|
imagePullTokenBucket chan bool
|
||||||
|
T *testing.T
|
||||||
}
|
}
|
||||||
|
|
||||||
const FakeHost = "localhost:12345"
|
const FakeHost = "localhost:12345"
|
||||||
@ -129,6 +134,17 @@ func (f *FakeRuntime) ClearCalls() {
|
|||||||
f.Err = nil
|
f.Err = nil
|
||||||
f.InspectErr = nil
|
f.InspectErr = nil
|
||||||
f.StatusErr = nil
|
f.StatusErr = nil
|
||||||
|
f.BlockImagePulls = false
|
||||||
|
if f.imagePullTokenBucket != nil {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case f.imagePullTokenBucket <- true:
|
||||||
|
default:
|
||||||
|
f.imagePullTokenBucket = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePodCIDR fulfills the cri interface.
|
// UpdatePodCIDR fulfills the cri interface.
|
||||||
@ -151,6 +167,23 @@ func (f *FakeRuntime) AssertCalls(calls []string) bool {
|
|||||||
return f.assertList(calls, f.CalledFunctions)
|
return f.assertList(calls, f.CalledFunctions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssertCallCounts checks if a certain call is called for a certain of numbers
|
||||||
|
func (f *FakeRuntime) AssertCallCounts(funcName string, expectedCount int) bool {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
|
actualCount := 0
|
||||||
|
for _, c := range f.CalledFunctions {
|
||||||
|
if funcName == c {
|
||||||
|
actualCount += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if expectedCount != actualCount {
|
||||||
|
f.T.Errorf("AssertCallCounts: expected %s to be called %d times, but was actually called %d times.", funcName, expectedCount, actualCount)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (f *FakeRuntime) AssertStartedPods(pods []string) bool {
|
func (f *FakeRuntime) AssertStartedPods(pods []string) bool {
|
||||||
f.Lock()
|
f.Lock()
|
||||||
defer f.Unlock()
|
defer f.Unlock()
|
||||||
@ -302,10 +335,8 @@ func (f *FakeRuntime) GetContainerLogs(_ context.Context, pod *v1.Pod, container
|
|||||||
return f.Err
|
return f.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeRuntime) PullImage(_ context.Context, image kubecontainer.ImageSpec, pullSecrets []v1.Secret, podSandboxConfig *runtimeapi.PodSandboxConfig) (string, error) {
|
func (f *FakeRuntime) PullImage(ctx context.Context, image kubecontainer.ImageSpec, pullSecrets []v1.Secret, podSandboxConfig *runtimeapi.PodSandboxConfig) (string, error) {
|
||||||
f.Lock()
|
f.Lock()
|
||||||
defer f.Unlock()
|
|
||||||
|
|
||||||
f.CalledFunctions = append(f.CalledFunctions, "PullImage")
|
f.CalledFunctions = append(f.CalledFunctions, "PullImage")
|
||||||
if f.Err == nil {
|
if f.Err == nil {
|
||||||
i := kubecontainer.Image{
|
i := kubecontainer.Image{
|
||||||
@ -314,7 +345,35 @@ func (f *FakeRuntime) PullImage(_ context.Context, image kubecontainer.ImageSpec
|
|||||||
}
|
}
|
||||||
f.ImageList = append(f.ImageList, i)
|
f.ImageList = append(f.ImageList, i)
|
||||||
}
|
}
|
||||||
return image.Image, f.Err
|
|
||||||
|
if !f.BlockImagePulls {
|
||||||
|
f.Unlock()
|
||||||
|
return image.Image, f.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
retErr := f.Err
|
||||||
|
if f.imagePullTokenBucket == nil {
|
||||||
|
f.imagePullTokenBucket = make(chan bool, 1)
|
||||||
|
}
|
||||||
|
// Unlock before waiting for UnblockImagePulls calls, to avoid deadlock.
|
||||||
|
f.Unlock()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case <-f.imagePullTokenBucket:
|
||||||
|
}
|
||||||
|
return image.Image, retErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnblockImagePulls unblocks a certain number of image pulls, if BlockImagePulls is true.
|
||||||
|
func (f *FakeRuntime) UnblockImagePulls(count int) {
|
||||||
|
if f.imagePullTokenBucket != nil {
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
select {
|
||||||
|
case f.imagePullTokenBucket <- true:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeRuntime) GetImageRef(_ context.Context, image kubecontainer.ImageSpec) (string, error) {
|
func (f *FakeRuntime) GetImageRef(_ context.Context, image kubecontainer.ImageSpec) (string, error) {
|
||||||
|
@ -52,14 +52,14 @@ type imageManager struct {
|
|||||||
var _ ImageManager = &imageManager{}
|
var _ ImageManager = &imageManager{}
|
||||||
|
|
||||||
// NewImageManager instantiates a new ImageManager object.
|
// NewImageManager instantiates a new ImageManager object.
|
||||||
func NewImageManager(recorder record.EventRecorder, imageService kubecontainer.ImageService, imageBackOff *flowcontrol.Backoff, serialized bool, qps float32, burst int, podPullingTimeRecorder ImagePodPullingTimeRecorder) ImageManager {
|
func NewImageManager(recorder record.EventRecorder, imageService kubecontainer.ImageService, imageBackOff *flowcontrol.Backoff, serialized bool, maxParallelImagePulls *int32, qps float32, burst int, podPullingTimeRecorder ImagePodPullingTimeRecorder) ImageManager {
|
||||||
imageService = throttleImagePulling(imageService, qps, burst)
|
imageService = throttleImagePulling(imageService, qps, burst)
|
||||||
|
|
||||||
var puller imagePuller
|
var puller imagePuller
|
||||||
if serialized {
|
if serialized {
|
||||||
puller = newSerialImagePuller(imageService)
|
puller = newSerialImagePuller(imageService)
|
||||||
} else {
|
} else {
|
||||||
puller = newParallelImagePuller(imageService)
|
puller = newParallelImagePuller(imageService, maxParallelImagePulls)
|
||||||
}
|
}
|
||||||
return &imageManager{
|
return &imageManager{
|
||||||
recorder: recorder,
|
recorder: recorder,
|
||||||
|
@ -19,6 +19,7 @@ package images
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ import (
|
|||||||
. "k8s.io/kubernetes/pkg/kubelet/container"
|
. "k8s.io/kubernetes/pkg/kubelet/container"
|
||||||
ctest "k8s.io/kubernetes/pkg/kubelet/container/testing"
|
ctest "k8s.io/kubernetes/pkg/kubelet/container/testing"
|
||||||
testingclock "k8s.io/utils/clock/testing"
|
testingclock "k8s.io/utils/clock/testing"
|
||||||
|
utilpointer "k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pullerExpects struct {
|
type pullerExpects struct {
|
||||||
@ -166,7 +168,7 @@ func (m *mockPodPullingTimeRecorder) RecordImageStartedPulling(podUID types.UID)
|
|||||||
|
|
||||||
func (m *mockPodPullingTimeRecorder) RecordImageFinishedPulling(podUID types.UID) {}
|
func (m *mockPodPullingTimeRecorder) RecordImageFinishedPulling(podUID types.UID) {}
|
||||||
|
|
||||||
func pullerTestEnv(c pullerTestCase, serialized bool) (puller ImageManager, fakeClock *testingclock.FakeClock, fakeRuntime *ctest.FakeRuntime, container *v1.Container) {
|
func pullerTestEnv(c pullerTestCase, serialized bool, maxParallelImagePulls *int32) (puller ImageManager, fakeClock *testingclock.FakeClock, fakeRuntime *ctest.FakeRuntime, container *v1.Container) {
|
||||||
container = &v1.Container{
|
container = &v1.Container{
|
||||||
Name: "container_name",
|
Name: "container_name",
|
||||||
Image: c.containerImage,
|
Image: c.containerImage,
|
||||||
@ -184,7 +186,7 @@ func pullerTestEnv(c pullerTestCase, serialized bool) (puller ImageManager, fake
|
|||||||
fakeRuntime.Err = c.pullerErr
|
fakeRuntime.Err = c.pullerErr
|
||||||
fakeRuntime.InspectErr = c.inspectErr
|
fakeRuntime.InspectErr = c.inspectErr
|
||||||
|
|
||||||
puller = NewImageManager(fakeRecorder, fakeRuntime, backOff, serialized, c.qps, c.burst, &mockPodPullingTimeRecorder{})
|
puller = NewImageManager(fakeRecorder, fakeRuntime, backOff, serialized, maxParallelImagePulls, c.qps, c.burst, &mockPodPullingTimeRecorder{})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +203,7 @@ func TestParallelPuller(t *testing.T) {
|
|||||||
|
|
||||||
useSerializedEnv := false
|
useSerializedEnv := false
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
puller, fakeClock, fakeRuntime, container := pullerTestEnv(c, useSerializedEnv)
|
puller, fakeClock, fakeRuntime, container := pullerTestEnv(c, useSerializedEnv, nil)
|
||||||
|
|
||||||
t.Run(c.testName, func(t *testing.T) {
|
t.Run(c.testName, func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@ -229,7 +231,7 @@ func TestSerializedPuller(t *testing.T) {
|
|||||||
|
|
||||||
useSerializedEnv := true
|
useSerializedEnv := true
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
puller, fakeClock, fakeRuntime, container := pullerTestEnv(c, useSerializedEnv)
|
puller, fakeClock, fakeRuntime, container := pullerTestEnv(c, useSerializedEnv, nil)
|
||||||
|
|
||||||
t.Run(c.testName, func(t *testing.T) {
|
t.Run(c.testName, func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@ -287,7 +289,7 @@ func TestPullAndListImageWithPodAnnotations(t *testing.T) {
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
useSerializedEnv := true
|
useSerializedEnv := true
|
||||||
puller, fakeClock, fakeRuntime, container := pullerTestEnv(c, useSerializedEnv)
|
puller, fakeClock, fakeRuntime, container := pullerTestEnv(c, useSerializedEnv, nil)
|
||||||
fakeRuntime.CalledFunctions = nil
|
fakeRuntime.CalledFunctions = nil
|
||||||
fakeRuntime.ImageList = []Image{}
|
fakeRuntime.ImageList = []Image{}
|
||||||
fakeClock.Step(time.Second)
|
fakeClock.Step(time.Second)
|
||||||
@ -312,3 +314,69 @@ func TestPullAndListImageWithPodAnnotations(t *testing.T) {
|
|||||||
assert.Equal(t, expectedAnnotations, image.Spec.Annotations, "image spec annotations")
|
assert.Equal(t, expectedAnnotations, image.Spec.Annotations, "image spec annotations")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMaxParallelImagePullsLimit(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
pod := &v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test_pod",
|
||||||
|
Namespace: "test-ns",
|
||||||
|
UID: "bar",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
}}
|
||||||
|
|
||||||
|
testCase := &pullerTestCase{
|
||||||
|
containerImage: "present_image",
|
||||||
|
testName: "image present, pull ",
|
||||||
|
policy: v1.PullAlways,
|
||||||
|
inspectErr: nil,
|
||||||
|
pullerErr: nil,
|
||||||
|
qps: 0.0,
|
||||||
|
burst: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
useSerializedEnv := false
|
||||||
|
maxParallelImagePulls := 5
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
puller, fakeClock, fakeRuntime, container := pullerTestEnv(*testCase, useSerializedEnv, utilpointer.Int32Ptr(int32(maxParallelImagePulls)))
|
||||||
|
fakeRuntime.BlockImagePulls = true
|
||||||
|
fakeRuntime.CalledFunctions = nil
|
||||||
|
fakeRuntime.T = t
|
||||||
|
fakeClock.Step(time.Second)
|
||||||
|
|
||||||
|
// First 5 EnsureImageExists should result in runtime calls
|
||||||
|
for i := 0; i < maxParallelImagePulls; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
_, _, err := puller.EnsureImageExists(ctx, pod, container, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
fakeRuntime.AssertCallCounts("PullImage", 5)
|
||||||
|
|
||||||
|
// Next two EnsureImageExists should be blocked because maxParallelImagePulls is hit
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
_, _, err := puller.EnsureImageExists(ctx, pod, container, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
fakeRuntime.AssertCallCounts("PullImage", 5)
|
||||||
|
|
||||||
|
// Unblock two image pulls from runtime, and two EnsureImageExists can go through
|
||||||
|
fakeRuntime.UnblockImagePulls(2)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
fakeRuntime.AssertCallCounts("PullImage", 7)
|
||||||
|
|
||||||
|
// Unblock the remaining 5 image pulls from runtime, and all EnsureImageExists can go through
|
||||||
|
fakeRuntime.UnblockImagePulls(5)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
fakeRuntime.AssertCallCounts("PullImage", 7)
|
||||||
|
}
|
||||||
|
@ -40,14 +40,22 @@ var _, _ imagePuller = ¶llelImagePuller{}, &serialImagePuller{}
|
|||||||
|
|
||||||
type parallelImagePuller struct {
|
type parallelImagePuller struct {
|
||||||
imageService kubecontainer.ImageService
|
imageService kubecontainer.ImageService
|
||||||
|
tokens chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newParallelImagePuller(imageService kubecontainer.ImageService) imagePuller {
|
func newParallelImagePuller(imageService kubecontainer.ImageService, maxParallelImagePulls *int32) imagePuller {
|
||||||
return ¶llelImagePuller{imageService}
|
if maxParallelImagePulls == nil || *maxParallelImagePulls < 1 {
|
||||||
|
return ¶llelImagePuller{imageService, nil}
|
||||||
|
}
|
||||||
|
return ¶llelImagePuller{imageService, make(chan struct{}, *maxParallelImagePulls)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pip *parallelImagePuller) pullImage(ctx context.Context, spec kubecontainer.ImageSpec, pullSecrets []v1.Secret, pullChan chan<- pullResult, podSandboxConfig *runtimeapi.PodSandboxConfig) {
|
func (pip *parallelImagePuller) pullImage(ctx context.Context, spec kubecontainer.ImageSpec, pullSecrets []v1.Secret, pullChan chan<- pullResult, podSandboxConfig *runtimeapi.PodSandboxConfig) {
|
||||||
go func() {
|
go func() {
|
||||||
|
if pip.tokens != nil {
|
||||||
|
pip.tokens <- struct{}{}
|
||||||
|
defer func() { <-pip.tokens }()
|
||||||
|
}
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
imageRef, err := pip.imageService.PullImage(ctx, spec, pullSecrets, podSandboxConfig)
|
imageRef, err := pip.imageService.PullImage(ctx, spec, pullSecrets, podSandboxConfig)
|
||||||
pullChan <- pullResult{
|
pullChan <- pullResult{
|
||||||
|
@ -660,6 +660,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
|
|||||||
insecureContainerLifecycleHTTPClient,
|
insecureContainerLifecycleHTTPClient,
|
||||||
imageBackOff,
|
imageBackOff,
|
||||||
kubeCfg.SerializeImagePulls,
|
kubeCfg.SerializeImagePulls,
|
||||||
|
kubeCfg.MaxParallelImagePulls,
|
||||||
float32(kubeCfg.RegistryPullQPS),
|
float32(kubeCfg.RegistryPullQPS),
|
||||||
int(kubeCfg.RegistryBurst),
|
int(kubeCfg.RegistryBurst),
|
||||||
imageCredentialProviderConfigFile,
|
imageCredentialProviderConfigFile,
|
||||||
|
@ -37,6 +37,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/logs"
|
"k8s.io/kubernetes/pkg/kubelet/logs"
|
||||||
proberesults "k8s.io/kubernetes/pkg/kubelet/prober/results"
|
proberesults "k8s.io/kubernetes/pkg/kubelet/prober/results"
|
||||||
|
utilpointer "k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -129,7 +130,8 @@ func newFakeKubeRuntimeManager(runtimeService internalapi.RuntimeService, imageS
|
|||||||
kubeRuntimeManager,
|
kubeRuntimeManager,
|
||||||
flowcontrol.NewBackOff(time.Second, 300*time.Second),
|
flowcontrol.NewBackOff(time.Second, 300*time.Second),
|
||||||
false,
|
false,
|
||||||
0, // Disable image pull throttling by setting QPS to 0,
|
utilpointer.Int32Ptr(0), // No limit on max parallel image pulls,
|
||||||
|
0, // Disable image pull throttling by setting QPS to 0,
|
||||||
0,
|
0,
|
||||||
&fakePodPullingTimeRecorder{},
|
&fakePodPullingTimeRecorder{},
|
||||||
)
|
)
|
||||||
|
@ -188,6 +188,7 @@ func NewKubeGenericRuntimeManager(
|
|||||||
insecureContainerLifecycleHTTPClient types.HTTPDoer,
|
insecureContainerLifecycleHTTPClient types.HTTPDoer,
|
||||||
imageBackOff *flowcontrol.Backoff,
|
imageBackOff *flowcontrol.Backoff,
|
||||||
serializeImagePulls bool,
|
serializeImagePulls bool,
|
||||||
|
maxParallelImagePulls *int32,
|
||||||
imagePullQPS float32,
|
imagePullQPS float32,
|
||||||
imagePullBurst int,
|
imagePullBurst int,
|
||||||
imageCredentialProviderConfigFile string,
|
imageCredentialProviderConfigFile string,
|
||||||
@ -275,6 +276,7 @@ func NewKubeGenericRuntimeManager(
|
|||||||
kubeRuntimeManager,
|
kubeRuntimeManager,
|
||||||
imageBackOff,
|
imageBackOff,
|
||||||
serializeImagePulls,
|
serializeImagePulls,
|
||||||
|
maxParallelImagePulls,
|
||||||
imagePullQPS,
|
imagePullQPS,
|
||||||
imagePullBurst,
|
imagePullBurst,
|
||||||
podPullingTimeRecorder)
|
podPullingTimeRecorder)
|
||||||
|
@ -482,6 +482,12 @@ type KubeletConfiguration struct {
|
|||||||
// Default: true
|
// Default: true
|
||||||
// +optional
|
// +optional
|
||||||
SerializeImagePulls *bool `json:"serializeImagePulls,omitempty"`
|
SerializeImagePulls *bool `json:"serializeImagePulls,omitempty"`
|
||||||
|
// MaxParallelImagePulls sets the maximum number of image pulls in parallel.
|
||||||
|
// This field cannot be set if SerializeImagePulls is true.
|
||||||
|
// Setting it to nil means no limit.
|
||||||
|
// Default: nil
|
||||||
|
// +optional
|
||||||
|
MaxParallelImagePulls *int32 `json:"maxParallelImagePulls,omitempty"`
|
||||||
// evictionHard is a map of signal names to quantities that defines hard eviction
|
// evictionHard is a map of signal names to quantities that defines hard eviction
|
||||||
// thresholds. For example: `{"memory.available": "300Mi"}`.
|
// thresholds. For example: `{"memory.available": "300Mi"}`.
|
||||||
// To explicitly disable, pass a 0% or 100% threshold on an arbitrary resource.
|
// To explicitly disable, pass a 0% or 100% threshold on an arbitrary resource.
|
||||||
|
@ -311,6 +311,11 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) {
|
|||||||
*out = new(bool)
|
*out = new(bool)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.MaxParallelImagePulls != nil {
|
||||||
|
in, out := &in.MaxParallelImagePulls, &out.MaxParallelImagePulls
|
||||||
|
*out = new(int32)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.EvictionHard != nil {
|
if in.EvictionHard != nil {
|
||||||
in, out := &in.EvictionHard, &out.EvictionHard
|
in, out := &in.EvictionHard, &out.EvictionHard
|
||||||
*out = make(map[string]string, len(*in))
|
*out = make(map[string]string, len(*in))
|
||||||
|
Loading…
Reference in New Issue
Block a user