fix(paperless): collapse seed Job into paperless pod initContainer
Same shape as the prior seed-Job → initContainer refactors: music-assistant (c9ea0af), audiobookshelf (b9717e9), immich (74994aa). Add a `seed` initContainer to the paperless Deployment that owns the S3-wait + extract + chown for the data and media RWX PVCs. paperless- consume is the live drop-folder (not seeded); paperless-redis is mounted by the redis pod (not paperless), so the seed never touched it. Paperless used RWX (ceph-filesystem) for its data PVCs, so the Multi-Attach race that broke music-assistant didn't surface here either — but the refactor removes one more standalone Flux Kustomization, kills the hourly Job churn, and matches the pattern across all migrated apps. Refactor: - paperless.yaml: add `seed` initContainer (the deployment had no initContainers before — the seed Job's sentinel was relied on purely via Flux dependsOn ordering). Mount paths /data + /media match the legacy Job's; the app container's /usr/src/paperless/{data,media} mounts are unchanged - move garage-migration-creds.sops.yaml + pvcs.yaml from kubernetes/apps/paperless-seed/ into kubernetes/apps/paperless/ - delete kubernetes/apps/paperless-seed/ - collapse the Flux Kustomizations: drop paperless-seed, replace `dependsOn: paperless-seed` with velero-bootstrap-restore on the app Kustomization (paperless-db dep stays — separate concern) - raise app Kustomization timeout 1h → 2h to absorb the worst-case tarball wait + extract Also drop the now-stale "during the migration window" comments on the audiobookshelf and music-assistant pvcs.yaml — the seed Kustomizations those comments referenced are gone. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
---
|
||||
# PVCs live in the seed Kustomization during the migration window (see
|
||||
# paperless-seed/pvcs.yaml for the rationale). Prune-disabled so the PVCs
|
||||
# survive retirement of this Kustomization.
|
||||
# Prune-disabled so the PVCs survive retirement of this Kustomization
|
||||
# (e.g. when the seed initContainer or the Garage migration creds are
|
||||
# removed once we're done with the migration phase).
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
# PVC lives in the seed Kustomization during the migration window
|
||||
# (matches audiobookshelf-seed / paperless-seed). Prune-disabled so the
|
||||
# PVC survives retirement of this Kustomization.
|
||||
# Prune-disabled so the PVC survives retirement of this Kustomization
|
||||
# (e.g. when the seed initContainer or the Garage migration creds are
|
||||
# removed once we're done with the migration phase).
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- garage-migration-creds.sops.yaml
|
||||
- pvcs.yaml
|
||||
- seed-job.yaml
|
||||
@@ -1,104 +0,0 @@
|
||||
---
|
||||
# Dev-phase seed Job for Paperless PVCs. Reads legacy tarballs from
|
||||
# Garage (s3://k8s-migration/paperless/...) written by the standalone
|
||||
# `migrate-paperless` Woodpecker pipeline — no cluster dependency on
|
||||
# the producer side. Idempotent: sentinel `/data/.seed-done` short-
|
||||
# circuits on subsequent runs (including after a Velero restore, which
|
||||
# preserves the sentinel). Legacy `.seed-v3-done` is honored and
|
||||
# normalized forward on re-run.
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: paperless-data-seed-v3
|
||||
namespace: paperless
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 3600
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: seed
|
||||
image: alpine:3.21
|
||||
securityContext:
|
||||
runAsNonRoot: false
|
||||
allowPrivilegeEscalation: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
add: ["CHOWN", "DAC_OVERRIDE", "FOWNER"]
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
set -eu
|
||||
if [ -f /data/.seed-done ] || [ -f /data/.seed-v3-done ]; then
|
||||
echo "Seed already applied (sentinel present); normalizing and exiting."
|
||||
touch /data/.seed-done
|
||||
chown 1000:1000 /data/.seed-done 2>/dev/null || true
|
||||
exit 0
|
||||
fi
|
||||
apk add --no-cache aws-cli tar ca-certificates >/dev/null
|
||||
export HOME=/tmp AWS_ENDPOINT_URL="$S3_ENDPOINT" AWS_DEFAULT_REGION=garage
|
||||
# TLS cert is for s3.nas-1.renner.uno only, not wildcarded, so
|
||||
# force path-style addressing (bucket in path, not subdomain).
|
||||
mkdir -p "$HOME/.aws"
|
||||
printf '[default]\ns3 =\n addressing_style = path\n' > "$HOME/.aws/config"
|
||||
|
||||
echo "Waiting for tarballs at s3://$S3_BUCKET/$S3_PREFIX/ ..."
|
||||
for i in $(seq 1 360); do
|
||||
if aws s3api head-object --bucket "$S3_BUCKET" --key "$S3_PREFIX/paperless-data.tar.gz" >/dev/null 2>&1 \
|
||||
&& aws s3api head-object --bucket "$S3_BUCKET" --key "$S3_PREFIX/paperless-media.tar.gz" >/dev/null 2>&1; then
|
||||
echo "Both tarballs found."; break
|
||||
fi
|
||||
echo " not ready yet ($i/360) — sleeping 10s"; sleep 10
|
||||
if [ "$i" = "360" ]; then
|
||||
echo "ERROR: tarballs not found after 60 minutes"; exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Streaming data tarball → /data ..."
|
||||
aws s3 cp "s3://$S3_BUCKET/$S3_PREFIX/paperless-data.tar.gz" - | tar -xz -C /data
|
||||
echo "Streaming media tarball → /media ..."
|
||||
aws s3 cp "s3://$S3_BUCKET/$S3_PREFIX/paperless-media.tar.gz" - | tar -xz -C /media
|
||||
|
||||
echo "Normalizing ownership to paperless (1000:1000) ..."
|
||||
chown -R 1000:1000 /data /media
|
||||
|
||||
echo "Writing seed sentinel ..."
|
||||
touch /data/.seed-done
|
||||
chown 1000:1000 /data/.seed-done
|
||||
|
||||
echo "Done. Summary:"
|
||||
du -sh /data /media
|
||||
env:
|
||||
- name: S3_ENDPOINT
|
||||
value: "https://s3.nas-1.renner.uno"
|
||||
- name: S3_BUCKET
|
||||
value: "k8s-migration"
|
||||
- name: S3_PREFIX
|
||||
value: "paperless"
|
||||
- name: AWS_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: garage-migration-creds
|
||||
key: AWS_ACCESS_KEY_ID
|
||||
- name: AWS_SECRET_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: garage-migration-creds
|
||||
key: AWS_SECRET_ACCESS_KEY
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: media
|
||||
mountPath: /media
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: paperless-data
|
||||
- name: media
|
||||
persistentVolumeClaim:
|
||||
claimName: paperless-media
|
||||
@@ -1,6 +1,8 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- garage-migration-creds.sops.yaml
|
||||
- pvcs.yaml
|
||||
- secrets.sops.yaml
|
||||
- ftp-scanner-secret.sops.yaml
|
||||
- redis.yaml
|
||||
|
||||
@@ -50,6 +50,89 @@ spec:
|
||||
fsGroupChangePolicy: OnRootMismatch
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
initContainers:
|
||||
# Restore paperless-data + paperless-media from the Garage tarballs
|
||||
# written by the `migrate-paperless` Woodpecker pipeline. Sentinel-
|
||||
# guarded so subsequent pod restarts short-circuit in <1s. Lives in
|
||||
# the app pod (not a separate Job) so no orphan Job churns after
|
||||
# ttlSecondsAfterFinished. On a fresh cluster rebuild the volumes
|
||||
# are empty, so this runs the full ~3-4 GB extract before paperless
|
||||
# starts. paperless-consume isn't seeded (live drop-folder for
|
||||
# incoming docs) and paperless-redis is mounted by the redis pod.
|
||||
- name: seed
|
||||
image: alpine:3.21
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
set -eu
|
||||
if [ -f /data/.seed-done ] || [ -f /data/.seed-v3-done ]; then
|
||||
echo "Seed already applied (sentinel present); normalizing and exiting."
|
||||
touch /data/.seed-done
|
||||
chown 1000:1000 /data/.seed-done 2>/dev/null || true
|
||||
exit 0
|
||||
fi
|
||||
apk add --no-cache aws-cli tar ca-certificates >/dev/null
|
||||
export HOME=/tmp AWS_ENDPOINT_URL="$S3_ENDPOINT" AWS_DEFAULT_REGION=garage
|
||||
# TLS cert is for s3.nas-1.renner.uno only, not wildcarded, so
|
||||
# force path-style addressing (bucket in path, not subdomain).
|
||||
mkdir -p "$HOME/.aws"
|
||||
printf '[default]\ns3 =\n addressing_style = path\n' > "$HOME/.aws/config"
|
||||
|
||||
echo "Waiting for tarballs at s3://$S3_BUCKET/$S3_PREFIX/ ..."
|
||||
for i in $(seq 1 360); do
|
||||
if aws s3api head-object --bucket "$S3_BUCKET" --key "$S3_PREFIX/paperless-data.tar.gz" >/dev/null 2>&1 \
|
||||
&& aws s3api head-object --bucket "$S3_BUCKET" --key "$S3_PREFIX/paperless-media.tar.gz" >/dev/null 2>&1; then
|
||||
echo "Both tarballs found."; break
|
||||
fi
|
||||
echo " not ready yet ($i/360) — sleeping 10s"; sleep 10
|
||||
if [ "$i" = "360" ]; then
|
||||
echo "ERROR: tarballs not found after 60 minutes"; exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Streaming data tarball → /data ..."
|
||||
aws s3 cp "s3://$S3_BUCKET/$S3_PREFIX/paperless-data.tar.gz" - | tar -xz -C /data
|
||||
echo "Streaming media tarball → /media ..."
|
||||
aws s3 cp "s3://$S3_BUCKET/$S3_PREFIX/paperless-media.tar.gz" - | tar -xz -C /media
|
||||
|
||||
echo "Normalizing ownership to paperless (1000:1000) ..."
|
||||
chown -R 1000:1000 /data /media
|
||||
|
||||
echo "Writing seed sentinel ..."
|
||||
touch /data/.seed-done
|
||||
chown 1000:1000 /data/.seed-done
|
||||
|
||||
echo "Done. Summary:"
|
||||
du -sh /data /media
|
||||
env:
|
||||
- name: S3_ENDPOINT
|
||||
value: "https://s3.nas-1.renner.uno"
|
||||
- name: S3_BUCKET
|
||||
value: "k8s-migration"
|
||||
- name: S3_PREFIX
|
||||
value: "paperless"
|
||||
- name: AWS_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: garage-migration-creds
|
||||
key: AWS_ACCESS_KEY_ID
|
||||
- name: AWS_SECRET_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: garage-migration-creds
|
||||
key: AWS_SECRET_ACCESS_KEY
|
||||
securityContext:
|
||||
runAsNonRoot: false
|
||||
allowPrivilegeEscalation: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
add: ["CHOWN", "DAC_OVERRIDE", "FOWNER"]
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: media
|
||||
mountPath: /media
|
||||
containers:
|
||||
- name: paperless
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:2.20.14
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
---
|
||||
# PVCs live in the seed Kustomization during the migration window so that
|
||||
# the seed Job can mount them before the app Deployment starts (the main
|
||||
# `paperless` Kustomization dependsOn this one). Annotated with
|
||||
# `kustomize.toolkit.fluxcd.io/prune: disabled` so when this seed
|
||||
# Kustomization is retired post-migration the PVCs survive — Velero's
|
||||
# bootstrap-restore then owns their contents; moving the PVC manifests
|
||||
# back into `kubernetes/apps/paperless/` at retirement is the cleanup.
|
||||
# Prune-disabled so the PVCs survive retirement of this Kustomization
|
||||
# (e.g. when the seed initContainer or the Garage migration creds are
|
||||
# removed once we're done with the migration phase). Velero's
|
||||
# bootstrap-restore then owns their contents.
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
@@ -30,43 +30,16 @@ spec:
|
||||
---
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: paperless-seed
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 10m
|
||||
retryInterval: 1m
|
||||
# Seed Job streams ~3-4 GB of documents/media; 2 h leaves margin.
|
||||
timeout: 2h
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
path: ./kubernetes/apps/paperless-seed
|
||||
prune: true
|
||||
wait: true
|
||||
decryption:
|
||||
provider: sops
|
||||
secretRef:
|
||||
name: sops-age
|
||||
healthCheckExprs:
|
||||
- apiVersion: batch/v1
|
||||
kind: Job
|
||||
current: "status.succeeded == 1"
|
||||
inProgress: "!has(status.succeeded) || status.succeeded == 0"
|
||||
failed: "has(status.failed) && status.failed >= 1"
|
||||
dependsOn:
|
||||
- name: infrastructure-ceph-cluster
|
||||
- name: velero-bootstrap-restore
|
||||
---
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: paperless
|
||||
namespace: flux-system
|
||||
spec:
|
||||
# Seed Job collapsed into the paperless pod's `seed` initContainer
|
||||
# (same RWX data + media mounts). Tarballs are ~3-4 GB so 2h timeout
|
||||
# absorbs upload + extract.
|
||||
interval: 10m
|
||||
retryInterval: 1m
|
||||
timeout: 1h
|
||||
timeout: 2h
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: flux-system
|
||||
@@ -79,4 +52,4 @@ spec:
|
||||
name: sops-age
|
||||
dependsOn:
|
||||
- name: paperless-db
|
||||
- name: paperless-seed
|
||||
- name: velero-bootstrap-restore
|
||||
|
||||
Reference in New Issue
Block a user