Skip to content

Latest commit

 

History

History
325 lines (261 loc) · 16.4 KB

File metadata and controls

325 lines (261 loc) · 16.4 KB

gatus-sidecar

A Kubernetes sidecar for Gatus — turns Ingress, Service, Gateway API HTTPRoute, and Traefik IngressRoute resources into Gatus endpoint configuration, automatically.

CI Release License


Why

Maintaining a Gatus config by hand stops scaling once you have more than a handful of services. This sidecar watches the cluster, derives an endpoint per resource, and writes a YAML file Gatus hot-reloads — no restarts, no template hell, no drift.

  • 🪶 Single binary, scratch image — minimal footprint, no runtime deps.
  • 🔄 Hot reload — atomic file writes; Gatus picks up changes automatically.
  • 🎯 Auto or opt-in — discover every resource, or only ones annotated explicitly.
  • 🧬 Annotation inheritance — Gateway → HTTPRoute, IngressClass → Ingress.
  • 🛣️ Path-aware URLs — extracts paths from Ingress rules and HTTPRoute matches.
  • 🏷️ Per-kind name prefixes — keep an Ingress and a Service with the same name from colliding.

Resource support

Resource Group / Version Parent (annotation inheritance) URL shape
Ingress networking.k8s.io/v1 IngressClass http(s)://<host><path>
Service v1 <proto>://<name>.<namespace>.svc.<cluster-domain>.:<port>
HTTPRoute gateway.networking.k8s.io/v1 Gateway https://<host><path>
IngressRoute traefik.io/v1alpha1 http(s)://<host><path>

Quick start

docker pull ghcr.io/home-operations/gatus-sidecar:latest

Minimum-viable local run against your current kubeconfig:

gatus-sidecar \
  --auto-ingress \
  --auto-service \
  --output=./gatus-sidecar.yaml

…then point Gatus at the generated file.

Deployment

Run alongside Gatus, sharing a config volume. The sidecar writes /config/gatus-sidecar.yaml, which contains only endpoints:. Gatus has no include directive: by default it reads a single file, so set GATUS_CONFIG_PATH to the shared directory. Gatus then merges every *.yaml/*.yml in it (endpoint lists are appended, maps are deep-merged). Put the rest of your Gatus config (alerting:, storage:, web:, ...) in another file in the same directory, for example a ConfigMap mounted at /config/config.yaml with subPath, so it doesn't shadow the sidecar's file.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gatus
spec:
  template:
    spec:
      serviceAccountName: gatus-sidecar
      containers:
        - name: gatus
          image: ghcr.io/twin/gatus:latest
          env:
            - { name: GATUS_CONFIG_PATH, value: /config }
          volumeMounts:
            - { name: gatus-config, mountPath: /config }
        - name: gatus-sidecar
          image: ghcr.io/home-operations/gatus-sidecar:latest
          args:
            - --auto-ingress
            - --auto-service
            - --auto-httproute
            - --gateway-name=production
          volumeMounts:
            - { name: gatus-config, mountPath: /config }
      volumes:
        - { name: gatus-config, emptyDir: {} }

Minimum RBAC for the controllers you enable:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata: { name: gatus-sidecar }
rules:
  - apiGroups: [""]
    resources: ["services"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses", "ingressclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["gateway.networking.k8s.io"]
    resources: ["httproutes", "gateways"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["traefik.io"]
    resources: ["ingressroutes"]
    verbs: ["get", "list", "watch"]

Configuration

Flag reference

Discovery modes

One per resource type. With no --enable-*/--auto-* flag set, every kind runs in annotation-only mode (resources must opt in).

Flag Effect
--auto-ingress Emit an endpoint for every in-scope Ingress.
--auto-service Emit an endpoint for every in-scope Service.
--auto-httproute Emit an endpoint for every in-scope HTTPRoute.
--auto-ingressroute Emit an endpoint for every Traefik IngressRoute.
--enable-ingress --enable-service --enable-httproute --enable-ingressroute Watch the kind, but only emit for resources annotated gatus.home-operations.com/enabled: "true".

Filtering

Flag Repeatable? Effect
--namespace no Watch a single namespace (empty = all).
--ingress-class yes Only Ingresses whose class is in the set are emitted.
--gateway-name yes Only HTTPRoutes referencing a Gateway in the set are emitted.

Repeatable flags can be passed multiple times: --ingress-class=nginx --ingress-class=traefik matches either.

Naming

Use these to disambiguate endpoints across resource kinds — Gatus rejects duplicate names, so prefix per-kind whenever an Ingress and a Service might share a name.

Flag Prepended to endpoint name
--prefix-ingress Ingress endpoints
--prefix-service Service endpoints
--prefix-httproute HTTPRoute endpoints
--prefix-ingressroute IngressRoute endpoints

Output & runtime

Flag Default Description
--output /config/gatus-sidecar.yaml Destination YAML file (written atomically).
--default-interval 1m Probe interval when not overridden by an annotation.
--annotation-config gatus.home-operations.com/endpoint Annotation key for YAML template overrides.
--annotation-enabled gatus.home-operations.com/enabled Annotation key for the on/off gate.
--cluster-domain cluster.local Cluster DNS domain used to qualify Service endpoints.
--log-level info debug | info | warn | error.

Annotations

Annotation Value Effect
gatus.home-operations.com/enabled "true" / "1" Force-include this resource in annotation-only mode, or keep it in --auto-* mode.
gatus.home-operations.com/enabled anything else Exclude this resource even when --auto-* is set.
gatus.home-operations.com/endpoint YAML fragment Merged into the generated endpoint (see below).

Template merging

The endpoint annotation accepts any subset of a Gatus endpoint. Known keys are merged into typed fields; unknown keys are inlined verbatim — so alerts:, headers:, body:, etc., all work out of the box.

Endpoint alerts: only reference a provider by type. The provider itself (e.g. alerting.slack.webhook-url) must be defined in the main Gatus configuration, which the sidecar does not manage. Without it, Gatus logs Alerting is not configured and endpoint alerts are silently ignored. Provider settings such as webhook-url are not valid fields on an endpoint alert; to vary one per endpoint, put it under the alert's provider-override: map.

Template key Behavior
name, group, url, interval Override the field.
conditions Replace the default conditions. Accepts string or list.
dns, client, ui Deep-merged into the field's map.
guarded If present, switches the endpoint to a DNS probe.
path Replace the auto-extracted path. Empty string forces bare host.
anything else Inlined into the YAML output as-is.

For resources with a parent (HTTPRoute → Gateway, Ingress → IngressClass) the parent's annotation is merged first; the child wins on conflicts for scalars, deep-merges for nested maps. Use the parent for common alerting and the child for per-route conditions.

URL derivation

Resource Host Scheme Path
Ingress First rule with host https if TLS covers that host, else http First non-/ path under the first rule's HTTP block
HTTPRoute spec.hostnames[0] https (always) First Exact/PathPrefix match value (regex matches skipped)
Service <name>.<namespace>.svc.<cluster-domain>. First port's protocol, lowercased (tcp://, udp://)
IngressRoute First Host(\...`)in a route'smatch` https if spec.tls is set, else http First Path(\...`)/PathPrefix(`...`)in the samematch`

Trivial paths (empty, /, non-rooted) are dropped so the URL stays bare.

If the host already starts with http:// or https:// the scheme is preserved verbatim (useful for explicit override).

To opt out of path extraction:

  • path: "" in the endpoint annotation forces a bare-host probe; path: /custom picks a specific path while keeping the auto-detected host.
  • --probe-paths=false strips paths cluster-wide; per-resource path: still applies.
  • A full url: annotation overrides both.

Guarded probes

Set guarded: true in a template to replace the HTTP probe with a DNS query against 1.1.1.1 for the resource's hostname. Useful when the sidecar pod can't actually reach the service (split-horizon DNS, external-only ingress) but you still want to know DNS is resolving.

metadata:
  annotations:
    gatus.home-operations.com/endpoint: |
      guarded: true

Examples

Inherit alerts from a Gateway, override per route

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: prod
  annotations:
    gatus.home-operations.com/endpoint: |
      alerts:
        - type: slack
          failure-threshold: 3
          send-on-resolved: true
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api
  annotations:
    gatus.home-operations.com/endpoint: |
      interval: 30s
      conditions:
        - "[STATUS] == 200"
        - "[RESPONSE_TIME] < 500"
spec:
  parentRefs: [{ name: prod }]
  hostnames: ["api.example.com"]
  rules:
    - matches: [{ path: { type: PathPrefix, value: "/v1" } }]

Generated endpoint URL: https://api.example.com/v1 — inherits the Gateway's alerts, uses the route's own interval/conditions.

Disambiguate same-named resources

gatus-sidecar --auto-ingress --auto-service \
  --prefix-ingress=ing/ --prefix-service=svc/

An Ingress and a Service both named web-app produce ing/web-app and svc/web-app instead of a Gatus startup error.

Watch multiple gateway classes at once

gatus-sidecar --auto-httproute \
  --gateway-name=public --gateway-name=internal

Development

go build -o gatus-sidecar ./cmd/gatus-sidecar
go test ./...                              # unit tests
mise run lint                              # golangci-lint
go test -tags e2e ./test/e2e/...           # Kind-based E2E (needs KUBECONFIG)

The project pins tool versions via mise. Running mise install resolves Go, golangci-lint, lefthook, yamlfmt, and zizmor.

Architecture

cmd/gatus-sidecar/       Entry point
internal/config/         CLI flag parsing & validation
internal/gatus/          Endpoint type, template merge, atomic YAML writer
internal/k8s/            Dynamic-informer controller, Resource interface
internal/resources/      Ingress / Service / HTTPRoute / IngressRoute
test/e2e/                Kind-driven end-to-end suite (build tag: e2e)

One Controller runs per enabled resource kind. Each uses a dynamicinformer to watch its GVR and feeds a shared gatus.Writer with the merged endpoint set; the writer renders YAML to disk via tempfile + rename, so Gatus never reads a partial file.

   ┌──────────────────┐                          ┌─────────────────┐
   │  Kubernetes API  │ ──── informers/watches ──▶│  gatus-sidecar  │
   │                  │                           │  ┌────────────┐ │
   │  Ingress         │                           │  │ Controller │ │── reconcile ──┐
   │  Service         │                           │  └────────────┘ │               │
   │  HTTPRoute       │                           │  ┌────────────┐ │               ▼
   │  IngressRoute    │                           │  │  Writer    │ ── atomic ──▶ /config/gatus-sidecar.yaml
   │  IngressClass    │                           │  └────────────┘ │               │
   │  Gateway         │                           └─────────────────┘               │
   └──────────────────┘                                                              ▼
                                                                            ┌──────────────┐
                                                                            │    Gatus     │ ◀── hot-reload
                                                                            └──────────────┘

License

MIT — see LICENSE.