From 3f6f1edcb3f15bfd5fa68a41abb1b160436fa355 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Fri, 28 Aug 2026 12:59:19 +0200 Subject: [PATCH] fix(paperless): probe a real Django view instead of a TCP connect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Under the split where gatus watches from outside and Kubernetes watches the pods, paperless was the weakest link on the Kubernetes side: both probes were tcpSocket, which only proves gunicorn accepted a connection. A worker deadlock or a broken Django config would keep the pod Ready. The existing comment explained why: HTTP probes against /api/ flood `paperless.auth`, because ENABLE_HTTP_REMOTE_USER makes the remote-user middleware log every unauthenticated API request. That was a real constraint and the TCP fallback was a deliberate trade, not an oversight. It is also narrower than the comment assumed. The middleware only covers the API surface, and /accounts/login/ is a plain Django view. Measured against the running pod: 4 requests to /accounts/login/ -> 0 new paperless.auth lines 4 requests to /api/ -> 4 new paperless.auth lines So the noise is avoidable without giving up an HTTP probe. /accounts/login/ renders a template, which means URL routing, settings and the template engine all have to be working. It does not prove Postgres is reachable — nothing cheap on this surface does, and the comment now says so rather than implying more. ALLOWED_HOSTS was the other way this could have failed: the kubelet sends the pod IP as Host, and a restrictive value would answer 400 and crashloop the pod. PAPERLESS_ALLOWED_HOSTS is "*", so it is safe today, and the comment records what narrowing it would require. Timings are unchanged — the 120s liveness delay is there for paperless's slow start and nothing here makes it start faster. Validated with Flux's own field manager. Applying as any other manager reports the deployment invalid with two handler types, because tcpSocket stays owned by kustomize-controller and is not cleared by a foreign apply; that is an artifact of the check, not of the manifest. --- kubernetes/apps/paperless/paperless.yaml | 30 +++++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/kubernetes/apps/paperless/paperless.yaml b/kubernetes/apps/paperless/paperless.yaml index 32de58cb..c0566eb0 100644 --- a/kubernetes/apps/paperless/paperless.yaml +++ b/kubernetes/apps/paperless/paperless.yaml @@ -155,21 +155,33 @@ spec: - name: bridge-cert mountPath: /shared readOnly: true - # TCP probes — HTTP probes against /api/ flooded the - # `paperless.auth` logger every 10 s with "No authentication - # provided from private IP …". With ENABLE_HTTP_REMOTE_USER - # the middleware logs every unauth API request, and there's - # no public health endpoint to probe. TCP confirms gunicorn - # is bound; that's the same liveness signal we'd get from - # /api/ minus the noise. + # /accounts/login/ rather than /api/ or a TCP connect. + # + # /api/ is unusable as a probe target: with ENABLE_HTTP_REMOTE_USER + # the remote-user middleware logs "No authentication provided from + # private IP …" for every unauthenticated API request, so probing it + # floods `paperless.auth`. Measured: four requests to /api/ produce + # four log lines, four to /accounts/login/ produce none — the + # middleware only covers the API surface. + # + # A TCP connect avoids the noise but only proves gunicorn accepted a + # socket. /accounts/login/ renders a real Django view, so URL + # routing, settings and templating all have to work. It does not + # prove Postgres is healthy; nothing cheap here does. + # + # Safe against ALLOWED_HOSTS: the kubelet sends the pod IP as Host, + # and PAPERLESS_ALLOWED_HOSTS is "*". Narrowing that value later + # means giving these probes an explicit Host header. livenessProbe: - tcpSocket: + httpGet: + path: /accounts/login/ port: http initialDelaySeconds: 120 periodSeconds: 30 timeoutSeconds: 10 readinessProbe: - tcpSocket: + httpGet: + path: /accounts/login/ port: http initialDelaySeconds: 30 periodSeconds: 10