Decision. Self-host an S3-compatible API
Alternative. MinIO or cloud object storage
Why. The S3 API is what unlocks tiered storage (AutoMQ, Velero) without egress costs, and RustFS stays light enough for arm64.
S3-compatible object storage that backs Velero, AutoMQ, and the media pipeline
There are two RustFS instances. This one runs in-cluster and holds live data: AutoMQ segments, application buckets, and Velero staging. A second, standalone instance runs on backup-raspi3 and holds the Velero backups themselves.
Excerpt from argocd-apps/values.yaml in the argocd-apps chart,
with annotations added for this site:
rustfs:
application: true
project: cluster-services
# false: this instance holds live AutoMQ segments and app buckets
autoSync: false
The full rustfs/values.yaml from the service's
own chart:
#### See Rustfs
image: registry.opi5cluster.co.uk/rustfs/rustfs:v1.0.0-rc.5
## Internal port number for Rustfs S3 API container
apiPort: 9000
## Internal port number for Rustfs Browser Console container
consolePort: 9001
persistence:
size: 600Gi
storageClass: longhorn-ssd-large
accessModes: ReadWriteOnce
volumeName: pvc-e9495802-6aa4-4f2a-bd6b-ac872d6748fd templates/data-pvc.yaml Longhorn-backed PersistentVolumeClaim for persistent data.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ .Release.Namespace }}-data-pvc
namespace: {{ .Release.Namespace }}
spec:
accessModes:
- {{ .Values.persistence.accessModes }}
resources:
requests:
storage: {{ .Values.persistence.size }}
storageClassName: {{ .Values.persistence.storageClass }}
volumeName: {{ .Values.persistence.volumeName }} templates/external-secret.yaml ExternalSecret syncing the service credentials from Vault into the namespace.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: {{ .Release.Namespace }}-external-secret
namespace: {{ .Release.Namespace }}
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: vault-cluster-secret-store
target:
name: {{ .Release.Namespace }}-secret
creationPolicy: Owner
data:
- secretKey: API_ENDPOINT_URL
remoteRef:
key: rustfs
property: internal-api-endpoint
- secretKey: WEB_ENDPOINT_URL
remoteRef:
key: rustfs
property: internal-console-endpoint
- secretKey: RUSTFS_ACCESS_KEY
remoteRef:
key: rustfs
property: user-name
- secretKey: RUSTFS_SECRET_KEY
remoteRef:
key: rustfs
property: user-password templates/http-route.yaml Gateway API HTTPRoute exposing the service through the Istio gateway under opi5cluster.co.uk.
# Console โ public HTTPS
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ .Release.Namespace }}-console-httproute
namespace: {{ .Release.Namespace }}
annotations:
link.argocd.argoproj.io/external-link: "https://{{ .Release.Namespace }}.opi5cluster.co.uk"
spec:
parentRefs:
- name: istio-gateway
namespace: istio
sectionName: websecure
hostnames:
- {{ .Release.Namespace }}.opi5cluster.co.uk
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: {{ .Release.Namespace }}-service
port: {{ .Values.consolePort }}
timeouts:
request: 0s
backendRequest: 0s
---
# API โ internal gateway listener
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ .Release.Namespace }}-api-httproute
namespace: {{ .Release.Namespace }}
annotations:
link.argocd.argoproj.io/external-link: "https://api.{{ .Release.Namespace }}.opi5cluster.co.uk"
spec:
parentRefs:
- name: istio-gateway
namespace: istio
sectionName: rustfs
hostnames:
- api.{{ .Release.Namespace }}.opi5cluster.co.uk
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: {{ .Release.Namespace }}-service
port: {{ .Values.apiPort }}
timeouts:
request: 0s
backendRequest: 0s templates/rollout.yaml Argo Rollout workload: container spec, probes, resources and rollout strategy.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: {{ .Release.Namespace }}-rollout
namespace: {{ .Release.Namespace }}
labels:
app: {{ .Release.Namespace }}
spec:
replicas: 1
strategy:
canary:
steps:
- setWeight: 25
- pause: {duration: 30s}
- setWeight: 50
- pause: {duration: 45s}
- setWeight: 100
selector:
matchLabels:
app: {{ .Release.Namespace }}
template:
metadata:
labels:
app: {{ .Release.Namespace }}
spec:
serviceAccountName: {{ .Release.Namespace }}-service-account
automountServiceAccountToken: false
containers:
- name: {{ .Release.Namespace }}
image: {{ .Values.image }}
env:
- name: RUSTFS_SECRET_KEY
valueFrom:
secretKeyRef:
name: {{ .Release.Namespace }}-secret
key: RUSTFS_SECRET_KEY
- name: RUSTFS_ACCESS_KEY
valueFrom:
secretKeyRef:
name: {{ .Release.Namespace }}-secret
key: RUSTFS_ACCESS_KEY
- name: RUSTFS_CONSOLE_ENABLE
value: "true"
- name: RUSTFS_ADDRESS
value: ":{{ .Values.apiPort }}"
ports:
- name: {{ .Release.Namespace }}-api
containerPort: {{ .Values.apiPort }}
protocol: TCP
- name: {{ .Release.Namespace }}-web
containerPort: {{ .Values.consolePort }}
protocol: TCP
volumeMounts:
- name: data
mountPath: /data
readOnly: false
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
volumes:
- name: data
persistentVolumeClaim:
claimName: {{ .Release.Namespace }}-data-pvc templates/service-account.yaml ServiceAccount the workload runs as.
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Release.Namespace }}-service-account
namespace: {{ .Release.Namespace }} templates/services.yaml ClusterIP Services fronting the workload.
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Namespace }}-service
namespace: {{ .Release.Namespace }}
spec:
type: ClusterIP
selector:
app: {{ .Release.Namespace }}
ports:
- name: {{ .Release.Namespace }}-api
protocol: TCP
port: {{ .Values.apiPort }}
targetPort: {{ .Values.apiPort }}
- name: {{ .Release.Namespace }}-web
protocol: TCP
port: {{ .Values.consolePort }}
targetPort: {{ .Values.consolePort }} Decision. Self-host an S3-compatible API
Alternative. MinIO or cloud object storage
Why. The S3 API is what unlocks tiered storage (AutoMQ, Velero) without egress costs, and RustFS stays light enough for arm64.