flexway
FRENTalk to an expert

Alerting from the console

Declare rules, signed webhooks and summaries in the console's values, tune the built-in rules and verify the signature.

GuideOperationsSince version 1.3.0

Markdown

On this page

Operations guide: configure, deploy and observe the gateway. What the feature does, product side: product guide; calling it: developer guide.

The console evaluates its rules every 15 s, in both modes, and notifies through a signed webhook. Nothing is typed into the console: rules, webhooks and summaries are declared in the chart's values, read again without a restart; an invalid version is refused and said on the Alerts screen, the previous one keeps applying.

Steps

Declare rules, webhooks and summaries

  1. Write the rules, webhooks and summaries in the console's values:

    values.yaml

    # The console's values
    alerts:
      rules:     # closed kinds: error_rate, p95, breaker_open, slo_burn, new_error_code
        - { name: reports-5xx, kind: error_rate, upstream: reports, threshold: 5%, for: 1m, notify: [ops] }
        - { name: orders-p95, kind: p95, operation: createOrder, threshold: 300ms, for: 2m }
      webhooks:  # https required (allowHttp lifts it); secretFile: whsec_<base64>, one secret per line
        - { name: ops, url: "https://hooks.example/flexway", secretFile: /etc/flexway-console-alerts/ops }
        - { name: chat, urlFile: /etc/flexway-console-alerts/chat-url, secretFile: /etc/flexway-console-alerts/chat, allowPrivate: true }
      summaries:
        - { name: weekly, every: weekly, at: "07:00", weekday: monday, webhook: chat }   # UTC
      existingSecret: console-alerts    # secrets and URLs carrying a token, mounted at /etc/flexway-console-alerts
    slos:
      - { operation: createOrder, availability: 99.9%, latency: { p95: 300ms, target: 99% }, window: 30d }
      - { name: orders-api, upstream: orders-svc, availability: 99.5% }   # a group: see SLO objectives on groups of operations
  2. Put the secrets and the URLs carrying a token in the Secret named by existingSecret.

  3. Deploy the values: the console reads the file again without a restart.

Tune the built-in rules

Tune a built-in rule rather than silence it - a few cases:

values.yaml

alerts:
  builtins:
    # Tighten: a small publication traffic fires too early; 5 % refused, from 100 publications.
    - { name: broker-backpressure, threshold: 5%, minRequests: 100 }
    # Delay: a broker failing over in less than 3 min is not an incident.
    - { name: broker-unavailable, for: 3m }
    # Sooner: an open breaker already fires at once; a degraded gateway, after 30 s.
    - { name: gateway-degraded, for: 30s }

With the native Kafka proxy (since 1.6.0), the kafka-proxy-refusals rule counts closed causes and accepts excludeCauses (Native Kafka clients: a virtual cluster per tenant):

values.yaml

alerts:
  builtins:
    - { name: kafka-proxy-refusals, threshold: 10%, excludeCauses: [quota] }   # do not fire on a quota reached

With the MQTT device listener (since 1.7.0), five more rules - device-connections-refused, sparkplug-node-offline, mqtt-listener-saturation, device-throttled and sparkplug-sequence-gaps, the last two disabled by default; the threshold of the two Sparkplug rules is a count, not a ratio (Console: the Devices screen).

Verify the signature on the receiving side

The body: {"type": "flexway.alert.firing", "timestamp": ..., "data": {"rule", "kind", "state", "since", "target", "value", "unit", "threshold", "subjects"}} (flexway.alert.resolved, flexway.summary.daily, flexway.summary.weekly) - never a tenant. Verify the signature (Standard Webhooks) before anything else:

import base64, hashlib, hmac, time

def verify(secret: str, headers, body: bytes) -> bool:
    key = base64.b64decode(secret.removeprefix("whsec_"))
    msg_id, ts = headers["webhook-id"], headers["webhook-timestamp"]
    if abs(time.time() - int(ts)) > 300:          # replay: 5 minutes of tolerance
        return False
    mac = hmac.new(key, f"{msg_id}.{ts}.".encode() + body, hashlib.sha256).digest()
    want = base64.b64encode(mac).decode()
    # two signatures during a rotation, separated by a space: one must match
    return any(hmac.compare_digest(sig.removeprefix("v1,"), want)
               for sig in headers["webhook-signature"].split())

Rotate a secret

  1. Put the new secret on the file's second line on the console's side.
  2. Deploy it on the receiving side.
  3. Remove the old one - the console signs with both meanwhile.

Verify

  • The Diagnostics -> Alerts screen (admins) lists each rule, its state (ok, pending, firing, resolved, unknown), its value against the threshold, its curve over 6 h and each webhook's deliveries (result, status, cause).
  • A refused file is said there, with its cause; the previous version keeps applying.
  • GET /api/alerts gives the built-in rules' values in force and where they come from (tuning).

Good to know

Topic What to know
Built-in rules Always on: breaker-open (an open breaker), upstream-degraded and gateway-degraded (1 min), new-error-code (a code never seen on the operation in 7 days - it reads flexway_errors_total), slo-burn (as soon as an objective is declared, an operation's or a group's), replica-silent (a gateway replica without a report for 2 min and no stop announced), broker-unavailable (a broker's breaker open or more than half its publications failing, 1 min), broker-backpressure (more than 1 % of 503 overloaded, 5 min), webhook-dead-letter (a delivery in the dead letter over 15 min)
Judgement error_rate = share of 5xx over 1 min, from 20 answers; p95 over 5 min. Silent metrics: no state changes, nothing is sent nor withdrawn, the screen says unknown. A target missing from the configuration makes the rule invalid, never notified
Who sends Every replica evaluates; one notifies, the holder of the chart's Lease. The webhook-id is the same from one replica to another: deduplicate on it (at least once)
Webhook https, the address checked when the connection is made: loopback, link-local (169.254.169.254) and multicast always refused, a private network only with allowPrivate: true (an internal target); no redirection followed; 5 s timeout; retries 5 s, 30 s, 2 min, 10 min, 30 min, then an abandon shown on the screen; a 4xx (but 408, 429) is a final refusal
Silence Alerts screen, 24 h at most, a reason required: it suspends a rule's notifications, never its evaluation; when it ends, what still fires is notified
Summaries daily or weekly at a UTC hour: requests against the previous period, 5xx and 4xx shares, new codes, each SLO's budget, the five largest changes, alerts - never a tenant's name
SLOs Availability (non-5xx answers) and latency (share of the minutes whose p95 holds the target) over the window; remaining budget, burn rate over 1 h, 6 h, 3 days; slo-burn fires above 14.4 over 1 h and 5 min, or 6 over 6 h and 30 min. On an operation or, since 1.6.0, a group of operations - an upstream, a tag, a list, every one (SLO objectives on groups of operations). Traffic's Objectives (SLO) tab, the Operation panel, the global objective on the Overview; "Create an alert" on the charts and the objectives writes the YAML to paste in these values
Tuning the built-in rules alerts.builtins: the name of a built-in rule, for (0 to 1 h) on every one; threshold (a ratio) and minRequests only on broker-unavailable (half, 20) and broker-backpressure (1 %, 20); excludeCauses only on a rule counting closed causes (the native Kafka proxy's rules, since 1.6.0). An unknown name, a rule tuned twice or a knob the rule does not carry refuses the file, as the rest does; without the key, the defaults. The Alerts screen shows the tuned threshold

Available since 1.3.0; tuning of the built-in rules in 1.6.0.

Next steps