The reference implementation for Gitea #5: one NetworkPolicy per workload, networkPolicy.enabled defaulting to true, plus the values block, the schema entry and the NOTES.txt section that go with it. The shape is taken from the two charts that already had one - valkey and stalwart - rather than invented, because they carry the expensive lessons: policyTypes only where there is something to restrict, namespaces always spelled out because an empty "from" allows everything, kubernetes.io/metadata.name so no foreign namespace needs labelling, POD ports and never NodePorts, egress by port because the object cannot express a hostname, and the probe path left open by default because the kubelet probes from the node's own IP. Three things are written down rather than assumed. The ingress source is measured on the cluster and is NOT what the issue body assumes: ingress-nginx is gone, the only IngressClass is traefik, and the controller is the DaemonSet rke2-traefik in kube-system - which also serves the Gateway API here, so one source covers both the Ingress and the HTTPRoute path. Egress stays coarse: with Canal (Flannel plus Calico) there is no FQDN rule, and Calico's GlobalNetworkSet would bind every chart to Calico, the same argument that kept implementation-specific objects out of #114. And a policy is only a boundary where the CNI enforces one - unenforced, it is accepted and filters nothing. All reasoning sits in Go template comments, not in "#" comments: those are part of the rendered object and travel into the cluster. Writing this header also found the second-order trap, which is now in the header itself - prose inside a template comment must not spell out the comment's own closing delimiter, or the parser ends the comment early and the remaining prose is emitted as manifest content. Part of #5
248 lines
13 KiB
Plaintext
248 lines
13 KiB
Plaintext
{{/*
|
|
NOTES.txt — the reference implementation for issue #43.
|
|
|
|
Copy this file with the rest of template-chart and replace every xxx: the
|
|
values paths (.Values.xxx.*), the helper prefix ("xxx.replicas") and the app
|
|
name in the headline, exactly as in the other template files. This comment
|
|
block renders nothing and is meant to be deleted along the way.
|
|
|
|
THE STANDARD THIS FILE IS MEASURED AGAINST. A NOTES.txt that repeats the
|
|
values is worthless - it is skimmed once after the first install and never
|
|
read again. Print what an operator would otherwise have to look up in the
|
|
templates: the rendered access URL, WHICH FIELDS the 1Password item has to
|
|
carry, the options that are actually active in THIS release, and the traps of
|
|
this chart. Keep it conditional on the values that are really set, so the
|
|
output describes the installation at hand instead of every possible one.
|
|
|
|
NEVER PUT A REAL DOMAIN IN HERE. The URL is rendered from
|
|
.Values.ingress.domain, so it comes from the operator's values and not from
|
|
the chart; where an example is unavoidable, use example.com.
|
|
|
|
Note for the copier: a NOTES.txt renders no Kubernetes object, so adding one
|
|
is purely additive - a MINOR bump, and safe to roll out at any time.
|
|
*/}}
|
|
{{- $replicas := include "xxx.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.xxx.replicas) -}}
|
|
{{- $httpRoute := .Values.httpRoute | default dict -}}
|
|
{{- $hostnames := $httpRoute.hostnames | default list -}}
|
|
{{- if not $hostnames -}}
|
|
{{- $hostnames = list .Values.ingress.domain -}}
|
|
{{- end -}}
|
|
xxx {{ .Chart.AppVersion }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
|
|
|
|
{{- if .Values.ingress.enabled }}
|
|
|
|
ACCESS — INGRESS MODE (ingress.enabled: true, httpRoute.enabled: false)
|
|
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 if $httpRoute.enabled }}
|
|
|
|
ACCESS — GATEWAY MODE (httpRoute.enabled: true, ingress.enabled: false)
|
|
{{- range $hostnames }}
|
|
https://{{ . }}
|
|
{{- end }}
|
|
|
|
This release renders an HTTPRoute ({{ .Release.Name }}-{{ .Chart.Name }}-httproute) and nothing else of
|
|
the routing path — no Gateway, no class, no Certificate. Three things
|
|
therefore belong to the cluster rather than to this chart, and the hostname
|
|
above does not answer while any of them is missing:
|
|
|
|
1. The Gateway-API CRDs — present, or this install would have failed.
|
|
2. The Gateway named in httpRoute.parentRefs
|
|
{{- range $httpRoute.parentRefs }}
|
|
{{ .name }}{{ if .namespace }} in namespace {{ .namespace }}{{ end }}{{ if .sectionName }}, listener {{ .sectionName }}{{ end }}
|
|
{{- end }}
|
|
must exist AND allow routes from namespace {{ .Release.Namespace }} in its
|
|
listener's allowedRoutes. A refused attachment shows up only in the
|
|
route's own status:
|
|
|
|
kubectl -n {{ .Release.Namespace }} get httproute {{ .Release.Name }}-{{ .Chart.Name }}-httproute -o yaml
|
|
|
|
"Accepted: True" and "ResolvedRefs: True" under status.parents[] is
|
|
what a working attachment looks like; an empty status means no
|
|
controller has looked at the route at all.
|
|
3. The certificate for the hostname, on the Gateway's listener.
|
|
ingress.clusterIssuer is NOT used in this mode and is not required —
|
|
this chart renders no Certificate here.
|
|
|
|
ingress.annotations is inert in this mode as well: the Gateway API has no
|
|
annotation channel. When you copy this chart, say here what that costs YOUR
|
|
app — an upload limit set through proxy-body-size has no Gateway-API
|
|
equivalent at all, so silence on this point means the operator gets the
|
|
Gateway's default and finds out from a proxy error message.
|
|
|
|
ingress.domain STAYS REQUIRED in this mode, and not only as the hostname
|
|
fallback: the env block in values.yaml reads it for NORMAL_ENV, which is why
|
|
that read carries a required() of its own. Check that per chart (README,
|
|
guideline 12, point 1) — in a chart where nothing outside the routing
|
|
templates reads it, httpRoute.hostnames can replace it entirely.
|
|
{{- else }}
|
|
|
|
ACCESS — NEITHER INGRESS NOR HTTPROUTE
|
|
ingress.enabled and httpRoute.enabled are both false, so this release has no
|
|
Ingress, no HTTPRoute and no certificate, and ingress.clusterIssuer is not
|
|
needed. ingress.domain STAYS REQUIRED even so: the env block in values.yaml
|
|
reads it for NORMAL_ENV, which is why that read carries a required() of its
|
|
own. Without it the workload would start with an empty address and nothing
|
|
would point at the mistake.
|
|
|
|
Reach the release inside the cluster with
|
|
|
|
kubectl -n {{ .Release.Namespace }} port-forward svc/{{ .Release.Name }}-{{ .Chart.Name }}-service 8080:80
|
|
|
|
and open http://localhost:8080.
|
|
{{- end }}
|
|
{{ if eq $replicas "0" }}
|
|
SCALED TO ZERO
|
|
{{ if .Values.scaleDown }}scaleDown: true{{ else }}xxx.replicas: 0{{ end }} — the workload runs with replicas: 0, so the endpoint
|
|
above does not answer until you set it back and upgrade again. PVC, Secret{{ if .Values.ingress.enabled }},
|
|
Service and Ingress stay in place, so the certificate is not re-issued.{{ else if $httpRoute.enabled }},
|
|
Service and HTTPRoute stay in place, so the route keeps pointing at a Service
|
|
with no endpoints — the Gateway then answers with an error of its own rather
|
|
than with nothing.{{ else }} and
|
|
Service stay in place.{{ end }}
|
|
{{ end }}
|
|
THE 1PASSWORD ITEM MUST CARRY THE FIELDS THE CHART READS
|
|
secrets.xxxSecretRef points at
|
|
{{ .Values.secrets.xxxSecretRef }}
|
|
The 1Password operator materializes that item as the Kubernetes Secret
|
|
{{ .Release.Name }}-{{ .Chart.Name }}-secret, with one Secret key per field of the item. So the
|
|
fields are not free-form: this chart reads the field
|
|
|
|
secretKey (values.yaml, env entry ENV_FROM_SECRET)
|
|
|
|
and an item without it leaves the container stuck in
|
|
CreateContainerConfigError. Watch the materialization with
|
|
|
|
kubectl -n {{ .Release.Namespace }} get onepassworditem {{ .Release.Name }}-{{ .Chart.Name }}-secret
|
|
kubectl -n {{ .Release.Namespace }} get secret {{ .Release.Name }}-{{ .Chart.Name }}-secret
|
|
|
|
When you copy this chart, list YOUR chart's fields here. This is the one
|
|
piece of information that is otherwise only findable by reading the
|
|
templates, and it is the reason this section exists.
|
|
|
|
CHANGING THE SECRET DOES NOT RESTART ANYTHING HERE
|
|
Neither editing the values inside the 1Password item nor repointing
|
|
secrets.xxxSecretRef at a different item rolls the pods: this chart stamps
|
|
checksum/config over the ConfigMap, but no checksum/secret-ref over the
|
|
rendered OnePasswordItem. Helm never sees a secret's content, so the content
|
|
case cannot be closed at all — but the ref case can, and a chart that needs
|
|
it adds the second annotation (README, "Config changes must restart the
|
|
workload"). Until then, after any secret change:
|
|
|
|
kubectl -n {{ .Release.Namespace }} rollout restart deployment/{{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
|
|
|
CONFIGURATION
|
|
{{ .Release.Name }}-{{ .Chart.Name }}-configmap is mounted into the workload, and the pod template
|
|
carries checksum/config over it — so a changed ConfigMap really does roll the
|
|
pods. That annotation is not decoration: a subPath mount is never refreshed
|
|
by the kubelet, and most apps read their config only at start, so without it
|
|
a config change would apply to nothing while everything stayed green.
|
|
|
|
The hash is taken over the rendered ConfigMap, not over a timestamp: an
|
|
unchanged config yields an identical hash and the upgrade stays a no-op.
|
|
|
|
STORAGE
|
|
{{ .Release.Name }}-{{ .Chart.Name }}-pvc, {{ .Values.storage.amount }}, {{ if .Values.storage.storageClass }}storage class "{{ .Values.storage.storageClass }}"{{ else }}on the cluster's default storage class
|
|
(storage.storageClass is unset){{ end }}.
|
|
|
|
helm uninstall does NOT delete this PVC, which is deliberate — the data
|
|
survives a reinstall of the same release name. It also means a release
|
|
removed and recreated under a DIFFERENT name starts on an empty volume while
|
|
the old one keeps occupying space.
|
|
|
|
WHAT A GREEN POD PROVES
|
|
All three probes are HTTP GETs against "/" on the port named http. They
|
|
prove that something answers on that port — not that the application is
|
|
functional, and not that anything behind the Ingress works, because probes
|
|
go straight to the pod and never pass through it.
|
|
|
|
When you copy this chart, point the probes at a real health endpoint and
|
|
carry the probe's CONTEXT into the fallback defaults in _helpers.tpl, not
|
|
just its timings: a default that loses a port name, a Host header or an exec
|
|
command leaves the workload with a probe that cannot succeed.
|
|
|
|
THIS CHART IS A SCAFFOLD — IT IS NOT MEANT TO RUN
|
|
If you are reading this from an actual install of template-chart, three
|
|
things will keep it from becoming Ready, all of them on purpose:
|
|
|
|
- The image is the placeholder "ghcr.io/ORG/IMAGE:{{ .Chart.AppVersion }}" and does not
|
|
exist, so the pods stay in ImagePullBackOff. This is also why the chart
|
|
sits in ci/excluded-charts.txt and is never published.
|
|
- The chart renders BOTH a Deployment and a StatefulSet, as the two shapes
|
|
to choose between. Delete the one your app does not need; the Service
|
|
selects the Deployment's pods, so a stateful app has to move that selector
|
|
along with it.
|
|
- The env block in values.yaml demonstrates the three supported forms
|
|
(value, secretKeyRef, configMapKeyRef) — it is an example, not a working
|
|
configuration. Note in particular that ENV_FROM_CONFIGMAP names
|
|
"{{ .Release.Name }}-{{ .Chart.Name }}-config-map" while the ConfigMap this chart creates is
|
|
"{{ .Release.Name }}-{{ .Chart.Name }}-configmap": replace the whole block with your app's real
|
|
variables rather than repairing the example.
|
|
{{- if .Values.networkPolicy.enabled }}
|
|
|
|
NETWORKPOLICY IS ACTIVE — WHAT IT CAPS, AND WHAT IT DOES NOT
|
|
{{ .Release.Name }}-{{ .Chart.Name }}-networkpolicy selects this workload's pods. Only the
|
|
container ports this chart declares are reachable at all; anything else the
|
|
process binds is blocked without anyone having to notice it exists. Outbound
|
|
traffic is capped at DNS{{ with .Values.networkPolicy.egress }}{{ with .extraPorts }} plus TCP {{ join ", " . }}{{ end }}{{ end }}.
|
|
|
|
Three limits belong in the same breath, because none of them is visible in
|
|
the object:
|
|
|
|
- EGRESS IS BY PORT, NEVER BY DESTINATION. A NetworkPolicy matches CIDRs and
|
|
selectors and cannot express a hostname, so every rule above permits its
|
|
port to ANY address. This is a limit of the object, not a shortcut — and
|
|
with this cluster's CNI (Canal: Flannel plus Calico) there is no FQDN rule
|
|
to reach for. An outbound port the chart does not know about is blocked:
|
|
add it to networkPolicy.egress.extraPorts, and expect to find out through
|
|
an application-level connection error rather than through this object.
|
|
{{- if .Values.networkPolicy.probeSources }}
|
|
- THE PROBE PATH IS RESTRICTED in this release, because
|
|
networkPolicy.probeSources is set to
|
|
{{ join ", " .Values.networkPolicy.probeSources }}
|
|
Probes come from the IP of the WORKER NODE running the pod. If none of
|
|
those CIDRs covers the node this pod lands on, the kubelet's probes are
|
|
dropped and THE POD NEVER BECOMES READY — the rollout then times out and
|
|
the symptom looks like a broken application. Check with
|
|
|
|
kubectl -n {{ .Release.Namespace }} describe pod -l app={{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
|
|
|
and compare "Readiness probe failed" against `kubectl get node -o wide`.
|
|
{{- else }}
|
|
- THE PROBED PORT IS OPEN INSIDE THE CLUSTER. networkPolicy.probeSources is
|
|
empty, so the rule for that port carries no source selector at all: any
|
|
pod the CNI routes to can reach it. Not from the internet — the port has no
|
|
NodePort — but this is NOT pod-level isolation. Narrowing it means listing
|
|
the worker node IPs in networkPolicy.probeSources, because the kubelet
|
|
probes from the node's own address and the chart cannot know it; a wrong
|
|
guess there leaves the pod permanently un-Ready.
|
|
{{- end }}
|
|
- IT IS ONLY A BOUNDARY WHERE THE CNI ENFORCES POLICIES. Where it does not,
|
|
this object is accepted and filters nothing, so everything reads as
|
|
configured while nothing is filtered. Prove it rather than assume it:
|
|
|
|
kubectl -n {{ .Release.Namespace }} get networkpolicy {{ .Release.Name }}-{{ .Chart.Name }}-networkpolicy
|
|
|
|
shows the object exists; only a connection attempt from an unrelated
|
|
namespace shows that it bites.
|
|
|
|
Switch it off with networkPolicy.enabled: false{{ if .Values.networkPolicy.allowedNamespaces }}; the namespaces currently
|
|
allowed in addition to {{ .Release.Namespace }} are {{ join ", " .Values.networkPolicy.allowedNamespaces }}{{ end }}.
|
|
{{- end }}
|
|
{{- if .Values.ingress.enabled }}
|
|
{{- with .Values.ingress.annotations }}
|
|
|
|
EXTRA INGRESS ANNOTATIONS ARE ACTIVE
|
|
{{- range $key, $value := . }}
|
|
{{ $key }}: {{ $value }}
|
|
{{- end }}
|
|
These are merged with the cert-manager cluster-issuer annotation the chart
|
|
always sets. Annotations are interpreted by the ingress controller, so a
|
|
misspelled key is silently ignored — it does not fail the render.
|
|
{{- end }}
|
|
{{- end }}
|