diff --git a/deploy/k8s/README.md b/deploy/k8s/README.md index b9614b0..a160ab4 100644 --- a/deploy/k8s/README.md +++ b/deploy/k8s/README.md @@ -8,5 +8,6 @@ Ausgerollt von ops mit `kubectl` (Rolle `k3s-apps`, Playbook `playbooks/k3s.yml` | `nvidia-device-plugin.yaml` | NVIDIA Device Plugin v0.20.1, nur auf Knoten mit Label `knaster.gpu=true`; liefert `nvidia.com/gpu` | | `portainer-agent.yaml` | Portainer Agent 2.45.2 (NodePort 30778), Quelle downloads.portainer.io/ce2-45 | | `nex-speech/` | NEX-Sprachdienste auf gpu-pve52 (Playbook `playbooks/nex-speech-k3s.yml`) | +| `nex-searxng/` | NEX-Websuche SearXNG, NodePort 30880 (Playbook `playbooks/nex-searxng-k3s.yml`), Einstellungen aus dem nex-Repo | Portainer: Umgebung „Kubernetes → Agent“, Adresse `:30778`. diff --git a/deploy/k8s/nex-searxng/deployment.yaml b/deploy/k8s/nex-searxng/deployment.yaml new file mode 100644 index 0000000..cafcaed --- /dev/null +++ b/deploy/k8s/nex-searxng/deployment.yaml @@ -0,0 +1,68 @@ +# NEX-Websuche (SearXNG), umgezogen von appsrv08 (Portainer-Stack nex, Dienst searxng) nach k3s (D6-Test, 15.10.2026). +# Quelle der Einstellungen: nex-Repo deploy/nex/searxng/settings.yml (Kopie hier, als ConfigMap eingehängt). +# SEARXNG_SECRET liegt im Secret nex-searxng (legt playbooks/nex-searxng-k3s.yml an, nicht im Repo). +# Zustandslos, kein Volume. Erreichbar im LAN über NodePort 30880 auf jeder Knoten-Adresse; die Hermes-Agenten +# auf appsrv08 brauchen dann SEARXNG_URL=http://:30880 statt http://nex-searxng:8080. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nex-searxng + namespace: nex + labels: {app: nex-searxng} +spec: + replicas: 1 + selector: + matchLabels: {app: nex-searxng} + template: + metadata: + labels: {app: nex-searxng} + spec: + # /etc/searxng muss beschreibbar sein (Image legt dort Dateien an): emptyDir, settings.yml aus der ConfigMap kopiert + initContainers: + - name: settings + image: searxng/searxng:2026.10.4-d48c4b555 + command: ["sh", "-c", "cp /config/settings.yml /etc/searxng/settings.yml"] + volumeMounts: + - {name: config, mountPath: /config, readOnly: true} + - {name: etc, mountPath: /etc/searxng} + containers: + - name: searxng + image: searxng/searxng:2026.10.4-d48c4b555 + ports: + - {name: http, containerPort: 8080} + env: + - name: SEARXNG_SECRET + valueFrom: + secretKeyRef: {name: nex-searxng, key: SEARXNG_SECRET} + - {name: GRANIAN_HOST, value: "0.0.0.0"} # IPv4: sonst erscheint die Absender-Adresse nur als ::ffff + - {name: GRANIAN_LOG_ACCESS_ENABLED, value: "true"} + - {name: GRANIAN_LOG_ACCESS_FMT, value: '[%(time)s] %(addr)s "%(method)s %(path)s" %(status)d %(dt_ms).0fms'} # ohne Query-String: keine Suchbegriffe im Log + resources: + requests: {cpu: 100m, memory: 128Mi} + limits: {memory: 256Mi} + volumeMounts: + - {name: etc, mountPath: /etc/searxng} + readinessProbe: + httpGet: {path: /healthz, port: http} + periodSeconds: 10 + livenessProbe: + httpGet: {path: /healthz, port: http} + periodSeconds: 30 + failureThreshold: 3 + volumes: + - name: config + configMap: {name: nex-searxng-settings} + - name: etc + emptyDir: {sizeLimit: 16Mi} +--- +apiVersion: v1 +kind: Service +metadata: + name: nex-searxng + namespace: nex + labels: {app: nex-searxng} +spec: + type: NodePort + selector: {app: nex-searxng} + ports: + - {name: http, port: 8080, targetPort: http, nodePort: 30880} diff --git a/deploy/k8s/nex-searxng/settings.yml b/deploy/k8s/nex-searxng/settings.yml new file mode 100644 index 0000000..8239051 --- /dev/null +++ b/deploy/k8s/nex-searxng/settings.yml @@ -0,0 +1,35 @@ +# SearXNG für die NEX-Agenten (Plan docs/plans/2026-10-06-nex-searxng.md). Nur intern im Netz llm-internal erreichbar. +# Quelle für den Dienst searxng in deploy/nex/docker-compose.appsrv08.yml: dort als base64 im Command eingebettet +# (keine Bind-Mounts im Portainer-Stack). Nach jeder Änderung neu einbetten: base64 -w0 deploy/nex/searxng/settings.yml +# secret_key kommt aus der Env SEARXNG_SECRET (Stack-Variable NEX_SEARXNG_SECRET), nicht aus dieser Datei. +# Engines am 2026-10-06 von appsrv08 aus getestet: duckduckgo und bing liefern, wikipedia ergänzt Lexikon-Treffer; +# google („access denied“), brave („too many requests“), qwant (CAPTCHA) blockiert, startpage/mojeek im Image inaktiv. +use_default_settings: + engines: + keep_only: [duckduckgo, bing, wikipedia] + +general: + instance_name: NEX-Suche + enable_metrics: false + +server: + limiter: false # nur interne Clients (Agenten-Container), kein Bot-Schutz nötig + image_proxy: false + public_instance: false + +search: + safe_search: 1 + default_lang: de + autocomplete: "" + formats: [html, json] # Hermes fragt GET /search?q=…&format=json&pageno=1 (plugins/web/searxng/provider.py) + +outgoing: + request_timeout: 3.0 + max_request_timeout: 6.0 + +engines: + - name: bing + disabled: false # im Image standardmäßig aus + # Wikipedia liefert sonst nur eine Infobox; Hermes liest ausschließlich "results" + - name: wikipedia + display_type: ["infobox", "list"] diff --git a/platform/proxmox/ansible/playbooks/nex-searxng-k3s.yml b/platform/proxmox/ansible/playbooks/nex-searxng-k3s.yml new file mode 100644 index 0000000..7257e85 --- /dev/null +++ b/platform/proxmox/ansible/playbooks/nex-searxng-k3s.yml @@ -0,0 +1,57 @@ +# NEX-Websuche (SearXNG) in k3s (D6-Umzugstest). Läuft auf ops: ansible-playbook playbooks/nex-searxng-k3s.yml +# Secret SEARXNG_SECRET wird einmal erzeugt und bleibt im Cluster (nicht im Repo, nicht auf ops). +- name: SearXNG nach k3s + hosts: localhost + connection: local + gather_facts: false + vars: + manifests: "{{ playbook_dir }}/../../../../deploy/k8s/nex-searxng" + tasks: + - name: Namespace nex + ansible.builtin.command: kubectl apply -f {{ playbook_dir }}/../../../../deploy/k8s/nex-speech/namespace.yaml + register: ns + changed_when: "'created' in ns.stdout" + + - name: Secret vorhanden? + ansible.builtin.command: kubectl -n nex get secret nex-searxng + register: secret + failed_when: false + changed_when: false + + - name: Secret anlegen (zufällig, 64 Hex-Zeichen) + ansible.builtin.shell: kubectl -n nex create secret generic nex-searxng --from-literal=SEARXNG_SECRET=$(openssl rand -hex 32) + when: secret.rc != 0 + no_log: true + + - name: ConfigMap aus settings.yml + ansible.builtin.shell: > + kubectl -n nex create configmap nex-searxng-settings --from-file=settings.yml={{ manifests }}/settings.yml + --dry-run=client -o yaml | kubectl apply -f - + register: cm + changed_when: "'configured' in cm.stdout or 'created' in cm.stdout" + + - name: Deployment und Service + ansible.builtin.command: kubectl apply -f {{ manifests }}/deployment.yaml + register: dep + changed_when: "'configured' in dep.stdout or 'created' in dep.stdout" + + - name: Neustart, wenn sich die Einstellungen geändert haben + ansible.builtin.command: kubectl -n nex rollout restart deployment/nex-searxng + when: cm.changed and not dep.changed + + - name: Warten, bis bereit + ansible.builtin.command: kubectl -n nex rollout status deployment/nex-searxng --timeout=300s + changed_when: false + + - name: Suchtest über NodePort (Knoten k3s) + ansible.builtin.uri: + url: "http://{{ hostvars['k3s'].ansible_host }}:30880/search?q=Proxmox&format=json" + return_content: true + register: probe + until: probe.status == 200 and (probe.json.results | length) > 0 + retries: 6 + delay: 5 + + - name: Ergebnis + ansible.builtin.debug: + msg: "{{ probe.json.results | length }} Treffer, erster: {{ probe.json.results[0].url }}"