ai: suppress backend content logs and verify isolated failures

This commit is contained in:
jenkins 2026-09-28 19:18:27 -05:00
parent 45e9fa5635
commit 1e084493a9
4 changed files with 55 additions and 6 deletions

View File

@ -30,7 +30,9 @@ spec:
args: args:
- | - |
while ! test -f /models/.pilot-v1-ready || ! test -f /reservation/reserved; do sleep 5; done while ! test -f /models/.pilot-v1-ready || ! test -f /reservation/reserved; do sleep 5; done
exec ollama serve # Backend parser diagnostics can contain submitted schema text.
# Keep request status/timing at the gateway; retain no backend output.
exec ollama serve >/dev/null 2>&1
env: env:
- {name: OLLAMA_HOST, value: "0.0.0.0:11434"} - {name: OLLAMA_HOST, value: "0.0.0.0:11434"}
- {name: OLLAMA_MODELS, value: /models} - {name: OLLAMA_MODELS, value: /models}

View File

@ -202,9 +202,12 @@ is accepted from clients or returned to them.
Traefik has access logs and tracing disabled. Gateway logs contain only a fixed Traefik has access logs and tracing disabled. Gateway logs contain only a fixed
route category and status; errors never echo request fields or backend bodies. route category and status; errors never echo request fields or backend bodies.
Ollama's debug logging is disabled; observed logs contain HTTP/timing metadata. Ollama's stdout/stderr are suppressed, including runner output, so backend parser
diagnostics cannot persist submitted schema text. Health, token counts, latency,
and GPU metrics remain available.
Both inference and gateway pods carry `fluentbit.io/exclude: "true"`, honored by Both inference and gateway pods carry `fluentbit.io/exclude: "true"`, honored by
the deployed collector. Fluent Bit also explicitly excludes their log paths. the deployed collector's existing `K8S-Logging.Exclude On` filter. No global
collector configuration change is required.
This matters because central OpenSearch uses Longhorn storage whose configured This matters because central OpenSearch uses Longhorn storage whose configured
backup target is external B2 (`s3://atlas-soteria@us-west-004/`). The endpoint does backup target is external B2 (`s3://atlas-soteria@us-west-004/`). The endpoint does
not send its logs or contents into that pipeline. Existing OpenTelemetry exports not send its logs or contents into that pipeline. Existing OpenTelemetry exports
@ -216,14 +219,15 @@ request tracing. The model volume is local-path, outside Longhorn backups.
Changes are Git/Flux managed: the dedicated MetalLB LAN address, Traefik LAN Changes are Git/Flux managed: the dedicated MetalLB LAN address, Traefik LAN
LoadBalancer, allowlist/prefix ingress, scoped Vault token, restricted gateway LoadBalancer, allowlist/prefix ingress, scoped Vault token, restricted gateway
listener/NetworkPolicy, native request validation, model pins, timeout transport, listener/NetworkPolicy, native request validation, model pins, timeout transport,
and Fluent Bit exclusions. The GPU reservation and dedicated runtime are also and pod-scoped log collection exclusions. The GPU reservation and dedicated runtime are also
Flux managed; the existing Jetson deployment remains untouched. Flux managed; the existing Jetson deployment remains untouched.
To disable the endpoint, remove `model-gate-lan-ingress.yaml` from To disable the endpoint, remove `model-gate-lan-ingress.yaml` from
`services/hermes/kustomization.yaml`, commit and push the reviewed change, then `services/hermes/kustomization.yaml`, commit and push the reviewed change, then
reconcile `hermes` with source. Flux pruning removes the route and its middleware reconcile `hermes` with source. Flux pruning removes the route and its middleware
and transport. Revert that commit to restore it. This does not restart Ollama. and transport. Revert that commit to restore it. This does not restart Ollama.
Keep log exclusions while any real-data inference remains enabled. To roll back Keep backend output suppression and pod log exclusions while any real-data
inference remains enabled. To roll back
code, revert the relevant deployment commit(s) through Git, retain the log code, revert the relevant deployment commit(s) through Git, retain the log
exclusions, and reconcile `hermes`; do not use manual kubectl edits. exclusions, and reconcile `hermes`; do not use manual kubectl edits.

View File

@ -74,7 +74,7 @@ spec:
Name tail Name tail
Tag kube.* Tag kube.*
Path /var/log/containers/*.log Path /var/log/containers/*.log
Exclude_Path /var/log/containers/*_POD_*.log,/var/log/containers/ollama-*_ai_ollama-*.log,/var/log/containers/hermes-model-gate-*_hermes_model-gate-*.log Exclude_Path /var/log/containers/*_POD_*.log
Parser cri Parser cri
Mem_Buf_Limit 50MB Mem_Buf_Limit 50MB
Skip_Long_Lines On Skip_Long_Lines On

View File

@ -2,6 +2,8 @@
import io import io
import json import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import threading
from urllib.error import HTTPError from urllib.error import HTTPError
from urllib.request import ProxyHandler from urllib.request import ProxyHandler
@ -83,3 +85,44 @@ def test_timeout_releases_capacity_and_redacts_error(monkeypatch):
assert status == 504 and "PRIVATE_PROMPT" not in json.dumps(result) assert status == 504 and "PRIVATE_PROMPT" not in json.dumps(result)
assert api._inference.acquire(blocking=False) assert api._inference.acquire(blocking=False)
api._inference.release() api._inference.release()
@pytest.mark.parametrize("failure,expected", [(503, 503), (307, 502)])
def test_isolated_http_backend_failure_never_routes_elsewhere(monkeypatch, failure, expected):
"""A fake local backend fails after preflight; no shared model is contacted."""
calls = []
class Backend(BaseHTTPRequestHandler):
"""Emulate metadata and a failing native generation operation."""
def do_GET(self):
calls.append(self.path)
result = ({"version": api.RUNTIME} if self.path == "/api/version" else
{"models": [{"name": api.MODEL, "digest": api.DIGEST}]})
self.send_response(200)
self.end_headers()
self.wfile.write(json.dumps(result).encode())
def do_POST(self):
calls.append(self.path)
self.rfile.read(int(self.headers["Content-Length"]))
self.send_response(failure)
self.send_header("Location", "/unapproved-fallback")
self.end_headers()
self.wfile.write(b'{"error":"PRIVATE_UPSTREAM_BODY"}')
def log_message(self, *args):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), Backend)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
monkeypatch.setattr(api, "UPSTREAM", f"http://127.0.0.1:{server.server_port}")
try:
status, result = api.generate(body())
assert status == expected and "PRIVATE_UPSTREAM_BODY" not in json.dumps(result)
assert calls == ["/api/version", "/api/tags", "/api/generate"]
finally:
server.shutdown()
server.server_close()
thread.join(timeout=2)