diff --git a/backend/tests/test_helm_init_containers.py b/backend/tests/test_helm_init_containers.py new file mode 100644 index 0000000000..71681805b6 --- /dev/null +++ b/backend/tests/test_helm_init_containers.py @@ -0,0 +1,51 @@ +"""The gateway's init containers must follow the chart's image and resource values. + +``init-home`` and ``init-extensions`` hard-coded ``busybox:1.36`` and set no +resources. Clusters that pull only from a mirror could not start the gateway, and +a namespace ResourceQuota on ``limits.*`` rejected the pod, while every main +container already has configurable images and requests+limits. +""" + +from __future__ import annotations + +import shutil +import subprocess +from pathlib import Path + +import pytest +import yaml + +REPO_ROOT = Path(__file__).resolve().parents[2] +CHART = REPO_ROOT / "deploy" / "helm" / "deer-flow" + + +def _init_containers(*settings: str) -> list[dict]: + helm = shutil.which("helm") + if helm is None: + pytest.skip("helm is unavailable") + command = [helm, "template", "deer-flow", str(CHART)] + for setting in settings: + command.extend(["--set", setting]) + rendered = subprocess.run(command, check=True, capture_output=True, text=True, encoding="utf-8").stdout + gateways = [document for document in yaml.safe_load_all(rendered) if isinstance(document, dict) and document.get("kind") == "Deployment" and document["metadata"]["name"].endswith("-gateway")] + assert len(gateways) == 1 + init_containers = gateways[0]["spec"]["template"]["spec"]["initContainers"] + assert [container["name"] for container in init_containers] == ["init-home", "init-extensions"] + return init_containers + + +def test_init_containers_keep_busybox_with_requests_and_limits_by_default() -> None: + for container in _init_containers(): + assert container["image"] == "busybox:1.36" + assert set(container["resources"]) == {"requests", "limits"} + assert set(container["resources"]["limits"]) == {"cpu", "memory"} + + +def test_init_containers_use_configured_image_and_resources() -> None: + for container in _init_containers( + "gateway.initContainers.image.repository=registry.example.com/mirror/busybox", + "gateway.initContainers.image.tag=1.36.1", + "gateway.initContainers.resources.limits.memory=32Mi", + ): + assert container["image"] == "registry.example.com/mirror/busybox:1.36.1" + assert container["resources"]["limits"]["memory"] == "32Mi" diff --git a/deploy/helm/deer-flow/README.md b/deploy/helm/deer-flow/README.md index 4260d043cd..c4e10d1e00 100644 --- a/deploy/helm/deer-flow/README.md +++ b/deploy/helm/deer-flow/README.md @@ -119,6 +119,21 @@ secrets: # add channel tokens, search keys, etc. as needed ``` +`image.registry` prefixes the DeerFlow gateway, frontend, and provisioner +images. In a cluster that pulls only from a mirror, set the full BusyBox +repository for `init-home` and `init-extensions` in the same values file. + +```yaml +gateway: + initContainers: + image: + repository: registry.example.com/mirror/busybox + tag: "1.36" +``` + +Set `nginx.image.repository`, `postgresql.image.repository`, and +`redis.image.repository` separately for the corresponding bundled services. + The default ingress annotations permit a 100 MiB local `.skill` archive plus multipart framing, stream request bodies without ingress buffering, and allow up to 600 seconds for a response, which the API requests that wait on a model @@ -485,7 +500,8 @@ triggers a rolling restart. **Resource defaults.** Every workload ships with modest requests+limits in `values.yaml`; override per workload (`gateway.resources`, `frontend.resources`, `nginx.resources`, `provisioner.resources`, `postgresql.primary.resources`, -`redis.primary.resources`). +`redis.primary.resources`). The gateway's init containers have their own +`gateway.initContainers.resources` and `gateway.initContainers.image`. ### Not yet enforced (deferred hardening) diff --git a/deploy/helm/deer-flow/templates/_helpers.tpl b/deploy/helm/deer-flow/templates/_helpers.tpl index 8812a73c88..7e079f6817 100644 --- a/deploy/helm/deer-flow/templates/_helpers.tpl +++ b/deploy/helm/deer-flow/templates/_helpers.tpl @@ -59,6 +59,11 @@ imagePullSecrets: {{- printf "%s:%s" .Values.nginx.image.repository .Values.nginx.image.tag -}} {{- end -}} +{{/* Image of the gateway's init containers (init-home, init-extensions). */}} +{{- define "deer-flow.initImage" -}} +{{- printf "%s:%s" .Values.gateway.initContainers.image.repository .Values.gateway.initContainers.image.tag -}} +{{- end -}} + {{/* PVC name for the .deer-flow home directory. */}} {{- define "deer-flow.homePVC" -}} {{- printf "%s-home" (include "deer-flow.fullname" .) -}} diff --git a/deploy/helm/deer-flow/templates/gateway-deployment.yaml b/deploy/helm/deer-flow/templates/gateway-deployment.yaml index df1d73aa05..77924d344e 100644 --- a/deploy/helm/deer-flow/templates/gateway-deployment.yaml +++ b/deploy/helm/deer-flow/templates/gateway-deployment.yaml @@ -51,17 +51,19 @@ spec: # API updates land in the home volume (PVC or pod-local emptyDir). initContainers: - name: init-home - image: busybox:1.36 + image: {{ include "deer-flow.initImage" . }} command: ["sh", "-c", "mkdir -p /home-pvc/deer-flow/data /home-pvc/deer-flow/extensions-config"] securityContext: allowPrivilegeEscalation: false capabilities: drop: ["ALL"] + resources: + {{- toYaml .Values.gateway.initContainers.resources | nindent 12 }} volumeMounts: - name: home mountPath: /home-pvc - name: init-extensions - image: busybox:1.36 + image: {{ include "deer-flow.initImage" . }} command: ["sh", "-c"] args: - | @@ -73,6 +75,8 @@ spec: allowPrivilegeEscalation: false capabilities: drop: ["ALL"] + resources: + {{- toYaml .Values.gateway.initContainers.resources | nindent 12 }} volumeMounts: - name: extensions-seed mountPath: /extensions-seed diff --git a/deploy/helm/deer-flow/values.yaml b/deploy/helm/deer-flow/values.yaml index 142a5f187e..4ca23cd42c 100644 --- a/deploy/helm/deer-flow/values.yaml +++ b/deploy/helm/deer-flow/values.yaml @@ -104,6 +104,20 @@ gateway: limits: cpu: "2" memory: 2Gi + # Init containers (init-home, init-extensions) that prepare the home volume + # before the gateway starts. Point `image` at a mirror if your nodes cannot + # pull from Docker Hub. + initContainers: + image: + repository: busybox + tag: "1.36" + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 100m + memory: 64Mi # -- Frontend (Next.js) deployment. frontend: