feat(cyberchef): Gateway-API as an alternative to the Ingress (pilot)
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 13s
Validate Helm Charts / Build cyberchef (pull_request) Successful in 6s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 18s
Validate Helm Charts / Build template-chart (pull_request) Successful in 7s
Validate Helm Charts / Trivy config template-chart (pull_request) Successful in 17s
Install-test / Install-test cyberchef (pull_request) Successful in 1m0s
Validate Helm Charts / Install-test (pull_request) Successful in 1m0s
Validate Helm Charts / Selector match (pull_request) Successful in 12s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 13s
Validate Helm Charts / Build cyberchef (pull_request) Successful in 6s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 18s
Validate Helm Charts / Build template-chart (pull_request) Successful in 7s
Validate Helm Charts / Trivy config template-chart (pull_request) Successful in 17s
Install-test / Install-test cyberchef (pull_request) Successful in 1m0s
Validate Helm Charts / Install-test (pull_request) Successful in 1m0s
Validate Helm Charts / Selector match (pull_request) Successful in 12s
The Gateway-API pilot of Gitea #114: one chart, the convention, the CI that holds it. The remaining 20 charts follow in their own PRs. The form, additive and off by default: httpRoute: enabled: false # counterpart to ingress.enabled parentRefs: [] # the Gateway - a platform object hostnames: [] # empty = [ingress.domain] Only HTTPRoute with parentRefs is rendered: no Gateway, no gatewayClassName, no Certificate, and no implementation-specific policy object. That subset is identical on nginx-gateway-fabric, Cilium, Envoy Gateway and Istio, which is what lets the implementation be chosen later without touching the charts again. Class and certificate move to the platform. Four guards, all against silent failures: - mutually exclusive with ingress.enabled, with a render abort naming BOTH value paths. A migration runs over a second hostname, not in parallel. - parentRefs mandatory in Gateway mode, render abort on the empty list: a route without a parent is created, is schema-valid, and routes nothing. - hostnames empty means [ingress.domain], so the hostname has one source. - the mode is a VALUE, not auto-detection: .Capabilities.APIVersions is unreliable at template time and a missing CRD fails at install time. ingress.clusterIssuer was already only read inside the Ingress template, so the "conditionally required" half of #114 needed no change in this chart - checked rather than assumed, and it has to be re-checked per chart in the sweep. CI, both halves, because neither covers the other: - the install-test installs cyberchef in GATEWAY mode against the REAL Gateway-API CRDs (standard channel v1.6.3, pinned and Renovate-tracked, applied --server-side). A schema-less stub would accept a malformed route and prove only that something was created. The Ingress path of this one chart is no longer installed; it is a built-in type that never routed in k3d anyway, and helm lint renders this chart's defaults - Ingress mode - on every PR. - ci/run-httproute-match-test.sh, over every chart on every PR: every backendRefs entry must name a Service, on a port, that the SAME render creates, and parentRefs must not be empty. The CRDs accept both of those mistakes, so the schema cannot catch them, and the only report would be a route status no controller writes in CI. What stays unprovable, and is written down instead of implied: that traffic flows. No Gateway controller runs in k3d. Minor: additive with unchanged defaults, proven by an empty helm template diff against origin/main for the default values. Part of #114
This commit is contained in:
18 files changed
+1225
-37
No files matched your search
@@ -22,25 +22,72 @@ 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
|
||||
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 — NO INGRESS
|
||||
ingress.enabled is false, so this release has no Ingress 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.
|
||||
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
|
||||
|
||||
@@ -52,7 +99,10 @@ ACCESS — NO INGRESS
|
||||
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 }} and
|
||||
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
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
{{/*
|
||||
HTTPRoute — the Gateway-API alternative to xxx.ingress.yaml (Gitea #114), and
|
||||
the reference implementation of that convention (README, "Ingress and
|
||||
Gateway API").
|
||||
|
||||
Copy this file with the rest of template-chart and rename it to
|
||||
<app>.httproute.yaml. There is nothing chart-specific to replace in the body
|
||||
except the BACKEND: the Service name follows the naming scheme and the port
|
||||
below must be the port number your <app>.service.yaml actually exposes.
|
||||
|
||||
WHAT THIS RENDERS, AND WHAT IT MUST NOT
|
||||
|
||||
Only an HTTPRoute with parentRefs. No Gateway, no gatewayClassName, no
|
||||
Certificate, and no implementation-specific policy object
|
||||
(ClientSettingsPolicy, ClientTrafficPolicy and their equivalents). That subset
|
||||
is the part which looks identical on nginx-gateway-fabric, Cilium, Envoy
|
||||
Gateway and Istio, and keeping to it is the whole reason the cluster's
|
||||
implementation can be chosen - or replaced - without touching the charts
|
||||
again. Adding a policy object here binds the chart to one implementation and
|
||||
to a CRD whose presence it cannot check.
|
||||
|
||||
The consequence is that class and certificate belong to the PLATFORM: they sit
|
||||
on the Gateway and its listeners, in cluster-config. A chart in this mode
|
||||
renders no Certificate, so ingress.clusterIssuer does nothing - which is why
|
||||
it may only be `required` under ingress.enabled.
|
||||
|
||||
WHAT HAS NO COUNTERPART AT ALL
|
||||
|
||||
The Gateway API has no annotation channel for controller behaviour. Everything
|
||||
a chart does through ingress.annotations today - proxy-body-size,
|
||||
rewrite-target, external auth - is gone in this mode. rewrite-target has a real
|
||||
equivalent (the URLRewrite filter in a rule, not added here because this
|
||||
scaffold has no base path to strip); an upload limit does NOT, and a chart that
|
||||
depends on one must SAY SO in its NOTES.txt for this mode rather than quietly
|
||||
inherit the Gateway's default.
|
||||
|
||||
WHY A VALUE AND NOT AUTO-DETECTION
|
||||
|
||||
.Capabilities.APIVersions is unreliable at template time, and an HTTPRoute
|
||||
without the Gateway-API CRDs fails at install time. A chart that guessed would
|
||||
sometimes render an object the cluster rejects and sometimes silently render
|
||||
nothing at all. So the mode is a value, and the CRDs are the cluster's
|
||||
business.
|
||||
*/}}
|
||||
{{- /*
|
||||
Null-safe like every other block a template here dereferences (#99): an
|
||||
override written without children - "httpRoute:" with its keys deleted - is
|
||||
YAML null, and `.Values.httpRoute.enabled` would abort with a nil pointer. An
|
||||
empty block means "chart defaults apply", and all three defaults are empty or
|
||||
false, so it collapses to "no HTTPRoute".
|
||||
*/ -}}
|
||||
{{- $httpRoute := .Values.httpRoute | default dict -}}
|
||||
{{- if and $httpRoute.enabled .Values.ingress.enabled -}}
|
||||
{{- fail "ingress.enabled and httpRoute.enabled are both true - set exactly one of them. The Ingress and the HTTPRoute would publish the same Service under the same hostname over two different data paths, which is already a conflict at DNS level: one name, two addresses, and which one a client reaches is luck. A migration runs over a SECOND hostname (set httpRoute.hostnames explicitly while the Ingress keeps ingress.domain), never over parallel operation of both." -}}
|
||||
{{- end -}}
|
||||
{{- if $httpRoute.enabled -}}
|
||||
{{- $parentRefs := $httpRoute.parentRefs | default list -}}
|
||||
{{- if not $parentRefs -}}
|
||||
{{- fail "httpRoute.enabled is true but httpRoute.parentRefs is empty - an HTTPRoute without a parent attaches to no Gateway. It is created, it is schema-valid, and it routes NOTHING, with no event and no condition that says so; the only symptom is a hostname that does not answer. Name the cluster's Gateway, e.g. httpRoute.parentRefs: [{name: public, namespace: gateway}]." -}}
|
||||
{{- end -}}
|
||||
{{- /*
|
||||
One source of truth for the hostname: an empty httpRoute.hostnames means
|
||||
[ingress.domain]. Two values that have to agree drift apart. ingress.domain is
|
||||
read here although no Ingress is rendered in this mode - it names the public
|
||||
hostname of the release, which both modes need, and it is not an Ingress
|
||||
switch. In THIS chart that read is not even the only one: the env block in
|
||||
values.yaml reads ingress.domain too, which is why it carries a required() of
|
||||
its own and stays mandatory with the Ingress switched off (README, guideline
|
||||
12, point 1). Check that per chart; do not copy the conclusion.
|
||||
*/ -}}
|
||||
{{- $hostnames := $httpRoute.hostnames | default list -}}
|
||||
{{- if not $hostnames -}}
|
||||
{{- $hostnames = list (required "A Domain/Host must be provided: set ingress.domain (used as the HTTPRoute's single hostname when httpRoute.hostnames is empty) or set httpRoute.hostnames explicitly" .Values.ingress.domain) -}}
|
||||
{{- end -}}
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-httproute
|
||||
spec:
|
||||
parentRefs:
|
||||
{{- toYaml $parentRefs | nindent 4 }}
|
||||
hostnames:
|
||||
{{- toYaml $hostnames | nindent 4 }}
|
||||
rules:
|
||||
# The Ingress counterpart is `path: / , pathType: Prefix` on the backend
|
||||
# Service. The port is the Service's port NUMBER, not its name:
|
||||
# backendRefs takes a PortNumber, so unlike the Ingress this reference
|
||||
# cannot be made by name, and the number here has to track
|
||||
# <app>.service.yaml by hand. ci/run-httproute-match-test.sh is what holds
|
||||
# that pair - it fails when a backendRef names a Service, or a port, that
|
||||
# the same render does not create.
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
||||
port: 80
|
||||
{{- end }}
|
||||
Reference in new issue
Block a user