diff --git a/zipline/Chart.yaml b/zipline/Chart.yaml index 4eb8f74..1855f0b 100644 --- a/zipline/Chart.yaml +++ b/zipline/Chart.yaml @@ -16,7 +16,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 7.0.0+up4.7.0 +version: 7.1.0+up4.7.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/zipline/templates/NOTES.txt b/zipline/templates/NOTES.txt new file mode 100644 index 0000000..aa51e2c --- /dev/null +++ b/zipline/templates/NOTES.txt @@ -0,0 +1,152 @@ +{{- $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 }} diff --git a/zipline/templates/_helpers.tpl b/zipline/templates/_helpers.tpl index ae97789..208fa7c 100644 --- a/zipline/templates/_helpers.tpl +++ b/zipline/templates/_helpers.tpl @@ -67,7 +67,7 @@ Handles plain numbers ("2", "0.5", 1073741824), decimal ("1.5Gi"), binary results are printed without a decimal point. */}} {{- define "zipline.multiplyQuantity" -}} -{{- $quantity := printf "%v" .quantity -}} +{{- $quantity := include "zipline.formatNumber" .quantity -}} {{- $number := regexFind "^[0-9]+(\\.[0-9]+)?" $quantity -}} {{- if not $number -}} {{- fail (printf "cannot parse resource quantity %q" $quantity) -}} @@ -80,7 +80,24 @@ results are printed without a decimal point. {{- if eq $product (floor $product) -}} {{- printf "%d%s" (int64 $product) $suffix -}} {{- else -}} -{{- printf "%v%s" $product $suffix -}} +{{- printf "%s%s" (include "zipline.formatNumber" $product) $suffix -}} +{{- end -}} +{{- end -}} + +{{/* +Render a number as a plain decimal string, never in exponential notation. + +Why this exists (issue #157): the null-safe fallback helpers pass values +through fromYaml, which parses every number as float64. Go's "%v" switches +float64 to exponential notation from about 1e6 upwards, so a bare byte count +like 1048576 became "1.048576e+06" - not a valid Kubernetes quantity, and the +render aborted. Strings ("512Mi") are passed through untouched. +*/}} +{{- define "zipline.formatNumber" -}} +{{- if kindIs "float64" . -}} +{{- regexReplaceAll "\\.?0+$" (printf "%.10f" .) "" -}} +{{- else -}} +{{- printf "%v" . -}} {{- end -}} {{- end -}}