Kubernetes Auto-Discovery¶
Overview¶
The UptimeHunt Kubernetes auto-discovery operator runs inside your cluster and
automatically turns your Ingress resources into UptimeHunt monitoring checks.
Deploy it once with an API token and every public endpoint your cluster exposes
is monitored — no need to add each URL by hand, and new services are picked up
as soon as their Ingress is created.
It is designed to be safe by default: it only ever adds and updates checks, and never deletes them automatically (a disappearing endpoint is often a misconfiguration you'd want to be alerted about, not silently un-monitored).
How It Works¶
For every Ingress it is allowed to see, the operator:
- Reads the hostnames and paths from
spec.rules. - Determines HTTP vs HTTPS from
spec.tls(a host covered by TLS — including wildcard certificates — is monitored over HTTPS). - Creates one HTTP check per unique host + path combination.
- Keeps those checks in sync as the Ingress changes (hosts/paths added, annotations updated).
Discovered checks appear in your UptimeHunt account grouped into a project named after the Kubernetes namespace (by default), tagged as Managed in the UI.
Wildcard hosts are skipped
A wildcard host such as *.example.com is not a concrete, probeable URL, so
no check is created for it. Use a specific hostname instead.
Only Exact and Prefix paths are monitored
The operator creates checks only for ingress paths whose pathType is
Exact or Prefix. ImplementationSpecific paths — which may contain
controller-specific regex or rewrite syntax rather than a literal URL path —
are skipped, because their effective probe URL can't be determined
reliably. Use an Exact/Prefix path (or the
path annotation) if you need such an endpoint monitored.
HTTP GET checks only
The operator currently creates plain HTTP GET checks. The request
method, custom request headers, expected status code, and authentication are
not yet mapped from the Ingress. If you need any of those, configure the
check manually in the UptimeHunt UI after it is discovered.
Prerequisites¶
- A Kubernetes cluster (the operator is installed with Helm).
- An UptimeHunt API token, created under Settings → API Tokens. This token determines which UptimeHunt account the discovered checks belong to.
Installation¶
Create a values file, then install with Helm:
saas:
url: https://app.uptimehunt.io/api/v1
token: uh_xxxxxxxxxxxxxxxxxxxx # from Settings → API Tokens
discovery:
mode: annotation-only # or "all"
watch:
scope: cluster # or "namespace"
helm install uptimehunt-kubediscovery \
oci://ohcr.io/uptimehunt/autodiscovery/kubernetes/charts/uptimehunt-kubediscovery \
--version 0.1.0 \
--namespace uptimehunt-kubediscovery --create-namespace \
-f values.yaml
Pin a released version
The OCI chart is published only when a version is tagged in the
repository: the release pipeline packages the chart with its version and
appVersion set to the tag, and the matching operator image is pushed as
:<version>. Always pass --version <X.Y.Z> with a
released version
(replace the 0.1.0 placeholder above) — an unpinned or latest install is
not guaranteed to resolve until a release exists.
Discovery Modes¶
Choose how the operator decides which Ingresses to monitor with
discovery.mode:
Only Ingresses explicitly opted in are monitored:
Best for selectively monitoring a few endpoints, and the safest default for a shared cluster.
You can also restrict which ingress classes are considered, to keep internal-only ingresses out:
discovery:
ingressClassAllow: ["public"] # only these classes
# or
ingressClassDeny: ["internal"] # everything except these
Annotations¶
Fine-tune individual checks with annotations under the
monitor.uptimehunt.io/ prefix:
| Annotation | Description |
|---|---|
enabled |
Opt in/out ("true" / "false"; meaning depends on the discovery mode) |
name |
Override the check's display name |
group |
Target project/group (defaults to the namespace name) |
interval |
Check interval — whole minutes ("5") or a duration ("90s", "2m") |
path |
Force a single probe path for all hosts on this Ingress |
scheme |
Force http or https when TLS auto-detection isn't right |
icon |
Custom icon URL for the check. By default the UI shows the monitored site's favicon; set this (or edit it in the UI) to override. |
allow-removal |
"true" lets the operator delete this Ingress's checks when paths are removed or it's opted out (see Check Removal). Can be set at the namespace level and overridden per-Ingress with "false". |
Example:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
monitor.uptimehunt.io/enabled: "true"
monitor.uptimehunt.io/name: "My App (production)"
monitor.uptimehunt.io/interval: "1m"
monitor.uptimehunt.io/path: "/healthz"
Namespace-Level Defaults¶
The same annotations can be set on a Namespace. Every Ingress in that namespace inherits them as defaults, which individual Ingress annotations override. This is handy for applying a setting across many services at once:
# Monitor everything in this namespace at a 5-minute interval
kubectl annotate namespace production \
monitor.uptimehunt.io/enabled=true \
monitor.uptimehunt.io/interval=5
An Ingress in production is then monitored every 5 minutes unless it sets its
own monitor.uptimehunt.io/interval.
Cluster scope only
Namespace inheritance reads the cluster-scoped Namespace object, so it is
available only when watch.scope: cluster (where the operator has
permission to read Namespaces). Under watch.scope: namespace the operator
uses Ingress annotations only, regardless of the watch.namespaces filter —
see Watch Scope.
The precedence is:
Stopping Monitoring of a Check¶
To silence a discovered check — for example one of several produced by a single Ingress — use Stop monitoring (Ignore) on that check in the UptimeHunt UI. The operator will not re-enable or recreate an ignored check, so the change sticks across reconciles.
Ignore, don't delete
Deleting a managed check in the UI alone does not stop monitoring: the operator re-creates it on the next reconcile (see Continuous Reconciliation). Ignoring the check is the correct, durable way to stop monitoring it.
Check Removal¶
The operator never deletes checks automatically. If an Ingress (or one of its hosts/paths) disappears, its checks are left in place so a misconfiguration or outage keeps alerting rather than going quiet.
To let the operator prune an Ingress's checks when its paths change or it is
opted out, set monitor.uptimehunt.io/allow-removal: "true". This can be set:
- Per-Ingress, affecting only that Ingress, or
- At the namespace level, where every Ingress in the namespace inherits it.
An individual Ingress can opt back out with
monitor.uptimehunt.io/allow-removal: "false".
Namespace-level allow-removal is broad
Setting allow-removal: "true" on a Namespace permits the operator to
delete checks for every Ingress in that namespace. Every removal the
operator performs is logged. Prefer per-Ingress allow-removal, or
ignore a check, when you want to stop
monitoring without granting cluster-wide deletion.
To stop monitoring a single check without enabling deletion, use Stop monitoring in the UI instead.
Watch Scope¶
watch.scope controls the operator's RBAC and whether
namespace-annotation inheritance is available.
watch.namespaces is an independent filter that narrows which namespaces
are watched — it applies in both scopes.
Uses a ClusterRole and can read Namespaces, so
namespace-level defaults are enabled.
watch.namespaces: []— watch Ingresses in all namespaces.watch.namespaces: ["team-a", "team-b"]— watch only those namespaces, while RBAC stays cluster-wide and inheritance stays enabled.
Uses namespaced Role/RoleBindings only — no ClusterRole is
created — so it fits multi-tenant or least-privilege setups. The operator
cannot read Namespaces, so namespace-annotation inheritance is not
available.
watch.namespaces: ["team-a", "team-b"]— watch only those namespaces (aRole/RoleBindingis created in each).watch.namespaces: []— watch only the operator's own (release) namespace.
Continuous Reconciliation¶
Beyond reacting to Kubernetes events, the operator periodically re-applies the
desired state on a timer (discovery.resyncInterval, default 10m), so any
drift between the cluster and your UptimeHunt account is corrected even without
new events.
Re-applies are idempotent and preserve user-controlled fields: a check's
enabled state, its ignored (Stop monitoring)
state, and an icon you set in the UI (when there is no icon annotation) all
survive across reconciles.
Configuration Reference¶
| Helm value | Default | Description |
|---|---|---|
saas.url |
— | UptimeHunt API base URL, e.g. https://app.uptimehunt.io/api/v1 |
saas.token |
— | API token (or use saas.existingSecret to reference a Secret) |
discovery.mode |
annotation-only |
annotation-only or all |
discovery.defaultIntervalMinutes |
3 |
Check interval when not overridden by annotation |
discovery.resyncInterval |
10m |
How often desired state is periodically re-applied (Go duration) — see Continuous Reconciliation |
discovery.ingressClassAllow / ingressClassDeny |
[] |
Ingress class filters |
watch.scope |
cluster |
RBAC + namespace-inheritance mode: cluster (ClusterRole, inheritance enabled) or namespace (Roles only, inheritance disabled) |
watch.namespaces |
[] |
Namespace filter applied in both scopes. Empty: all namespaces in cluster scope, or the operator's own namespace in namespace scope |
metrics.enabled |
false |
Expose the controller-runtime metrics endpoint (off by default; scraping requires extra RBAC and a ServiceMonitor/PodMonitor not shipped by the chart) |
Security¶
- The operator is read-only on your Ingresses — it never modifies them.
- It needs only
get/list/watchon Ingresses (and, for cluster-wide installs, on Namespaces). - The API token is stored in a Kubernetes Secret. Use a dedicated token so it can be rotated or revoked independently from Settings → API Tokens.