Skip to content

Kubernetes Ingress Controller

dwaar-ingress is a standalone binary that watches Kubernetes Ingress, Service, and Secret resources and reconciles them into Dwaar’s route table via the admin API. Deploy it alongside your Dwaar proxy pods; it requires no sidecar and carries a ~64 MiB memory footprint at rest.

Two replicas run by default. Leader election ensures exactly one replica drives route mutations at any time while the standby is ready to take over within seconds.

Terminal window
helm install dwaar-ingress ./deploy/helm/dwaar-ingress \
--namespace dwaar-system \
--create-namespace \
--set controller.adminUrl=http://dwaar-admin:9000

All four resource types are watched via kube::runtime::reflector — in-memory caches that receive change events from the API server. The IngressWatcher drives reconciliation events; the periodic reconciler runs a full-sync pass every 60 seconds to correct any drift from missed events or controller restarts.

The IngressTranslator maps each spec.rules[].host to a domain key and resolves the backend Service to a ClusterIP:port upstream using the local Service store — no API round-trip per reconcile event.

Add dwaar.dev/* annotations to any Ingress to override per-route behaviour. Unknown dwaar.dev/* annotations are logged as warnings and ignored; non-dwaar.dev/* annotations are never read.

AnnotationTypeDescriptionExample
dwaar.dev/rate-limitu32 req/sApply rate limiting at the proxy. Zero disables."500"
dwaar.dev/tls-redirectboolIssue HTTP 301 → HTTPS for all plaintext requests on this Ingress."true"
dwaar.dev/upstream-proto"h2" or "http"Force a specific application protocol on the upstream connection. Omit to let Dwaar negotiate."h2"
dwaar.dev/under-attackboolEnable challenge / JavaScript proof-of-work mode before forwarding."true"
dwaar.dev/ip-filter-allowCIDR listComma-separated allowlist. All IPs not in the list are denied."10.0.0.0/8,172.16.0.0/12"
dwaar.dev/ip-filter-denyCIDR listComma-separated denylist. All IPs not in the list are allowed."203.0.113.0/24"

CIDR format: addr/prefix. IPv4 prefix max is /32; IPv6 max is /128. Individual invalid entries are skipped with a warning — a single typo does not discard the rest of the list.

Bool accepted values: true, 1, yes / false, 0, no (case-insensitive).

IngressClass filtering: The controller only processes Ingresses whose spec.ingressClassName matches --ingress-class. When --ingress-class is unset, all Ingresses are processed. The legacy kubernetes.io/ingress.class annotation is also checked as a fallback when spec.ingressClassName is absent.

When replicaCount is greater than one, every replica runs the leader election loop. Only the current leader starts the IngressWatcher and drives route mutations.

The protocol uses coordination.k8s.io/v1 Lease objects:

  1. Candidate — attempt to CREATE the Lease with holderIdentity set to the pod hostname. A 409 Conflict means another pod holds it; enter observer mode.
  2. Leader — renew renewTime on the Lease every renew_deadline (default 10 s). If renewal fails or returns 409, immediately drop to Candidate and signal the watcher to stop.
  3. Observer — poll every retry_period (default 2 s). If renewTime is older than lease_duration (default 15 s), the current holder is presumed dead; attempt acquisition via strategic merge patch with the stored resourceVersion to win concurrent races safely.

The leader_ready and sync_ready health flags both clear on leadership loss. The standby replica does not report /readyz true until it acquires the lease and completes a fresh informer sync.

ParameterDefaultCLI flagEnv var
Lease namedwaar-ingress-leader--lease-nameLEASE_NAME
Lease namespacekube-system--lease-namespaceLEASE_NAMESPACE
Lease duration15 s
Renew deadline10 s
Retry period2 s

Reference a Kubernetes TLS Secret from an Ingress spec.tls block:

spec:
tls:
- hosts:
- app.example.com
secretName: app-tls-secret

When the watcher processes the Ingress, it reads tls.crt and tls.key from the Secret’s binary data and writes them to cert-dir as:

{namespace}_{secret_name}.crt # mode 0644
{namespace}_{secret_name}.key # mode 0600

For example, Secret app-tls-secret in namespace production becomes:

/var/lib/dwaar-ingress/certs/production_app-tls-secret.crt
/var/lib/dwaar-ingress/certs/production_app-tls-secret.key

Namespace and secret name components are validated before any filesystem operation — segments containing /, \, null bytes, or .. are rejected to prevent path traversal. When an Ingress is deleted its PEM files are removed from disk.

Configure cert-dir so Dwaar can read the files. Point Dwaar’s TLS configuration at the same directory or mount the same volume.

Every flag also has an environment variable equivalent (shown in the Env var column).

FlagEnv varDefaultDescription
--admin-urlDWAAR_ADMIN_URLhttp://127.0.0.1:6190Dwaar admin API base URL.
--admin-tokenDWAAR_ADMIN_TOKEN(none)Bearer token for admin API authentication.
--health-addrHEALTH_ADDR0.0.0.0:8080Address for /healthz and /readyz endpoints.
--ingress-classINGRESS_CLASS(none — manage all)Only manage Ingresses with this IngressClass name.
--namespaceWATCH_NAMESPACE(none — all namespaces)Restrict watching to a single namespace.
--lease-nameLEASE_NAMEdwaar-ingress-leaderName of the leader election Lease object.
--lease-namespaceLEASE_NAMESPACEkube-systemNamespace that holds the Lease object.
--cert-dirCERT_DIR/var/lib/dwaar-ingress/certsDirectory where TLS PEM files are written.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
namespace: production
annotations:
dwaar.dev/tls-redirect: "true"
dwaar.dev/rate-limit: "300"
dwaar.dev/upstream-proto: "h2"
dwaar.dev/ip-filter-deny: "203.0.113.0/24,198.51.100.0/24"
spec:
ingressClassName: dwaar
tls:
- hosts:
- app.example.com
secretName: app-tls-secret
rules:
- host: app.example.com
http:
paths:
- path: /api/
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 3000

This Ingress produces two routes in the Dwaar route table:

  • app.example.com/api/ClusterIP:8080, TLS termination enabled
  • app.example.comClusterIP:3000, TLS termination enabled

Both routes receive the rate limit (300 req/s), HTTP→HTTPS redirect, forced HTTP/2 upstream, and the two denied CIDR blocks.

  • Helm Chart — deploy dwaar-ingress via Helm with all values documented
  • Automatic HTTPS — how Dwaar manages TLS certificates
  • Reverse Proxy — the core proxy behaviour that ingress routes target