bentopdf: new chart for the PDF toolbox (1.0.0+up2.8.7)
BentoPDF computes entirely in the visitor's browser; the container only serves static files through nginx. The chart is therefore close to cyberchef - same nginx-unprivileged base, uid 101, port 8080, probes on "/" - with four differences that shaped the design. Runtime config.json. The one setting reachable at runtime is the tool list. It is a static file in the middle of the web root, so it is mounted by subPath: a directory mount would hide the whole application. subPath mounts are never refreshed by the kubelet, which makes the checksum/config annotation (#82) load-bearing rather than decorative. bentopdf.config is passed through verbatim, like searxng's settings, so a key upstream adds tomorrow is a values change and not a chart change. Three env switches that would kill the pod. PORT, DISABLE_IPV6 and ROBOTS_NOINDEX are implemented as "sed -i" against files in the image, run under "set -e" in /docker-entrypoint.d/, and the base image's entrypoint aborts when one of them fails. With readOnlyRootFilesystem: true the pod therefore never starts instead of ignoring the switch - measured in k3d as CrashLoopBackOff, exit 1, "sed: can't create temp file '/etc/nginx/nginx.confXXXXXX': Read-only file system". None of the three is offered as a first-class value; values.yaml names the failure picture and the escape hatch (env block plus readOnlyRootFilesystem: false). Writable paths were measured, not copied from the upstream chart. /etc/nginx/tmp holds the pid file and /var/cache/nginx is where nginx creates its temp directories at startup - without it the master aborts with 'mkdir() "/var/cache/nginx/client_temp" failed'. /var/run is deliberately not mounted: the running pod proves it is unnecessary. Everything else about BentoPDF is a build-time argument and out of reach for any chart; values.yaml says so explicitly rather than leaving it to be discovered. Two accepted consequences are named there too: some tools fetch WASM modules from a public CDN at runtime, and the served pages carry a canonical link to the vendor's site. Fixes #174
This commit is contained in:
14 files changed
+1049
No files matched your search
@@ -0,0 +1,81 @@
|
||||
{{- $replicas := include "bentopdf.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.bentopdf.replicas) -}}
|
||||
BentoPDF {{ .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
|
||||
|
||||
This chart has no secrets and no volumes — the two ingress values above are
|
||||
everything an installation needs.
|
||||
{{- else }}
|
||||
|
||||
ACCESS — NO INGRESS
|
||||
ingress.enabled is false, so this release has no Ingress and no certificate;
|
||||
ingress.domain and ingress.clusterIssuer are not needed. The app is only
|
||||
reachable inside the cluster, as
|
||||
{{ .Release.Name }}-{{ .Chart.Name }}-service:80. To open it locally:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} port-forward svc/{{ .Release.Name }}-{{ .Chart.Name }}-service 8080:80
|
||||
|
||||
Then browse to http://localhost:8080. Set ingress.enabled=true together with
|
||||
ingress.domain and ingress.clusterIssuer to publish it instead.
|
||||
{{- end }}
|
||||
{{ if eq $replicas "0" }}
|
||||
SCALED TO ZERO
|
||||
{{ if .Values.scaleDown }}scaleDown: true{{ else }}bentopdf.replicas: 0{{ end }} — the deployment runs with replicas: 0, so the endpoint
|
||||
above does not answer until you set it back and upgrade again. Service{{ if .Values.ingress.enabled }} and
|
||||
Ingress stay in place, so the certificate is not re-issued.{{ else }} stays in
|
||||
place.{{ end }}
|
||||
{{ end }}
|
||||
A GREEN POD ONLY PROVES DELIVERY
|
||||
BentoPDF computes in the visitor's browser: not one of its tools runs in
|
||||
this pod, which serves static files and nothing else. The probes hit "/"
|
||||
because the image has no health endpoint, so they prove that nginx is
|
||||
handing out index.html — no more. Whether PDF processing works is a
|
||||
question about the browser, not about this release.
|
||||
|
||||
TWO HEADERS HAVE TO SURVIVE YOUR INGRESS CONTROLLER
|
||||
The image sends Cross-Origin-Opener-Policy: same-origin and
|
||||
Cross-Origin-Embedder-Policy: credentialless. Without both arriving at the
|
||||
browser there is no SharedArrayBuffer, and the LibreOffice-backed
|
||||
conversions (Word, Excel, PowerPoint) fail while everything else keeps
|
||||
working. Check them through the ingress, not at the pod:
|
||||
|
||||
curl -sI https://{{ if .Values.ingress.enabled }}{{ .Values.ingress.domain }}{{ else }}<host>{{ end }}/ | grep -i cross-origin
|
||||
|
||||
ANYONE WHO REACHES THE URL CAN USE IT
|
||||
BentoPDF has no login, and this chart adds none. That is defensible here —
|
||||
the server takes nothing in, stores nothing and has no session — but a
|
||||
public instance also gets indexed, and its pages carry a canonical link to
|
||||
the vendor's site. Both are addressed through ingress.annotations; see
|
||||
values.yaml.
|
||||
|
||||
NOTHING TO BACK UP, NOTHING TO MIGRATE
|
||||
No database, no volumes, no uploads: documents never leave the visitor's
|
||||
device. App-version upgrades and rollbacks carry no data risk.
|
||||
{{- if .Values.bentopdf.config }}
|
||||
|
||||
TOOLS ARE DISABLED VIA config.json
|
||||
bentopdf.config is set and is served as /config.json. A change here rolls
|
||||
the pods (checksum/config), but browsers may keep the old file for up to a
|
||||
week — the image serves .json with Cache-Control: max-age=604800. Verify
|
||||
what is actually being served with:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} exec deploy/{{ .Release.Name }}-{{ .Chart.Name }}-deployment -- wget -qO- localhost:8080/config.json
|
||||
{{- end }}
|
||||
{{- if .Values.bentopdf.env }}
|
||||
|
||||
bentopdf.env HAS NO EFFECT ON THE APP — AND THREE VALUES BREAK THE POD
|
||||
The served BentoPDF bundle is static and reads no environment variables;
|
||||
entries only reach nginx and its entrypoint scripts. Beware of PORT,
|
||||
DISABLE_IPV6 and ROBOTS_NOINDEX: all three rewrite files with "sed -i" and
|
||||
abort the entrypoint on a read-only root filesystem, so the pod never
|
||||
starts. Using them means also setting
|
||||
securityContext.container.readOnlyRootFilesystem: false.
|
||||
{{- end }}
|
||||
@@ -0,0 +1,297 @@
|
||||
{{/*
|
||||
Render the body of a container "resources:" block from a values dict of the form
|
||||
|
||||
resources:
|
||||
limitFactor: 3 # optional, default 3
|
||||
requests: { cpu, memory, ephemeral-storage / ephemeralStorage, ... }
|
||||
limits: { ... } # optional, every entry optional
|
||||
|
||||
Rules (variant B, see issue #19):
|
||||
- requests are rendered as given (camelCase "ephemeralStorage" is normalized
|
||||
to the Kubernetes key "ephemeral-storage").
|
||||
- an explicitly set limit always wins.
|
||||
- a missing memory / ephemeral-storage limit is computed as
|
||||
limitFactor x the corresponding request.
|
||||
- a missing cpu limit is NOT set at all (the kubelet then treats CPU as
|
||||
unlimited; CPU is compressible, so this cannot kill the workload).
|
||||
|
||||
Usage (the values path to the resources dict varies per chart):
|
||||
resources:
|
||||
{{- include "bentopdf.resources" .Values.<app>.resources | nindent 12 }}
|
||||
|
||||
NOTE: charts in this repo deliberately have no dependencies, so this helper is
|
||||
duplicated into every chart's templates/_helpers.tpl with the chart's name as
|
||||
define prefix. This file in template-chart is the reference implementation.
|
||||
*/}}
|
||||
{{- define "bentopdf.resources" -}}
|
||||
{{- $resources := . | default dict -}}
|
||||
{{- $factor := 3.0 -}}
|
||||
{{- if not (kindIs "invalid" $resources.limitFactor) -}}
|
||||
{{- $factor = float64 $resources.limitFactor -}}
|
||||
{{- end -}}
|
||||
{{- $requests := dict -}}
|
||||
{{- range $key, $value := ($resources.requests | default dict) -}}
|
||||
{{- if not (kindIs "invalid" $value) -}}
|
||||
{{- $_ := set $requests (include "bentopdf.resourceKey" $key) $value -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $limits := dict -}}
|
||||
{{- range $key, $value := ($resources.limits | default dict) -}}
|
||||
{{- if not (kindIs "invalid" $value) -}}
|
||||
{{- $_ := set $limits (include "bentopdf.resourceKey" $key) $value -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- range $key, $request := $requests -}}
|
||||
{{- if and (ne $key "cpu") (not (hasKey $limits $key)) -}}
|
||||
{{- $_ := set $limits $key (include "bentopdf.multiplyQuantity" (dict "quantity" $request "factor" $factor)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $out := dict -}}
|
||||
{{- with $requests }}{{- $_ := set $out "requests" . }}{{- end -}}
|
||||
{{- with $limits }}{{- $_ := set $out "limits" . }}{{- end -}}
|
||||
{{- toYaml $out -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Normalize a resource name from values to its Kubernetes key.
|
||||
*/}}
|
||||
{{- define "bentopdf.resourceKey" -}}
|
||||
{{- if eq . "ephemeralStorage" -}}ephemeral-storage{{- else -}}{{ . }}{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Multiply a Kubernetes quantity by a factor, keeping its suffix.
|
||||
Input: dict "quantity" <string|number> "factor" <float64>.
|
||||
Handles plain numbers ("2", "0.5", 1073741824), decimal ("1.5Gi"), binary
|
||||
("512Mi", "1Gi", "10Ki") and milli/SI suffixes ("100m", "500M"). Integer
|
||||
results are printed without a decimal point.
|
||||
*/}}
|
||||
{{- define "bentopdf.multiplyQuantity" -}}
|
||||
{{- $quantity := printf "%v" .quantity -}}
|
||||
{{- $number := regexFind "^[0-9]+(\\.[0-9]+)?" $quantity -}}
|
||||
{{- if not $number -}}
|
||||
{{- fail (printf "cannot parse resource quantity %q" $quantity) -}}
|
||||
{{- end -}}
|
||||
{{- $suffix := trimPrefix $number $quantity -}}
|
||||
{{- if not (regexMatch "^(m|k|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$" $suffix) -}}
|
||||
{{- fail (printf "unsupported resource quantity suffix %q in %q" $suffix $quantity) -}}
|
||||
{{- end -}}
|
||||
{{- $product := mulf (float64 $number) .factor -}}
|
||||
{{- if eq $product (floor $product) -}}
|
||||
{{- printf "%d%s" (int64 $product) $suffix -}}
|
||||
{{- else -}}
|
||||
{{- printf "%v%s" $product $suffix -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Render the replica count for a Deployment/StatefulSet.
|
||||
|
||||
Input: dict
|
||||
"scaleDown" the chart-global .Values.scaleDown flag (may be nil)
|
||||
"replicas" the workload's replicas value (may be nil)
|
||||
|
||||
Rules (issue #25):
|
||||
- scaleDown: true takes precedence and always yields 0.
|
||||
- otherwise an explicitly set replicas value is used as given, including an
|
||||
explicit 0 — Helm's "default" would swallow 0, hence the explicit
|
||||
kindIs "invalid" nil check.
|
||||
- a missing replicas value defaults to 1.
|
||||
|
||||
Usage (the values path to the workload's replicas varies per chart):
|
||||
replicas: {{ include "bentopdf.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.<app>.replicas) }}
|
||||
|
||||
NOTE: like the resources helper above, this is duplicated into every chart's
|
||||
templates/_helpers.tpl with the chart's name as define prefix. This file in
|
||||
template-chart is the reference implementation.
|
||||
*/}}
|
||||
{{- define "bentopdf.replicas" -}}
|
||||
{{- if .scaleDown -}}
|
||||
0
|
||||
{{- else if not (kindIs "invalid" .replicas) -}}
|
||||
{{- .replicas -}}
|
||||
{{- else -}}
|
||||
1
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Merge a user-supplied values sub-tree onto a dict of chart defaults so that a
|
||||
null / empty override falls back to the defaults instead of erasing them.
|
||||
|
||||
Input: dict "defaults" <dict> "given" <dict|nil> "path" <string, optional
|
||||
values path used in error messages>. Output: YAML of the merged dict (consume
|
||||
it with fromYaml).
|
||||
|
||||
Why this exists (issue #99): Helm merges values maps recursively, but an
|
||||
override key written without children — e.g. a leftover
|
||||
|
||||
securityContext:
|
||||
|
||||
after deleting its last remaining sub-key — is YAML null and replaces the
|
||||
whole default sub-tree. The container then renders with "securityContext:
|
||||
null": no readOnlyRootFilesystem, no dropped capabilities, no
|
||||
allowPrivilegeEscalation: false. The workload starts and reports healthy while
|
||||
being unhardened, so nothing points at the mistake. The same shape erases a
|
||||
probe (it disappears) or the resources block (no requests, no computed
|
||||
limits). This helper defines the opposite semantics: an empty block means
|
||||
"chart defaults apply".
|
||||
|
||||
Rules:
|
||||
- a nil "given" is treated as an empty dict (all defaults apply).
|
||||
- a key whose override value is null keeps the default (nulls never delete).
|
||||
- a key present in both as a map is merged recursively, so a partially
|
||||
filled block only overrides the keys it actually sets.
|
||||
- a scalar override for a key the defaults define as a map is rejected with
|
||||
a message naming the values path, instead of failing later with a template
|
||||
field error.
|
||||
- any other set value wins as given, including an explicit false or 0 —
|
||||
Helm's "default" and sprig's "mergeOverwrite" would both swallow those
|
||||
zero values, hence the explicit kindIs "invalid" nil check.
|
||||
- keys only present in the override are passed through unchanged.
|
||||
|
||||
NOTE: like the helpers above, this is duplicated into every chart's
|
||||
templates/_helpers.tpl with the chart's name as define prefix, because charts
|
||||
in this repo deliberately have no dependencies.
|
||||
*/}}
|
||||
{{- define "bentopdf.defaultedDict" -}}
|
||||
{{- $defaults := .defaults | default dict -}}
|
||||
{{- $given := .given -}}
|
||||
{{- $path := .path | default "value" -}}
|
||||
{{- if kindIs "invalid" $given -}}
|
||||
{{- $given = dict -}}
|
||||
{{- end -}}
|
||||
{{- if not (kindIs "map" $given) -}}
|
||||
{{- fail (printf "%s must be a mapping or null, got %s (%v)" $path (kindOf $given) $given) -}}
|
||||
{{- end -}}
|
||||
{{- $out := deepCopy $defaults -}}
|
||||
{{- range $key, $value := $given -}}
|
||||
{{- if not (kindIs "invalid" $value) -}}
|
||||
{{- $default := index $defaults $key -}}
|
||||
{{- if and (kindIs "map" $value) (kindIs "map" $default) -}}
|
||||
{{- $_ := set $out $key (fromYaml (include "bentopdf.defaultedDict" (dict "defaults" $default "given" $value "path" (printf "%s.%s" $path $key)))) -}}
|
||||
{{- else if kindIs "map" $default -}}
|
||||
{{- fail (printf "%s.%s must be a mapping or null, got %s (%v)" $path $key (kindOf $value) $value) -}}
|
||||
{{- else -}}
|
||||
{{- $_ := set $out $key $value -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- toYaml $out -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Pick one sub-block out of a values block that may itself be null.
|
||||
|
||||
Input: dict "block" <dict|nil> "key" <string> "path" <string>.
|
||||
Returns YAML of dict "value" <the sub-block, may be nil>, after rejecting a
|
||||
non-mapping block with a message that names the values path. Needed because
|
||||
".Values.bentopdf.securityContext" can itself be erased.
|
||||
*/}}
|
||||
{{- define "bentopdf.subBlock" -}}
|
||||
{{- $block := .block -}}
|
||||
{{- if kindIs "invalid" $block -}}
|
||||
{{- $block = dict -}}
|
||||
{{- end -}}
|
||||
{{- if not (kindIs "map" $block) -}}
|
||||
{{- fail (printf "%s must be a mapping or null, got %s (%v)" .path (kindOf $block) $block) -}}
|
||||
{{- end -}}
|
||||
{{- toYaml (dict "value" (index $block .key)) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The effective security contexts, probes and resources (issue #99).
|
||||
|
||||
The defaults below MUST stay in sync with the corresponding blocks in
|
||||
values.yaml, which stays the documented, user-facing place for them; this copy
|
||||
is only the fallback for the case where an override has erased the block.
|
||||
|
||||
The probes address the container port by its name ("http"), so a rebuilt
|
||||
default probe needs no further context.
|
||||
*/}}
|
||||
{{- define "bentopdf.podSecurityContext" -}}
|
||||
{{- $given := (fromYaml (include "bentopdf.subBlock" (dict "block" . "key" "pod" "path" "bentopdf.securityContext"))).value -}}
|
||||
{{- $defaults := dict
|
||||
"runAsNonRoot" true
|
||||
"runAsUser" 101
|
||||
"runAsGroup" 101
|
||||
"seccompProfile" (dict "type" "RuntimeDefault") -}}
|
||||
{{- include "bentopdf.defaultedDict" (dict "defaults" $defaults "given" $given "path" "bentopdf.securityContext.pod") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "bentopdf.containerSecurityContext" -}}
|
||||
{{- $given := (fromYaml (include "bentopdf.subBlock" (dict "block" . "key" "container" "path" "bentopdf.securityContext"))).value -}}
|
||||
{{- $defaults := dict
|
||||
"allowPrivilegeEscalation" false
|
||||
"readOnlyRootFilesystem" true
|
||||
"runAsNonRoot" true
|
||||
"capabilities" (dict "drop" (list "ALL")) -}}
|
||||
{{- include "bentopdf.defaultedDict" (dict "defaults" $defaults "given" $given "path" "bentopdf.securityContext.container") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "bentopdf.livenessProbe" -}}
|
||||
{{- $defaults := dict
|
||||
"httpGet" (dict "path" "/" "port" "http")
|
||||
"periodSeconds" 10
|
||||
"timeoutSeconds" 5
|
||||
"failureThreshold" 3 -}}
|
||||
{{- include "bentopdf.defaultedDict" (dict "defaults" $defaults "given" . "path" "bentopdf.livenessProbe") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "bentopdf.readinessProbe" -}}
|
||||
{{- $defaults := dict
|
||||
"httpGet" (dict "path" "/" "port" "http")
|
||||
"periodSeconds" 10
|
||||
"timeoutSeconds" 5
|
||||
"failureThreshold" 3 -}}
|
||||
{{- include "bentopdf.defaultedDict" (dict "defaults" $defaults "given" . "path" "bentopdf.readinessProbe") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "bentopdf.startupProbe" -}}
|
||||
{{- $defaults := dict
|
||||
"httpGet" (dict "path" "/" "port" "http")
|
||||
"periodSeconds" 5
|
||||
"timeoutSeconds" 5
|
||||
"failureThreshold" 30 -}}
|
||||
{{- include "bentopdf.defaultedDict" (dict "defaults" $defaults "given" . "path" "bentopdf.startupProbe") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The effective resources block, fed into the resources helper above so that an
|
||||
erased "resources:" still yields the chart's requests and computed limits
|
||||
instead of a container without any resource accounting.
|
||||
*/}}
|
||||
{{- define "bentopdf.effectiveResources" -}}
|
||||
{{- $defaults := dict
|
||||
"limitFactor" 3
|
||||
"requests" (dict "cpu" 0.1 "memory" "128Mi" "ephemeralStorage" "16Mi") -}}
|
||||
{{- $merged := fromYaml (include "bentopdf.defaultedDict" (dict "defaults" $defaults "given" . "path" "bentopdf.resources")) -}}
|
||||
{{- include "bentopdf.resources" $merged -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The body of config.json, rendered from bentopdf.config.
|
||||
|
||||
The block is passed through verbatim rather than being built from named keys:
|
||||
the schema of this file is upstream's (today "disabledTools" and
|
||||
"editorDisabledCategories"), so a key it adds tomorrow is a values change and
|
||||
not a chart change.
|
||||
|
||||
Unlike the probe and securityContext helpers, this one does NOT merge onto
|
||||
chart defaults - there are none to merge onto, and inventing some would mean
|
||||
disabling tools nobody asked to disable. An erased or missing block therefore
|
||||
renders "{}", which is the same file the image itself ships. What the helper
|
||||
does add is the null check plus a typed error: a scalar under bentopdf.config
|
||||
would otherwise reach toJson and produce a config.json that is valid JSON,
|
||||
serves with 200 and configures nothing.
|
||||
*/}}
|
||||
{{- define "bentopdf.configJson" -}}
|
||||
{{- $config := . -}}
|
||||
{{- if kindIs "invalid" $config -}}
|
||||
{{- $config = dict -}}
|
||||
{{- end -}}
|
||||
{{- if not (kindIs "map" $config) -}}
|
||||
{{- fail (printf "bentopdf.config must be a mapping or null, got %s (%v)" (kindOf $config) $config) -}}
|
||||
{{- end -}}
|
||||
{{- toPrettyJson $config -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: "{{ .Release.Name }}-{{ .Chart.Name }}-configmap"
|
||||
data:
|
||||
# Mounted at /usr/share/nginx/html/config.json via subPath. The subPath is
|
||||
# not a matter of taste here: the file sits in the middle of the web root,
|
||||
# and a plain directory mount would hide the entire served application
|
||||
# behind an almost empty volume.
|
||||
#
|
||||
# It is always rendered, even for an empty config - a file that sometimes
|
||||
# exists and sometimes does not would mean the path through checksum, mount
|
||||
# and subPath is normally never exercised and first fails when someone
|
||||
# actually uses it. Empty renders as "{}", which is byte-identical to the
|
||||
# config.json the image ships anyway.
|
||||
config.json: |
|
||||
{{- include "bentopdf.configJson" .Values.bentopdf.config | nindent 4 }}
|
||||
@@ -0,0 +1,120 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
labels:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
spec:
|
||||
replicas: {{ include "bentopdf.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.bentopdf.replicas) }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
template:
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
labels:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
annotations:
|
||||
# Hash of the rendered ConfigMap: a changed config.json changes the pod
|
||||
# template and thus rolls the pods. Mandatory here rather than nice to
|
||||
# have - config.json is a subPath mount, which the kubelet never
|
||||
# refreshes, so without this annotation a changed tool list would stay
|
||||
# invisible until someone restarted the pods for an unrelated reason.
|
||||
checksum/config: {{ include (print $.Template.BasePath "/bentopdf.configmap.yaml") . | sha256sum }}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
{{- include "bentopdf.podSecurityContext" .Values.bentopdf.securityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
# The "-simple" variant on purpose: it is built from the same
|
||||
# Dockerfile as ghcr.io/alam00000/bentopdf, the difference being the
|
||||
# SIMPLE_MODE build argument, which strips the marketing surface of
|
||||
# the commercial site. That has no place on an internal instance.
|
||||
image: "ghcr.io/alam00000/bentopdf-simple:{{ .Chart.AppVersion }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
{{- if .Values.bentopdf.env }}
|
||||
env:
|
||||
{{- range $value := .Values.bentopdf.env }}
|
||||
{{- if $value.value }}
|
||||
- name: {{ $value.name | quote }}
|
||||
value: {{ tpl $value.value $ | quote }}
|
||||
{{- else if $value.valueFrom }}
|
||||
{{- if $value.valueFrom.secretKeyRef }}
|
||||
- name: {{ $value.name | quote }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ tpl $value.valueFrom.secretKeyRef.name $ | quote }}
|
||||
key: {{ tpl $value.valueFrom.secretKeyRef.key $ | quote }}
|
||||
{{- else if $value.valueFrom.configMapKeyRef }}
|
||||
- name: {{ $value.name | quote }}
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ tpl $value.valueFrom.configMapKeyRef.name $ | quote }}
|
||||
key: {{ tpl $value.valueFrom.configMapKeyRef.key $ | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- include "bentopdf.containerSecurityContext" .Values.bentopdf.securityContext | nindent 12 }}
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /usr/share/nginx/html/config.json
|
||||
subPath: config.json
|
||||
readOnly: true
|
||||
# The two writable paths below are what keeps
|
||||
# readOnlyRootFilesystem: true possible. Both are image facts, not
|
||||
# user configuration, so neither is a value - and both are here
|
||||
# because a run without them failed, not because the upstream
|
||||
# chart mounts them.
|
||||
- name: nginx-tmp
|
||||
# The nginx.conf puts its pid file at /etc/nginx/tmp/nginx.pid.
|
||||
mountPath: /etc/nginx/tmp
|
||||
- name: nginx-cache
|
||||
# NOT optional: nginx creates its temp directories at startup and
|
||||
# the image ships /var/cache/nginx without them. Missing this
|
||||
# mount, the master process aborts before serving anything, with
|
||||
# [emerg] mkdir() "/var/cache/nginx/client_temp" failed
|
||||
# (30: Read-only file system)
|
||||
mountPath: /var/cache/nginx
|
||||
# /var/run is deliberately NOT mounted. The upstream chart mounts
|
||||
# it, but this image keeps its pid file under /etc/nginx/tmp, and a
|
||||
# run with only the two mounts above serves / and /config.json with
|
||||
# 200. An emptyDir nobody writes to is a claim the chart cannot
|
||||
# back up.
|
||||
ports:
|
||||
# 8080 is the image's EXPOSE and the port its nginx.conf listens
|
||||
# on. It is not a value, because a value here would lie: the image
|
||||
# switch that moves the port (PORT) rewrites nginx.conf with sed
|
||||
# and therefore cannot run with a read-only root filesystem at all
|
||||
# - see the comment in values.yaml. Moving the port means changing
|
||||
# the chart, on purpose.
|
||||
#
|
||||
# The port NAME is load-bearing and must not be renamed: the
|
||||
# Service selects it by name and so do all three probes.
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
name: http
|
||||
# Probes, resources and both security contexts go through the
|
||||
# null-safe helpers (#99): an erased block means "chart defaults
|
||||
# apply" instead of a vanished probe or an unhardened container.
|
||||
# Never wrap these in an "if" - a guard hides the erased case
|
||||
# completely instead of rendering a visible null.
|
||||
livenessProbe:
|
||||
{{- include "bentopdf.livenessProbe" .Values.bentopdf.livenessProbe | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- include "bentopdf.readinessProbe" .Values.bentopdf.readinessProbe | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- include "bentopdf.startupProbe" .Values.bentopdf.startupProbe | nindent 12 }}
|
||||
resources:
|
||||
{{- include "bentopdf.effectiveResources" .Values.bentopdf.resources | nindent 12 }}
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: "{{ .Release.Name }}-{{ .Chart.Name }}-configmap"
|
||||
- name: nginx-tmp
|
||||
emptyDir: {}
|
||||
- name: nginx-cache
|
||||
emptyDir: {}
|
||||
restartPolicy: Always
|
||||
@@ -0,0 +1,28 @@
|
||||
{{- if .Values.ingress.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: {{ required "A Cluster-Issuer must be provided" .Values.ingress.clusterIssuer }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
rules:
|
||||
- host: {{ required "A Domain/Host must be provided" .Values.ingress.domain }}
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
service:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
||||
port:
|
||||
name: http
|
||||
path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ required "A Domain/Host must be provided" .Values.ingress.domain }}
|
||||
secretName: tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
||||
{{- end }}
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
||||
spec:
|
||||
selector:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: http
|
||||
name: http
|
||||
type: ClusterIP
|
||||
Reference in new issue
Block a user