A Secret volume is owned by root and the sidecar runs as 65532, so defaultMode 0400 left it unable to open /var/run/llm-key/key: the container restarted with 'permission denied' on every start and never minted a token. Set mode 0440 and give the pod fsGroup 65532, so the group that owns the volume can read the key while nothing else on the host can.
132 lines
5.1 KiB
YAML
132 lines
5.1 KiB
YAML
# Secret paperless-ai-config managed via SOPS (argocd/secrets/paperless-ai-secrets.enc.yaml)
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: paperless-ai
|
|
namespace: paperless
|
|
labels:
|
|
app.kubernetes.io/name: paperless-ai
|
|
spec:
|
|
replicas: 1
|
|
# Recreate, not RollingUpdate: paperless-ai-data is a single RWO PVC. With
|
|
# RollingUpdate the new pod tries to mount the PVC while the old pod still
|
|
# holds it -> "Multi-Attach error" / stuck Init:0/1 forever, rollout wedged
|
|
# at "1 desired, 1 ready, 1 updated, 1 unavailable". Recreate terminates the
|
|
# old pod first so the PVC detaches before the new one starts. Same pattern
|
|
# as the Forgejo gitea Deployment.
|
|
strategy:
|
|
type: Recreate
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: paperless-ai
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/name: paperless-ai
|
|
spec:
|
|
tolerations:
|
|
- key: node-role.kubernetes.io/control-plane
|
|
operator: Exists
|
|
effect: NoSchedule
|
|
volumes:
|
|
- name: paperless-ai-data
|
|
persistentVolumeClaim:
|
|
claimName: paperless-ai-data
|
|
- name: paperless-ai-env-config
|
|
configMap:
|
|
name: paperless-ai-env
|
|
- name: llm-key
|
|
secret:
|
|
secretName: paperless-llm-key
|
|
# 0440 with the pod's fsGroup. A Secret volume is owned by root, and the
|
|
# vendor container runs as 65532, so 0400 left the sidecar unable to read
|
|
# its own key: "permission denied" on every start.
|
|
defaultMode: 0440
|
|
# No init container. The token used to be fetched once at pod start with a
|
|
# client_credentials grant against a web app, which cannot work: a web app has
|
|
# no such grant, and even a token that was minted would be dead within the hour
|
|
# while this pod runs for weeks.
|
|
#
|
|
# llm-token-vendor runs beside the app instead. It signs a machine-key assertion,
|
|
# exchanges it for a token, and rewrites /app/data/llm_token.txt at 80% of the
|
|
# token lifetime. The vendor image keeps reading CUSTOM_API_KEY_FILE unchanged.
|
|
# The key is re-read on every mint, so a rotation lands without a restart.
|
|
securityContext:
|
|
fsGroup: 65532
|
|
fsGroupChangePolicy: OnRootMismatch
|
|
containers:
|
|
- name: llm-token-vendor
|
|
image: forgejo.riotpiao.com/riotpiao-poimen/llm-token-vendor:0.1.0
|
|
imagePullPolicy: Always
|
|
args:
|
|
- --token-url=https://zitadel.riotpiao.com/oauth/v2/token
|
|
- --issuer=https://zitadel.riotpiao.com
|
|
- --user-id=397072668735570071
|
|
- --key-id=397072674272051351
|
|
- --key-file=/var/run/llm-key/key
|
|
- --scope=openid urn:zitadel:iam:org:project:id:396757796898471937:aud
|
|
- --out=/app/data/llm_token.txt
|
|
volumeMounts:
|
|
- {name: paperless-ai-data, mountPath: /app/data}
|
|
- {name: llm-key, mountPath: /var/run/llm-key, readOnly: true}
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities: {drop: ["ALL"]}
|
|
runAsNonRoot: true
|
|
runAsUser: 65532
|
|
seccompProfile: {type: RuntimeDefault}
|
|
resources:
|
|
requests: {cpu: 10m, memory: 32Mi}
|
|
limits: {cpu: 100m, memory: 64Mi}
|
|
- name: paperless-ai
|
|
image: clusterzx/paperless-ai:latest
|
|
env:
|
|
# Paperless-ngx connection
|
|
- name: PAPERLESS_API_URL
|
|
value: "http://paperless.paperless.svc.cluster.local:8000"
|
|
- name: PAPERLESS_API_TOKEN
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: paperless-ai-config
|
|
key: PAPERLESS_API_TOKEN
|
|
- name: PAPERLESS_USERNAME
|
|
value: "admin"
|
|
# LLM API — via public gateway with JWT auth
|
|
- name: AI_PROVIDER
|
|
value: "custom"
|
|
- name: CUSTOM_BASE_URL
|
|
value: "https://api.riotpiao.com/v1"
|
|
# Token will be read from file at runtime by the application
|
|
# The init container fetches it and stores in /app/data/llm_token.txt
|
|
- name: CUSTOM_API_KEY_FILE
|
|
value: "/app/data/llm_token.txt"
|
|
- name: CUSTOM_MODEL
|
|
value: "qwen2.5:3b-instruct"
|
|
# Behavior - scan for new documents and tag them
|
|
- name: SCAN_INTERVAL
|
|
value: "60"
|
|
- name: PROCESS_PREDEFINED_DOCUMENTS
|
|
value: "no"
|
|
- name: ADD_AI_TAG
|
|
value: "yes"
|
|
- name: AI_TAG_NAME
|
|
value: "ai-processed"
|
|
- name: USE_PROMPT_TAGS
|
|
value: "yes"
|
|
- name: ADD_AI_DESCRIPTIONS
|
|
value: "yes"
|
|
volumeMounts:
|
|
- name: paperless-ai-data
|
|
mountPath: /app/data
|
|
- name: paperless-ai-env-config
|
|
mountPath: /app/data/.env
|
|
subPath: paperless-ai-config.env
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 512Mi
|
|
limits:
|
|
cpu: "1"
|
|
memory: 2Gi
|