Crossplane turns your Kubernetes cluster into a universal infrastructure control plane. Instead of writing Terraform modules or clicking through cloud consoles, you declare AWS S3 buckets, GCP CloudSQL instances, and Azure VNets as Kubernetes custom resources — and Crossplane's providers reconcile them into reality. That power comes with a new reliability concern: if Crossplane's controllers, providers, or webhooks go down, infrastructure provisioning silently stalls. New environments don't get created, database claims stay pending, and the developers waiting on them have no idea why. Vigilmon gives you external monitoring that watches Crossplane from outside the cluster and alerts the moment something breaks.
What You'll Build
- A Vigilmon HTTP monitor for Crossplane's webhook server
- Healthcheck monitors for each installed provider
- A heartbeat monitor to verify the Crossplane reconciliation loop is running
- Alert channels so your platform team gets notified before developers notice
Prerequisites
- Crossplane installed in a Kubernetes cluster (version 1.14+)
- At least one provider installed (e.g.,
provider-aws,provider-gcp) - A reverse proxy or load balancer exposing Crossplane health endpoints over HTTPS
- A free account at vigilmon.online
Why Monitoring Crossplane Matters
Crossplane runs as a set of Kubernetes controllers plus a mutating/validating webhook. Failures here are particularly damaging because they're invisible to application teams:
Provider pods crashing means no infrastructure gets reconciled. New Bucket or RDSInstance claims sit in Pending state forever with no clear error surfaced to the requester.
Webhook server downtime is catastrophic — Kubernetes rejects all writes to Crossplane resources (CRDs) if the webhook is unreachable, since it's registered with failurePolicy: Fail by default. This can block deployments that depend on infrastructure claims.
Composition function failures break complex resource pipelines silently. A function that generates 15 managed resources from one claim might fail at step 8 with no alert to the platform team.
External monitoring from Vigilmon catches these failures from the perspective of the users and automation that depend on Crossplane.
Step 1: Expose Health Endpoints
Crossplane and its providers expose health endpoints on their pods. The quickest way to make them accessible externally is to create a Kubernetes Service and Ingress for each.
The Crossplane manager pod exposes /healthz and /readyz on port 8081 by default:
# Check the healthz endpoint from inside the cluster
kubectl exec -n crossplane-system deploy/crossplane -- \
wget -qO- http://localhost:8081/healthz
Expose it via an Ingress:
# crossplane-health-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: crossplane-health
namespace: crossplane-system
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: crossplane-health.internal.yourdomain.com
http:
paths:
- path: /healthz
pathType: Prefix
backend:
service:
name: crossplane
port:
number: 8081
- path: /readyz
pathType: Prefix
backend:
service:
name: crossplane
port:
number: 8081
Apply it:
kubectl apply -f crossplane-health-ingress.yaml
Verify the endpoint resolves externally:
curl https://crossplane-health.internal.yourdomain.com/healthz
# Expected: {"status":"ok"}
Step 2: Monitor Provider Health Endpoints
Each installed Crossplane provider runs as its own pod with its own /healthz endpoint. A provider crash stops reconciliation of every managed resource it owns.
List your providers and their health ports:
kubectl get pods -n crossplane-system -l pkg.crossplane.io/revision
Create a small script to expose all provider healthz endpoints:
#!/bin/bash
# expose-provider-health.sh
# For each provider pod, port-forward and verify health
for pod in $(kubectl get pods -n crossplane-system -l pkg.crossplane.io/revision -o name); do
echo "Checking $pod"
kubectl exec -n crossplane-system "$pod" -- \
wget -qO- http://localhost:8081/healthz 2>/dev/null || echo "UNHEALTHY"
done
Create a Service for each provider to expose its health endpoint through your Ingress. For provider-aws:
# provider-aws-health-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: provider-aws-health
namespace: crossplane-system
spec:
selector:
pkg.crossplane.io/revision: provider-aws
ports:
- port: 8081
targetPort: 8081
Then add Vigilmon HTTP monitors for each provider health URL (Step 3 covers the UI steps).
Step 3: Add Vigilmon HTTP Monitors
In the Vigilmon dashboard:
- Go to Add Monitor → HTTP
- Create monitors for each endpoint:
| Monitor Name | URL | Expected Status |
|---|---|---|
| crossplane manager /healthz | https://crossplane-health.yourdomain.com/healthz | 200 |
| crossplane manager /readyz | https://crossplane-health.yourdomain.com/readyz | 200 |
| provider-aws /healthz | https://provider-aws-health.yourdomain.com/healthz | 200 |
| provider-gcp /healthz | https://provider-gcp-health.yourdomain.com/healthz | 200 |
For each monitor:
- Check interval: 1 minute
- Timeout: 10 seconds
- Expected status code: 200
- Response body contains:
ok - Alert threshold: Alert after 2 consecutive failures (reduces false positives)
Vigilmon probes from multiple geographic regions. You need both a cloud-local and truly external probe — configure the Ingress to restrict access by IP if needed while still allowing Vigilmon's probe IPs through.
Step 4: Monitor the Crossplane Webhook
The Crossplane webhook server handles admission for all Crossplane custom resources. If it's down, Kubernetes will reject writes to CRDs with failurePolicy: Fail. This is the highest-severity failure mode.
The webhook runs on port 9443 within the cluster. To monitor it externally:
# Verify webhook is responding inside the cluster
kubectl exec -n crossplane-system deploy/crossplane -- \
wget -qO- --no-check-certificate https://localhost:9443/healthz 2>&1
Expose the webhook health endpoint via Ingress (separate from the main health endpoint so you can track them independently in Vigilmon):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: crossplane-webhook-health
namespace: crossplane-system
spec:
rules:
- host: crossplane-webhook.internal.yourdomain.com
http:
paths:
- path: /healthz
pathType: Prefix
backend:
service:
name: crossplane
port:
number: 9443
Add a Vigilmon monitor for this URL with a P0 alert channel — webhook downtime is your highest-priority alert.
Step 5: Heartbeat Monitor for Reconciliation
Provider health endpoints confirm the pod is alive, but not that reconciliation is actually succeeding. Use a Crossplane CronJob that creates and deletes a test resource to verify the full reconciliation loop:
# crossplane-smoke-test-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: crossplane-smoke-test
namespace: crossplane-system
spec:
schedule: "*/15 * * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
serviceAccountName: crossplane-smoke-test
containers:
- name: smoke-test
image: bitnami/kubectl:latest
env:
- name: HEARTBEAT_URL
valueFrom:
secretKeyRef:
name: vigilmon-secrets
key: crossplane-heartbeat-url
command:
- /bin/sh
- -c
- |
set -e
# Verify Crossplane CRDs are reachable and providers are healthy
kubectl get providers.pkg.crossplane.io -n crossplane-system
# Check all providers are HEALTHY=True
UNHEALTHY=$(kubectl get providers.pkg.crossplane.io \
-o jsonpath='{range .items[*]}{.metadata.name}{" "}{.status.conditions[?(@.type=="Healthy")].status}{"\n"}{end}' \
| grep -v "True" | wc -l)
if [ "$UNHEALTHY" -gt 0 ]; then
echo "FAIL: $UNHEALTHY providers are not healthy"
exit 1
fi
# Send heartbeat only if everything is healthy
wget -qO- "$HEARTBEAT_URL"
echo "Heartbeat sent"
Create the heartbeat monitor in Vigilmon:
- Go to Add Monitor → Heartbeat
- Name:
crossplane reconciliation loop - Expected interval: 15 minutes
- Grace period: 5 minutes
- Copy the heartbeat URL into a Kubernetes Secret:
kubectl create secret generic vigilmon-secrets \
--from-literal=crossplane-heartbeat-url='https://vigilmon.online/heartbeat/YOUR_TOKEN' \
-n crossplane-system
If the smoke test fails (a provider is unhealthy, CRDs are unreachable, or the job itself crashes), the heartbeat ping won't fire — Vigilmon opens an incident after the grace period.
Step 6: Configure Alert Channels
Route Crossplane alerts to your platform team's Slack channel:
- In Vigilmon: Alert Channels → Add Channel → Slack Webhook
- Paste your
#platform-alertschannel webhook URL - Assign the channel to all Crossplane monitors
For webhook downtime (the highest-severity scenario), add a PagerDuty channel as well:
- Alert Channels → Add Channel → PagerDuty
- Paste your PagerDuty integration key
- Assign to the
crossplane webhook /healthzmonitor only
This gives you tiered alerting: most Crossplane issues go to Slack, but webhook downtime pages on-call immediately.
What You're Now Monitoring
| Component | Monitor Type | What It Detects |
|---|---|---|
| Crossplane manager | HTTP /healthz | Controller pod crash |
| Crossplane manager | HTTP /readyz | Controller not ready to reconcile |
| Crossplane webhook | HTTP /healthz | Admission webhook down (blocks all CRD writes) |
| provider-aws | HTTP /healthz | AWS provider crash |
| provider-gcp | HTTP /healthz | GCP provider crash |
| Reconciliation loop | Heartbeat | Providers healthy but not reconciling |
Between these monitors, you'll know about Crossplane failures within 1–2 minutes — long before your developers start wondering why their database claims are stuck in Pending.
Conclusion
Crossplane is powerful precisely because it abstracts infrastructure into Kubernetes resources — but that abstraction means failures are further removed from the developers who depend on it. External monitoring with Vigilmon gives your platform team the visibility needed to catch problems before they cascade into multi-hour provisioning delays.
Get started free at vigilmon.online. Your first Crossplane health monitor takes under two minutes to configure, and the free tier is sufficient to cover the core failure modes described above.