Add garage helm chart wrapper

S3-compatible object store (dxflrs/garage), single-node by default with
hostPath/PVC persistence, existingSecret for rpc/admin/metrics tokens, and
a postStart hook that bootstraps the single-node layout automatically.
Tracks upstream via dockerhub-tags-pattern for scripts/update-charts.sh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ZA4cJygj4VPCYVms4TDrH
This commit is contained in:
sha
2026-09-13 12:54:40 +03:00
co-authored by Claude Sonnet 5
parent bd8abd53d2
commit 52783d47a8
9 changed files with 473 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
Garage {{ .Chart.AppVersion }} has been deployed.
S3 API: http://{{ include "garage.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.s3ApiPort }}
Admin API: http://{{ include "garage.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.adminPort }}
{{- if .Values.garage.web.enabled }}
Web: http://{{ include "garage.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.s3WebPort }}
{{- end }}
On first install, the pod's postStart hook assigns and applies a single-node
layout automatically. Check with:
kubectl -n {{ .Release.Namespace }} exec deploy/{{ include "garage.fullname" . }} -- /garage status
Create a bucket and access key with:
kubectl -n {{ .Release.Namespace }} exec deploy/{{ include "garage.fullname" . }} -- /garage bucket create my-bucket
kubectl -n {{ .Release.Namespace }} exec deploy/{{ include "garage.fullname" . }} -- /garage key create my-key
kubectl -n {{ .Release.Namespace }} exec deploy/{{ include "garage.fullname" . }} -- /garage bucket allow --read --write --key my-key my-bucket
+34
View File
@@ -0,0 +1,34 @@
{{- define "garage.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "garage.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{- define "garage.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "garage.labels" -}}
helm.sh/chart: {{ include "garage.chart" . }}
{{ include "garage.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "garage.selectorLabels" -}}
app.kubernetes.io/name: {{ include "garage.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
+41
View File
@@ -0,0 +1,41 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "garage.fullname" . }}
labels:
{{- include "garage.labels" . | nindent 4 }}
data:
garage.toml: |
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = {{ .Values.garage.dbEngine | quote }}
replication_factor = {{ .Values.garage.replicationFactor }}
consistency_mode = {{ .Values.garage.consistencyMode | quote }}
rpc_bind_addr = {{ .Values.garage.rpcBindAddr | quote }}
{{- if .Values.garage.rpcPublicAddr }}
rpc_public_addr = {{ .Values.garage.rpcPublicAddr | quote }}
{{- end }}
rpc_secret_file = "/etc/garage/secrets/rpc-secret"
[s3_api]
s3_region = {{ .Values.garage.s3.region | quote }}
api_bind_addr = "[::]:{{ .Values.service.s3ApiPort }}"
{{- if .Values.garage.s3.rootDomain }}
root_domain = {{ .Values.garage.s3.rootDomain | quote }}
{{- end }}
{{- if .Values.garage.web.enabled }}
[s3_web]
bind_addr = "[::]:{{ .Values.service.s3WebPort }}"
{{- if .Values.garage.web.rootDomain }}
root_domain = {{ .Values.garage.web.rootDomain | quote }}
{{- end }}
index = {{ .Values.garage.web.index | quote }}
{{- end }}
[admin]
api_bind_addr = "[::]:{{ .Values.service.adminPort }}"
admin_token_file = "/etc/garage/secrets/admin-token"
metrics_token_file = "/etc/garage/secrets/metrics-token"
+139
View File
@@ -0,0 +1,139 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "garage.fullname" . }}
labels:
{{- include "garage.labels" . | nindent 4 }}
annotations:
kustomize.toolkit.fluxcd.io/prune: disabled
spec:
replicas: {{ .Values.replicaCount }}
strategy:
type: Recreate
selector:
matchLabels:
{{- include "garage.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "garage.selectorLabels" . | nindent 8 }}
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
spec:
containers:
- name: garage
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
- server
env:
- name: RUST_LOG
value: garage=info
ports:
- name: s3-api
containerPort: {{ .Values.service.s3ApiPort }}
- name: rpc
containerPort: {{ .Values.service.rpcPort }}
{{- if .Values.garage.web.enabled }}
- name: s3-web
containerPort: {{ .Values.service.s3WebPort }}
{{- end }}
- name: admin
containerPort: {{ .Values.service.adminPort }}
volumeMounts:
- name: config
mountPath: /etc/garage.toml
subPath: garage.toml
- name: secrets
mountPath: /etc/garage/secrets
readOnly: true
- name: data
mountPath: /var/lib/garage/data
- name: meta
mountPath: /var/lib/garage/meta
{{- if .Values.layoutJob.enabled }}
lifecycle:
postStart:
exec:
command:
- /bin/sh
- -c
- |
NODE_ID=""
for i in $(seq 1 60); do
NODE_ID=$(/garage node id -q 2>/dev/null | cut -d@ -f1)
[ -n "$NODE_ID" ] && break
sleep 2
done
[ -z "$NODE_ID" ] && exit 0
if /garage layout show 2>/dev/null | grep -q "$NODE_ID"; then
exit 0
fi
/garage layout assign -z {{ .Values.garage.zone }} -c {{ .Values.garage.capacity }} "$NODE_ID"
VERSION=$(/garage layout show 2>/dev/null | grep -o 'version [0-9]*' | tail -1 | awk '{print $2+1}')
/garage layout apply --version "${VERSION:-1}" || true
{{- end }}
readinessProbe:
tcpSocket:
port: s3-api
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: s3-api
initialDelaySeconds: 15
periodSeconds: 10
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
- name: config
configMap:
name: {{ include "garage.fullname" . }}
- name: secrets
secret:
secretName: {{ .Values.existingSecret }}
items:
- key: {{ .Values.secretKeys.rpcSecret }}
path: rpc-secret
- key: {{ .Values.secretKeys.adminToken }}
path: admin-token
- key: {{ .Values.secretKeys.metricsToken }}
path: metrics-token
- name: data
{{- if .Values.persistence.data.enabled }}
{{- if eq .Values.persistence.data.type "hostPath" }}
hostPath:
path: {{ .Values.persistence.data.hostPath }}
type: DirectoryOrCreate
{{- else }}
persistentVolumeClaim:
claimName: {{ .Values.persistence.data.existingClaim | default (printf "%s-data" (include "garage.fullname" .)) }}
{{- end }}
{{- else }}
emptyDir: {}
{{- end }}
- name: meta
{{- if .Values.persistence.meta.enabled }}
{{- if eq .Values.persistence.meta.type "hostPath" }}
hostPath:
path: {{ .Values.persistence.meta.hostPath }}
type: DirectoryOrCreate
{{- else }}
persistentVolumeClaim:
claimName: {{ .Values.persistence.meta.existingClaim | default (printf "%s-meta" (include "garage.fullname" .)) }}
{{- end }}
{{- else }}
emptyDir: {}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
+50
View File
@@ -0,0 +1,50 @@
{{- $fullName := include "garage.fullname" . -}}
{{- $labels := include "garage.labels" . -}}
{{- $kubeVersion := .Capabilities.KubeVersion.GitVersion -}}
{{- range $key, $port := (dict "s3" .Values.service.s3ApiPort "web" .Values.service.s3WebPort "admin" .Values.service.adminPort) }}
{{- $ing := index $.Values.ingress $key }}
{{- if $ing.enabled }}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}-{{ $key }}
labels:
{{- $labels | nindent 4 }}
{{- with $ing.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if $ing.className }}
ingressClassName: {{ $ing.className }}
{{- end }}
{{- if $ing.tls }}
tls:
{{- range $ing.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range $ing.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- if and .pathType (semverCompare ">=1.18-0" $kubeVersion) }}
pathType: {{ .pathType }}
{{- end }}
backend:
service:
name: {{ $fullName }}
port:
number: {{ $port }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
+47
View File
@@ -0,0 +1,47 @@
{{- if .Values.persistence.data.enabled }}
{{- if eq .Values.persistence.data.type "pvc" }}
{{- if not .Values.persistence.data.existingClaim }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "garage.fullname" . }}-data
labels:
{{- include "garage.labels" . | nindent 4 }}
annotations:
kustomize.toolkit.fluxcd.io/prune: disabled
spec:
accessModes:
{{- toYaml .Values.persistence.data.accessModes | nindent 4 }}
resources:
requests:
storage: {{ .Values.persistence.data.size | quote }}
{{- if .Values.persistence.data.storageClass }}
storageClassName: {{ .Values.persistence.data.storageClass | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
---
{{- if .Values.persistence.meta.enabled }}
{{- if eq .Values.persistence.meta.type "pvc" }}
{{- if not .Values.persistence.meta.existingClaim }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "garage.fullname" . }}-meta
labels:
{{- include "garage.labels" . | nindent 4 }}
annotations:
kustomize.toolkit.fluxcd.io/prune: disabled
spec:
accessModes:
{{- toYaml .Values.persistence.meta.accessModes | nindent 4 }}
resources:
requests:
storage: {{ .Values.persistence.meta.size | quote }}
{{- if .Values.persistence.meta.storageClass }}
storageClassName: {{ .Values.persistence.meta.storageClass | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
+29
View File
@@ -0,0 +1,29 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "garage.fullname" . }}
labels:
{{- include "garage.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
selector:
{{- include "garage.selectorLabels" . | nindent 4 }}
ports:
- name: s3-api
port: {{ .Values.service.s3ApiPort }}
targetPort: s3-api
protocol: TCP
- name: rpc
port: {{ .Values.service.rpcPort }}
targetPort: rpc
protocol: TCP
{{- if .Values.garage.web.enabled }}
- name: s3-web
port: {{ .Values.service.s3WebPort }}
targetPort: s3-web
protocol: TCP
{{- end }}
- name: admin
port: {{ .Values.service.adminPort }}
targetPort: admin
protocol: TCP