diff --git a/docs/resources/node_policy.md b/docs/resources/node_policy.md
index a9fd91f..812c39e 100644
--- a/docs/resources/node_policy.md
+++ b/docs/resources/node_policy.md
@@ -442,11 +442,29 @@ Optional:
- `fips_mode` (String) FIPS 140-2 mode. Valid values: `FIPS`, `Disabled`.
- `image_family` (String) Azure image family. Valid values: `Ubuntu`, `Ubuntu2204`, `Ubuntu2404`, `AzureLinux`.
+- `kubelet` (Attributes) Azure kubelet configuration overrides (see [below for nested schema](#nestedatt--azure--kubelet))
- `max_pods` (Number) Maximum number of pods per node
- `os_disk_size_gb` (Number) OS disk size in GB
- `tags` (Map of String) Azure tags to apply to resources
- `vnet_subnet_id` (String) VNet subnet ID
+
+### Nested Schema for `azure.kubelet`
+
+Optional:
+
+- `allowed_unsafe_sysctls` (List of String) List of allowed unsafe sysctls
+- `container_log_max_files` (Number) Maximum number of container log files
+- `container_log_max_size` (String) Maximum container log file size
+- `cpu_cfs_quota` (Boolean) Enable CPU CFS quota enforcement
+- `cpu_cfs_quota_period` (String) CPU CFS quota period
+- `cpu_manager_policy` (String) CPU manager policy (None, static)
+- `image_gc_high_threshold_percent` (Number) Image GC high threshold percent
+- `image_gc_low_threshold_percent` (Number) Image GC low threshold percent
+- `pod_pids_limit` (Number) Maximum number of PIDs per pod
+- `topology_manager_policy` (String) Topology manager policy
+
+
### Nested Schema for `capacity_types`
diff --git a/internal/provider/node_policy.go b/internal/provider/node_policy.go
index f4428f1..18aeb17 100644
--- a/internal/provider/node_policy.go
+++ b/internal/provider/node_policy.go
@@ -616,6 +616,53 @@ func (r *NodePolicyResource) Schema(ctx context.Context, req resource.SchemaRequ
Description: "Maximum number of pods per node",
Optional: true,
},
+ "kubelet": schema.SingleNestedAttribute{
+ Description: "Azure kubelet configuration overrides",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "cpu_manager_policy": schema.StringAttribute{
+ Description: "CPU manager policy (None, static)",
+ Optional: true,
+ },
+ "cpu_cfs_quota": schema.BoolAttribute{
+ Description: "Enable CPU CFS quota enforcement",
+ Optional: true,
+ },
+ "cpu_cfs_quota_period": schema.StringAttribute{
+ Description: "CPU CFS quota period",
+ Optional: true,
+ },
+ "image_gc_high_threshold_percent": schema.Int32Attribute{
+ Description: "Image GC high threshold percent",
+ Optional: true,
+ },
+ "image_gc_low_threshold_percent": schema.Int32Attribute{
+ Description: "Image GC low threshold percent",
+ Optional: true,
+ },
+ "topology_manager_policy": schema.StringAttribute{
+ Description: "Topology manager policy",
+ Optional: true,
+ },
+ "allowed_unsafe_sysctls": schema.ListAttribute{
+ Description: "List of allowed unsafe sysctls",
+ Optional: true,
+ ElementType: types.StringType,
+ },
+ "container_log_max_size": schema.StringAttribute{
+ Description: "Maximum container log file size",
+ Optional: true,
+ },
+ "container_log_max_files": schema.Int32Attribute{
+ Description: "Maximum number of container log files",
+ Optional: true,
+ },
+ "pod_pids_limit": schema.Int64Attribute{
+ Description: "Maximum number of PIDs per pod",
+ Optional: true,
+ },
+ },
+ },
},
},
// Raw Karpenter specs
diff --git a/internal/provider/node_policy_test.go b/internal/provider/node_policy_test.go
index 50adefc..ef658b1 100644
--- a/internal/provider/node_policy_test.go
+++ b/internal/provider/node_policy_test.go
@@ -154,6 +154,18 @@ func TestNodePolicyResourceModel(t *testing.T) {
FipsMode: types.StringValue("Disabled"),
Tags: types.MapValueMust(types.StringType, map[string]attr.Value{"Environment": types.StringValue("production")}),
MaxPods: types.Int32Value(110),
+ Kubelet: &AzureKubeletConfiguration{
+ CpuManagerPolicy: types.StringValue("static"),
+ CpuCfsQuota: types.BoolValue(true),
+ CpuCfsQuotaPeriod: types.StringValue("100ms"),
+ ImageGcHighThresholdPercent: types.Int32Value(85),
+ ImageGcLowThresholdPercent: types.Int32Value(70),
+ TopologyManagerPolicy: types.StringValue("restricted"),
+ AllowedUnsafeSysctls: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("net.ipv4.tcp_syncookies")}),
+ ContainerLogMaxSize: types.StringValue("50Mi"),
+ ContainerLogMaxFiles: types.Int32Value(5),
+ PodPidsLimit: types.Int64Value(4096),
+ },
}
if azureConfig.VnetSubnetId.ValueString() == "" {
@@ -168,6 +180,18 @@ func TestNodePolicyResourceModel(t *testing.T) {
if azureConfig.MaxPods.ValueInt32() != 110 {
t.Errorf("Expected max_pods to be 110, got %d", azureConfig.MaxPods.ValueInt32())
}
+ if azureConfig.Kubelet == nil {
+ t.Fatal("Expected Kubelet to be non-nil")
+ }
+ if azureConfig.Kubelet.CpuManagerPolicy.ValueString() != "static" {
+ t.Errorf("Expected CpuManagerPolicy to be 'static', got %s", azureConfig.Kubelet.CpuManagerPolicy.ValueString())
+ }
+ if !azureConfig.Kubelet.CpuCfsQuota.ValueBool() {
+ t.Error("Expected CpuCfsQuota to be true")
+ }
+ if azureConfig.Kubelet.PodPidsLimit.ValueInt64() != 4096 {
+ t.Errorf("Expected PodPidsLimit to be 4096, got %d", azureConfig.Kubelet.PodPidsLimit.ValueInt64())
+ }
})
// Test RawKarpenterSpec
@@ -1014,6 +1038,10 @@ func TestNodePolicyResourceModel(t *testing.T) {
})
}
+// singleNestedSchema is a local alias to allow type-asserting schema.Attribute
+// to access nested Attributes without importing internal framework packages.
+type singleNestedSchema = schema.SingleNestedAttribute
+
func validateNodePolicySchema(t *testing.T, schema schema.Schema) {
// Validate required attributes
requiredAttrs := []string{"name"}
@@ -1070,4 +1098,49 @@ func validateNodePolicySchema(t *testing.T, schema schema.Schema) {
if _, exists := schema.Attributes["azure"]; !exists {
t.Error("Azure configuration not found in schema")
}
+
+ // Validate azure nested attributes include kubelet (regression test for missing kubelet schema bug)
+ azureRaw, ok := schema.Attributes["azure"]
+ if !ok {
+ t.Fatal("azure attribute not found in schema")
+ }
+ azureSingle, ok := azureRaw.(singleNestedSchema)
+ if !ok {
+ t.Fatal("azure attribute is not a SingleNestedAttribute")
+ }
+ azureKubeletFields := []string{
+ "kubelet",
+ "vnet_subnet_id",
+ "os_disk_size_gb",
+ "image_family",
+ "fips_mode",
+ "tags",
+ "max_pods",
+ }
+ for _, field := range azureKubeletFields {
+ if _, exists := azureSingle.Attributes[field]; !exists {
+ t.Errorf("azure schema is missing '%s' attribute", field)
+ }
+ }
+
+ // Validate kubelet sub-attributes
+ kubeletRaw, ok := azureSingle.Attributes["kubelet"]
+ if !ok {
+ t.Fatal("azure schema is missing 'kubelet' attribute")
+ }
+ kubeletSingle, ok := kubeletRaw.(singleNestedSchema)
+ if !ok {
+ t.Fatal("azure.kubelet is not a SingleNestedAttribute")
+ }
+ kubeletFields := []string{
+ "cpu_manager_policy", "cpu_cfs_quota", "cpu_cfs_quota_period",
+ "image_gc_high_threshold_percent", "image_gc_low_threshold_percent",
+ "topology_manager_policy", "allowed_unsafe_sysctls",
+ "container_log_max_size", "container_log_max_files", "pod_pids_limit",
+ }
+ for _, field := range kubeletFields {
+ if _, exists := kubeletSingle.Attributes[field]; !exists {
+ t.Errorf("azure.kubelet schema is missing '%s' attribute", field)
+ }
+ }
}