From f2a24ef7bdd6f124f2851f44c39fb254e7d00cac Mon Sep 17 00:00:00 2001 From: Leif Madsen Date: Mon, 15 Jan 2024 15:59:36 -0500 Subject: [PATCH] Implement support for Grafana Operator v5 (#561) * Implement support for Grafana Operator v5 Implement changes to support Grafana Operator v5 when the new grafana.integreatly.org CRD is available. Use the new CRDs as default when they are available. Fallover to deploying with Grafana Operator v4 when the Grafana Operator v5 CRDs are not available, thereby providing backwards compatibility to allow administrators time to migrate. Additionally, the polystat plugin has been removed from the rhos-cloud dashboard due to compatibility issues with grafana-cli usage when dynamically loading plugins. Usage of Grafana Operator v5 is also a target for disconnected support, and dynamically loading plugins in these environments is expected to be a problem. Related: OSPRH-2577 Closes: STF-1667 * Default Grafana role set to Admin In order to match the previous (Grafana Operator v4) role, set auto_assign_org_role to the Admin value. Default is Viewer. --- ...emetry-operator.clusterserviceversion.yaml | 1 + deploy/role.yaml | 1 + .../files/rhos-cloud-dashboard.json | 119 ------ .../tasks/component_grafana.yml | 362 ++++++++++++------ roles/servicetelemetry/tasks/main.yml | 8 +- .../manifest_grafana_ds_prometheus.j2 | 24 ++ .../templates/manifest_grafana_v5.j2 | 97 +++++ 7 files changed, 376 insertions(+), 236 deletions(-) create mode 100644 roles/servicetelemetry/templates/manifest_grafana_ds_prometheus.j2 create mode 100644 roles/servicetelemetry/templates/manifest_grafana_v5.j2 diff --git a/deploy/olm-catalog/service-telemetry-operator/manifests/service-telemetry-operator.clusterserviceversion.yaml b/deploy/olm-catalog/service-telemetry-operator/manifests/service-telemetry-operator.clusterserviceversion.yaml index 63e94c60f..610a279a4 100644 --- a/deploy/olm-catalog/service-telemetry-operator/manifests/service-telemetry-operator.clusterserviceversion.yaml +++ b/deploy/olm-catalog/service-telemetry-operator/manifests/service-telemetry-operator.clusterserviceversion.yaml @@ -365,6 +365,7 @@ spec: - monitoring.coreos.com - monitoring.rhobs - elasticsearch.k8s.elastic.co + - grafana.integreatly.org - integreatly.org resources: - '*' diff --git a/deploy/role.yaml b/deploy/role.yaml index 6e22854e4..cdade2ce7 100644 --- a/deploy/role.yaml +++ b/deploy/role.yaml @@ -120,6 +120,7 @@ rules: - monitoring.coreos.com - monitoring.rhobs - elasticsearch.k8s.elastic.co + - grafana.integreatly.org - integreatly.org resources: - '*' diff --git a/roles/servicetelemetry/files/rhos-cloud-dashboard.json b/roles/servicetelemetry/files/rhos-cloud-dashboard.json index d3ed49146..47525b043 100644 --- a/roles/servicetelemetry/files/rhos-cloud-dashboard.json +++ b/roles/servicetelemetry/files/rhos-cloud-dashboard.json @@ -1418,125 +1418,6 @@ "align": false, "alignLevel": null } - }, - { - "collapsed": false, - "datasource": "STFPrometheus", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 33 - }, - "id": 23, - "panels": [], - "title": "Instances", - "type": "row" - }, - { - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a", - "#4040a0" - ], - "datasource": "STFPrometheus", - "description": "Click instance for drill down view", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 24, - "x": 0, - "y": 34 - }, - "id": 17, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "polystat": { - "animationSpeed": 2500, - "columnAutoSize": true, - "columns": "", - "defaultClickThrough": "", - "defaultClickThroughNewTab": false, - "defaultClickThroughSanitize": false, - "displayLimit": 100, - "fontAutoColor": true, - "fontAutoScale": true, - "fontSize": 12, - "fontType": "Roboto", - "globalDecimals": 2, - "globalDisplayMode": "all", - "globalDisplayTextTriggeredEmpty": "OK", - "globalOperatorName": "avg", - "globalUnitFormat": "short", - "gradientEnabled": true, - "hexagonSortByDirection": 1, - "hexagonSortByField": "name", - "maxMetrics": 0, - "polygonBorderColor": "black", - "polygonBorderSize": 2, - "polygonGlobalFillColor": "#FFF899", - "radius": "", - "radiusAutoSize": true, - "rowAutoSize": true, - "rows": "", - "shape": "hexagon_pointed_top", - "tooltipDisplayMode": "all", - "tooltipDisplayTextTriggeredEmpty": "OK", - "tooltipFontSize": 12, - "tooltipFontType": "Roboto", - "tooltipPrimarySortDirection": 2, - "tooltipPrimarySortField": "thresholdLevel", - "tooltipSecondarySortDirection": 2, - "tooltipSecondarySortField": "value", - "tooltipTimestampEnabled": true, - "valueEnabled": true - }, - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "repeat": "projects", - "repeatDirection": "h", - "savedComposites": [], - "savedOverrides": [], - "targets": [ - { - "exemplar": true, - "expr": "sum by (resource, plugin_instance) (label_replace(collectd_virt_memory{service=~\".+-$clouds-.+\"}, \"resource\", \"$1\", \"host\", \".+:(.+):.+\")) + on(resource) group_right(plugin_instance) ceilometer_cpu{project=\"$projects\", service=~\".+-$clouds-.+\"}", - "instant": true, - "interval": "", - "legendFormat": "{{plugin_instance}}", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Project $projects", - "type": "grafana-polystat-panel", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ] } ], "refresh": "1m", diff --git a/roles/servicetelemetry/tasks/component_grafana.yml b/roles/servicetelemetry/tasks/component_grafana.yml index e5c9ba989..7bdd939ee 100644 --- a/roles/servicetelemetry/tasks/component_grafana.yml +++ b/roles/servicetelemetry/tasks/component_grafana.yml @@ -1,30 +1,9 @@ -- name: Construct oauth redirect reference - set_fact: - grafana_oauth_redir_ref: - kind: OAuthRedirectReference - apiVersion: v1 - reference: - kind: Route - name: 'grafana-route' - -- name: Lookup template - debug: - msg: "{{ lookup('template', './manifest_grafana.j2') | from_yaml }}" - -- name: Set default Grafana manifest - set_fact: - grafana_manifest: "{{ lookup('template', './manifest_grafana.j2') | from_yaml }}" - when: grafana_manifest is not defined - -- name: Create an instance of Grafana - k8s: - state: '{{ "present" if servicetelemetry_vars.graphing.enabled else "absent" }}' - definition: - '{{ grafana_manifest }}' - +# dashboard setup first looks for Grafana Operator v5 CRDs. If existing, prefer setup with v5. +# If v5 doesn't exist, then try v4. Don't create objects for v4 if v5 CRDs exist. - when: servicetelemetry_vars.graphing.enabled block: - when: servicetelemetry_vars.backends.metrics.prometheus.enabled + name: Get auth data for datasources to Prometheus block: - name: Retrieve configmap for OAUTH CA certs k8s_info: @@ -47,94 +26,247 @@ set_fact: prometheus_reader_token: '{{ prometheus_reader_secret.resources[0].data.token | b64decode }}' - # Lookup existing datasources - - name: Remove legacy datasources - k8s: - api_version: integreatly.org/v1alpha1 - name: '{{ ansible_operator_meta.name }}-ds-prometheus' - kind: GrafanaDataSource - namespace: '{{ ansible_operator_meta.namespace }}' - state: absent - - # NOTE: this can fail if you enable grafana without prometheus due to missing resources referenced in the template - - name: Set datasources - set_fact: - ds_manifest: "{{ lookup('template', './manifest_grafana_ds.j2') | from_yaml }}" - when: ds_manifest is not defined - - - name: Create the datasources - k8s: - state: '{{ "present" if servicetelemetry_vars.graphing.enabled else "absent" }}' - definition: - '{{ ds_manifest }}' - - - name: Load Cloud Overview Dashboard - k8s: - state: '{{ "present" if servicetelemetry_vars.graphing.grafana.dashboards.enabled else "absent" }}' - definition: - apiVersion: integreatly.org/v1alpha1 - kind: GrafanaDashboard - metadata: - labels: - app: grafana - stf_owner: "{{ ansible_operator_meta.name }}" - name: rhos-cloud-dashboard-1 - namespace: "{{ ansible_operator_meta.namespace }}" - spec: - name: rhos-cloud-dashboard.json - plugins: - - name: grafana-polystat-panel - version: "1.2.11" - json: | - {{ lookup('file', 'rhos-cloud-dashboard.json') | string }} - - - name: Load Infrastructure Overview Dashboard - k8s: - state: '{{ "present" if servicetelemetry_vars.graphing.grafana.dashboards.enabled else "absent" }}' - definition: - apiVersion: integreatly.org/v1alpha1 - kind: GrafanaDashboard - metadata: - labels: - app: grafana - stf_owner: "{{ ansible_operator_meta.name }}" - name: rhos-dashboard-1 - namespace: "{{ ansible_operator_meta.namespace }}" - spec: - name: rhos-dashboard.json - json: | - {{ lookup('file', 'rhos-dashboard.json') | string }} - - - name: Load Memcached Dashboard - k8s: - state: '{{ "present" if servicetelemetry_vars.graphing.grafana.dashboards.enabled else "absent" }}' - definition: - apiVersion: integreatly.org/v1alpha1 - kind: GrafanaDashboard - metadata: - labels: - app: grafana - stf_owner: "{{ ansible_operator_meta.name }}" - name: memcached-dashboard-1 - namespace: "{{ ansible_operator_meta.namespace }}" - spec: - name: memcached-dashboard.json - json: | - {{ lookup('file', 'memcached-dashboard.json') | string }} - - - name: Load Virtual Machine View Dashboard - k8s: - state: '{{ "present" if servicetelemetry_vars.graphing.grafana.dashboards.enabled else "absent" }}' - definition: - apiVersion: integreatly.org/v1alpha1 - kind: GrafanaDashboard - metadata: - labels: - app: grafana - stf_owner: "{{ ansible_operator_meta.name }}" - name: virtual-machine-dashboard-1 - namespace: "{{ ansible_operator_meta.namespace }}" - spec: - name: virtual-machine-view.json - json: | - {{ lookup('file', 'virtual-machine-view.json') | string }} +#---- deploy Grafana with v5 Operator (preferred) + - when: has_grafana_integreatly_api + name: Deploying with Grafana Operator v5 + block: + - name: Construct oauth redirect reference + set_fact: + grafana_oauth_redir_ref: + kind: OAuthRedirectReference + apiVersion: v1 + reference: + kind: Route + name: '{{ ansible_operator_meta.name }}-grafana-route' + + - name: Lookup template + debug: + msg: "{{ lookup('template', './manifest_grafana_v5.j2') | from_yaml }}" + + - name: Set default Grafana manifest (Grafana Operator v5) + set_fact: + grafana_manifest: "{{ lookup('template', './manifest_grafana_v5.j2') | from_yaml }}" + when: grafana_manifest is not defined + + - name: Create an instance of Grafana (Grafana Operator v5) + k8s: + state: '{{ "present" if servicetelemetry_vars.graphing.enabled else "absent" }}' + definition: + '{{ grafana_manifest }}' + + # NOTE: we only provide events forwarding with STF. We don't use events + # in dashboards, so there is no need to create an Elasticsearch + # datasource. + - when: servicetelemetry_vars.backends.metrics.prometheus.enabled + name: Create Grafana datasource for Prometheus + block: + - name: Set datasource for Prometheus + set_fact: + ds_manifest: "{{ lookup('template', './manifest_grafana_ds_prometheus.j2') | from_yaml }}" + when: ds_manifest is not defined + + - name: Create the datasource for Prometheus + k8s: + state: '{{ "present" if servicetelemetry_vars.graphing.enabled else "absent" }}' + definition: + '{{ ds_manifest }}' + + - name: Load Cloud Overview Dashboard + k8s: + state: '{{ "present" if servicetelemetry_vars.graphing.grafana.dashboards.enabled else "absent" }}' + definition: + apiVersion: grafana.integreatly.org/v1beta1 + kind: GrafanaDashboard + metadata: + labels: + app: grafana + stf_owner: "{{ ansible_operator_meta.name }}" + name: rhos-cloud-dashboard-1 + namespace: "{{ ansible_operator_meta.namespace }}" + spec: + instanceSelector: + matchLabels: + dashboards: "stf" + name: rhos-cloud-dashboard.json + json: | + {{ lookup('file', 'rhos-cloud-dashboard.json') | string }} + + - name: Load Infrastructure Overview Dashboard + k8s: + state: '{{ "present" if servicetelemetry_vars.graphing.grafana.dashboards.enabled else "absent" }}' + definition: + apiVersion: grafana.integreatly.org/v1beta1 + kind: GrafanaDashboard + metadata: + labels: + app: grafana + stf_owner: "{{ ansible_operator_meta.name }}" + name: rhos-dashboard-1 + namespace: "{{ ansible_operator_meta.namespace }}" + spec: + instanceSelector: + matchLabels: + dashboards: "stf" + name: rhos-dashboard.json + json: | + {{ lookup('file', 'rhos-dashboard.json') | string }} + + - name: Load Memcached Dashboard + k8s: + state: '{{ "present" if servicetelemetry_vars.graphing.grafana.dashboards.enabled else "absent" }}' + definition: + apiVersion: grafana.integreatly.org/v1beta1 + kind: GrafanaDashboard + metadata: + labels: + app: grafana + stf_owner: "{{ ansible_operator_meta.name }}" + name: memcached-dashboard-1 + namespace: "{{ ansible_operator_meta.namespace }}" + spec: + instanceSelector: + matchLabels: + dashboards: "stf" + name: memcached-dashboard.json + json: | + {{ lookup('file', 'memcached-dashboard.json') | string }} + + - name: Load Virtual Machine View Dashboard + k8s: + state: '{{ "present" if servicetelemetry_vars.graphing.grafana.dashboards.enabled else "absent" }}' + definition: + apiVersion: grafana.integreatly.org/v1beta1 + kind: GrafanaDashboard + metadata: + labels: + app: grafana + stf_owner: "{{ ansible_operator_meta.name }}" + name: virtual-machine-dashboard-1 + namespace: "{{ ansible_operator_meta.namespace }}" + spec: + instanceSelector: + matchLabels: + dashboards: "stf" + name: virtual-machine-view.json + json: | + {{ lookup('file', 'virtual-machine-view.json') | string }} + +#---- deploy Grafana with v4 Operator if v5 CRDs are not available (legacy deployments) + - when: has_integreatly_api and not has_grafana_integreatly_api + name: Deploying with Grafana Operator v4 + block: + - name: Construct oauth redirect reference + set_fact: + grafana_oauth_redir_ref: + kind: OAuthRedirectReference + apiVersion: v1 + reference: + kind: Route + name: 'grafana-route' + + - name: Lookup template + debug: + msg: "{{ lookup('template', './manifest_grafana.j2') | from_yaml }}" + + - name: Set default Grafana manifest (Grafana Operator v4) + set_fact: + grafana_manifest: "{{ lookup('template', './manifest_grafana.j2') | from_yaml }}" + when: grafana_manifest is not defined + + - name: Create an instance of Grafana (Grafana Operator v4) + k8s: + state: '{{ "present" if servicetelemetry_vars.graphing.enabled else "absent" }}' + definition: + '{{ grafana_manifest }}' + + - name: Remove legacy datasources + k8s: + api_version: integreatly.org/v1alpha1 + name: '{{ ansible_operator_meta.name }}-ds-prometheus' + kind: GrafanaDataSource + namespace: '{{ ansible_operator_meta.namespace }}' + state: absent + + # NOTE: This can fail if you enable grafana without prometheus due + # to missing resources referenced in the template. The v1alpha1 CRD + # of GrafanaDatasources uses a list, so logic would need to be + # added to the template directly checking for parameters set in + # ServiceTelemetry. + - name: Set datasources + set_fact: + ds_manifest: "{{ lookup('template', './manifest_grafana_ds.j2') | from_yaml }}" + when: ds_manifest is not defined + + - name: Create the datasources + k8s: + state: '{{ "present" if servicetelemetry_vars.graphing.enabled else "absent" }}' + definition: + '{{ ds_manifest }}' + + - name: Load Cloud Overview Dashboard + k8s: + state: '{{ "present" if servicetelemetry_vars.graphing.grafana.dashboards.enabled else "absent" }}' + definition: + apiVersion: integreatly.org/v1alpha1 + kind: GrafanaDashboard + metadata: + labels: + app: grafana + stf_owner: "{{ ansible_operator_meta.name }}" + name: rhos-cloud-dashboard-1 + namespace: "{{ ansible_operator_meta.namespace }}" + spec: + name: rhos-cloud-dashboard.json + json: | + {{ lookup('file', 'rhos-cloud-dashboard.json') | string }} + + - name: Load Infrastructure Overview Dashboard + k8s: + state: '{{ "present" if servicetelemetry_vars.graphing.grafana.dashboards.enabled else "absent" }}' + definition: + apiVersion: integreatly.org/v1alpha1 + kind: GrafanaDashboard + metadata: + labels: + app: grafana + stf_owner: "{{ ansible_operator_meta.name }}" + name: rhos-dashboard-1 + namespace: "{{ ansible_operator_meta.namespace }}" + spec: + name: rhos-dashboard.json + json: | + {{ lookup('file', 'rhos-dashboard.json') | string }} + + - name: Load Memcached Dashboard + k8s: + state: '{{ "present" if servicetelemetry_vars.graphing.grafana.dashboards.enabled else "absent" }}' + definition: + apiVersion: integreatly.org/v1alpha1 + kind: GrafanaDashboard + metadata: + labels: + app: grafana + stf_owner: "{{ ansible_operator_meta.name }}" + name: memcached-dashboard-1 + namespace: "{{ ansible_operator_meta.namespace }}" + spec: + name: memcached-dashboard.json + json: | + {{ lookup('file', 'memcached-dashboard.json') | string }} + + - name: Load Virtual Machine View Dashboard + k8s: + state: '{{ "present" if servicetelemetry_vars.graphing.grafana.dashboards.enabled else "absent" }}' + definition: + apiVersion: integreatly.org/v1alpha1 + kind: GrafanaDashboard + metadata: + labels: + app: grafana + stf_owner: "{{ ansible_operator_meta.name }}" + name: virtual-machine-dashboard-1 + namespace: "{{ ansible_operator_meta.namespace }}" + spec: + name: virtual-machine-view.json + json: | + {{ lookup('file', 'virtual-machine-view.json') | string }} diff --git a/roles/servicetelemetry/tasks/main.yml b/roles/servicetelemetry/tasks/main.yml index dc3e881c7..3bef4a10f 100644 --- a/roles/servicetelemetry/tasks/main.yml +++ b/roles/servicetelemetry/tasks/main.yml @@ -87,12 +87,16 @@ loop_var: this_cloud # --> graphing -- name: Check if we have integreatly.org API +- name: Check if we have integreatly.org API (Grafana Operator v4) set_fact: has_integreatly_api: "{{ True if 'integreatly.org' in api_groups else False }}" +- name: Check if we have grafana.integreatly.org API (Grafana Operator v5) + set_fact: + has_grafana_integreatly_api: "{{ True if 'grafana.integreatly.org' in api_groups else False }}" + - when: - - has_integreatly_api | bool + - (has_integreatly_api | bool) or (has_grafana_integreatly_api | bool) name: Start graphing component plays include_tasks: component_grafana.yml diff --git a/roles/servicetelemetry/templates/manifest_grafana_ds_prometheus.j2 b/roles/servicetelemetry/templates/manifest_grafana_ds_prometheus.j2 new file mode 100644 index 000000000..473389cf8 --- /dev/null +++ b/roles/servicetelemetry/templates/manifest_grafana_ds_prometheus.j2 @@ -0,0 +1,24 @@ +apiVersion: grafana.integreatly.org/v1beta1 +kind: GrafanaDatasource +metadata: + name: {{ ansible_operator_meta.name }}-ds-stf-prometheus + namespace: {{ ansible_operator_meta.namespace }} +spec: + instanceSelector: + matchLabels: + dashboards: "stf" + datasource: + name: STFPrometheus + type: prometheus + access: proxy + url: 'https://{{ ansible_operator_meta.name }}-prometheus-proxy.{{ ansible_operator_meta.namespace }}.svc:9092' + isDefault: true + editable: true + jsonData: + 'timeInterval': "5s" + 'tlsAuthWithCACert': true + 'httpHeaderName1': 'Authorization' + secureJsonData: + 'httpHeaderValue1': 'Bearer {{prometheus_reader_token}}' + 'tlsCACert': | + {{ serving_certs_ca.resources[0].data['service-ca.crt'] | indent(8) }} diff --git a/roles/servicetelemetry/templates/manifest_grafana_v5.j2 b/roles/servicetelemetry/templates/manifest_grafana_v5.j2 new file mode 100644 index 000000000..278e452ff --- /dev/null +++ b/roles/servicetelemetry/templates/manifest_grafana_v5.j2 @@ -0,0 +1,97 @@ +apiVersion: grafana.integreatly.org/v1beta1 +kind: Grafana +metadata: + name: {{ ansible_operator_meta.name }}-grafana + namespace: {{ ansible_operator_meta.namespace }} + labels: + dashboards: "stf" +spec: + serviceAccount: + metadata: + annotations: + serviceaccounts.openshift.io/oauth-redirectreference.primary: '{{ grafana_oauth_redir_ref | to_json }}' +{% if servicetelemetry_vars.graphing.grafana.ingress_enabled is defined and servicetelemetry_vars.graphing.grafana.ingress_enabled %} + route: + spec: + port: + targetPort: web + tls: + termination: reencrypt + to: + kind: Service + name: {{ ansible_operator_meta.name }}-grafana-service + weight: 100 + wildcardPolicy: None +{% endif %} + client: + preferIngress: false + config: + auth: + disable_signout_menu: "{{ servicetelemetry_vars.graphing.grafana.disable_signout_menu }}" + disable_login_form: "True" + auth.anonymous: + enabled: "True" + auth.proxy: + enabled: "True" + enable_login_token: "True" + header_property: "username" + header_name: "X-Forwarded-User" + log: + level: warn + mode: "console" + users: + auto_assign_org_role: Admin + deployment: + spec: + template: + spec: + volumes: + - name: 'secret-{{ ansible_operator_meta.name }}-grafana-proxy-tls' + secret: + secretName: '{{ ansible_operator_meta.name }}-grafana-proxy-tls' + - name: 'secret-{{ ansible_operator_meta.name }}-session-secret' + secret: + secretName: '{{ ansible_operator_meta.name }}-session-secret' + containers: + - name: oauth-proxy + image: {{ oauth_proxy_image }} + args: + - '-provider=openshift' + - '-pass-basic-auth=false' + - '-https-address=:3002' + - '-http-address=' + - '-email-domain=*' + - '-openshift-sar={"namespace":"{{ ansible_operator_meta.namespace }}","resource": "grafana", "group":"grafana.integreatly.org", "verb":"get"}' + - '-upstream=http://localhost:3000' + - '-tls-cert=/etc/tls/private/tls.crt' + - '-tls-key=/etc/tls/private/tls.key' + - '-client-secret-file=/var/run/secrets/kubernetes.io/serviceaccount/token' + - '-cookie-secret-file=/etc/proxy/secrets/session_secret' + - '-openshift-service-account={{ ansible_operator_meta.name }}-grafana-sa' + - '-openshift-ca=/etc/pki/tls/cert.pem' + - '-openshift-ca=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt' + - '-skip-auth-regex=^/metrics' + ports: + - containerPort: 3002 + name: https + protocol: TCP + resources: { } + volumeMounts: + - mountPath: /etc/tls/private + name: secret-{{ ansible_operator_meta.name }}-grafana-proxy-tls + - mountPath: /etc/proxy/secrets + name: secret-{{ ansible_operator_meta.name }}-session-secret +{% if servicetelemetry_vars.graphing.grafana.base_image is defined %} + - name: grafana + image: {{ servicetelemetry_vars.graphing.grafana.base_image }} +{% endif %} + service: + metadata: + annotations: + service.alpha.openshift.io/serving-cert-secret-name: {{ ansible_operator_meta.name }}-grafana-proxy-tls + spec: + ports: + - name: web + port: 3002 + protocol: TCP + targetPort: https