NetworkPolicies in every chart (Gitea #5): the convention, the gate, and what it does not do
29 chart pages plus Chart-Style-Guide, CI-CD, Charts and Lessons-Learned.
Chart-Style-Guide gains section 15, and its core is the FOUR PORT CLASSES -
the part that is actually hard to get right. A port a network probe addresses
stays open until probeSources is set, because the kubelet probes from the
worker node's IP and no chart can know it. A NodePort-published port stays open
because the source is already masqueraded. Everything else can be and is
restricted by source, which is why the charts with a real inbound restriction
are exactly those whose probes use a different port. A replica-to-replica bus
gets a bare podSelector - and, since policyTypes lists Egress, its outbound
side needs a rule of its own or the cluster never forms while every node looks
healthy.
The exec-probe distinction is written down as load-bearing rather than
pedantic: it is the whole reason valkey and minecraft-server are the tightest
policies in the repository and every HTTP-probed web app is looser.
CI-CD gains the networkpolicy job as section 8 (the later two renumbered), and
it ends with the measurement rather than a claim. Two experiments ran on the PR
and were reverted: cyberchef's probe port restricted to an address no probe can
come from stayed GREEN at 1m9s, and outline with its DNS egress rule removed
went RED at 5m59s. Together those say k3s exempts the kubelet's probe traffic
while enforcing everything else - so the install-test's net is an EGRESS net,
and a wrong ingress port is caught by the render assertion or not at all.
Every chart page gets a networkPolicy values table and an operating note naming
that chart's own trap, not a boilerplate one:
- keycloak carries the blast radius: it is the cluster's identity provider, its
http rule is the only source-restricted inbound rule in the repo, and nothing
in CI can catch a wrong controller - so the page asks for a real login and a
look at consumers' issuer URLs after the first upgrade.
- garage asks the reader to check their S3 consumers, because anything in
another namespace is blocked until it is in allowedNamespaces.
- bulwark names the quietest failure in the sweep: its probes test its own port,
so a jmapServerUrl on a blocked port yields a Ready pod serving an empty
webmail.
- gotenberg explains what its DNS-only cap COSTS - HTML conversion silently
loses remote assets - rather than only what it buys.
- satisfactory and homeassistant each argue their own Ingress-only policy;
satisfactory's deciding half is that it is the one chart with no install-test
at all, so an egress cap there would be the only one with no empirical check
behind it.
- paperless-ngx names the two pods of its release that the object does NOT
cover, including the consume CronJob whose pod template carries no labels.
- freesailarr explains why ten containers behind a VPN need only three egress
rules, and that extraPorts cannot help with an alternative WireGuard port
because it is TCP-only.
valkey and stalwart are corrected rather than extended. valkey's page now says
why having no probeSources is what makes it the tightest policy here and not an
incomplete one. stalwart's carries a dated correction: this page and the chart
both advised putting the ingress controller's namespace into allowedNamespaces
with ["ingress-nginx"] as the example, and both halves are wrong - ingress-nginx
is gone from the cluster entirely, and allowedNamespaces would have opened http
to every pod in that namespace anyway.
Charts drops the claim that valkey is "the first chart with a NetworkPolicy on
by default" and says instead that every chart has one, with the two shapes a
reader needs before opening any chart page.
Lessons-Learned gains four, all transferable:
- the net that existed for a different failure than the one it was built for,
and why both experiments were needed before either could be read;
- a comment that could not describe its own syntax (a template comment must not
spell out its own closing delimiter; the render then fails pointing at the
template, not at the sentence);
- three values that reached no rule and would have read as controls - a
convention about value NAMES is not a convention about which values exist;
- the subchart whose comments the parent could not fix, and the rule it
produced: before making a check a gate, ask which failures the author of this
diff can actually fix.
docs: Gateway-API routing mode on the eight group-1 chart pages (#114)
bentopdf, garage, patchmon, rallly, searxng, sure, urlaubsverwaltung and
voucher-vault now offer the Gateway-API HTTPRoute as an alternative to the
Ingress (Gitea PR #116, group 1 of the #114 sweep). Per page: the summary
table's "Ingress" row becomes a "Routing" row naming both modes, an
installation example in Gateway mode, an httpRoute values table, a "Routing:
Ingress or Gateway API" section with the three hard rules and what each chart
in particular loses in that mode, and a CI note explaining why the install-test
stays in Ingress mode while the route is held by the httproute-match render
test through ci/httproute/<chart>.yaml.
Corrections rather than additions, where a page said something the second
routing template makes false:
garage claimed nothing outside the Ingress template reads ingress.domain, so
that switching the Ingress off freed domain as well as clusterIssuer. The
HTTPRoute reads it as its hostname, so that now holds only while BOTH modes
are off. bentopdf and urlaubsverwaltung carried the same claim in weaker
form.
sure's ingress.enabled row said it decides RAILS_ASSUME_SSL/RAILS_FORCE_SSL.
Those now follow ingress.enabled OR httpRoute.enabled, because Gateway mode
sets ingress.enabled false by construction while TLS is terminated at the
Gateway - the old reading would have meant insecure cookies and http:// links
with a green pod.
garage additionally gets the upload-limit regression spelled out: proxy-body-
size has no Gateway-API counterpart, so an S3 endpoint published through an
HTTPRoute inherits whatever limit the Gateway carries. #114 counted five charts
affected by this, measured on charts that hardcode the annotation; garage
depends on one without setting it and is a sixth.
Lessons-Learned gains "The switch that stopped meaning what it was read for" -
the sure finding in general form, with the grep that finds the remaining cases
(freesailarr and ytdlp-web-player both read .Values.ingress.enabled outside
their Ingress templates) - plus two lines in "The recurring shape".
No new Charts row and no _Sidebar entry: the charts already exist.
Chart-Style-Guide section 4 is unchanged - the convention was applied, not
altered.
Both version rows leave every chart page's at-a-glance table; the image
repository stays, because it does not rot. A plain app-version bump obliges
no wiki edit — that rule is deliberate and stays — so any number written
here is stale within a Renovate round, as urlaubsverwaltung's already was.
Chart.yaml is where the pair is read, exactly as the chart index has said
from the start.
Home and Workflow no longer promise version snapshots. Forge and tooling
described remotes named github and JLab-Git; the checkout has exactly one,
origin, pointing at Gitea. Workflow gains the rule the wiki never had:
fetch and rebase before every push, never force-push.
urlaubsverwaltung: version snapshot 10.0.0+up6.13.1
Renovate lifted the app to 6.13.1 while the required()-guard branch was open.
The rebase keeps the major and takes the new appVersion, so the snapshot here
is 10.0.0+up6.13.1 - and the App version row was still on 6.12.0, a bump older
than the page itself.
required() for the four mandatory OnePasswordItem references (#18)
Chart pages: calibre-web-automated 7.0.0, minecraft-server 6.0.0 and
urlaubsverwaltung 10.0.0 now guard their secret references, so the values
tables no longer say "not guarded by a required in the template". searxng
1.0.6 is a version snapshot only - its valkey reference was already correctly
optional and stays that way.
Style guide 12: the bullet on how null reaches the schema was wrong in the
case it was being applied to. A null chart default is NOT coalesced away; only
a user null over a non-null default is. Added the rule that follows from it -
a value with no valid default cannot be enforced by values-schema at all while
helm lint --strict has to pass on the defaults, so required() is the whole
enforcement and a required array would be a guard that cannot fire.
Lessons learned: both halves of that, since a safeguard that looks like one
and never fires is the same failure shape as the missing guard it replaced.
One page per chart - prerequisites, installation walkthrough, the required
1Password fields, the complete values reference and the chart's operating
traps - plus the long form of the workflow, the forge and its measured
differences, the pipelines, versioning, Renovate, the chart style guide, the
procedure for a new chart, local development, and the incidents each rule was
bought with.
Chart pages quote versions as snapshots; Chart.yaml stays authoritative,
because Renovate moves app versions continuously and never edits the wiki.