Envoy Gateway
Base Envoy Gateway infrastructure for platform / Loadbalancer regions. This block deploys the envoy-gateway control plane, Gateway API CRDs (optional), two GatewayClasses, matching EnvoyProxy pools, and optionally a bootstrap Gateway that brings up the shared data-plane LoadBalancer VIP immediately.
Per-tenant routes (HTTPRoute / TLSRoute / Backend / policies) are not owned here — they are
created by consumers (Managed Controlplane k3s-server, polycrate-api LBI, or hand-written manifests).
| Wert | |
|---|---|
| Block | cargo.ayedo.cloud/ayedo/k8s/envoy |
| Version | 0.2.0 |
| Kind / Type / Flavor | k8sapp / lb / envoy |
| Chart | OCI oci://docker.io/envoyproxy/gateway-helm v1.8.2 (app 1.8.2) |
| Icon | https://icons.ayedo.de/png/envoy.png |
| Upstream | https://gateway.envoyproxy.io |
Beispiele: polycrate block examples envoy
Related specs (polycrate-api): polycrate spec inspect 486, polycrate spec inspect 385.
What it deploys
- Helm
gateway-helmintoconfig.namespace(defaultenvoy-gateway-system) - EnvoyProxy CRs:
eg-shared(mergeGateways: true) +eg-dedicated(mergeGateways: false) - GatewayClass CRs with the same names (contract to API / MCP)
- Optional default Gateway (
config.default_gateway) — bootstrap listener(s), creates the shared LB - Optional ClientTrafficPolicy / BackendTrafficPolicy (Proxy Protocol, timeouts)
- Optional WAF Coraza path via
ext_proc(Service/Deployment; policies are user/API-owned) - Optional VMPodScrape for data-plane Prometheus stats (
:19001)
Host cluster
├── NS envoy-gateway-system
│ ├── Deployment envoy-gateway (controller)
│ ├── EnvoyProxy eg-shared|eg-dedicated
│ ├── Gateway eg-shared (optional default_gateway)
│ ├── DaemonSet/Deployment envoy-… (data plane, after Gateway attaches)
│ └── Service LoadBalancer (shared VIP when mergeGateways)
└── cluster-scoped GatewayClass eg-shared|eg-dedicated
GatewayClasses (contract)
| Class | mergeGateways | Use |
|---|---|---|
eg-shared |
true |
One fleet + one VIP; SNI/hostname multiplex. Default for MCP edge / dense LBI. |
eg-dedicated |
false |
Own fleet/service/IP per Gateway (raw TCP / dedicated VIP). |
Class names are configurable under config.gatewayclass.*.name but should stay stable once
polycrate-api / workspaces depend on them.
Shared VIP + default Gateway
With mergeGateways: true, Envoy creates the LoadBalancer only after a Gateway attaches.
For platform edges that need a stable VIP (parallel to nginx), enable:
service:
type: LoadBalancer
external_traffic_policy: Local # needs DaemonSet for true client-IP
loadbalancer_ip: "203.0.113.20" # Cilium lbipam + MetalLB annotations + loadBalancerIP
default_gateway:
enabled: true
name: eg-shared
listeners:
- name: tls
protocol: TLS
port: 443
tls_mode: Passthrough
allowed_routes_namespaces: All
loadbalancer_ipis applied only to the shared EnvoyProxy (dedicated IPs stay per-Gateway / API).- Bootstrap listener
tls(Passthrough) +allowedRoutes=Alllets other namespaces attachTLSRouteviaparentRefs(sectionName: tls). - Listener
http(:80) acceptsHTTPRoute(ACME HTTP-01 / plaintext).
Passthrough vs TLS termination
| Goal | Listener | Route kind |
|---|---|---|
| SNI passthrough (MCP kube-apiserver terminates TLS) | protocol: TLS, tls_mode: Passthrough |
TLSRoute |
| Edge terminates TLS, then HTTP routing / WAF | protocol: HTTPS, tls_mode: Terminate + certificate_refs |
HTTPRoute |
The shared bootstrap Gateway keeps Passthrough on :443 on purpose (MCP + SNI multiplex).
That does not block termination elsewhere: with mergeGateways=true on eg-shared, the API
or operator can create additional Gateways on the same class (same VIP) that add an
HTTPS/Terminate listener + cert Secret refs; HTTPRoute attaches to that listener
(sectionName: https or whatever you name it).
Do not put a catch-all HTTPS/Terminate listener on the bootstrap Gateway without certs —
Gateway API requires certificateRefs for Terminate.
Parallel to nginx
nginx keeps classic Ingress + its own VIP. Envoy is an additional edge:
| Edge | Class / IngressClass | Typical VIP |
|---|---|---|
| nginx | IngressClass nginx |
existing platform LB |
| envoy | GatewayClass eg-shared |
service.loadbalancer_ip |
Consumers choose deliberately (e.g. k3s-server ingress.type: gateway).
Consumer: Managed Controlplane (k3s-server)
# envoy (platform)
config:
service:
loadbalancer_ip: "203.0.113.20"
default_gateway:
enabled: true
# k3s-server (MCP)
config:
loadbalancer:
enabled: false # mutual exclusive with ingress
ingress:
enabled: true
type: gateway # TLSRoute → platform Gateway (defaults)
k3s:
cluster_domain: "my-ws.k8s.example.com" # API host = api.<cluster_domain>
token: "…"
Defaults in k3s-server: parent_gateway_name=eg-shared, namespace envoy-gateway-system,
section tls. See polycrate block examples k3s-server → managed-controlplane-gateway.
HTTP:80 / ACME
Add an HTTP listener for cert-manager HTTP-01 (block does not create issuers/certificates):
default_gateway:
enabled: true
listeners:
- name: http
protocol: HTTP
port: 80
allowed_routes_namespaces: All
- name: tls
protocol: TLS
port: 443
tls_mode: Passthrough
allowed_routes_namespaces: All
Proxy Protocol
client_traffic:
enabled: true # requires default_gateway.enabled or target_gateway_name
proxy_protocol:
enabled: true # optional: false (strict)
WAF (Coraza via ext_proc)
Block provisions an optional regional gRPC service. Toggle per route/gateway with
EnvoyExtensionPolicy (extProc). Wasm / Dynamic Modules are not supported.
Image build: see waf/BUILD.md. Default image (public):
cargo.ayedo.cloud/library/envoy-waf:0.1.0. Build only from waf/ (never the block root).
waf:
enabled: true # uses block default image when repository/tag omitted
default_policy:
enabled: false # opt-in bootstrap policy on the Gateway
Chart OCI mirror / images
- Chart:
chart.repo.url(e.g.oci://cargo.ayedo.cloud/...) +chart.auth - Controller:
image.controller.{registry,repository,tag} - Data-plane Envoy:
image.proxy.{registry,repository,tag}
Configuration reference
| Key | Default | Notes |
|---|---|---|
namespace |
envoy-gateway-system |
Controller + proxies |
manage_crds |
true |
Chart owns Gateway API CRDs (single cluster owner) |
enable_backend |
true |
Backend CRD for out-of-cluster origins |
workload_kind |
daemonset |
daemonset | deployment |
replicas |
2 |
Only for deployment |
service.type |
LoadBalancer |
|
service.external_traffic_policy |
Local |
Prefer DaemonSet |
service.loadbalancer_ip |
"" |
Shared pool VIP |
service.annotations |
{} |
Merged onto envoyService |
service.openstack |
off | Octavia convenience annotations |
default_gateway.enabled |
false |
Bootstrap Gateway |
default_gateway.name |
eg-shared |
|
default_gateway.listeners |
TLS:443 Passthrough, All NS | Add HTTP:80 for ACME |
client_traffic.* |
off | Proxy Protocol / timeouts / buffer |
backend_traffic.* |
off | Upstream timeouts |
waf.* |
off | Coraza ext_proc path |
image.controller / image.proxy |
chart defaults | Optional overrides |
tls.cluster_issuer_name |
letsencrypt-production |
Reference only — not created |
metrics.* / vmpodscrape |
on | Port 19001; optional relabel for polycrate_lbi_* |
access_log |
on, JSON | Independent of metrics.enabled |
Actions
| Action | Description |
|---|---|
install |
Helm + EnvoyProxies + GatewayClasses + optional Gateway/policies/WAF + VMPodScrape |
uninstall |
Remove WAF/policies/Gateway/EnvoyProxies/GatewayClasses/VMPodScrape + Helm (CRDs kept) |
polycrate run envoy install
polycrate run envoy uninstall
polycrate block examples envoy
TLS
The block does not create a ClusterIssuer or Certificate.
- Passthrough (default shared listener): backend terminates TLS; attach
TLSRoute. - Terminate: add an
HTTPSlistener (tls_mode: Terminate,certificate_refs) on a Gateway of classeg-shared(merged VIP) oreg-dedicated; attachHTTPRoute. Prefer an explicit cert-managerCertificate→ Secret, then reference that Secret.
Observability
- Metrics: Envoy admin Prometheus on
:19001, scraped viaVMPodScrapewhen enabled - Access logs: JSON to stdout → Vector → VictoriaLogs (
polycrate_log_source=appon pods)
Prerequisites
- Cilium LB-IPAM and/or MetalLB (or cloud LB) for
service.type=LoadBalancer - Optional: cert-manager ClusterIssuer named in
tls.cluster_issuer_name - Optional: VictoriaMetrics Operator for
metrics.vmpodscrape
Troubleshooting
| Symptom | Fix |
|---|---|
Gateway Programmed=False / NoResources |
Wait for data-plane DaemonSet pull; check EnvoyProxy |
| No EXTERNAL-IP | Enable default_gateway or create any Gateway on eg-shared; check loadbalancer_ip in pool |
| TLSRoute not Accepted | parentRefs name/namespace/sectionName must match default Gateway listener |
| Conflict with nginx | Different VIP + GatewayClass; do not share the same LB IP |
Changelog
See CHANGELOG.poly.