From 0e112c0d8febd75d531eb52f81b32fffccaade15 Mon Sep 17 00:00:00 2001 From: Mitali Paygude Date: Fri, 24 May 2024 15:53:04 -0700 Subject: [PATCH] Kubelet configuration customization Docker cp and wn (#8187) --- .../anywhere.eks.amazonaws.com_clusters.yaml | 12 +- config/manifest/eksa-components.yaml | 12 +- go.mod | 1 + go.sum | 2 + pkg/api/v1alpha1/cluster.go | 49 +++++++ pkg/api/v1alpha1/cluster_test.go | 133 ++++++++++++++++++ pkg/api/v1alpha1/cluster_types.go | 9 +- pkg/api/v1alpha1/zz_generated.deepcopy.go | 8 ++ pkg/providers/docker/config/template-cp.yaml | 15 ++ pkg/providers/docker/config/template-md.yaml | 13 +- pkg/providers/docker/docker.go | 21 +++ pkg/providers/docker/docker_test.go | 59 ++++++-- 12 files changed, 322 insertions(+), 12 deletions(-) diff --git a/config/crd/bases/anywhere.eks.amazonaws.com_clusters.yaml b/config/crd/bases/anywhere.eks.amazonaws.com_clusters.yaml index 492d5b4c0664..7033fcb0363e 100644 --- a/config/crd/bases/anywhere.eks.amazonaws.com_clusters.yaml +++ b/config/crd/bases/anywhere.eks.amazonaws.com_clusters.yaml @@ -178,6 +178,11 @@ spec: required: - host type: object + kubeletConfiguration: + description: KubeletConfiguration is a struct that exposes the + Kubelet settings for the user to set on control plane nodes. + type: object + x-kubernetes-preserve-unknown-fields: true labels: additionalProperties: type: string @@ -572,8 +577,13 @@ spec: description: Count defines the number of desired worker nodes. Defaults to 1. type: integer + kubeletConfiguration: + description: KubeletConfiguration is a struct that exposes the + Kubelet settings for the user to set on worker nodes. + type: object + x-kubernetes-preserve-unknown-fields: true kubernetesVersion: - description: KuberenetesVersion defines the version for worker + description: KubernetesVersion defines the version for worker nodes. If not set, the top level spec kubernetesVersion will be used. type: string diff --git a/config/manifest/eksa-components.yaml b/config/manifest/eksa-components.yaml index 76ff8d1e65da..179d992644b2 100644 --- a/config/manifest/eksa-components.yaml +++ b/config/manifest/eksa-components.yaml @@ -3881,6 +3881,11 @@ spec: required: - host type: object + kubeletConfiguration: + description: KubeletConfiguration is a struct that exposes the + Kubelet settings for the user to set on control plane nodes. + type: object + x-kubernetes-preserve-unknown-fields: true labels: additionalProperties: type: string @@ -4275,8 +4280,13 @@ spec: description: Count defines the number of desired worker nodes. Defaults to 1. type: integer + kubeletConfiguration: + description: KubeletConfiguration is a struct that exposes the + Kubelet settings for the user to set on worker nodes. + type: object + x-kubernetes-preserve-unknown-fields: true kubernetesVersion: - description: KuberenetesVersion defines the version for worker + description: KubernetesVersion defines the version for worker nodes. If not set, the top level spec kubernetesVersion will be used. type: string diff --git a/go.mod b/go.mod index 6fc0d9107cb0..2e49888ac537 100644 --- a/go.mod +++ b/go.mod @@ -196,6 +196,7 @@ require ( k8s.io/apiextensions-apiserver v0.29.1 // indirect k8s.io/cluster-bootstrap v0.28.5 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/kubelet v0.29.3 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) diff --git a/go.sum b/go.sum index 56291cd7858f..e59d62b47044 100644 --- a/go.sum +++ b/go.sum @@ -1507,6 +1507,8 @@ k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKf k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/kubelet v0.29.3 h1:X9h0ZHzc+eUeNTaksbN0ItHyvGhQ7Z0HPjnQD2oHdwU= +k8s.io/kubelet v0.29.3/go.mod h1:jDiGuTkFOUynyBKzOoC1xRSWlgAZ9UPcTYeFyjr6vas= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= diff --git a/pkg/api/v1alpha1/cluster.go b/pkg/api/v1alpha1/cluster.go index c36230b0808b..9303ca3556b3 100644 --- a/pkg/api/v1alpha1/cluster.go +++ b/pkg/api/v1alpha1/cluster.go @@ -15,10 +15,12 @@ import ( "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/validation" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/validation/field" yamlutil "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/kubelet/config/v1beta1" "sigs.k8s.io/yaml" "github.com/aws/eks-anywhere/pkg/constants" @@ -192,6 +194,8 @@ var clusterConfigValidations = []func(*Cluster) error{ validateControlPlaneCertSANs, validateControlPlaneAPIServerExtraArgs, validateControlPlaneAPIServerOIDCExtraArgs, + validateControlPlaneKubeletConfiguration, + validateWorkerNodeKubeletConfiguration, } // GetClusterConfig parses a Cluster object from a multiobject yaml file in disk @@ -530,6 +534,51 @@ func validateControlPlaneAPIServerOIDCExtraArgs(clusterConfig *Cluster) error { return nil } +func validateControlPlaneKubeletConfiguration(clusterConfig *Cluster) error { + cpKubeletConfig := clusterConfig.Spec.ControlPlaneConfiguration.KubeletConfiguration + + return validateKubeletConfiguration(cpKubeletConfig) +} + +func validateWorkerNodeKubeletConfiguration(clusterConfig *Cluster) error { + workerNodeGroupConfigs := clusterConfig.Spec.WorkerNodeGroupConfigurations + + for _, workerNodeGroupConfig := range workerNodeGroupConfigs { + wnKubeletConfig := workerNodeGroupConfig.KubeletConfiguration + + if err := validateKubeletConfiguration(wnKubeletConfig); err != nil { + return err + } + } + + return nil +} + +func validateKubeletConfiguration(eksakubeconfig *unstructured.Unstructured) error { + if eksakubeconfig == nil { + return nil + } + + var kubeletConfig v1beta1.KubeletConfiguration + + kcString, err := yaml.Marshal(eksakubeconfig) + if err != nil { + return err + } + + _, err = yaml.YAMLToJSONStrict([]byte(kcString)) + if err != nil { + return fmt.Errorf("unmarshaling the yaml, malformed yaml %v", err) + } + + err = yaml.UnmarshalStrict(kcString, &kubeletConfig) + if err != nil { + return fmt.Errorf("unmarshaling KubeletConfiguration for %v", err) + } + + return nil +} + func validateWorkerNodeGroups(clusterConfig *Cluster) error { workerNodeGroupConfigs := clusterConfig.Spec.WorkerNodeGroupConfigurations if len(workerNodeGroupConfigs) <= 0 { diff --git a/pkg/api/v1alpha1/cluster_test.go b/pkg/api/v1alpha1/cluster_test.go index 5a7c5e8b097f..fc6ef7e2423f 100644 --- a/pkg/api/v1alpha1/cluster_test.go +++ b/pkg/api/v1alpha1/cluster_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/yaml" "github.com/aws/eks-anywhere/pkg/features" @@ -1096,6 +1097,138 @@ func TestGetAndValidateClusterConfig(t *testing.T) { } } +type clusterOpt func(c *Cluster) + +func baseCluster(opts ...clusterOpt) *Cluster { + c := &Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + }, + Spec: ClusterSpec{ + ControlPlaneConfiguration: ControlPlaneConfiguration{ + Count: 1, + Endpoint: &Endpoint{ + Host: "1.1.1.1", + }, + MachineGroupRef: &Ref{}, + }, + WorkerNodeGroupConfigurations: []WorkerNodeGroupConfiguration{ + { + Count: ptr.Int(3), + MachineGroupRef: &Ref{ + Kind: VSphereMachineConfigKind, + Name: "eksa-unit-test-1", + }, + Name: "wn-1", + }, + }, + KubernetesVersion: Kube129, + ExternalEtcdConfiguration: &ExternalEtcdConfiguration{ + MachineGroupRef: &Ref{ + Kind: VSphereMachineConfigKind, + Name: "eksa-unit-test-etcd", + }, + Count: 1, + }, + DatacenterRef: Ref{ + Kind: VSphereDatacenterKind, + Name: "eksa-unit-test", + }, + ClusterNetwork: ClusterNetwork{ + CNIConfig: &CNIConfig{Cilium: &CiliumConfig{}}, + Pods: Pods{ + CidrBlocks: []string{"192.168.0.0/16"}, + }, + Services: Services{ + CidrBlocks: []string{"10.96.0.0/12"}, + }, + }, + }, + } + + for _, opt := range opts { + opt(c) + } + + return c +} + +func TestValidateClusterConfigContent(t *testing.T) { + tests := []struct { + testName string + cluster *Cluster + wantErr bool + err string + }{ + { + testName: "valid cluster without kubelet", + cluster: baseCluster(), + wantErr: false, + }, + { + testName: "valid cluster with kubelet config for cp and wn", + cluster: baseCluster(func(c *Cluster) { + c.Spec.ControlPlaneConfiguration.KubeletConfiguration = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "maxPods": 20, + "apiVersion": "kubelet.config.k8s.io/v1beta1", + "kind": "KubeletConfiguration", + }, + } + c.Spec.WorkerNodeGroupConfigurations[0].KubeletConfiguration = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "maxPods": 20, + "apiVersion": "kubelet.config.k8s.io/v1beta1", + "kind": "KubeletConfiguration", + }, + } + }), + wantErr: false, + }, + { + testName: "invalid cluster with kubelet config for cp", + cluster: baseCluster(func(c *Cluster) { + c.Spec.ControlPlaneConfiguration.KubeletConfiguration = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "maxPodss": 20, + "apiVersion": "kubelet.config.k8s.io/v1beta1", + "kind": "KubeletConfiguration", + }, + } + }), + wantErr: true, + err: "unknown field", + }, + { + testName: "invalid cluster with kubelet config for wn", + cluster: baseCluster(func(c *Cluster) { + c.Spec.WorkerNodeGroupConfigurations[0].KubeletConfiguration = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "maxPodss": 20, + "apiVersion": "kubelet.config.k8s.io/v1beta1", + "kind": "KubeletConfiguration", + }, + } + }), + wantErr: true, + err: "unknown field", + }, + } + + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + err := ValidateClusterConfigContent(tt.cluster) + if (err != nil) != tt.wantErr { + t.Fatalf("ValidateClusterConfigContent() error = %v, wantErr %v", err, tt.wantErr) + } + + if len(tt.err) > 0 && !strings.Contains(err.Error(), tt.err) { + t.Fatalf("ValidateClusterConfigContent() error = %s, wantErr %s", err.Error(), tt.err) + } + }) + } +} + func TestGetClusterConfig(t *testing.T) { tests := []struct { testName string diff --git a/pkg/api/v1alpha1/cluster_types.go b/pkg/api/v1alpha1/cluster_types.go index 54697ebac7ee..12628f72e938 100644 --- a/pkg/api/v1alpha1/cluster_types.go +++ b/pkg/api/v1alpha1/cluster_types.go @@ -8,6 +8,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/intstr" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" @@ -309,6 +310,9 @@ type ControlPlaneConfiguration struct { MachineHealthCheck *MachineHealthCheck `json:"machineHealthCheck,omitempty"` // APIServerExtraArgs defines the flags to configure for the API server. APIServerExtraArgs map[string]string `json:"apiServerExtraArgs,omitempty"` + // KubeletConfiguration is a struct that exposes the Kubelet settings for the user to set on control plane nodes. + // +kubebuilder:pruning:PreserveUnknownFields + KubeletConfiguration *unstructured.Unstructured `json:"kubeletConfiguration,omitempty"` } // MachineHealthCheck allows to configure timeouts for machine health checks. Machine Health Checks are responsible for remediating unhealthy Machines. @@ -453,10 +457,13 @@ type WorkerNodeGroupConfiguration struct { // UpgradeRolloutStrategy determines the rollout strategy to use for rolling upgrades // and related parameters/knobs UpgradeRolloutStrategy *WorkerNodesUpgradeRolloutStrategy `json:"upgradeRolloutStrategy,omitempty"` - // KuberenetesVersion defines the version for worker nodes. If not set, the top level spec kubernetesVersion will be used. + // KubernetesVersion defines the version for worker nodes. If not set, the top level spec kubernetesVersion will be used. KubernetesVersion *KubernetesVersion `json:"kubernetesVersion,omitempty"` // MachineHealthCheck is a worker node level override for the timeouts and maxUnhealthy specified in the top-level MHC configuration. If not configured, the defaults in the top-level MHC configuration are used. MachineHealthCheck *MachineHealthCheck `json:"machineHealthCheck,omitempty"` + // KubeletConfiguration is a struct that exposes the Kubelet settings for the user to set on worker nodes. + // +kubebuilder:pruning:PreserveUnknownFields + KubeletConfiguration *unstructured.Unstructured `json:"kubeletConfiguration,omitempty"` } // Equal compares two WorkerNodeGroupConfigurations. diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index e77f5f744048..14f6a15e57f1 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -885,6 +885,10 @@ func (in *ControlPlaneConfiguration) DeepCopyInto(out *ControlPlaneConfiguration (*out)[key] = val } } + if in.KubeletConfiguration != nil { + in, out := &in.KubeletConfiguration, &out.KubeletConfiguration + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControlPlaneConfiguration. @@ -3470,6 +3474,10 @@ func (in *WorkerNodeGroupConfiguration) DeepCopyInto(out *WorkerNodeGroupConfigu *out = new(MachineHealthCheck) (*in).DeepCopyInto(*out) } + if in.KubeletConfiguration != nil { + in, out := &in.KubeletConfiguration, &out.KubeletConfiguration + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkerNodeGroupConfiguration. diff --git a/pkg/providers/docker/config/template-cp.yaml b/pkg/providers/docker/config/template-cp.yaml index 2cb630b6f527..b9f8434e7bc9 100644 --- a/pkg/providers/docker/config/template-cp.yaml +++ b/pkg/providers/docker/config/template-cp.yaml @@ -137,6 +137,13 @@ spec: {{ .schedulerExtraArgs.ToYaml | indent 10 }} {{- end }} files: +{{- if .kubeletConfiguration }} + - content: | +{{ .kubeletConfiguration | indent 8}} + owner: root:root + permissions: "0644" + path: /etc/kubernetes/patches/kubeletconfiguration0+strategic.yaml +{{- end }} - content: | {{ .auditPolicy | indent 8 }} owner: root:root @@ -209,6 +216,10 @@ spec: path: /var/lib/kubeadm/aws-iam-authenticator/pki/key.pem {{- end}} initConfiguration: +{{- if .kubeletConfiguration }} + patches: + directory: /etc/kubernetes/patches +{{- end }} nodeRegistration: criSocket: /var/run/containerd/containerd.sock kubeletExtraArgs: @@ -230,6 +241,10 @@ spec: {{- end }} {{- end }} joinConfiguration: +{{- if .kubeletConfiguration }} + patches: + directory: /etc/kubernetes/patches +{{- end }} nodeRegistration: criSocket: /var/run/containerd/containerd.sock kubeletExtraArgs: diff --git a/pkg/providers/docker/config/template-md.yaml b/pkg/providers/docker/config/template-md.yaml index 94d8867e7437..b0e9cc8592ce 100644 --- a/pkg/providers/docker/config/template-md.yaml +++ b/pkg/providers/docker/config/template-md.yaml @@ -7,6 +7,10 @@ spec: template: spec: joinConfiguration: +{{- if .kubeletConfiguration }} + patches: + directory: /etc/kubernetes/patches +{{- end }} nodeRegistration: criSocket: /var/run/containerd/containerd.sock {{- if .workerNodeGroupTaints }} @@ -26,9 +30,16 @@ spec: {{- if .kubeletExtraArgs }} {{ .kubeletExtraArgs.ToYaml | indent 12 }} {{- end }} -{{- if .registryMirrorMap }} +{{- if or .registryMirrorMap .kubeletConfiguration }} files: {{- end }} +{{- if .kubeletConfiguration }} + - content: | +{{ .kubeletConfiguration | indent 10 }} + owner: root:root + permissions: "0644" + path: /etc/kubernetes/patches/kubeletconfiguration0+strategic.yaml +{{- end }} {{- if .registryCACert }} - content: | {{ .registryCACert | indent 10 }} diff --git a/pkg/providers/docker/docker.go b/pkg/providers/docker/docker.go index 6fc48d335f9a..77e91dd9d908 100644 --- a/pkg/providers/docker/docker.go +++ b/pkg/providers/docker/docker.go @@ -12,6 +12,7 @@ import ( etcdv1 "github.com/aws/etcdadm-controller/api/v1beta1" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" + "sigs.k8s.io/yaml" "github.com/aws/eks-anywhere/pkg/api/v1alpha1" "github.com/aws/eks-anywhere/pkg/bootstrapper" @@ -358,6 +359,16 @@ func buildTemplateMapCP(clusterSpec *cluster.Spec) (map[string]interface{}, erro values["maxSurge"] = clusterSpec.Cluster.Spec.ControlPlaneConfiguration.UpgradeRolloutStrategy.RollingUpdate.MaxSurge } + if clusterSpec.Cluster.Spec.ControlPlaneConfiguration.KubeletConfiguration != nil { + cpKubeletConfig := clusterSpec.Cluster.Spec.ControlPlaneConfiguration.KubeletConfiguration.Object + kcString, err := yaml.Marshal(cpKubeletConfig) + if err != nil { + return nil, fmt.Errorf("marshaling control plane node Kubelet Configuration while building CAPI template %v", err) + } + + values["kubeletConfiguration"] = string(kcString) + } + return values, nil } @@ -397,6 +408,16 @@ func buildTemplateMapMD(clusterSpec *cluster.Spec, workerNodeGroupConfiguration } } + if workerNodeGroupConfiguration.KubeletConfiguration != nil { + wnKubeletConfig := workerNodeGroupConfiguration.KubeletConfiguration.Object + kcString, err := yaml.Marshal(wnKubeletConfig) + if err != nil { + return nil, fmt.Errorf("marshaling Kubelet Configuration for worker node %s: %v", workerNodeGroupConfiguration.Name, err) + } + + values["kubeletConfiguration"] = string(kcString) + } + return values, nil } diff --git a/pkg/providers/docker/docker_test.go b/pkg/providers/docker/docker_test.go index b5df9fa043a5..774ee109f1e4 100644 --- a/pkg/providers/docker/docker_test.go +++ b/pkg/providers/docker/docker_test.go @@ -14,6 +14,7 @@ import ( . "github.com/onsi/gomega" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" @@ -812,10 +813,9 @@ func TestDockerTemplateBuilderGenerateCAPISpecControlPlane(t *testing.T) { buildOptions []providers.BuildMapOption } tests := []struct { - name string - args args - wantContent []byte - wantErr error + name string + args args + wantErr error }{ { name: "kube 119 test", @@ -838,6 +838,28 @@ func TestDockerTemplateBuilderGenerateCAPISpecControlPlane(t *testing.T) { }, wantErr: fmt.Errorf("error building template map for CP "), }, + { + name: "kubelet config specified", + args: args{ + clusterSpec: test.NewClusterSpec(func(s *cluster.Spec) { + s.Cluster.Name = "test-cluster" + s.Cluster.Spec.ControlPlaneConfiguration = v1alpha1.ControlPlaneConfiguration{ + KubeletConfiguration: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "maxPods": 20, + "apiVersion": "kubelet.config.k8s.io/v1beta1", + "kind": "KubeletConfiguration", + }, + }, + Count: 1, + Endpoint: &v1alpha1.Endpoint{ + Host: "1.1.1.1", + }, + } + }), + }, + wantErr: nil, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -896,10 +918,9 @@ func TestDockerTemplateBuilderGenerateCAPISpecWorkers(t *testing.T) { clusterSpec *cluster.Spec } tests := []struct { - name string - args args - wantContent []byte - wantErr error + name string + args args + wantErr error }{ { name: "kube version not specified", @@ -911,6 +932,28 @@ func TestDockerTemplateBuilderGenerateCAPISpecWorkers(t *testing.T) { }, wantErr: fmt.Errorf("error building template map for MD "), }, + { + name: "kubelet config specified", + args: args{ + clusterSpec: test.NewClusterSpec(func(s *cluster.Spec) { + s.Cluster.Name = "test-cluster" + s.Cluster.Spec.WorkerNodeGroupConfigurations = []v1alpha1.WorkerNodeGroupConfiguration{ + { + KubeletConfiguration: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "maxPods": 20, + "apiVersion": "kubelet.config.k8s.io/v1beta1", + "kind": "KubeletConfiguration", + }, + }, + Count: ptr.Int(1), + Name: "test", + }, + } + }), + }, + wantErr: nil, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {