Files
fil/docs/guides/kubernetes.md
Henrik Jess Nielsen b4c07d3693
All checks were successful
Deploy fil (kreuzberg) / deploy (push) Successful in 49s
Nomad changes
2026-06-01 23:40:55 +02:00

531 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Kubernetes Deployment <span class="version-badge new">v4.2.2</span>
Deploy Kreuzberg to Kubernetes with proper OCR configuration, permissions, and health checks.
## Helm Chart <span class="version-badge new">v4.8.4</span>
Deploy via the official Helm chart (OCI artifact on GHCR).
### Install
```bash title="Terminal"
helm install kreuzberg oci://ghcr.io/kreuzberg-dev/charts/kreuzberg --version 4.8.4
```
### Configure
Override defaults with a `values.yaml` file:
```yaml title="values.yaml"
# NOTE: cache.enabled=true uses ReadWriteOnce by default; keep replicaCount: 1
# with RWO storage or switch to ReadWriteMany before increasing replicas.
replicaCount: 1
image:
tag: "4.8.4"
kreuzberg:
logLevel: "info"
ocrLanguage: "eng"
resources:
requests:
memory: "1Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
cache:
enabled: true
size: 5Gi
ingress:
enabled: true
className: "nginx"
hosts:
- host: kreuzberg.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: kreuzberg-tls
hosts:
- kreuzberg.example.com
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
podDisruptionBudget:
enabled: true
minAvailable: 1
```
```bash title="Terminal"
helm install kreuzberg oci://ghcr.io/kreuzberg-dev/charts/kreuzberg \
--version 4.8.4 \
-f values.yaml
```
### Upgrade
```bash title="Terminal"
helm upgrade kreuzberg oci://ghcr.io/kreuzberg-dev/charts/kreuzberg --version 4.8.4
```
### What's Included
The chart creates the following resources:
| Resource | Description | Conditional |
| ----------------------- | ---------------------------------------------------------- | ----------------------------- |
| Deployment | Main application with health probes and security hardening | Always |
| Service | ClusterIP service on port 80 → 8000 | Always |
| ServiceAccount | Dedicated service account | Always |
| PersistentVolumeClaim | Cache for embedding models and assets | `cache.enabled` |
| Ingress | HTTP(S) ingress with TLS | `ingress.enabled` |
| HorizontalPodAutoscaler | CPU/memory-based autoscaling | `autoscaling.enabled` |
| PodDisruptionBudget | Availability during disruptions | `podDisruptionBudget.enabled` |
All values are documented in the chart's [`values.yaml`](https://github.com/kreuzberg-dev/kreuzberg/blob/main/charts/kreuzberg/values.yaml).
---
## Quick Start
```yaml title="minimal-deployment.yaml"
apiVersion: apps/v1
kind: Deployment
metadata:
name: kreuzberg-api
spec:
replicas: 2
selector:
matchLabels:
app: kreuzberg
template:
metadata:
labels:
app: kreuzberg
spec:
containers:
- name: kreuzberg
image: ghcr.io/kreuzberg-dev/kreuzberg:latest
ports:
- containerPort: 8000
name: http
env:
- name: RUST_LOG
value: "info"
- name: TESSDATA_PREFIX
value: "/usr/share/tesseract-ocr/5/tessdata"
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: kreuzberg-api
spec:
selector:
app: kreuzberg
ports:
- protocol: TCP
port: 80
targetPort: 8000
type: LoadBalancer
```
```bash title="Terminal"
kubectl apply -f minimal-deployment.yaml
```
## Tesseract Configuration
### TESSDATA_PREFIX (Critical)
Without `TESSDATA_PREFIX`, OCR silently falls back to non-OCR extraction. Official images ship Tesseract 5.x with tessdata at `/usr/share/tesseract-ocr/5/tessdata/`.
```yaml
env:
- name: TESSDATA_PREFIX
value: "/usr/share/tesseract-ocr/5/tessdata"
- name: KREUZBERG_OCR_LANGUAGE
value: "eng"
- name: KREUZBERG_CACHE_DIR
value: "/app/.kreuzberg"
- name: HF_HOME
value: "/app/.kreuzberg/huggingface"
```
**Pre-installed languages:** `eng`, `spa`, `fra`, `deu`, `ita`, `por`, `chi_sim`, `chi_tra`, `jpn`, `ara`, `rus`, `hin`
!!! Note "Tesseract Version" The path varies by version. Verify yours with `tesseract --version` inside the container if using a custom base image.
### Custom Languages via ConfigMap
```bash title="Terminal"
kubectl create configmap tessdata \
--from-file=/path/to/eng.traineddata \
--from-file=/path/to/deu.traineddata
```
```yaml
spec:
containers:
- name: kreuzberg
env:
- name: TESSDATA_PREFIX
value: "/etc/tessdata"
volumeMounts:
- name: tessdata
mountPath: /etc/tessdata
volumes:
- name: tessdata
configMap:
name: tessdata
```
For large custom language sets, use a PVC instead of a ConfigMap.
### Verify Tesseract
```bash title="Terminal"
kubectl exec -it deployment/kreuzberg-api -- tesseract --version
kubectl exec -it deployment/kreuzberg-api -- tesseract --list-langs
kubectl exec -it deployment/kreuzberg-api -- printenv TESSDATA_PREFIX
```
## Permissions
Kreuzberg runs as non-root (UID 1000, GID 1000). Fix PVC permissions with either approach:
=== "Init Container"
```yaml
spec:
initContainers:
- name: init-permissions
image: busybox:1.37-glibc
command: ['sh', '-c', 'chown -R 1000:1000 /app/.kreuzberg']
securityContext:
runAsUser: 0
allowPrivilegeEscalation: false
capabilities:
add: ["CHOWN"]
drop: ["ALL"]
volumeMounts:
- name: cache
mountPath: /app/.kreuzberg
containers:
- name: kreuzberg
volumeMounts:
- name: cache
mountPath: /app/.kreuzberg
```
=== "fsGroup"
```yaml
spec:
securityContext:
fsGroup: 1000
containers:
- name: kreuzberg
securityContext:
runAsUser: 1000
runAsGroup: 1000
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
```
## Health Checks
```yaml
containers:
- name: kreuzberg
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 2
startupProbe:
httpGet:
path: /health
port: 8000
periodSeconds: 10
failureThreshold: 30
```
## Logging
```yaml
env:
- name: RUST_LOG
value: "kreuzberg=debug,warn"
```
Levels: `trace`, `debug`, `info`, `warn`, `error`
```bash title="Terminal"
kubectl logs deployment/kreuzberg-api --tail=50
kubectl logs deployment/kreuzberg-api -f
kubectl logs deployment/kreuzberg-api --previous
```
## Production Deployment
Full production manifest with namespace, PVC, security context, init container, PDB, and all probes:
```yaml title="production-deployment.yaml"
apiVersion: v1
kind: Namespace
metadata:
name: kreuzberg
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: kreuzberg-cache
namespace: kreuzberg
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 2Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kreuzberg-api
namespace: kreuzberg
# NOTE: PVC uses ReadWriteOnce; keep replicas: 1 with RWO storage.
# Increase replicas only when using ReadWriteMany storage.
spec:
replicas: 1
selector:
matchLabels:
app: kreuzberg
template:
metadata:
labels:
app: kreuzberg
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
initContainers:
- name: init-cache
image: busybox:1.37-glibc
command: ["sh", "-c", "mkdir -p /app/.kreuzberg && chown -R 1000:1000 /app/.kreuzberg"]
securityContext:
runAsUser: 0
allowPrivilegeEscalation: false
capabilities:
add: ["CHOWN"]
drop: ["ALL"]
volumeMounts:
- name: cache
mountPath: /app/.kreuzberg
containers:
- name: kreuzberg
image: ghcr.io/kreuzberg-dev/kreuzberg:latest
ports:
- containerPort: 8000
name: http
env:
- name: RUST_LOG
value: "info"
- name: TESSDATA_PREFIX
value: "/usr/share/tesseract-ocr/5/tessdata"
- name: KREUZBERG_CACHE_DIR
value: "/app/.kreuzberg"
- name: HF_HOME
value: "/app/.kreuzberg/huggingface"
- name: KREUZBERG_CORS_ORIGINS
value: "https://app.example.com"
- name: KREUZBERG_MAX_UPLOAD_SIZE_MB
value: "500"
args: ["serve", "--host", "0.0.0.0", "--port", "8000"]
resources:
requests:
memory: "1Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 15
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 10
startupProbe:
httpGet:
path: /health
port: 8000
periodSeconds: 10
failureThreshold: 30
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: cache
mountPath: /app/.kreuzberg
- name: tmp
mountPath: /tmp
volumes:
- name: cache
persistentVolumeClaim:
claimName: kreuzberg-cache
- name: tmp
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: kreuzberg-api
namespace: kreuzberg
spec:
type: LoadBalancer
selector:
app: kreuzberg
ports:
- protocol: TCP
port: 80
targetPort: 8000
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: kreuzberg-pdb
namespace: kreuzberg
spec:
minAvailable: 1
selector:
matchLabels:
app: kreuzberg
```
```bash title="Terminal"
kubectl apply -f production-deployment.yaml
```
!!! Note "Model Persistence" Embedding models download on first use (~90 MB 1.2 GB). Use a PVC for `/app/.kreuzberg` to avoid re-downloading on pod restart. Outside containers, models are cached in the platform-specific global cache directory (for example, `~/.cache/kreuzberg/` on Linux, `~/Library/Caches/kreuzberg/` on macOS).
## High Availability
Add pod anti-affinity and rolling update strategy:
```yaml title="ha-additions.yaml"
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: [kreuzberg]
topologyKey: kubernetes.io/hostname
```
## Troubleshooting
??? Question "OCR silently failing"
Verify `TESSDATA_PREFIX` is set and tessdata files exist:
```bash title="Terminal"
kubectl exec -it deployment/kreuzberg-api -- printenv TESSDATA_PREFIX
kubectl exec -it deployment/kreuzberg-api -- ls /usr/share/tesseract-ocr/5/tessdata/
```
??? Question "Permission denied on cache directory"
Use an init container or `fsGroup` (see [Permissions](#permissions)).
??? Question "OOMKilled"
Increase memory limits. Reduce OCR resource usage with `KREUZBERG_PDF_DPI=150` and single-language OCR.
??? Question "Startup probe timeout"
Increase `failureThreshold` on the startup probe (e.g., `60` for 10-minute timeout).
??? Question "Language not found"
Check installed languages with `kubectl exec -it deployment/kreuzberg-api -- tesseract --list-langs`. Mount custom tessdata via ConfigMap or PVC.
### Diagnostic Commands
```bash title="Terminal"
kubectl logs deployment/kreuzberg-api --tail=200
kubectl describe deployment kreuzberg-api
kubectl get events -n kreuzberg
kubectl exec -it deployment/kreuzberg-api -- env | sort
kubectl port-forward service/kreuzberg-api 8000:8000 && curl http://localhost:8000/health
```
## Next Steps
- [Docker Deployment](docker.md) — container configuration and image variants
- [API Server Guide](api-server.md) — endpoint documentation
- [OCR Guide](ocr.md) — backend installation and language setup
- [Configuration](configuration.md) — all configuration options