Rendering config with kustomize¶
HOG's config resources (Gateway, Route, RouteGroup, Policy, IdP,
Telemetry, …) are deliberately Kubernetes-style: every resource has
apiVersion / kind / metadata / spec, the same envelope
kubectl and
kustomize
already know how to find, identify, and patch — even though these are not
real Kubernetes API objects and never reach a cluster's API server. That
means you can manage a base config plus any number of per-environment
overlays with the exact same tool you already use for Kubernetes manifests,
without HOG needing to know anything about kustomize at all: you run
kustomize build yourself (or in CI, or in a Dockerfile — see
below) and hand HOG the rendered
output.
apiVersion is required for this to work¶
HOG itself never reads or validates apiVersion — every example elsewhere
in these docs could drop it and HOG wouldn't notice. But kustomize uses
apiVersion + kind + metadata.name (+ metadata.namespace, unused
here) as a resource's identity, and it skips any YAML document that doesn't
look like a resource at all. So every resource you want kustomize to manage
needs an apiVersion, even a made-up one:
hog.dev/v1 isn't a registered/versioned API in any schema registry — it's
just a stable, unambiguous string. Pick one and keep it consistent across
your resources.
Layout: base + overlays¶
The standard kustomize shape works unchanged:
config/
├── base/
│ ├── gateway.yaml
│ ├── route.yaml
│ └── kustomization.yaml # resources: [gateway.yaml, route.yaml]
└── overlays/
├── staging/
│ └── kustomization.yaml # resources: [../../base], patches: [...]
└── prod/
└── kustomization.yaml # resources: [../../base], patches: [...]
base/ holds the resources that are identical across every environment.
Each overlays/<env>/kustomization.yaml references the base and layers on
the fields that differ — upstream hostnames, trusted-proxy CIDRs, replica
counts if you template your Deployment alongside this, etc.
Use JSON6902 patches, not strategic-merge¶
Kustomize supports several patch styles. For HOG resources, use JSON6902
(patches: with an inline op/path/value list) or replacements —
not strategic-merge patches (a bare partial resource under patches: with
only the fields you want to change, which kustomize deep-merges by key).
Strategic-merge relies on OpenAPI schema annotations (x-kubernetes-patch-strategy,
x-kubernetes-patch-merge-key) to know whether a list field should be
merged element-by-element or replaced wholesale. Kubernetes' built-in types
ship that schema; HOG's custom kinds publish none, so kustomize falls back
to a generic merge that treats every list (trustedProxies, backends,
scopes, …) as replace the whole list, and offers no way to patch a
single named list element (e.g. one backends[].upstream among several)
predictably. JSON6902's explicit path (a JSON Pointer) and replacements'
explicit source/target field paths don't depend on any schema at all — they
say exactly which field to touch, so they behave the same for a Route as
for a Deployment.
A complete worked example¶
config/base/gateway.yaml:
apiVersion: hog.dev/v1
kind: Gateway
metadata:
name: hog
spec:
listen: ":8080"
trustedProxies:
- 10.0.0.0/8
config/base/route.yaml:
apiVersion: hog.dev/v1
kind: Route
metadata:
name: account
spec:
match: /account/
handler:
type: reverse-proxy
upstream: http://account-svc.dev.svc.cluster.local:9000
stripPrefix: /account
access:
auth: required
config/base/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gateway.yaml
- route.yaml
config/overlays/prod/kustomization.yaml patches the backend's
upstream to the prod service address and widens trustedProxies to the
prod cluster's pod CIDR:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
- target:
kind: Route
name: account
patch: |-
- op: replace
path: /spec/handler/upstream
value: http://account-svc.prod.svc.cluster.local:9000
- target:
kind: Gateway
name: hog
patch: |-
- op: replace
path: /spec/trustedProxies
value: [10.1.0.0/16]
Render it:
This is real output from that exact layout, using the local kustomize
binary (kustomize version → v5.8.1 at the time this was written)
— no placeholders:
apiVersion: hog.dev/v1
kind: Gateway
metadata:
name: hog
spec:
listen: :8080
trustedProxies:
- 10.1.0.0/16
---
apiVersion: hog.dev/v1
kind: Route
metadata:
name: account
spec:
access:
auth: required
handler:
stripPrefix: /account
type: reverse-proxy
upstream: http://account-svc.prod.svc.cluster.local:9000
match: /account/
Two things to notice: kustomize re-serializes YAML (keys sorted
alphabetically, :8080 unquoted) — both are semantically identical to the
base and parse the same for HOG — and only the two patched fields
(spec.trustedProxies and spec.handler.upstream) changed; everything
else passed through from the base untouched.
Pipe the rendered output straight into a file HOG (or hog-build) reads:
kustomize in the hog-builder image¶
The hog-builder image (used to compose a custom binary
with hog-build) ships kustomize alongside the Go toolchain, so a
Dockerfile build stage can render an overlay and hand the result straight to
hog-build without adding another base image or installing kustomize
yourself. See
Building binaries: rendering config with kustomize
for the Dockerfile pattern.
See also¶
- Configuration reference — every resource kind and field kustomize might patch, including the complete annotated example used as the source for the base/overlay pattern above.
- Building binaries — the plugin
manifest and
hog-buildCLI this config ultimately feeds.