Part of #43 — templates/NOTES.txt. The rule from the approved plan was that a NOTES.txt repeating the values is worthless, so this one prints what an operator would otherwise look up in the templates: the rendered URL (or the port-forward command when the Ingress is off, plus the reason ingress.domain stays required — it is CORE_DEFAULT_DOMAIN), the two op:// refs together with the exact field names the 1Password items must carry ("connection-url" and "server-secret", because the operator names the Secret keys after the item's fields), and the traps of this chart: the setup page hands the super administrator to whoever reaches the URL first, migrations run inside the startup probe's window (computed from the effective probe values), a database outage leaves the pod running and NotReady because liveness deliberately uses "/", the upload ceiling that actually bites is the Ingress annotation and not the app's fixed 500mb, /tmp is an emptyDir counted against the ephemeral-storage limit (computed) so an oversized upload evicts the pod, and the uploads PVC is an ordinary release resource that "helm uninstall" deletes. Conditional sections cover scaleDown / replicas: 0, more than one replica against the single ReadWriteOnce claim, and a set zipline.env. No domain is baked into the chart; every host is rendered from the values, examples use example.com. Part of #157 — multiplyQuantity. The null-safe fallbacks pass values through fromYaml, which parses every number as float64, and Go's "%v" switches float64 to exponential notation from about 1e6 upwards: a bare "1048576" arrived as "1.048576e+06" and the render aborted on the suffix check. This is the canonical correction agreed for all charts, taken over verbatim with this chart's prefix — the input and the non-integer product now go through a formatNumber helper, and the toYaml/fromYaml round trip is not touched, since it is not the cause. Measured against ci/zipline/test-values.yaml on the chart's only resources path, zipline.resources: before, both requests.memory=1048576 and requests.ephemeralStorage=1048576 aborted with unsupported resource quantity suffix "e+06" in "1.048576e+06" after, they render with the limits "3145728", while the 65536 fallback probe still yields 196608 unchanged. Rendered manifests are byte-identical to the previous version for the default values.
153 lines
8.4 KiB
Plaintext
153 lines
8.4 KiB
Plaintext
{{- $replicas := include "zipline.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.zipline.replicas) -}}
|
|
{{- $res := fromYaml (include "zipline.effectiveResources" .Values.zipline.resources) -}}
|
|
{{- $eph := index ($res.limits | default dict) "ephemeral-storage" | default "the computed ephemeral-storage limit" -}}
|
|
{{- $startup := fromYaml (include "zipline.startupProbe" .Values.zipline.startupProbe) -}}
|
|
{{- $budget := "" -}}
|
|
{{- if and (kindIs "float64" $startup.periodSeconds) (kindIs "float64" $startup.failureThreshold) -}}
|
|
{{- $budget = printf "%.0f seconds" (mulf $startup.periodSeconds $startup.failureThreshold) -}}
|
|
{{- end -}}
|
|
Zipline {{ .Chart.AppVersion }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
|
|
|
|
{{- if .Values.ingress.enabled }}
|
|
|
|
ACCESS
|
|
https://{{ .Values.ingress.domain }}
|
|
|
|
The certificate is requested by cert-manager through the "{{ .Values.ingress.clusterIssuer }}"
|
|
cluster issuer. Until it has been issued the browser shows a warning; follow it with
|
|
|
|
kubectl -n {{ .Release.Namespace }} get certificate tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
|
{{- else }}
|
|
|
|
ACCESS — NO INGRESS
|
|
ingress.enabled is false, so this release has no Ingress and no certificate;
|
|
ingress.clusterIssuer is not needed. ingress.domain still is: it is also
|
|
CORE_DEFAULT_DOMAIN, the host Zipline puts into every share link it hands
|
|
out. Reach the instance inside the cluster with
|
|
|
|
kubectl -n {{ .Release.Namespace }} port-forward svc/{{ .Release.Name }}-{{ .Chart.Name }}-service 8080:80
|
|
|
|
and open http://localhost:8080 — but every link the app generates will still
|
|
point at https://{{ .Values.ingress.domain }}/, which nothing in this release serves.
|
|
{{- end }}
|
|
{{ if eq $replicas "0" }}
|
|
SCALED TO ZERO
|
|
{{ if .Values.scaleDown }}scaleDown: true{{ else }}zipline.replicas: 0{{ end }} — the StatefulSet runs with replicas: 0, so the endpoint
|
|
above does not answer until you set it back and upgrade again. The uploads
|
|
PVC, both Secrets and the Service{{ if .Values.ingress.enabled }} and Ingress stay in place, so no data is
|
|
lost and the certificate is not re-issued.{{ else }} stay in place, so no data is lost.{{ end }}
|
|
{{ end }}
|
|
THE FIRST VISITOR CHOOSES THE ADMINISTRATOR
|
|
Against an empty database Zipline has no users, and it sends every visitor
|
|
to its setup page to name the super administrator and its password; the
|
|
endpoint behind that page refuses to run a second time once an account
|
|
exists. Whoever reaches the URL first therefore owns this instance. Do the
|
|
setup right after the first rollout, not the next morning. Against a
|
|
database that already carries a Zipline schema the step is skipped and the
|
|
normal login appears.
|
|
|
|
SECRETS — WHAT THE TWO 1PASSWORD ITEMS MUST CONTAIN
|
|
The operator copies an item's FIELDS into the Kubernetes Secret, one key per
|
|
field name, and this chart reads exactly one key from each. The field names
|
|
therefore have to match to the letter:
|
|
|
|
secrets.databaseSecretRef → {{ .Values.secrets.databaseSecretRef }}
|
|
field "connection-url" → DATABASE_URL, a full PostgreSQL URL such as
|
|
postgresql://zipline:password@postgres.example.com:5432/zipline
|
|
The chart deploys no database — that server has to exist already and be
|
|
reachable from this namespace.
|
|
|
|
secrets.ziplineSecretRef → {{ .Values.secrets.ziplineSecretRef }}
|
|
field "server-secret" → CORE_SECRET, which signs sessions and tokens.
|
|
Generate one with: openssl rand -base64 32
|
|
|
|
A field spelled differently yields a Secret without that key. The pod then
|
|
hangs in CreateContainerConfigError while the Secret itself exists, so it
|
|
looks as if the operator had done its job. What it actually delivered:
|
|
|
|
kubectl -n {{ .Release.Namespace }} get secret {{ .Release.Name }}-{{ .Chart.Name }}-db-secret -o jsonpath='{.data}'
|
|
|
|
Pointing a ref at a DIFFERENT item rolls the pod; changing the value INSIDE
|
|
the same item does not — Helm never sees that content, so the rendered
|
|
StatefulSet is unchanged and nothing restarts:
|
|
|
|
kubectl -n {{ .Release.Namespace }} rollout restart statefulset/{{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
|
|
|
Note that a new CORE_SECRET logs every user out.
|
|
|
|
STARTUP RUNS THE DATABASE MIGRATIONS
|
|
Zipline migrates its schema on start, before it serves anything. The startup
|
|
probe covers that window{{ if $budget }} ({{ $budget }}){{ end }} and keeps liveness and readiness off
|
|
until it is through; if a migration needs longer, the kubelet kills the
|
|
container and the next attempt begins the migration again. A migration that
|
|
fails looks like CrashLoopBackOff and only the log says why:
|
|
|
|
kubectl -n {{ .Release.Namespace }} logs statefulset/{{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
|
|
|
helm rollback returns the chart to the previous app version; the database
|
|
does not come back with it, because the schema was migrated in place. Take a
|
|
database backup before an app-version upgrade, not after.
|
|
|
|
WHAT A READY POD PROVES — AND WHAT AN OUTAGE LOOKS LIKE
|
|
Readiness and startup use /api/healthcheck, which also checks the database,
|
|
so Ready here does mean "server up, database answering". Liveness
|
|
deliberately uses "/" instead, so a database outage cannot restart the pod.
|
|
Know the resulting picture in advance: during such an outage the container
|
|
keeps running and stays quiet, the pod goes NotReady, its endpoint is
|
|
dropped{{ if .Values.ingress.enabled }} and the Ingress answers 503{{ end }} — and nothing in the pod's log or
|
|
restart count points at the database. Look there first.
|
|
|
|
TWO DIFFERENT CEILINGS APPLY TO AN UPLOAD
|
|
The app-side limit is pinned by the chart at UPLOADER_ADMIN_LIMIT=500mb and
|
|
is not exposed as a value.
|
|
{{- if .Values.ingress.enabled }}
|
|
In front of it the Ingress caps the request body at config.maxUploadFileSize
|
|
({{ .Values.config.maxUploadFileSize }}), so that is the ceiling that actually bites: anything larger is
|
|
rejected by ingress-nginx with 413 before Zipline sees a byte of it. Raise
|
|
config.maxUploadFileSize to move it.
|
|
{{- else }}
|
|
config.maxUploadFileSize ({{ .Values.config.maxUploadFileSize }}) is rendered nowhere in this release: it exists
|
|
only as an ingress-nginx annotation, and the Ingress is switched off.
|
|
Whatever proxy you put in front needs its own body-size limit.
|
|
{{- end }}
|
|
|
|
CHUNKS GO THROUGH /tmp, AND /tmp IS ACCOUNTED FOR
|
|
The root filesystem is read-only, so /tmp is an emptyDir — and an emptyDir
|
|
counts against the container's ephemeral-storage limit ({{ $eph }}).
|
|
An upload that pushes it over does not fail as a request: the kubelet evicts
|
|
the whole pod, and the reason is on the pod, not in Zipline's log. Where
|
|
uploads approach the 500mb the app allows, raise
|
|
zipline.resources.requests.ephemeralStorage — the limit follows it through
|
|
zipline.resources.limitFactor.
|
|
|
|
THE UPLOADS VOLUME BELONGS TO THIS RELEASE
|
|
uploads, public and themes share one PersistentVolumeClaim,
|
|
{{ .Release.Name }}-{{ .Chart.Name }}-vc ({{ .Values.config.storage }} from storageClass {{ .Values.config.storageClass }}), each on its own
|
|
subPath. It is an ordinary PVC in this release, not a StatefulSet
|
|
volumeClaimTemplate: "helm uninstall {{ .Release.Name }}" deletes it, and with a Delete
|
|
reclaim policy every uploaded file goes with it. Back the volume up before
|
|
uninstalling; a resource-policy annotation added to the live PVC afterwards
|
|
does not help, because Helm reads that annotation from the release manifest.
|
|
|
|
Growing config.storage only takes effect if the StorageClass allows volume
|
|
expansion. Shrinking it is refused by Kubernetes and makes the next
|
|
helm upgrade fail on the PVC instead of touching the data.
|
|
{{- if gt (atoi $replicas) 1 }}
|
|
|
|
MORE THAN ONE REPLICA WILL NOT COME UP
|
|
zipline.replicas is {{ $replicas }}, but every replica mounts the same
|
|
ReadWriteOnce claim {{ .Release.Name }}-{{ .Chart.Name }}-vc — there is no volumeClaimTemplate and no
|
|
per-pod volume. Unless the storage class hands that claim to several nodes,
|
|
the additional pods stay stuck before start. This chart is built for one
|
|
replica.
|
|
{{- end }}
|
|
{{- if .Values.zipline.env }}
|
|
|
|
zipline.env IS APPENDED, NOT MERGED
|
|
The entries land after the variables the chart sets itself — DATABASE_URL,
|
|
CORE_DEFAULT_DOMAIN, CORE_SECRET and UPLOADER_ADMIN_LIMIT — and are rendered
|
|
through tpl, so a value may contain template expressions. Anything sensitive
|
|
belongs in valueFrom.secretKeyRef: a plain "value" is stored in the pod spec
|
|
and readable by everyone who can read pods in {{ .Release.Namespace }}.
|
|
{{- end }}
|