diff --git a/pkg/provision/config/common.go b/pkg/provision/config/common.go index 4c1d8b6..c6df467 100644 --- a/pkg/provision/config/common.go +++ b/pkg/provision/config/common.go @@ -146,11 +146,40 @@ type GatewayConfig struct { // // Ignored when Skip is true. ClassName string `yaml:"className,omitempty" json:"className,omitempty" jsonschema:"default=y-cluster,description=GatewayClass name. Consumer Gateway resources reference this via gatewayClassName. Ignored when skip is true."` + + // Resources tunes resource requests on the EG controller pod + // and the per-Gateway envoy proxy pod. Defaults target a + // single-user/dev cluster; bump for production-shaped load. + // Upstream defaults are 100m/256Mi (controller) and + // 100m/512Mi (proxy), which oversubscribe a 2GB-RAM + // appliance node. + Resources GatewayResources `yaml:"resources,omitempty" json:"resources,omitempty" jsonschema:"description=Resource requests for the bundled EG install. Defaults: controller 10m/64Mi, proxy 10m/128Mi. Limits are left as upstream sets them."` +} + +// GatewayResources groups the two pods whose resource requests +// y-cluster manages: the EG controller (Deployment in +// envoy-gateway-system) and the per-Gateway envoy proxy +// (spawned by EG via the EnvoyProxy CR our default GatewayClass +// references). +type GatewayResources struct { + Controller ResourceRequests `yaml:"controller,omitempty" json:"controller,omitempty" jsonschema:"description=EG controller container requests. Default cpu 10m, memory 64Mi."` + Proxy ResourceRequests `yaml:"proxy,omitempty" json:"proxy,omitempty" jsonschema:"description=Per-Gateway envoy proxy container requests. Default cpu 10m, memory 128Mi."` +} + +// ResourceRequests is a minimal Kubernetes-resource-style +// shape covering CPU + memory requests only. Limits are not +// modelled here on purpose: y-cluster's stance is that bursty +// idle workloads are healthier under upstream's existing +// limits than under tighter ones we'd have to guess at. +type ResourceRequests struct { + CPU string `yaml:"cpu,omitempty" json:"cpu,omitempty" jsonschema:"description=CPU request in Kubernetes notation (e.g. 10m, 0.5, 1)."` + Memory string `yaml:"memory,omitempty" json:"memory,omitempty" jsonschema:"description=Memory request in Kubernetes notation (e.g. 64Mi, 256Mi, 1Gi)."` } -// applyGatewayDefaults fills ClassName when the install is -// enabled. When Skip is set, ClassName is left as the user -// supplied it so debug logs make the operator's intent obvious. +// applyGatewayDefaults fills ClassName + Resources when the +// install is enabled. When Skip is set everything is left as +// the user supplied it so debug logs make the operator's +// intent obvious. func (c *CommonConfig) applyGatewayDefaults() { if c.Gateway.Skip { return @@ -158,6 +187,18 @@ func (c *CommonConfig) applyGatewayDefaults() { if c.Gateway.ClassName == "" { c.Gateway.ClassName = "y-cluster" } + if c.Gateway.Resources.Controller.CPU == "" { + c.Gateway.Resources.Controller.CPU = "10m" + } + if c.Gateway.Resources.Controller.Memory == "" { + c.Gateway.Resources.Controller.Memory = "64Mi" + } + if c.Gateway.Resources.Proxy.CPU == "" { + c.Gateway.Resources.Proxy.CPU = "10m" + } + if c.Gateway.Resources.Proxy.Memory == "" { + c.Gateway.Resources.Proxy.Memory = "128Mi" + } } // EffectiveGatewayClassName returns the GatewayClass name the diff --git a/pkg/provision/config/gateway_test.go b/pkg/provision/config/gateway_test.go index 4269113..213ed23 100644 --- a/pkg/provision/config/gateway_test.go +++ b/pkg/provision/config/gateway_test.go @@ -36,6 +36,59 @@ func TestGateway_SkipLeavesClassNameAlone(t *testing.T) { if c.Gateway.ClassName != "" { t.Fatalf("Skip:true should leave ClassName empty, got %q", c.Gateway.ClassName) } + // Skip also keeps Resources empty so a downstream consumer + // reading the rendered config can tell "operator didn't ask + // for an install" from "operator asked, defaults applied". + if c.Gateway.Resources != (GatewayResources{}) { + t.Fatalf("Skip:true should leave Resources zero, got %+v", c.Gateway.Resources) + } +} + +// TestGateway_DefaultResources pins the lower-than-upstream +// defaults that single-user dev clusters benefit from. Upstream +// EG ships 100m/256Mi controller and 100m/512Mi proxy, which +// oversubscribe a 2GB appliance node. Changing these values is +// a contract change for anyone running on those budgets. +func TestGateway_DefaultResources(t *testing.T) { + c := &CommonConfig{} + c.applyCommonDefaults() + if c.Gateway.Resources.Controller.CPU != "10m" { + t.Errorf("Controller.CPU: got %q, want 10m", c.Gateway.Resources.Controller.CPU) + } + if c.Gateway.Resources.Controller.Memory != "64Mi" { + t.Errorf("Controller.Memory: got %q, want 64Mi", c.Gateway.Resources.Controller.Memory) + } + if c.Gateway.Resources.Proxy.CPU != "10m" { + t.Errorf("Proxy.CPU: got %q, want 10m", c.Gateway.Resources.Proxy.CPU) + } + if c.Gateway.Resources.Proxy.Memory != "128Mi" { + t.Errorf("Proxy.Memory: got %q, want 128Mi", c.Gateway.Resources.Proxy.Memory) + } +} + +// TestGateway_PreservesExplicitResources: an operator setting +// any subset of fields keeps their explicit values; only +// unset fields default. +func TestGateway_PreservesExplicitResources(t *testing.T) { + c := &CommonConfig{Gateway: GatewayConfig{ + Resources: GatewayResources{ + Controller: ResourceRequests{CPU: "200m"}, + Proxy: ResourceRequests{Memory: "1Gi"}, + }, + }} + c.applyCommonDefaults() + if c.Gateway.Resources.Controller.CPU != "200m" { + t.Errorf("explicit Controller.CPU lost: %q", c.Gateway.Resources.Controller.CPU) + } + if c.Gateway.Resources.Controller.Memory != "64Mi" { + t.Errorf("unset Controller.Memory should default, got %q", c.Gateway.Resources.Controller.Memory) + } + if c.Gateway.Resources.Proxy.CPU != "10m" { + t.Errorf("unset Proxy.CPU should default, got %q", c.Gateway.Resources.Proxy.CPU) + } + if c.Gateway.Resources.Proxy.Memory != "1Gi" { + t.Errorf("explicit Proxy.Memory lost: %q", c.Gateway.Resources.Proxy.Memory) + } } // TestEffectiveGatewayClassName covers the helper Provision uses diff --git a/pkg/provision/docker/docker.go b/pkg/provision/docker/docker.go index 07a5860..b25269d 100644 --- a/pkg/provision/docker/docker.go +++ b/pkg/provision/docker/docker.go @@ -280,10 +280,14 @@ func Provision(ctx context.Context, cfg config.DockerConfig, logger *zap.Logger) logger.Info("envoy gateway install skipped (gateway.skip)") } else { if err := envoygateway.Install(ctx, envoygateway.Options{ - ContextName: cfg.Context, - GatewayClassName: cfg.Gateway.ClassName, - DNSHintIP: cfg.HostRoutableIP(), - Logger: logger, + ContextName: cfg.Context, + GatewayClassName: cfg.Gateway.ClassName, + DNSHintIP: cfg.HostRoutableIP(), + Logger: logger, + ControllerCPURequest: cfg.Gateway.Resources.Controller.CPU, + ControllerMemRequest: cfg.Gateway.Resources.Controller.Memory, + ProxyCPURequest: cfg.Gateway.Resources.Proxy.CPU, + ProxyMemRequest: cfg.Gateway.Resources.Proxy.Memory, }); err != nil { return nil, fmt.Errorf("install envoy gateway: %w", err) } diff --git a/pkg/provision/envoygateway/embed.go b/pkg/provision/envoygateway/embed.go index 6f129a0..c5d136e 100644 --- a/pkg/provision/envoygateway/embed.go +++ b/pkg/provision/envoygateway/embed.go @@ -25,15 +25,27 @@ const DNSHintIPAnnotation = "yolean.se/dns-hint-ip" // the configured class name. dnsHintIP is the value the provisioner // stamps under the DNSHintIPAnnotation; empty string omits the // annotations block entirely so an absent hint is distinguishable -// from a present-but-empty one. +// from a present-but-empty one. envoyProxyName, when non-empty, +// adds a parametersRef pointing at the EnvoyProxy CR of that name +// in the envoy-gateway-system namespace -- the upstream-blessed +// extension point for tuning the data-plane proxy's resources. // // Pure function so unit tests can pin the rendered shape against // a known-good baseline. -func GatewayClassYAML(name, dnsHintIP string) []byte { +func GatewayClassYAML(name, dnsHintIP, envoyProxyName string) []byte { var annotations string if dnsHintIP != "" { annotations = fmt.Sprintf(" annotations:\n %s: %s\n", DNSHintIPAnnotation, dnsHintIP) } + var parametersRef string + if envoyProxyName != "" { + parametersRef = fmt.Sprintf(` parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: %s + namespace: %s +`, envoyProxyName, Namespace) + } return []byte(fmt.Sprintf(`--- # y-cluster default GatewayClass for the bundled Envoy Gateway # install. Consumer Gateway resources reference this name via @@ -47,6 +59,72 @@ metadata: name: %s %sspec: controllerName: %s -`, name, name, annotations, EGControllerName)) +%s`, name, name, annotations, EGControllerName, parametersRef)) +} + +// EnvoyProxyName is the metadata.name of the EnvoyProxy CR +// y-cluster applies in the envoy-gateway-system namespace. The +// default GatewayClass references it via parametersRef so +// Gateways under that class inherit the tuned resources without +// any per-Gateway boilerplate. +const EnvoyProxyName = "y-cluster" + +// EnvoyProxyYAML renders the EnvoyProxy CR that tunes the +// data-plane envoy proxy pod's resource requests. cpuRequest / +// memRequest land under spec.provider.kubernetes.envoyDeployment +// .container.resources.requests. Limits are left for EG's +// defaults (and the cluster's LimitRange, if any). +// +// The CR lives in envoy-gateway-system because that's the only +// namespace EG looks at for parametersRef of GatewayClass. +// +// Pure function for unit-test pinning. +func EnvoyProxyYAML(cpuRequest, memRequest string) []byte { + return []byte(fmt.Sprintf(`--- +# y-cluster's tuning for the per-Gateway envoy proxy pod. +# Referenced by the GatewayClass via parametersRef. +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyProxy +metadata: + name: %s + namespace: %s +spec: + provider: + type: Kubernetes + kubernetes: + envoyDeployment: + container: + resources: + requests: + cpu: %s + memory: %s +`, EnvoyProxyName, Namespace, cpuRequest, memRequest)) +} + +// ControllerResourcesYAML is a partial Deployment manifest +// declaring ownership over the envoy-gateway controller +// container's resources.requests under server-side apply. The +// apply uses field-manager y-cluster; existing fields (image, +// env, replicas, container args) stay with their original +// owners. Limits are not declared -- intentional, so the +// upstream limit (currently 1Gi memory, no CPU cap) stays in +// effect. +func ControllerResourcesYAML(cpuRequest, memRequest string) []byte { + return []byte(fmt.Sprintf(`--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: %s + namespace: %s +spec: + template: + spec: + containers: + - name: envoy-gateway + resources: + requests: + cpu: %s + memory: %s +`, DeploymentName, Namespace, cpuRequest, memRequest)) } diff --git a/pkg/provision/envoygateway/embed_test.go b/pkg/provision/envoygateway/embed_test.go index 81cd918..ed1c41f 100644 --- a/pkg/provision/envoygateway/embed_test.go +++ b/pkg/provision/envoygateway/embed_test.go @@ -10,7 +10,7 @@ import ( // entirely, so an absent hint is distinguishable from // "annotation present with empty value". func TestGatewayClassYAML_NoHintIP(t *testing.T) { - got := string(GatewayClassYAML("y-cluster", "")) + got := string(GatewayClassYAML("y-cluster", "", "")) if strings.Contains(got, "annotations") { t.Fatalf("expected no annotations block:\n%s", got) } @@ -23,13 +23,16 @@ func TestGatewayClassYAML_NoHintIP(t *testing.T) { if !strings.Contains(got, "controllerName: "+EGControllerName) { t.Fatalf("missing controller name:\n%s", got) } + if strings.Contains(got, "parametersRef") { + t.Fatalf("empty envoyProxyName should omit parametersRef:\n%s", got) + } } // TestGatewayClassYAML_WithHintIP guards the qemu/docker // host-loopback shape: the dnsHintIP value lands as a single // annotation under the GatewayClass metadata. func TestGatewayClassYAML_WithHintIP(t *testing.T) { - got := string(GatewayClassYAML("y-cluster", "127.0.0.1")) + got := string(GatewayClassYAML("y-cluster", "127.0.0.1", "")) if !strings.Contains(got, "annotations:") { t.Fatalf("missing annotations block:\n%s", got) } @@ -51,7 +54,7 @@ func TestGatewayClassYAML_WithHintIP(t *testing.T) { // both metadata.name and the doc comment. The comment header line // is part of the contract -- consumers grep for it during debug. func TestGatewayClassYAML_RespectsCustomName(t *testing.T) { - got := string(GatewayClassYAML("eg", "")) + got := string(GatewayClassYAML("eg", "", "")) if !strings.Contains(got, "name: eg") { t.Fatalf("missing custom name:\n%s", got) } @@ -59,3 +62,69 @@ func TestGatewayClassYAML_RespectsCustomName(t *testing.T) { t.Fatalf("comment should reference the configured name:\n%s", got) } } + +// TestGatewayClassYAML_WithEnvoyProxyRef pins the parametersRef +// shape EG expects: group / kind / name / namespace under +// spec.parametersRef, namespace fixed to the EG namespace. +func TestGatewayClassYAML_WithEnvoyProxyRef(t *testing.T) { + got := string(GatewayClassYAML("y-cluster", "", EnvoyProxyName)) + for _, want := range []string{ + "parametersRef:", + "group: gateway.envoyproxy.io", + "kind: EnvoyProxy", + "name: " + EnvoyProxyName, + "namespace: " + Namespace, + } { + if !strings.Contains(got, want) { + t.Errorf("missing %q:\n%s", want, got) + } + } +} + +// TestEnvoyProxyYAML_ShapesResources pins the EnvoyProxy CR +// fields y-cluster actually owns: requests under provider. +// kubernetes.envoyDeployment.container.resources. +func TestEnvoyProxyYAML_ShapesResources(t *testing.T) { + got := string(EnvoyProxyYAML("10m", "128Mi")) + for _, want := range []string{ + "apiVersion: gateway.envoyproxy.io/v1alpha1", + "kind: EnvoyProxy", + "name: " + EnvoyProxyName, + "namespace: " + Namespace, + "cpu: 10m", + "memory: 128Mi", + } { + if !strings.Contains(got, want) { + t.Errorf("missing %q:\n%s", want, got) + } + } + if strings.Contains(got, "limits:") { + t.Errorf("CR should declare requests only, not limits:\n%s", got) + } +} + +// TestControllerResourcesYAML_RequestsOnly pins the partial +// Deployment shape: requests-only, container matched by name so +// SSA targets the right container, no limits/image/env claimed +// (so upstream owners keep them). +func TestControllerResourcesYAML_RequestsOnly(t *testing.T) { + got := string(ControllerResourcesYAML("10m", "64Mi")) + for _, want := range []string{ + "kind: Deployment", + "name: " + DeploymentName, + "namespace: " + Namespace, + "- name: envoy-gateway", + "cpu: 10m", + "memory: 64Mi", + } { + if !strings.Contains(got, want) { + t.Errorf("missing %q:\n%s", want, got) + } + } + if strings.Contains(got, "limits:") { + t.Errorf("patch should declare requests only:\n%s", got) + } + if strings.Contains(got, "image:") { + t.Errorf("patch must not claim image (would fight upstream owner):\n%s", got) + } +} diff --git a/pkg/provision/envoygateway/install.go b/pkg/provision/envoygateway/install.go index 23519dd..21a4a6f 100644 --- a/pkg/provision/envoygateway/install.go +++ b/pkg/provision/envoygateway/install.go @@ -61,6 +61,20 @@ type Options struct { // // Ignored when GatewayClassName is empty (no GatewayClass apply). DNSHintIP string + + // ControllerCPURequest / ControllerMemRequest set the + // controller container's resources.requests via SSA. Empty + // strings mean "leave upstream's defaults"; the provisioner + // fills these from CommonConfig.Gateway.Resources.Controller. + ControllerCPURequest string + ControllerMemRequest string + + // ProxyCPURequest / ProxyMemRequest land on the EnvoyProxy + // CR the default GatewayClass references via parametersRef. + // When both are empty, no EnvoyProxy CR is applied and the + // GatewayClass has no parametersRef -- EG uses its defaults. + ProxyCPURequest string + ProxyMemRequest string } // Install resolves the per-version install.yaml from cache @@ -124,6 +138,22 @@ func Install(ctx context.Context, opts Options) error { return fmt.Errorf("apply install.yaml: %w", err) } + // Patch the controller container's resource requests before + // the rollout wait so the wait sees the ReplicaSet we shaped. + // Empty strings here mean "leave upstream alone" -- a test + // path or a config that explicitly wants stock requests. + if opts.ControllerCPURequest != "" || opts.ControllerMemRequest != "" { + logger.Info("patching envoy-gateway controller resources", + zap.String("cpu", opts.ControllerCPURequest), + zap.String("memory", opts.ControllerMemRequest), + ) + if err := kubectlApplyStdin(ctx, opts.ContextName, + ControllerResourcesYAML(opts.ControllerCPURequest, opts.ControllerMemRequest), + ); err != nil { + return fmt.Errorf("apply controller resources: %w", err) + } + } + if opts.ReadyTimeout >= 0 { timeout := opts.ReadyTimeout if timeout == 0 { @@ -139,12 +169,32 @@ func Install(ctx context.Context, opts Options) error { } } + // Apply EnvoyProxy first so the GatewayClass.parametersRef + // resolves on first reconcile. When proxy resources aren't + // configured we skip the CR and leave the GatewayClass + // parametersRef-less -- EG uses its built-in defaults. + envoyProxyName := "" + if opts.ProxyCPURequest != "" || opts.ProxyMemRequest != "" { + envoyProxyName = EnvoyProxyName + logger.Info("applying EnvoyProxy CR", + zap.String("name", envoyProxyName), + zap.String("cpu", opts.ProxyCPURequest), + zap.String("memory", opts.ProxyMemRequest), + ) + if err := kubectlApplyStdin(ctx, opts.ContextName, + EnvoyProxyYAML(opts.ProxyCPURequest, opts.ProxyMemRequest), + ); err != nil { + return fmt.Errorf("apply EnvoyProxy: %w", err) + } + } + if opts.GatewayClassName != "" { logger.Info("applying default GatewayClass", zap.String("name", opts.GatewayClassName), zap.String("dnsHintIP", opts.DNSHintIP), + zap.String("envoyProxyRef", envoyProxyName), ) - if err := kubectlApplyStdin(ctx, opts.ContextName, GatewayClassYAML(opts.GatewayClassName, opts.DNSHintIP)); err != nil { + if err := kubectlApplyStdin(ctx, opts.ContextName, GatewayClassYAML(opts.GatewayClassName, opts.DNSHintIP, envoyProxyName)); err != nil { return fmt.Errorf("apply GatewayClass: %w", err) } } diff --git a/pkg/provision/multipass/multipass.go b/pkg/provision/multipass/multipass.go index c247cd5..4dd6154 100644 --- a/pkg/provision/multipass/multipass.go +++ b/pkg/provision/multipass/multipass.go @@ -206,9 +206,13 @@ func Provision(ctx context.Context, cfg Config, logger *zap.Logger) (*Cluster, e // non-tunnel-NAT path the CHANGE_REQUEST_HINT_IP migration // names). if err := envoygateway.Install(ctx, envoygateway.Options{ - ContextName: cfg.Context, - GatewayClassName: cfg.Gateway.ClassName, - Logger: logger, + ContextName: cfg.Context, + GatewayClassName: cfg.Gateway.ClassName, + Logger: logger, + ControllerCPURequest: cfg.Gateway.Resources.Controller.CPU, + ControllerMemRequest: cfg.Gateway.Resources.Controller.Memory, + ProxyCPURequest: cfg.Gateway.Resources.Proxy.CPU, + ProxyMemRequest: cfg.Gateway.Resources.Proxy.Memory, }); err != nil { return nil, fmt.Errorf("install envoy gateway: %w", err) } diff --git a/pkg/provision/qemu/qemu.go b/pkg/provision/qemu/qemu.go index 4f33b2f..aeabdfa 100644 --- a/pkg/provision/qemu/qemu.go +++ b/pkg/provision/qemu/qemu.go @@ -345,10 +345,14 @@ func Provision(ctx context.Context, cfg Config, logger *zap.Logger) (*Cluster, e logger.Info("envoy gateway install skipped (gateway.skip)") } else { if err := envoygateway.Install(ctx, envoygateway.Options{ - ContextName: cfg.Context, - GatewayClassName: cfg.Gateway.ClassName, - DNSHintIP: cfg.hostRoutableIP(), - Logger: logger, + ContextName: cfg.Context, + GatewayClassName: cfg.Gateway.ClassName, + DNSHintIP: cfg.hostRoutableIP(), + Logger: logger, + ControllerCPURequest: cfg.Gateway.Resources.Controller.CPU, + ControllerMemRequest: cfg.Gateway.Resources.Controller.Memory, + ProxyCPURequest: cfg.Gateway.Resources.Proxy.CPU, + ProxyMemRequest: cfg.Gateway.Resources.Proxy.Memory, }); err != nil { return nil, fmt.Errorf("install envoy gateway: %w", err) } diff --git a/pkg/provision/schema/common.schema.json b/pkg/provision/schema/common.schema.json index f6c4511..8b8d0f2 100644 --- a/pkg/provision/schema/common.schema.json +++ b/pkg/provision/schema/common.schema.json @@ -66,6 +66,10 @@ "description": "GatewayClass name. Consumer Gateway resources reference this via gatewayClassName. Ignored when skip is true.", "type": "string" }, + "resources": { + "$ref": "#/$defs/GatewayResources", + "description": "Resource requests for the bundled EG install. Defaults: controller 10m/64Mi" + }, "skip": { "description": "If true", "type": "boolean" @@ -73,6 +77,20 @@ }, "type": "object" }, + "GatewayResources": { + "additionalProperties": false, + "properties": { + "controller": { + "$ref": "#/$defs/ResourceRequests", + "description": "EG controller container requests. Default cpu 10m" + }, + "proxy": { + "$ref": "#/$defs/ResourceRequests", + "description": "Per-Gateway envoy proxy container requests. Default cpu 10m" + } + }, + "type": "object" + }, "K3sConfig": { "additionalProperties": false, "properties": { @@ -209,6 +227,20 @@ }, "type": "object" }, + "ResourceRequests": { + "additionalProperties": false, + "properties": { + "cpu": { + "description": "CPU request in Kubernetes notation (e.g. 10m", + "type": "string" + }, + "memory": { + "description": "Memory request in Kubernetes notation (e.g. 64Mi", + "type": "string" + } + }, + "type": "object" + }, "StorageConfig": { "additionalProperties": false, "properties": { diff --git a/pkg/provision/schema/docker.schema.json b/pkg/provision/schema/docker.schema.json index 700d8c8..796b644 100644 --- a/pkg/provision/schema/docker.schema.json +++ b/pkg/provision/schema/docker.schema.json @@ -65,6 +65,10 @@ "description": "GatewayClass name. Consumer Gateway resources reference this via gatewayClassName. Ignored when skip is true.", "type": "string" }, + "resources": { + "$ref": "#/$defs/GatewayResources", + "description": "Resource requests for the bundled EG install. Defaults: controller 10m/64Mi" + }, "skip": { "description": "If true", "type": "boolean" @@ -72,6 +76,20 @@ }, "type": "object" }, + "GatewayResources": { + "additionalProperties": false, + "properties": { + "controller": { + "$ref": "#/$defs/ResourceRequests", + "description": "EG controller container requests. Default cpu 10m" + }, + "proxy": { + "$ref": "#/$defs/ResourceRequests", + "description": "Per-Gateway envoy proxy container requests. Default cpu 10m" + } + }, + "type": "object" + }, "K3sConfig": { "additionalProperties": false, "properties": { @@ -208,6 +226,20 @@ }, "type": "object" }, + "ResourceRequests": { + "additionalProperties": false, + "properties": { + "cpu": { + "description": "CPU request in Kubernetes notation (e.g. 10m", + "type": "string" + }, + "memory": { + "description": "Memory request in Kubernetes notation (e.g. 64Mi", + "type": "string" + } + }, + "type": "object" + }, "StorageConfig": { "additionalProperties": false, "properties": { diff --git a/pkg/provision/schema/multipass.schema.json b/pkg/provision/schema/multipass.schema.json index 61c3a72..df86eba 100644 --- a/pkg/provision/schema/multipass.schema.json +++ b/pkg/provision/schema/multipass.schema.json @@ -8,6 +8,10 @@ "description": "GatewayClass name. Consumer Gateway resources reference this via gatewayClassName. Ignored when skip is true.", "type": "string" }, + "resources": { + "$ref": "#/$defs/GatewayResources", + "description": "Resource requests for the bundled EG install. Defaults: controller 10m/64Mi" + }, "skip": { "description": "If true", "type": "boolean" @@ -15,6 +19,20 @@ }, "type": "object" }, + "GatewayResources": { + "additionalProperties": false, + "properties": { + "controller": { + "$ref": "#/$defs/ResourceRequests", + "description": "EG controller container requests. Default cpu 10m" + }, + "proxy": { + "$ref": "#/$defs/ResourceRequests", + "description": "Per-Gateway envoy proxy container requests. Default cpu 10m" + } + }, + "type": "object" + }, "K3sConfig": { "additionalProperties": false, "properties": { @@ -213,6 +231,20 @@ }, "type": "object" }, + "ResourceRequests": { + "additionalProperties": false, + "properties": { + "cpu": { + "description": "CPU request in Kubernetes notation (e.g. 10m", + "type": "string" + }, + "memory": { + "description": "Memory request in Kubernetes notation (e.g. 64Mi", + "type": "string" + } + }, + "type": "object" + }, "StorageConfig": { "additionalProperties": false, "properties": { diff --git a/pkg/provision/schema/qemu.schema.json b/pkg/provision/schema/qemu.schema.json index 909db33..cd99139 100644 --- a/pkg/provision/schema/qemu.schema.json +++ b/pkg/provision/schema/qemu.schema.json @@ -8,6 +8,10 @@ "description": "GatewayClass name. Consumer Gateway resources reference this via gatewayClassName. Ignored when skip is true.", "type": "string" }, + "resources": { + "$ref": "#/$defs/GatewayResources", + "description": "Resource requests for the bundled EG install. Defaults: controller 10m/64Mi" + }, "skip": { "description": "If true", "type": "boolean" @@ -15,6 +19,20 @@ }, "type": "object" }, + "GatewayResources": { + "additionalProperties": false, + "properties": { + "controller": { + "$ref": "#/$defs/ResourceRequests", + "description": "EG controller container requests. Default cpu 10m" + }, + "proxy": { + "$ref": "#/$defs/ResourceRequests", + "description": "Per-Gateway envoy proxy container requests. Default cpu 10m" + } + }, + "type": "object" + }, "K3sConfig": { "additionalProperties": false, "properties": { @@ -222,6 +240,20 @@ }, "type": "object" }, + "ResourceRequests": { + "additionalProperties": false, + "properties": { + "cpu": { + "description": "CPU request in Kubernetes notation (e.g. 10m", + "type": "string" + }, + "memory": { + "description": "Memory request in Kubernetes notation (e.g. 64Mi", + "type": "string" + } + }, + "type": "object" + }, "StorageConfig": { "additionalProperties": false, "properties": {