Install
The Deployment that works today, and the operator install that will replace it.
View as MarkdownThe worker in a cluster is one Deployment, one Secret and one scratch volume. Nothing listens, nothing is exposed, and there is no Service.
Provision the worker first: the config below contains the key that provisioning prints once. See Registration.
The Secret
The config holds the worker key and every source password this worker uses. It is a Secret, never a ConfigMap.
kubectl create namespace saved
kubectl create secret generic saved-worker-config \
--namespace saved \
--from-file=config.yaml=./config.yamlPoint the staging directory at the volume the Deployment mounts, in that same config.yaml:
api_url: https://api.saved.sh
token: "<shown once when the worker was provisioned>"
local_temp_path: /var/lib/savedEverything else the file accepts is on the configuration page.
The Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: saved-worker
namespace: saved
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: saved-worker
template:
metadata:
labels:
app.kubernetes.io/name: saved-worker
spec:
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: worker
image: ghcr.io/savedhq/local-worker:v0.1.0
workingDir: /etc/saved
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: config
mountPath: /etc/saved
readOnly: true
- name: scratch
mountPath: /var/lib/saved
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 1Gi
volumes:
- name: config
secret:
secretName: saved-worker-config
defaultMode: 0440
- name: scratch
emptyDir:
sizeLimit: 20Gikubectl apply -f worker.yaml
kubectl -n saved logs -f deploy/saved-workerA healthy start logs the tools it resolved, the hub it dialled, and how many backups the config declares. See what healthy looks like.
Six things that are load-bearing
workingDir: /etc/saved. The image sets no working directory and the worker reads
config.yaml from whatever the working directory is. Setting it here means the Secret can
mount as a plain directory, so a rotated key propagates into the pod. Mounting the file
directly at /config.yaml with subPath also works and is worse: a subPath mount never
sees an update.
replicas: 1 with strategy: Recreate. Two processes sharing one worker credential poll
the same queue, and a run's steps hand each other a path to a file on local disk. Recreate
is what stops a rolling update from briefly running two.
Why.
fsGroup: 65532. The image runs as uid 65532. Without an fsGroup the Secret's files
at mode 0440 are root-owned and the worker cannot read them, and the emptyDir is
root-owned and it cannot write to it. The pod starts and then fails on a file it was told it
could use.
A scratch volume with room for two copies. The dump and its encrypted copy exist at the
same time, so budget roughly twice the dump size. An emptyDir without a sizeLimit can
evict the node it is running on.
No probes, no ports, no Service. The worker dials out and polls. It serves no HTTP, so a readiness probe has nothing to talk to and will restart a perfectly healthy pod.
The image is Linux and, for v0.1.0, arm64 only. On a mixed cluster, pin the node
architecture so the pod does not land somewhere it cannot run:
nodeSelector:
kubernetes.io/arch: arm64Check what a tag actually carries before you rely on it:
docker buildx imagetools inspect ghcr.io/savedhq/local-worker:v0.1.0Reaching your sources
The worker connects to sources the way any other pod does. An in-cluster Postgres is
postgres.default.svc.cluster.local in config.yaml; anything outside the cluster needs the
usual egress path. Outbound TCP 443 to api.saved.sh and hub.saved.sh is required, and
nothing needs to reach the pod.
A default-deny NetworkPolicy needs an egress rule. There is no ingress rule to write.
Rotating the key
Rotation revokes the old key the moment the new one is issued, so the pod has to restart.
kubectl -n saved create secret generic saved-worker-config \
--from-file=config.yaml=./config.yaml \
--dry-run=client -o yaml | kubectl apply -f -
kubectl -n saved rollout restart deploy/saved-workerWhen the operator ships
Not yet available
This section describes the intended design. The operator has no reconcile logic today, so none of it works yet.
The operator replaces the Deployment above with a
LocalWorker resource, and takes over the six load-bearing
details with it. It will install as a Helm chart, with plain kustomize manifests as the
alternative for clusters that do not use Helm.
What will not change: the config still lives in a Secret you create, the worker still needs
one credential per running process, and the key still never leaves your cluster.