diff --git a/operator/api/loki/v1/lokistack_types.go b/operator/api/loki/v1/lokistack_types.go index 0b727e5c548c1..f5febb24763fb 100644 --- a/operator/api/loki/v1/lokistack_types.go +++ b/operator/api/loki/v1/lokistack_types.go @@ -370,6 +370,16 @@ type LokiComponentSpec struct { // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors="urn:alm:descriptor:com.tectonic.ui:podAntiAffinity",displayName="PodAntiAffinity" PodAntiAffinity *corev1.PodAntiAffinity `json:"podAntiAffinity,omitempty"` + + // Resources defines the resource requirements for the component. + // These values override the default resources configured by the operator + // based on the selected size. Users can use this to customize CPU/memory + // requests and limits for each component. + // + // +optional + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Component Resources" + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` } // LokiTemplateSpec defines the template of all requirements to configure diff --git a/operator/api/loki/v1/zz_generated.deepcopy.go b/operator/api/loki/v1/zz_generated.deepcopy.go index fc1e05178e850..f0aa9882b649f 100644 --- a/operator/api/loki/v1/zz_generated.deepcopy.go +++ b/operator/api/loki/v1/zz_generated.deepcopy.go @@ -623,6 +623,11 @@ func (in *LokiComponentSpec) DeepCopyInto(out *LokiComponentSpec) { *out = new(corev1.PodAntiAffinity) (*in).DeepCopyInto(*out) } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(corev1.ResourceRequirements) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LokiComponentSpec. diff --git a/operator/config/crd/bases/loki.grafana.com_lokistacks.yaml b/operator/config/crd/bases/loki.grafana.com_lokistacks.yaml index 7c9fe864d73b2..297104cc94d52 100644 --- a/operator/config/crd/bases/loki.grafana.com_lokistacks.yaml +++ b/operator/config/crd/bases/loki.grafana.com_lokistacks.yaml @@ -1194,6 +1194,63 @@ spec: the component. format: int32 type: integer + resources: + description: |- + Resources defines the resource requirements for the component. + These values override the default resources configured by the operator + based on the selected size. Users can use this to customize CPU/memory + requests and limits for each component. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object tolerations: description: |- Tolerations defines the tolerations required by a node to schedule @@ -1539,6 +1596,63 @@ spec: the component. format: int32 type: integer + resources: + description: |- + Resources defines the resource requirements for the component. + These values override the default resources configured by the operator + based on the selected size. Users can use this to customize CPU/memory + requests and limits for each component. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object tolerations: description: |- Tolerations defines the tolerations required by a node to schedule @@ -1884,6 +1998,63 @@ spec: the component. format: int32 type: integer + resources: + description: |- + Resources defines the resource requirements for the component. + These values override the default resources configured by the operator + based on the selected size. Users can use this to customize CPU/memory + requests and limits for each component. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object tolerations: description: |- Tolerations defines the tolerations required by a node to schedule @@ -2230,6 +2401,63 @@ spec: the component. format: int32 type: integer + resources: + description: |- + Resources defines the resource requirements for the component. + These values override the default resources configured by the operator + based on the selected size. Users can use this to customize CPU/memory + requests and limits for each component. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object tolerations: description: |- Tolerations defines the tolerations required by a node to schedule @@ -2575,6 +2803,63 @@ spec: the component. format: int32 type: integer + resources: + description: |- + Resources defines the resource requirements for the component. + These values override the default resources configured by the operator + based on the selected size. Users can use this to customize CPU/memory + requests and limits for each component. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object tolerations: description: |- Tolerations defines the tolerations required by a node to schedule @@ -2920,6 +3205,63 @@ spec: the component. format: int32 type: integer + resources: + description: |- + Resources defines the resource requirements for the component. + These values override the default resources configured by the operator + based on the selected size. Users can use this to customize CPU/memory + requests and limits for each component. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object tolerations: description: |- Tolerations defines the tolerations required by a node to schedule @@ -3266,6 +3608,63 @@ spec: the component. format: int32 type: integer + resources: + description: |- + Resources defines the resource requirements for the component. + These values override the default resources configured by the operator + based on the selected size. Users can use this to customize CPU/memory + requests and limits for each component. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object tolerations: description: |- Tolerations defines the tolerations required by a node to schedule @@ -3611,6 +4010,63 @@ spec: the component. format: int32 type: integer + resources: + description: |- + Resources defines the resource requirements for the component. + These values override the default resources configured by the operator + based on the selected size. Users can use this to customize CPU/memory + requests and limits for each component. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object tolerations: description: |- Tolerations defines the tolerations required by a node to schedule diff --git a/operator/internal/manifests/build.go b/operator/internal/manifests/build.go index f81b39ed159c0..7470dccf8cfad 100644 --- a/operator/internal/manifests/build.go +++ b/operator/internal/manifests/build.go @@ -146,6 +146,10 @@ func ApplyDefaultSettings(opts *Options) error { useRequestsAsLimits = opts.Stack.Template.UseRequestsAsLimits } opts.ResourceRequirements = internal.ResourceRequirementsForSize(opts.Stack.Size, useRequestsAsLimits) + + // Apply user-provided resource overrides + internal.ApplyResourceOverrides(&opts.ResourceRequirements, opts.Stack.Template) + opts.Stack = *spec return nil diff --git a/operator/internal/manifests/build_test.go b/operator/internal/manifests/build_test.go index 48944aa30a2cb..e6a33d0b25e34 100644 --- a/operator/internal/manifests/build_test.go +++ b/operator/internal/manifests/build_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" "sigs.k8s.io/controller-runtime/pkg/client" configv1 "github.com/grafana/loki/operator/api/config/v1" @@ -124,6 +125,49 @@ func TestApplyDefaultSettings_UseRequestsAsLimits(t *testing.T) { } } +func TestApplyDefaultSettings_ResourceOverrides(t *testing.T) { + customCPU := corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("256Mi"), + } + + opt := Options{ + Name: "abcd", + Namespace: "efgh", + Stack: lokiv1.LokiStackSpec{ + Size: lokiv1.SizeOneXPico, + Template: &lokiv1.LokiTemplateSpec{ + Ingester: &lokiv1.LokiComponentSpec{ + Resources: &corev1.ResourceRequirements{ + Requests: customCPU, + Limits: customCPU, + }, + }, + Distributor: &lokiv1.LokiComponentSpec{ + Resources: &corev1.ResourceRequirements{ + Requests: customCPU, + }, + }, + }, + }, + Timeouts: defaultTimeoutConfig, + } + err := ApplyDefaultSettings(&opt) + + require.NoError(t, err) + + // Check that ingester resources were overridden for both requests and limits + require.Equal(t, customCPU, opt.ResourceRequirements.Ingester.Requests) + require.Equal(t, customCPU, opt.ResourceRequirements.Ingester.Limits) + + // Check that distributor requests were overridden but limits remain from size defaults + require.Equal(t, customCPU, opt.ResourceRequirements.Distributor.Requests) + + // Check that other components still have their size defaults + require.NotEmpty(t, opt.ResourceRequirements.Querier.Requests) + require.NotEmpty(t, opt.ResourceRequirements.QueryFrontend.Requests) +} + func TestApplyTLSSettings_OverrideDefaults(t *testing.T) { type tt struct { desc string diff --git a/operator/internal/manifests/internal/sizes.go b/operator/internal/manifests/internal/sizes.go index 98847cca0760d..402bc21e7cdf4 100644 --- a/operator/internal/manifests/internal/sizes.go +++ b/operator/internal/manifests/internal/sizes.go @@ -322,6 +322,59 @@ func ResourceRequirementsForSize(size lokiv1.LokiStackSizeType, useRequestsAsLim return resources } +// ApplyResourceOverrides merges user-provided resource overrides from LokiComponentSpec +// into the component resources. User-provided resources take precedence over size defaults. +func ApplyResourceOverrides(resources *ComponentResources, template *lokiv1.LokiTemplateSpec) { + if template == nil { + return + } + + if template.Ingester != nil && template.Ingester.Resources != nil { + applyResourceOverride(&resources.Ingester, template.Ingester.Resources) + } + if template.Querier != nil && template.Querier.Resources != nil { + applyResourceOverrideToCorev1(&resources.Querier, template.Querier.Resources) + } + if template.Distributor != nil && template.Distributor.Resources != nil { + applyResourceOverrideToCorev1(&resources.Distributor, template.Distributor.Resources) + } + if template.QueryFrontend != nil && template.QueryFrontend.Resources != nil { + applyResourceOverrideToCorev1(&resources.QueryFrontend, template.QueryFrontend.Resources) + } + if template.Gateway != nil && template.Gateway.Resources != nil { + applyResourceOverrideToCorev1(&resources.Gateway, template.Gateway.Resources) + } + if template.IndexGateway != nil && template.IndexGateway.Resources != nil { + applyResourceOverride(&resources.IndexGateway, template.IndexGateway.Resources) + } + if template.Compactor != nil && template.Compactor.Resources != nil { + applyResourceOverride(&resources.Compactor, template.Compactor.Resources) + } + if template.Ruler != nil && template.Ruler.Resources != nil { + applyResourceOverride(&resources.Ruler, template.Ruler.Resources) + } +} + +// applyResourceOverride applies a ResourceRequirements override to a ResourceRequirements struct +func applyResourceOverride(target *ResourceRequirements, override *corev1.ResourceRequirements) { + if override.Requests != nil { + target.Requests = override.Requests.DeepCopy() + } + if override.Limits != nil { + target.Limits = override.Limits.DeepCopy() + } +} + +// applyResourceOverrideToCorev1 applies a ResourceRequirements override to a corev1.ResourceRequirements struct +func applyResourceOverrideToCorev1(target *corev1.ResourceRequirements, override *corev1.ResourceRequirements) { + if override.Requests != nil { + target.Requests = override.Requests.DeepCopy() + } + if override.Limits != nil { + target.Limits = override.Limits.DeepCopy() + } +} + // StackSizeTable defines the default configurations for each size var StackSizeTable = map[lokiv1.LokiStackSizeType]lokiv1.LokiStackSpec{ lokiv1.SizeOneXDemo: {