Grafana Grafana

使用 Grafana Operator 管理 Grafana

将实例、文件夹、仪表板、数据源和告警规则声明为 Kubernetes 自定义资源。

15 min 高级

Grafana Operator 把每一种 Grafana 资源都变成一个 Kubernetes 对象。GrafanaDashboard 是一个清单,GrafanaDatasource 是一个清单,operator 通过 API 针对 Grafana 实例对它们进行调谐。这天然契合 Flux 和 Argo CD 之类的 GitOps 工具,并且适用于集群内外的 Grafana 实例,包括 Grafana Cloud。所有资源都使用 apiVersion: grafana.integreatly.org/v1beta1。

安装 operator

用 Helm 从它的 OCI chart 安装(请查看 operator 的安装文档以获取当前的 chart 位置和版本,并固定版本):

helm upgrade -i grafana-operator \
  oci://ghcr.io/grafana/helm-charts/grafana-operator \
  --namespace grafana --create-namespace \
  --version <operator-version>

operator 的文档也介绍了 Kustomize 安装方式。使用 Flux 时,把同一个 chart 放进一个 HelmRelease 中。

描述实例

实例可以是受管的(由 operator 部署 Grafana),也可以是外部的(连接到已经存在的 Grafana)。受管实例:

apiVersion: grafana.integreatly.org/v1beta1
kind: Grafana
metadata:
  name: grafana
  namespace: grafana
  labels:
    dashboards: "grafana"
spec:
  config:
    log:
      mode: console
    security:
      admin_user: root
      admin_password: secret     # example only; see below

外部实例,例如 Grafana Cloud stack,指向一个 URL,并从 Secret 获取令牌:

apiVersion: grafana.integreatly.org/v1beta1
kind: Grafana
metadata:
  name: my-stack
  namespace: grafana
  labels:
    dashboards: my-stack
spec:
  external:
    url: https://my-stack.grafana.net/
    apiKey:
      name: grafana-cloud-credentials
      key: GRAFANA_CLOUD_INSTANCE_TOKEN

spec.config 块使用与 grafana.ini 相同的 section 和键。不要在清单里写真实的密码。operator 文档展示了如何从 Secret 读取管理员凭据。

实例上的标签很重要:其他每个资源都通过 instanceSelector.matchLabels,使用这些标签来选择它的目标实例。

文件夹

apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaFolder
metadata:
  name: applications
spec:
  instanceSelector:
    matchLabels:
      dashboards: "grafana"
  title: Applications
  uid: applications     # set a stable UID so other resources can reference it

如果省略 title,它默认取 metadata.name。只要其他资源会用 folderRef 指向该文件夹,就请设置 spec.uid,因为没有它时,调谐器可能会接管一个同名的已有文件夹。

数据源

apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaDatasource
metadata:
  name: prometheus
spec:
  instanceSelector:
    matchLabels:
      dashboards: "grafana"
  datasource:
    name: prometheus
    type: prometheus
    access: proxy
    url: http://prometheus-service:9090
    isDefault: true
    jsonData:
      timeInterval: 5s

凭据通过 valuesFrom 来自同一命名空间中的 Secret,所以它们永远不会出现在清单中:

apiVersion: v1
kind: Secret
metadata:
  name: credentials
type: Opaque
stringData:
  PROMETHEUS_USERNAME: grafana
  PROMETHEUS_PASSWORD: change-me
---
apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaDatasource
metadata:
  name: prometheus-secured
spec:
  instanceSelector:
    matchLabels:
      dashboards: "grafana"
  valuesFrom:
    - targetPath: basicAuthUser
      valueFrom:
        secretKeyRef:
          name: credentials
          key: PROMETHEUS_USERNAME
    - targetPath: secureJsonData.basicAuthPassword
      valueFrom:
        secretKeyRef:
          name: credentials
          key: PROMETHEUS_PASSWORD
  datasource:
    name: prometheus-secured
    type: prometheus
    access: proxy
    basicAuth: true
    url: http://prometheus-service:9090
    secureJsonData:
      basicAuthPassword: ${PROMETHEUS_PASSWORD}

不要把凭据放在 url 中。像对待任何 Kubernetes Secret 一样,用 SOPS 或 External Secrets 加密或同步该 Secret。

仪表板

GrafanaDashboard 可以从多种来源获取它的 JSON,这是 operator 最大的优势:

# inline JSON
apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaDashboard
metadata:
  name: simple
spec:
  resyncPeriod: 30s
  instanceSelector:
    matchLabels:
      dashboards: "grafana"
  folderRef: applications
  json: >
    { "uid": "simple", "title": "Simple Dashboard", "panels": [] }
# from a ConfigMap
spec:
  instanceSelector:
    matchLabels:
      dashboards: "grafana"
  configMapRef:
    name: dashboard-definition
    key: json
# from a URL, such as grafana.com or a raw Git file
spec:
  instanceSelector:
    matchLabels:
      dashboards: "grafana"
  url: "https://grafana.com/api/dashboards/1860/revisions/30/download"
# from an OCI artifact
spec:
  instanceSelector:
    matchLabels:
      dashboards: "grafana"
  oci:
    reference: ghcr.io/team-a/dashboards:v1.4.7
    path: board.json
    pullSecretRef:
      name: ghcr-pull

你会用到的其他字段:

  • folderRef 按名称指向一个 GrafanaFolder,folderUID 则指向已有文件夹的 UID。
  • gzipJson 将仪表板保存为经 base64 编码的 gzip(cat dashboard.json | gzip | base64 -w0),用于对清单来说过大的仪表板。
  • variables 覆盖模板变量,例如为每个环境设置 namespace 或数据源。
  • resyncPeriod 和 contentCacheDuration 控制多久重新应用一次内容,以及获取到的 URL 缓存多久。

告警规则

apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaAlertRuleGroup
metadata:
  name: webapp-alerts
spec:
  folderRef: applications
  instanceSelector:
    matchLabels:
      dashboards: "grafana"
  interval: 5m
  rules:
    - uid: webapp-down
      title: Webapp targets down
      condition: C
      for: 5m
      noDataState: NoData
      execErrState: Error
      data:
        - refId: A
          # the Prometheus query step, as in the alerting guide
        - refId: C
          # the threshold expression step

每个规则组都需要一个文件夹和至少一条规则。数据步骤使用与 provisioning 文件相同的结构,所以你可以复用告警指南中的 YAML。operator 还有用于联系点的资源;请查看它的示例以了解当前的列表。

运维它

kubectl get grafanas,grafanafolders,grafanadatasources,grafanadashboards -A
kubectl describe grafanadashboard simple -n grafana

describe 显示资源的状态条件,说明最近一次同步到 Grafana 是否成功,失败的话原因是什么。常见原因:instanceSelector 没有匹配到任何实例、令牌错误,或者 Secret 位于错误的命名空间。

Operator 还是文件 provisioning?

当你已经在 Kubernetes 上运行 GitOps、希望每个团队把自己的仪表板和告警作为独立对象来拥有,或者管理 Grafana Cloud 或多个实例时,请使用 Operator。当你只有一个 Grafana 和少数几个资源时,请使用 Helm chart 的 provisioning values,因为这不需要额外的控制器。两者可以共存,但要让一个工具拥有每个资源。

热门指南