Files
grafana/apps/example
Charandas 50a1952f34 Health API: integrate apiserver readiness into /api/health (#120131)
* Health API: integrate apiserver readiness into /api/health

The /api/health endpoint is used as the Kubernetes readiness probe for
ST Grafana. Previously it only checked database connectivity, so
Kubernetes would route traffic before the apiserver's boot sequence
completed — causing 503s for aggregated API requests during the brief
window where serviceAvailable=false in proxy handlers.

When the apiserver's clientConfigProvider is available, /api/health now
makes an internal call to /readyz via DirectlyServeHTTP. If /readyz
returns non-200 (boot sequence health checks haven't passed), /api/health
returns 503 with "apiserver": "not ready" in the response body.

This eliminates user-facing 503s during pod startup without requiring
changes to deployment manifests (readiness probe stays on /api/health).

Stress test results (mt-tilt, 5 replicas scaling up simultaneously):
- 1240 requests during scale-up, zero 503s

When clientConfigProvider is nil (no apiserver, OSS without aggregation,
or unit tests), the check is skipped — no behavior change.

Companion to grafana-enterprise#11254 which adds the
remote-apiservice-initialization boot sequence health check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Health API: use timeout for apiserver readiness check

DirectlyServeHTTP blocks until the apiserver is ready. Without a
timeout, the health check would hang during startup (or in tests where
the apiserver hasn't started). Use a 2-second context timeout so the
health check returns "not ready" promptly instead of blocking.

Also check for context timeout explicitly — httptest.NewRecorder
defaults to status 200 if no response is written, which would
incorrectly pass the readiness check on timeout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Health API: integrate apiserver readiness into /api/health

The /api/health endpoint is used as the Kubernetes readiness probe for
ST Grafana. Previously it only checked database connectivity, so
Kubernetes would route traffic before the apiserver's boot sequence
completed — causing 503s for aggregated API requests during the brief
window where serviceAvailable=false in proxy handlers.

Add IsReady() to the DirectRestConfigProvider interface. The
eventualRestConfigProvider implements it with a non-blocking select on
the ready channel — returns true immediately once the apiserver has
started, false otherwise.

When the apiserver is not ready, /api/health returns 503 with
"apiserver": "not ready" in the response body. When clientConfigProvider
is nil (no apiserver, or unit tests), the check is skipped.

Stress test results (mt-tilt, 5 replicas scaling up simultaneously):
- 1240 requests during scale-up, zero 503s

Companion to grafana-enterprise#11254 which adds the
remote-apiservice-initialization boot sequence health check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* make gofmt

* fix

* regenerate health response with apiserver key

* make gen-apps

* fix

* remaining gen-apps

* lint

* gofmt

* trigger build

* fix

* fix

* fix

* lint frontend

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-11 19:38:07 -07:00
..

Example App

This App is an example of general app capabilities when developing on the grafana app platform.

Enabling the App

By default, the example app is disabled. To enable this App, add the following to your conf/custom.ini:

[grafana-apiserver]
runtime_config = example.grafana.app/v0alpha1=true,example.grafana.app/v1alpha1=true

Manifest

The source of the app's schemas and list of capabilities is the manifest, which is generated from kinds/manifest.cue. The Example kind is defined for v0alpha1 here and v1alpha1 (default) here. The root definition of the Example kind that both versions share is defined here.

The CUE is used to generate code (and the AppManifest) when make generate is run.

Code

All of the app's code is located in pkg/app. The New() function in pkg/app/app.go is the entry point of the app, and everything should be discoverable from there.

The code to register the app with the grafana API server (including inserting the app-specific config ExampleConfig) is located in /pkg/registry/apps/example/register.go.

Any app must also have its installer listed in WireSet and added to installers in ProvideAppInstallers. When building a new app, make to to regerate wire (make build in the root of the repo does this).

Generated Code

The pkg/apis package, and all its subdirectories, contain code generated by make generate. This code should not be edited, but it can be useful to look at when working through the flow of the app.

Sample Swagger Payloads

Navigate to localhost:3000/swagger?api=example.grafana.app-v1alpha1 to view the swagger for the app's v1alpha1 version (this version has the most capabilities/endpoints). You can use the Execute button to make requests via the swagger UI.

Create a new Example resource with via swagger with:

{
  "apiVersion": "example.grafana.app/v1alpha1",
  "kind": "Example",
  "metadata": {
    "name": "test",
    "namespace": "default"
  },
  "spec": {
    "firstField": "test",
    "secondField": 0,
    "list": {
      "info": "foo",
      "next": {
        "info": "bar"
      }
    }
  }
}

Create an invalid object which will be rejected by validation:

{
  "apiVersion": "example.grafana.app/v1alpha1",
  "kind": "Example",
  "metadata": {
    "name": "invalid",
    "namespace": "default"
  },
  "spec": {
    "firstField": "test",
    "secondField": 0,
    "list": {
      "info": "foo",
      "next": {
        "info": "bar"
      }
    }
  }
}

Update custom subresource:

{
  "apiVersion": "example.grafana.app/v1alpha1",
  "kind": "Example",
  "metadata": {
    "namespace": "default",
    "name": "test",
    "resourceVersion": "<REPLACEME>"
  },
  "custom": {
    "myField": "foo",
    "otherField": "bar"
  }
}

(metadata.resourceVersion is required for an update, use the value you get from a GET request)

cURL

You can also interact with the grafana API server via a kubeconfig set up for it, or via curl using the -u <username>:<password> flag. Currently, cluster-scoped custom routes are erased from the swagger as part of grafana's APIServer code, but can still be called via curl, like so:

curl -u admin:admin http://localhost:3000/apis/example.grafana.app/v1alpha1/other
% curl -u admin:admin http://localhost:3000/apis/example.grafana.app/v1alpha1/other
{"message":"This is a cluster route"}