Skip to content

Commit 8bde9f9

Browse files
Merge pull request #602 from lmiccini/instanceha-production-hardening
InstanceHA cliff detection expiry, per-cycle fencing cap, Nova request ID correlation
2 parents ac53eb7 + 9172c2c commit 8bde9f9

21 files changed

Lines changed: 2287 additions & 1038 deletions

docs/instanceha_application_credentials.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# InstanceHA Application Credential Authentication
22

3+
> **Related docs**: [instanceha_guide.md -- Authentication](instanceha_guide.md#authentication) (quick setup) | [instanceha_architecture.md -- Authentication](instanceha_architecture.md#authentication) (internal design)
4+
35
By default InstanceHA authenticates to OpenStack using username/password
46
credentials from `clouds.yaml` and `secure.yaml`. As an alternative you can
57
use a **Keystone Application Credential**, which provides scoped, time-limited
@@ -10,10 +12,10 @@ access that can be rotated independently of the service user password.
1012
- A Keystone user with sufficient privileges to query and evacuate VMs
1113
(typically the `admin` role on the relevant project).
1214
- The `keystone-operator` deployed with `KeystoneApplicationCredential` CR support.
13-
- The `clouds.yaml` ConfigMap must still be present InstanceHA reads
15+
- The `clouds.yaml` ConfigMap must still be present -- InstanceHA reads
1416
`auth_url` and `region_name` from it even when using Application Credentials.
1517

16-
## Step 1 Create a KeystoneApplicationCredential CR
18+
## Step 1 -- Create a KeystoneApplicationCredential CR
1719

1820
Create a `KeystoneApplicationCredential` custom resource. The keystone-operator
1921
will create the credential in Keystone and store the resulting ID and secret in
@@ -86,7 +88,7 @@ oc get keystoneapplicationcredential ac-instanceha -o jsonpath='{.status.conditi
8688
oc get secret ac-instanceha-secret -o jsonpath='{.data}' | jq
8789
```
8890

89-
## Step 2 Configure the InstanceHa CR
91+
## Step 2 -- Configure the InstanceHa CR
9092

9193
Set `spec.auth.applicationCredentialSecret` to the name of the Secret created
9294
above:
@@ -117,7 +119,7 @@ The InstanceHA Python process detects `AC_ENABLED`, reads `AC_ID` and
117119
`v3applicationcredential` keystoneauth plugin. If the credential files are
118120
missing or empty it falls back to password authentication.
119121

120-
## Step 3 Verify
122+
## Step 3 -- Verify
121123

122124
Check that the pod has the AC volume mounted:
123125

@@ -165,7 +167,7 @@ InstanceHA needs to:
165167
| List/show images (Glance) | `GET /v2/images` | `admin` |
166168

167169
If you omit `accessRules` from the `KeystoneApplicationCredential` spec, the
168-
credential inherits all permissions of its parent user simpler but less
170+
credential inherits all permissions of its parent user -- simpler but less
169171
restrictive.
170172

171173
## Credential rotation

docs/instanceha_architecture.md

Lines changed: 242 additions & 151 deletions
Large diffs are not rendered by default.

docs/instanceha_guide.md

Lines changed: 548 additions & 92 deletions
Large diffs are not rendered by default.

docs/instanceha_heartbeat_performance.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# InstanceHA Heartbeat Performance at 1000-Node Scale
22

3+
> **Related docs**: [instanceha_guide.md -- Heartbeat Verification](instanceha_guide.md#heartbeat-verification) (operator setup) | [instanceha_architecture.md](instanceha_architecture.md) (internal design)
4+
35
## Overview
46

57
This document presents performance profiling results for the InstanceHA
@@ -18,7 +20,7 @@ measurements were taken using the benchmark script at
1820
| HEARTBEAT_TIMEOUT | 120s |
1921
| THRESHOLD | 5% and 10% |
2022
| POLL | 45s |
21-
| HEARTBEAT_CLEANUP_THRESHOLD | 2000 |
23+
| UDP_CLEANUP_THRESHOLD | 2000 |
2224

2325
### HBV2 Packet Format
2426

@@ -29,7 +31,7 @@ Each heartbeat packet carries HMAC-SHA256 authentication:
2931
| Magic | 4 bytes | Network (BE) |
3032
| Timestamp | 8 bytes | Network (BE) |
3133
| Hostname | 1-64 bytes | UTF-8 |
32-
| HMAC-SHA256 | 32 bytes | |
34+
| HMAC-SHA256 | 32 bytes | -- |
3335

3436
Minimum packet size: 45 bytes. Typical packet for a hostname like
3537
`compute-0000`: 56 bytes. The HMAC is computed over everything except
@@ -127,11 +129,11 @@ it counts the number of hosts with a fresh heartbeat (within
127129
`HEARTBEAT_TIMEOUT`) by scanning all entries in the timestamp snapshot,
128130
and compares this count against the previous cycle's value. If the drop
129131
exceeds `HEARTBEAT_CLIFF_THRESHOLD` (default 50%) and the previous count
130-
was 3, fencing is skipped entirely for that cycle.
132+
was >= 3, fencing is skipped entirely for that cycle.
131133

132134
This adds an O(n) pass over all timestamps (where n = total nodes, not
133135
just down hosts). At 1000 nodes this is a single dict iteration with one
134-
comparison per entry well under 100 us and negligible compared to the
136+
comparison per entry -- well under 100 us and negligible compared to the
135137
Nova API call latency. The per-host filter logic that follows is
136138
unchanged.
137139

@@ -203,7 +205,7 @@ The function-level CPU breakdown is consistent across both threshold
203205
values. Shown here for 10,000 iterations at the respective threshold
204206
limits:
205207

206-
#### THRESHOLD=5% (50 down hosts, 10,000 iterations 1.48s total)
208+
#### THRESHOLD=5% (50 down hosts, 10,000 iterations -- 1.48s total)
207209

208210
| Function | % Time | Notes |
209211
|---------------------------|-------:|--------------------------------|
@@ -213,7 +215,7 @@ limits:
213215
| `dict.get` | 4.6% | Timestamp lookup per host |
214216
| `list.append` | 4.1% | Building result lists |
215217

216-
#### THRESHOLD=10% (100 down hosts, 10,000 iterations 2.64s total)
218+
#### THRESHOLD=10% (100 down hosts, 10,000 iterations -- 2.64s total)
217219

218220
| Function | % Time | Notes |
219221
|---------------------------|-------:|--------------------------------|
@@ -235,7 +237,7 @@ acceptable overhead.
235237
Simulates 10 consecutive polling cycles with concurrent heartbeat
236238
packet arrival and increasing down-host counts.
237239

238-
### THRESHOLD=5% (down hosts: 10 50)
240+
### THRESHOLD=5% (down hosts: 10 -> 50)
239241

240242
| Cycle | Down Hosts | Fenced | Skipped | Filter Time |
241243
|------:|-----------:|-------:|--------:|------------:|
@@ -253,7 +255,7 @@ packet arrival and increasing down-host counts.
253255
- **Average filter time: 59 us**
254256
- **Maximum filter time: 144 us**
255257

256-
### THRESHOLD=10% (down hosts: 10 55)
258+
### THRESHOLD=10% (down hosts: 10 -> 55)
257259

258260
| Cycle | Down Hosts | Fenced | Skipped | Filter Time |
259261
|------:|-----------:|-------:|--------:|------------:|
@@ -324,7 +326,7 @@ heartbeat overhead is < 0.2% of total process memory.
324326

325327
Each HBV2 heartbeat packet is ~56 bytes (4-byte magic + 8-byte timestamp
326328
+ hostname + 32-byte HMAC). At 33 packets/second, the bandwidth is
327-
**~1.8 KB/s** negligible on any network.
329+
**~1.8 KB/s** -- negligible on any network.
328330

329331
### Container Resource Limits
330332

docs/instanceha_prometheus.md

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# InstanceHA Prometheus Monitoring Guide
22

3+
> **Related docs**: [instanceha_guide.md -- Prometheus Metrics](instanceha_guide.md#prometheus-metrics) (summary and auto-scraping) | [instanceha_architecture.md](instanceha_architecture.md) (internal design)
4+
35
## Overview
46

5-
InstanceHA exposes Prometheus metrics at `:8080/metrics` on the workload pod, covering the full evacuation lifecycle: host failure detection, fencing, evacuation, recovery, and poll loop health. These metrics complement the Kubernetes Events emitted on the InstanceHa CR events provide human-readable audit records, while metrics provide numeric time-series data suitable for dashboards, alerting, and capacity planning.
7+
InstanceHA exposes Prometheus metrics at `:8080/metrics` on the workload pod, covering the full evacuation lifecycle: host failure detection, fencing, evacuation, recovery, and poll loop health. These metrics complement the Kubernetes Events emitted on the InstanceHa CR -- events provide human-readable audit records, while metrics provide numeric time-series data suitable for dashboards, alerting, and capacity planning.
68

79
The metrics are served by the `prometheus_client` Python library on the same HTTP server used for liveness and readiness probes. No sidecar or additional container is needed.
810

@@ -43,7 +45,7 @@ spec:
4345
secretName: my-custom-metrics-cert
4446
```
4547
46-
When the telemetry-operator is deployed, its `ScrapeConfig` automatically switches to `scheme: HTTPS` with the appropriate TLS configuration when `PrometheusTLS` is enabled no manual changes are needed.
48+
When the telemetry-operator is deployed, its `ScrapeConfig` automatically switches to `scheme: HTTPS` with the appropriate TLS configuration when `PrometheusTLS` is enabled -- no manual changes are needed.
4749

4850
---
4951

@@ -103,7 +105,7 @@ After applying the PodMonitor, verify that Prometheus has discovered the target:
103105
oc get podmonitor -n openstack instanceha-metrics
104106
105107
# In the Prometheus UI (or via API), check the target is UP:
106-
# Targets page: Status Targets search for "instanceha"
108+
# Targets page: Status -> Targets -> search for "instanceha"
107109
108110
# On OpenShift with user workload monitoring, query the user-workload Prometheus
109111
# (not the cluster Prometheus in openshift-monitoring):
@@ -148,15 +150,17 @@ Counters increase monotonically and reset to zero on pod restart.
148150
| `instanceha_evacuation_total` | `host`, `result` | Host-level evacuation operations. `result`: `started`, `succeeded`, `failed` |
149151
| `instanceha_instance_evacuation_total` | `host`, `result` | Per-instance evacuation operations (smart/orchestrated mode). `result`: `started`, `succeeded`, `failed` |
150152
| `instanceha_host_down_total` | `host` | Host-down detections (each poll cycle where a host is seen as down) |
151-
| `instanceha_host_reachable_total` | `host` | Hosts reported down by Nova but still reachable via heartbeat fencing skipped |
153+
| `instanceha_host_reachable_total` | `host` | Hosts reported down by Nova but still reachable via heartbeat -- fencing skipped |
152154
| `instanceha_host_reenabled_total` | `host` | Hosts re-enabled after successful evacuation |
153-
| `instanceha_threshold_exceeded_total` | | Evacuations skipped because the percentage of failed hosts exceeded the global threshold |
155+
| `instanceha_threshold_exceeded_total` | -- | Evacuations skipped because the percentage of failed hosts exceeded the global threshold |
154156
| `instanceha_aggregate_threshold_exceeded_total` | `aggregate` | Evacuations blocked for an aggregate due to per-aggregate `instanceha:max_failures` metadata threshold |
155157
| `instanceha_recovery_completed_total` | `host` | Full recovery workflows completed (fence + evacuate + recovery) |
156158
| `instanceha_processing_failed_total` | `host` | Unhandled exceptions during service processing |
157-
| `instanceha_orphaned_host_recovered_total` | | Orphaned fenced hosts recovered during startup reconciliation |
159+
| `instanceha_orphaned_host_recovered_total` | -- | Orphaned fenced hosts recovered during startup reconciliation |
158160
| `instanceha_heartbeat_rejected_total` | `reason` | Heartbeat packets rejected. `reason`: `hmac_failed`, `timestamp_invalid` |
159-
| `instanceha_heartbeat_cliff_total` | — | Fencing skipped due to sudden heartbeat loss (possible network partition) |
161+
| `instanceha_heartbeat_cliff_total` | -- | Fencing skipped due to sudden heartbeat loss (possible network partition) |
162+
| `instanceha_fencing_rate_limited_total` | -- | Times fencing was capped by `MAX_HOSTS_PER_CYCLE` rate limit |
163+
| `instanceha_all_services_stale_total` | -- | Times fencing was skipped because all active services appeared stale |
160164
| `instanceha_poll_cycles_total` | `result` | Poll cycles executed. `result`: `success`, `error` |
161165

162166
### Gauges
@@ -174,6 +178,7 @@ Gauges represent current values that can go up or down.
174178
| Metric | Labels | Buckets (seconds) | Description |
175179
|--------|--------|-------------------|-------------|
176180
| `instanceha_instance_evacuation_duration_seconds` | `host` | 10, 30, 60, 120, 180, 300, 600 | Time from evacuation request to completion for individual instances |
181+
| `instanceha_poll_duration_seconds` | -- | 1, 5, 10, 30, 60, 120, 300 | Duration of each poll cycle |
177182

178183
---
179184

@@ -254,7 +259,7 @@ spec:
254259
Evacuation has been blocked for hosts in this aggregate.
255260
256261
# --- Critical: InstanceHA is down ---
257-
# No successful poll cycles for 5 minutes the agent is not functioning.
262+
# No successful poll cycles for 5 minutes -- the agent is not functioning.
258263
- alert: InstanceHADown
259264
expr: rate(instanceha_poll_cycles_total{result="success"}[5m]) == 0
260265
for: 5m
@@ -296,14 +301,14 @@ spec:
296301
or insufficient capacity on target hosts.
297302
298303
# --- Warning: K8s API unreachable (partition) ---
299-
# The agent cannot reach the K8s API fencing is blocked.
304+
# The agent cannot reach the K8s API -- fencing is blocked.
300305
- alert: InstanceHAK8sPartition
301306
expr: instanceha_k8s_api_reachable == 0
302307
for: 1m
303308
labels:
304309
severity: warning
305310
annotations:
306-
summary: "InstanceHA K8s API unreachable possible network partition"
311+
summary: "InstanceHA K8s API unreachable -- possible network partition"
307312
description: >-
308313
The InstanceHA pod cannot reach the Kubernetes API.
309314
Fencing is blocked to prevent split-brain evacuations.
@@ -317,7 +322,7 @@ spec:
317322
labels:
318323
severity: warning
319324
annotations:
320-
summary: "Heartbeat cliff detected possible network partition"
325+
summary: "Heartbeat cliff detected -- possible network partition"
321326
description: >-
322327
InstanceHA detected a sudden drop in heartbeat hosts,
323328
suggesting the pod's network is partitioned rather than
@@ -348,7 +353,7 @@ oc apply -f instanceha-prometheusrule.yaml
348353
oc get prometheusrule -n openstack instanceha-alerts
349354
350355
# Verify rules appear in Prometheus
351-
# Prometheus UI: Status Rules search for "instanceha"
356+
# Prometheus UI: Status -> Rules -> search for "instanceha"
352357
353358
# Lint the rules file locally (optional)
354359
promtool check rules instanceha-prometheusrule.yaml
@@ -441,10 +446,13 @@ Expected output:
441446
# TYPE instanceha_orphaned_host_recovered_total counter
442447
# TYPE instanceha_heartbeat_rejected_total counter
443448
# TYPE instanceha_heartbeat_cliff_total counter
449+
# TYPE instanceha_fencing_rate_limited_total counter
450+
# TYPE instanceha_all_services_stale_total counter
444451
# TYPE instanceha_poll_consecutive_failures gauge
445452
# TYPE instanceha_hosts_processing gauge
446453
# TYPE instanceha_k8s_api_reachable gauge
447454
# TYPE instanceha_poll_cycles_total counter
455+
# TYPE instanceha_poll_duration_seconds histogram
448456
```
449457

450458
### Test With Noop Fencing
@@ -513,7 +521,7 @@ To create this dashboard in Grafana:
513521
1. Create a new dashboard and add panels using the queries above.
514522
2. Set the data source to your Prometheus instance.
515523
3. Use `$namespace` as a template variable (filter: `label_values(instanceha_poll_cycles_total, namespace)`) to support multi-namespace deployments.
516-
4. Set the default time range to **Last 6 hours** InstanceHA events are infrequent in healthy environments.
524+
4. Set the default time range to **Last 6 hours** -- InstanceHA events are infrequent in healthy environments.
517525

518526
---
519527

@@ -545,7 +553,7 @@ time() - instanceha_poll_cycles_total{result="success"} @ end()
545553

546554
## Integration with telemetry-operator
547555

548-
When the [telemetry-operator](https://github.com/openstack-k8s-operators/telemetry-operator) is deployed, it provisions a **separate Prometheus instance** via the Cluster Observability Operator (COO). This Prometheus is independent from OpenShift's built-in user workload monitoring the two stacks coexist without conflict, but metrics live in different places:
556+
When the [telemetry-operator](https://github.com/openstack-k8s-operators/telemetry-operator) is deployed, it provisions a **separate Prometheus instance** via the Cluster Observability Operator (COO). This Prometheus is independent from OpenShift's built-in user workload monitoring -- the two stacks coexist without conflict, but metrics live in different places:
549557

550558
| Stack | Prometheus instance | Query endpoint |
551559
|-------|-------------------|----------------|
@@ -554,7 +562,7 @@ When the [telemetry-operator](https://github.com/openstack-k8s-operators/telemet
554562

555563
### Automatic Discovery (default)
556564

557-
The telemetry-operator **automatically discovers and scrapes InstanceHA metrics** no manual configuration is required. The infra-operator creates a Kubernetes Service (`<instance-name>-metrics`) with the labels `metrics: enabled` and `service: instanceha`. The telemetry-operator's `MetricStorage` controller watches for Services with these labels and automatically generates a `ScrapeConfig` CR named `telemetry-instanceha` targeting port 8080.
565+
The telemetry-operator **automatically discovers and scrapes InstanceHA metrics** -- no manual configuration is required. The infra-operator creates a Kubernetes Service (`<instance-name>-metrics`) with the labels `metrics: enabled` and `service: instanceha`. The telemetry-operator's `MetricStorage` controller watches for Services with these labels and automatically generates a `ScrapeConfig` CR named `telemetry-instanceha` targeting port 8080.
558566

559567
This works the same way as the OVN metrics integration. When a `MetricStorage` CR exists in the namespace:
560568

@@ -588,14 +596,14 @@ spec:
588596
### Which Approach to Use
589597

590598
- **OpenShift user workload monitoring only** (no telemetry-operator): Use the PodMonitor approach from [Enabling Scraping](#enabling-scraping). This is simpler and uses automatic pod discovery.
591-
- **telemetry-operator deployed** (default): InstanceHA metrics are automatically scraped by the COO Prometheus alongside other OpenStack metrics (Ceilometer, RabbitMQ, node-exporter, OVN). No manual configuration needed. You can also deploy the PodMonitor simultaneously it targets the OpenShift user workload Prometheus and does not conflict with the COO scrapeconfig.
599+
- **telemetry-operator deployed** (default): InstanceHA metrics are automatically scraped by the COO Prometheus alongside other OpenStack metrics (Ceilometer, RabbitMQ, node-exporter, OVN). No manual configuration needed. You can also deploy the PodMonitor simultaneously -- it targets the OpenShift user workload Prometheus and does not conflict with the COO scrapeconfig.
592600
- **Querying across both**: OpenShift's `thanos-querier` route aggregates the cluster and user workload Prometheus instances. The COO Prometheus is separate and must be queried directly at `metric-storage-prometheus.openstack.svc:9090`.
593601

594602
---
595603

596604
## References
597605

598-
- [InstanceHA Operator Guide](instanceha_guide.md) Full deployment and configuration reference
599-
- [InstanceHA Architecture](instanceha_architecture.md) Internal architecture and event catalog
606+
- [InstanceHA Operator Guide](instanceha_guide.md) -- Full deployment and configuration reference
607+
- [InstanceHA Architecture](instanceha_architecture.md) -- Internal architecture and event catalog
600608
- [Prometheus Operator Documentation](https://prometheus-operator.dev/docs/getting-started/introduction/)
601609
- [PromQL Basics](https://prometheus.io/docs/prometheus/latest/querying/basics/)

0 commit comments

Comments
 (0)