diff --git a/alicloud/resource_alicloud_cs_kubernetes_node_pool.go b/alicloud/resource_alicloud_cs_kubernetes_node_pool.go index 0de6b3650b4d..b80728286127 100644 --- a/alicloud/resource_alicloud_cs_kubernetes_node_pool.go +++ b/alicloud/resource_alicloud_cs_kubernetes_node_pool.go @@ -473,6 +473,67 @@ func resourceAliCloudAckNodepool() *schema.Resource { }, }, }, + "containerd_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max_concurrent_downloads": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: IntBetween(1, 20), + }, + "ignore_image_defined_volume": { + Type: schema.TypeBool, + Optional: true, + }, + "limit_core": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: IntBetween(0, 9007199254740991), + }, + "limit_no_file": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: IntBetween(1024, 9007199254740991), + }, + "limit_mem_lock": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: IntBetween(65536, 9007199254740991), + }, + "registry_mirrors": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "registry": { + Type: schema.TypeString, + Optional: true, + }, + "mirror": { + Type: schema.TypeString, + Optional: true, + }, + "override_path": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + }, + "insecure_registries": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, "labels": { Type: schema.TypeList, Optional: true, @@ -2157,6 +2218,85 @@ func resourceAliCloudAckNodepoolRead(d *schema.ResourceData, meta interface{}) e if err := d.Set("kubelet_configuration", kubeletConfigurationMaps); err != nil { return err } + containerdConfigMaps := make([]map[string]interface{}, 0) + containerd_configRawObj, _ := jsonpath.Get("$.node_config.containerd_config", objectRaw) + containerd_configRaw := make(map[string]interface{}) + if containerd_configRawObj != nil { + containerd_configRaw = containerd_configRawObj.(map[string]interface{}) + } + if len(containerd_configRaw) > 0 { + containerdConfigMap := make(map[string]interface{}) + if v, ok := containerd_configRaw["maxConcurrentDownloads"].(json.Number); ok { + val, _ := strconv.Atoi(v.String()) + if val > 0 { + containerdConfigMap["max_concurrent_downloads"] = val + } + } + if v, ok := containerd_configRaw["ignoreImageDefinedVolume"].(bool); ok && v { + containerdConfigMap["ignore_image_defined_volume"] = v + } + if v, ok := containerd_configRaw["limitCore"].(json.Number); ok { + val, _ := strconv.Atoi(v.String()) + if val > 0 { + containerdConfigMap["limit_core"] = val + } + } + if v, ok := containerd_configRaw["limitNoFile"].(json.Number); ok { + val, _ := strconv.Atoi(v.String()) + if val > 0 { + containerdConfigMap["limit_no_file"] = val + } + } + if v, ok := containerd_configRaw["limitMemLock"].(json.Number); ok { + val, _ := strconv.Atoi(v.String()) + if val > 0 { + containerdConfigMap["limit_mem_lock"] = val + } + } + registryMirrorsRaw, _ := jsonpath.Get("$.node_config.containerd_config.registryMirrors", objectRaw) + if registryMirrorsRaw != nil { + registryMirrorsArray := convertToInterfaceArray(registryMirrorsRaw) + if len(registryMirrorsArray) > 0 { + registryMirrorsMaps := make([]map[string]interface{}, 0) + for _, mirrorRaw := range registryMirrorsArray { + if mirrorRaw != nil { + mirrorStr := mirrorRaw.(string) + // 解析格式: registry=mirror 或 registry=mirror&override_path + mirrorMap := make(map[string]interface{}) + overridePath := false + if strings.Contains(mirrorStr, "&override_path") { + overridePath = true + mirrorStr = strings.Replace(mirrorStr, "&override_path", "", 1) + } + parts := strings.SplitN(mirrorStr, "=", 2) + if len(parts) == 2 { + mirrorMap["registry"] = parts[0] + mirrorMap["mirror"] = parts[1] + mirrorMap["override_path"] = overridePath + registryMirrorsMaps = append(registryMirrorsMaps, mirrorMap) + } + } + } + if len(registryMirrorsMaps) > 0 { + containerdConfigMap["registry_mirrors"] = registryMirrorsMaps + } + } + } + insecureRegistriesRaw, _ := jsonpath.Get("$.node_config.containerd_config.insecureRegistries", objectRaw) + if insecureRegistriesRaw != nil { + insecureRegistriesArray := convertToInterfaceArray(insecureRegistriesRaw) + if len(insecureRegistriesArray) > 0 { + containerdConfigMap["insecure_registries"] = insecureRegistriesArray + } + } + + if len(containerdConfigMap) > 0 { + containerdConfigMaps = append(containerdConfigMaps, containerdConfigMap) + } + } + if err := d.Set("containerd_config", containerdConfigMaps); err != nil { + return err + } labelsRaw, _ := jsonpath.Get("$.kubernetes_config.labels", objectRaw) labelsMaps := make([]map[string]interface{}, 0) if labelsRaw != nil { @@ -3225,6 +3365,87 @@ func resourceAliCloudAckNodepoolUpdate(d *schema.ResourceData, meta interface{}) } } + if d.HasChange("containerd_config") { + update = true + containerd_config := make(map[string]interface{}) + + if v := d.Get("containerd_config"); v != nil && len(v.([]interface{})) > 0 { + maxConcurrentDownloadsRaw, _ := jsonpath.Get("$[0].max_concurrent_downloads", v) + if maxConcurrentDownloadsRaw != nil && maxConcurrentDownloadsRaw != "" { + maxConcurrentDownloads, _ := strconv.ParseInt(fmt.Sprint(maxConcurrentDownloadsRaw), 10, 64) + if maxConcurrentDownloads > 0 { + containerd_config["maxConcurrentDownloads"] = maxConcurrentDownloads + } + } + ignoreImageDefinedVolumeRaw, _ := jsonpath.Get("$[0].ignore_image_defined_volume", v) + if ignoreImageDefinedVolumeRaw != nil { + containerd_config["ignoreImageDefinedVolume"] = ignoreImageDefinedVolumeRaw.(bool) + } + limitCoreRaw, _ := jsonpath.Get("$[0].limit_core", v) + if limitCoreRaw != nil && limitCoreRaw != "" { + limitCore, _ := strconv.ParseInt(fmt.Sprint(limitCoreRaw), 10, 64) + if limitCore > 0 { + containerd_config["limitCore"] = limitCore + } + } + limitNoFileRaw, _ := jsonpath.Get("$[0].limit_no_file", v) + if limitNoFileRaw != nil && limitNoFileRaw != "" { + limitNoFile, _ := strconv.ParseInt(fmt.Sprint(limitNoFileRaw), 10, 64) + if limitNoFile > 0 { + containerd_config["limitNoFile"] = limitNoFile + } + } + limitMemLockRaw, _ := jsonpath.Get("$[0].limit_mem_lock", v) + if limitMemLockRaw != nil && limitMemLockRaw != "" { + limitMemLock, _ := strconv.ParseInt(fmt.Sprint(limitMemLockRaw), 10, 64) + if limitMemLock > 0 { + containerd_config["limitMemLock"] = limitMemLock + } + } + registryMirrorsRaw, _ := jsonpath.Get("$[0].registry_mirrors", v) + if registryMirrorsRaw != nil { + registryMirrorsArray := convertToInterfaceArray(registryMirrorsRaw) + if len(registryMirrorsArray) > 0 { + registryMirrorsList := make([]string, 0) + for _, mirrorRaw := range registryMirrorsArray { + if mirrorRaw != nil { + mirrorTmp := mirrorRaw.(map[string]interface{}) + registryRaw, ok1 := mirrorTmp["registry"] + mirrorRaw, ok2 := mirrorTmp["mirror"] + if !ok1 || registryRaw == nil || registryRaw == "" || !ok2 || mirrorRaw == nil || mirrorRaw == "" { + return fmt.Errorf("when configuring 'registry_mirrors', both 'registry' and 'mirror' must be specified together. If either field is empty, the configuration is invalid") + } + registry := registryRaw.(string) + mirror := mirrorRaw.(string) + overridePath := false + if op, ok := mirrorTmp["override_path"]; ok && op != nil { + overridePath = op.(bool) + } + // registry=mirror or registry=mirror&override_path + mirrorStr := fmt.Sprintf("%s=%s", registry, mirror) + if overridePath { + mirrorStr = mirrorStr + "&override_path" + } + registryMirrorsList = append(registryMirrorsList, mirrorStr) + } + } + if len(registryMirrorsList) > 0 { + containerd_config["registryMirrors"] = registryMirrorsList + } + } + } + insecureRegistriesRaw, _ := jsonpath.Get("$[0].insecure_registries", v) + if insecureRegistriesRaw != nil { + insecureRegistriesArray := convertToInterfaceArray(insecureRegistriesRaw) + if len(insecureRegistriesArray) > 0 { + containerd_config["insecureRegistries"] = insecureRegistriesArray + } + } + } + + request["containerd_config"] = containerd_config + } + rolling_policy := make(map[string]interface{}) if v := d.Get("rolling_policy"); v != nil { diff --git a/alicloud/resource_alicloud_cs_kubernetes_node_pool_test.go b/alicloud/resource_alicloud_cs_kubernetes_node_pool_test.go index 8b903342a573..0619c2f28295 100644 --- a/alicloud/resource_alicloud_cs_kubernetes_node_pool_test.go +++ b/alicloud/resource_alicloud_cs_kubernetes_node_pool_test.go @@ -8055,3 +8055,113 @@ resource "alicloud_cs_managed_kubernetes" "defaultNppPcz" { } // Test Ack Nodepool. <<< Resource test cases, automatically generated. + +func TestAccAliCloudCSKubernetesNodePool_containerdConfig(t *testing.T) { + var v *cs.NodePoolDetail + + resourceId := "alicloud_cs_kubernetes_node_pool.containerd_config" + ra := resourceAttrInit(resourceId, AlicloudAckNodepoolMap12069) + + serviceFunc := func() interface{} { + return &CsService{testAccProvider.Meta().(*connectivity.AliyunClient)} + } + rc := resourceCheckInit(resourceId, &v, serviceFunc) + + rac := resourceAttrCheckInit(rc, ra) + + testAccCheck := rac.resourceAttrMapUpdateSet() + rand := acctest.RandIntRange(1000000, 9999999) + name := fmt.Sprintf("tf-testAccNodePool-containerd-%d", rand) + testAccConfig := resourceTestAccConfigFunc(resourceId, name, AlicloudAckNodepoolBasicDependence12069) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckWithRegions(t, true, []connectivity.Region{"cn-hangzhou"}) + }, + IDRefreshName: resourceId, + Providers: testAccProviders, + CheckDestroy: rac.checkResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccConfig(map[string]interface{}{ + "name": name, + "cluster_id": "${alicloud_cs_managed_kubernetes.defaultNppPcz.id}", + "vswitch_ids": []string{"${alicloud_vswitch.defaultT8D8ss.id}"}, + "instance_types": []string{ + "ecs.g7.xlarge", + }, + "desired_size": "1", + "system_disk_category": "cloud_essd", + "system_disk_size": "40", + "image_type": "AliyunLinux3ContainerOptimized", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "name": name, + "cluster_id": CHECKSET, + }), + ), + }, + // check: containerd_config basic + { + Config: testAccConfig(map[string]interface{}{ + "containerd_config": []map[string]interface{}{{ + "max_concurrent_downloads": "10", + "ignore_image_defined_volume": "true", + "insecure_registries": []string{"registry.example.com"}, + }}, + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "containerd_config.#": "1", + "containerd_config.0.max_concurrent_downloads": "10", + "containerd_config.0.ignore_image_defined_volume": "true", + "containerd_config.0.insecure_registries.#": "1", + "containerd_config.0.insecure_registries.0": "registry.example.com", + }), + ), + }, + // check: containerd_config update + { + Config: testAccConfig(map[string]interface{}{ + "containerd_config": []map[string]interface{}{{ + "max_concurrent_downloads": "5", + "limit_core": "10", + "limit_no_file": "1024", + "limit_mem_lock": "65536", + "ignore_image_defined_volume": "true", + "insecure_registries": []string{"registry.example.com"}, + "registry_mirrors": []map[string]interface{}{{ + "registry": "docker.io", + "mirror": "https://mirror.example.com", + "override_path": "true", + }}, + }}, + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "containerd_config.#": "1", + "containerd_config.0.max_concurrent_downloads": "5", + "containerd_config.0.limit_core": "10", + "containerd_config.0.limit_no_file": "1024", + "containerd_config.0.limit_mem_lock": "65536", + "containerd_config.0.ignore_image_defined_volume": "true", + "containerd_config.0.insecure_registries.#": "1", + "containerd_config.0.insecure_registries.0": "registry.example.com", + "containerd_config.0.registry_mirrors.#": "1", + "containerd_config.0.registry_mirrors.0.registry": "docker.io", + "containerd_config.0.registry_mirrors.0.mirror": "https://mirror.example.com", + "containerd_config.0.registry_mirrors.0.override_path": "true", + }), + ), + }, + { + ResourceName: resourceId, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"eflo_node_group", "password", "rolling_policy", "update_nodes", "upgrade_policy"}, + }, + }, + }) +} diff --git a/website/docs/r/cs_kubernetes_node_pool.html.markdown b/website/docs/r/cs_kubernetes_node_pool.html.markdown index 1c070d4937dc..9e50ebe835d6 100644 --- a/website/docs/r/cs_kubernetes_node_pool.html.markdown +++ b/website/docs/r/cs_kubernetes_node_pool.html.markdown @@ -300,6 +300,29 @@ resource "alicloud_cs_kubernetes_node_pool" "customized_kubelet" { allowed_unsafe_sysctls = ["net.ipv4.route.min_pmtu"] } + # containerd configuration parameters + containerd_config { + max_concurrent_downloads = 10 + ignore_image_defined_volume = true + limit_core = 10 + limit_no_file = 1024 + limit_mem_lock = 65536 + + registry_mirrors { + registry = "docker.io" + mirror = "https://registry.cn-hangzhou.aliyuncs.com" + override_path = false + } + + registry_mirrors { + registry = "gcr.io" + mirror = "https://gcr-mirror.example.com" + override_path = true + } + + insecure_registries = ["registry.example.com", "192.168.1.1:5000"] + } + # rolling policy: works when updating rolling_policy { max_parallelism = 1 @@ -706,6 +729,9 @@ The following arguments are supported: * `internet_max_bandwidth_out` - (Optional, Int) The maximum bandwidth of the public IP address of the node. The unit is Mbps(Mega bit per second). The value range is:\[1,100\] * `key_name` - (Optional) The name of the key pair. When the node pool is a managed node pool, only `key_name` is supported. * `kubelet_configuration` - (Optional, Set) Kubelet configuration parameters for worker nodes. See [`kubelet_configuration`](#kubelet_configuration) below. More information in [Kubelet Configuration](https://kubernetes.io/docs/reference/config-api/kubelet-config.v1beta1/). See [`kubelet_configuration`](#kubelet_configuration) below. +* `containerd_config` - (Optional, Set, Available since v1.277.0) Containerd configuration parameters for worker nodes. + + -> **Note:** This parameter only takes effect during update operations. See [`containerd_config`](#containerd_config) below. * `labels` - (Optional, List) A List of Kubernetes labels to assign to the nodes . Only labels that are applied with the ACK API are managed by this argument. Detailed below. More information in [Labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/). See [`labels`](#labels) below. * `login_as_non_root` - (Optional, ForceNew) Whether the ECS instance is logged on as a ecs-user user. Valid value: `true` and `false`. * `management` - (Optional, Computed, Set) Managed node pool configuration. See [`management`](#management) below. @@ -958,6 +984,28 @@ The kubelet_configuration-tracing supports the following: * `endpoint` - (Optional, Available since v1.242.0) The endpoint of the collector. * `sampling_rate_per_million` - (Optional) Number of samples to be collected per million span. +### `containerd_config` + +-> **NOTE:** The `containerd_config` only takes effect during update operations. To use this feature, you need to create the node pool first, then update it with the `containerd_config` configuration. + +The containerd_config supports the following: +* `max_concurrent_downloads` - (Optional, Int) The maximum number of concurrent downloads for container images. Valid values: `1` to `20`. +* `ignore_image_defined_volume` - (Optional, Bool) Whether to ignore volumes defined in the image. Valid values: `true` or `false`. +* `limit_core` - (Optional, Int) The coredump size limit. Valid values: `0` to `9007199254740991`. +* `limit_no_file` - (Optional, Int) The maximum number of file handles. Valid values: `1024` to `9007199254740991`. +* `limit_mem_lock` - (Optional, Int) The maximum locked memory limit. Valid values: `65536` to `9007199254740991`. +* `registry_mirrors` - (Optional, List) Configure mirror sites for container image registries to accelerate image pulls. See [`registry_mirrors`](#containerd_config-registry_mirrors) below. +* `insecure_registries` - (Optional, List) Allow the container runtime to skip TLS certificate verification when pulling images. Typically used in test environments with self-signed certificate registries. The format is domain name or IP address without protocol prefix (e.g., `registry.example.com`, `192.168.1.1:5000`). + +### `containerd_config-registry_mirrors` + +The containerd_config-registry_mirrors supports the following: +* `registry` - (Optional, String) The registry address without protocol prefix (e.g., `docker.io`, `registry.example.com`, `192.168.1.1:5000`). +* `mirror` - (Optional, String) The mirror URL with protocol prefix (e.g., `https://mirror.example.com`, `http://192.168.1.1:5000`). +* `override_path` - (Optional, Bool) Whether to override the path. Default value: `false`. + + -> **NOTE:** When configuring `registry_mirrors`, both `registry` and `mirror` must be specified together. If either field is empty, an error will be returned. + ### `labels` The labels supports the following: