From 0382b341fc2bee54c3fc3b4e761b817865fe9f45 Mon Sep 17 00:00:00 2001 From: Michael Weibel Date: Thu, 28 May 2026 13:46:48 +0200 Subject: [PATCH] chore: add csi and observability addon templates Adds CSI-cloudscale in it's latest version which is a prerequisite for the added observability addon. The observability addon will setup alloy, loki, and grafana in labeled clusters. Env vars for configuration (and their defaults): - GRAFANA_ADMIN_PASSWORD=admin - LOKI_RETENTION_HOURS=720h - LOKI_VOLUME_SIZE=20Gi When applying it with envsubst, make sure to use envsubst which is capable of defaults, for example: https://github.com/a8m/envsubst use as follows: on management cluster: $ kubectl label cluster -n csi=cloudscale observability=enabled $ NAMESPACE= envsubst < templates/addons/csi-cloudscale.yaml | kubectl apply -f - $ NAMESPACE= envsubst < templates/addons/observability.yaml | kubectl apply -f - on workload cluster: $ kubectl port-forward -n observability svc/grafana 3000 now open localhost:3000 --- templates/addons/csi-cloudscale.yaml | 395 +++++++++++++++++++ templates/addons/observability.yaml | 547 +++++++++++++++++++++++++++ 2 files changed, 942 insertions(+) create mode 100644 templates/addons/csi-cloudscale.yaml create mode 100644 templates/addons/observability.yaml diff --git a/templates/addons/csi-cloudscale.yaml b/templates/addons/csi-cloudscale.yaml new file mode 100644 index 0000000..c461f4e --- /dev/null +++ b/templates/addons/csi-cloudscale.yaml @@ -0,0 +1,395 @@ +# cloudscale.ch CSI driver, vendored from +# https://github.com/cloudscale-ch/csi-cloudscale/blob/v4.0.0/deploy/kubernetes/releases/csi-cloudscale-v4.0.0.yaml +# +# Differences from upstream: +# - the `cloudscale` Secret in kube-system is not included here; it is +# already provisioned by templates/addons/ccm.yaml (reused as-is). +# - VolumeSnapshotClass, the snapshotter sidecar, and the snapshotter +# ClusterRole/Binding are stripped +# - The two LUKS-encrypted StorageClasses (cloudscale-volume-ssd-luks, +# cloudscale-volume-bulk-luks) are stripped +# +# Activate on a cluster by labelling it `csi: cloudscale`. +apiVersion: v1 +kind: ConfigMap +metadata: + name: "csi-cloudscale" + namespace: "${NAMESPACE}" +data: + csi-cloudscale.yaml: | + --- + apiVersion: v1 + kind: ServiceAccount + metadata: + name: csi-cloudscale-controller-sa + namespace: kube-system + --- + apiVersion: v1 + kind: ServiceAccount + metadata: + name: csi-cloudscale-node-sa + namespace: kube-system + --- + apiVersion: storage.k8s.io/v1 + kind: StorageClass + metadata: + name: cloudscale-volume-ssd + annotations: + storageclass.kubernetes.io/is-default-class: "true" + provisioner: csi.cloudscale.ch + allowVolumeExpansion: true + reclaimPolicy: Delete + volumeBindingMode: Immediate + parameters: + csi.cloudscale.ch/volume-type: ssd + --- + apiVersion: storage.k8s.io/v1 + kind: StorageClass + metadata: + name: cloudscale-volume-bulk + provisioner: csi.cloudscale.ch + allowVolumeExpansion: true + reclaimPolicy: Delete + volumeBindingMode: Immediate + parameters: + csi.cloudscale.ch/volume-type: bulk + --- + kind: ClusterRole + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: csi-cloudscale-provisioner-role + rules: + - apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "list", "watch", "create", "patch", "delete"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list", "watch", "update"] + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["list", "watch", "create", "update", "patch"] + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] + - apiGroups: ["storage.k8s.io"] + resources: ["csinodes"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["nodes"] + verbs: ["get", "list", "watch"] + - apiGroups: ["storage.k8s.io"] + resources: ["volumeattachments"] + verbs: ["get", "list", "watch"] + --- + kind: ClusterRole + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: csi-cloudscale-attacher-role + rules: + - apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "list", "watch", "update", "patch"] + - apiGroups: ["storage.k8s.io"] + resources: ["csinodes"] + verbs: ["get", "list", "watch"] + - apiGroups: ["storage.k8s.io"] + resources: ["volumeattachments"] + verbs: ["get", "list", "watch", "update", "patch"] + - apiGroups: ["storage.k8s.io"] + resources: ["volumeattachments/status"] + verbs: ["patch"] + --- + kind: ClusterRole + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: csi-cloudscale-resizer-role + rules: + - apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "list", "watch", "update", "patch"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["persistentvolumeclaims/status"] + verbs: ["update", "patch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["list", "watch", "create", "update", "patch"] + - apiGroups: ["storage.k8s.io"] + resources: ["volumeattributesclasses"] + verbs: ["get", "list", "watch"] + --- + kind: ClusterRole + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: csi-cloudscale-node-driver-registrar-role + rules: + - apiGroups: [""] + resources: ["events"] + verbs: ["get", "list", "watch", "create", "update", "patch"] + --- + kind: ClusterRoleBinding + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: csi-cloudscale-provisioner-binding + subjects: + - kind: ServiceAccount + name: csi-cloudscale-controller-sa + namespace: kube-system + roleRef: + kind: ClusterRole + name: csi-cloudscale-provisioner-role + apiGroup: rbac.authorization.k8s.io + --- + kind: ClusterRoleBinding + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: csi-cloudscale-resizer-binding + subjects: + - kind: ServiceAccount + name: csi-cloudscale-controller-sa + namespace: kube-system + roleRef: + kind: ClusterRole + name: csi-cloudscale-resizer-role + apiGroup: rbac.authorization.k8s.io + --- + kind: ClusterRoleBinding + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: csi-cloudscale-attacher-binding + subjects: + - kind: ServiceAccount + name: csi-cloudscale-controller-sa + namespace: kube-system + roleRef: + kind: ClusterRole + name: csi-cloudscale-attacher-role + apiGroup: rbac.authorization.k8s.io + --- + kind: ClusterRoleBinding + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: csi-cloudscale-node-driver-registrar-binding + subjects: + - kind: ServiceAccount + name: csi-cloudscale-node-sa + namespace: kube-system + roleRef: + kind: ClusterRole + name: csi-cloudscale-node-driver-registrar-role + apiGroup: rbac.authorization.k8s.io + --- + kind: DaemonSet + apiVersion: apps/v1 + metadata: + name: csi-cloudscale-node + namespace: kube-system + spec: + selector: + matchLabels: + app: csi-cloudscale-node + template: + metadata: + labels: + app: csi-cloudscale-node + role: csi-cloudscale + spec: + priorityClassName: system-node-critical + serviceAccountName: csi-cloudscale-node-sa + hostNetwork: true + tolerations: + - operator: Exists + containers: + - name: csi-node-driver-registrar + image: "registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.15.0" + imagePullPolicy: IfNotPresent + args: + - "--v=5" + - "--csi-address=$(ADDRESS)" + - "--kubelet-registration-path=$(DRIVER_REG_SOCK_PATH)" + lifecycle: + preStop: + exec: + command: ["/bin/sh", "-c", "rm -rf /registration/csi.cloudscale.ch /registration/csi.cloudscale.ch-reg.sock"] + env: + - name: ADDRESS + value: /csi/csi.sock + - name: DRIVER_REG_SOCK_PATH + value: /var/lib/kubelet/plugins/csi.cloudscale.ch/csi.sock + - name: KUBE_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + volumeMounts: + - name: plugin-dir + mountPath: /csi/ + - name: registration-dir + mountPath: /registration/ + - name: csi-cloudscale-plugin + image: "quay.io/cloudscalech/cloudscale-csi-plugin:v4.0.0" + imagePullPolicy: IfNotPresent + args: + - "--endpoint=$(CSI_ENDPOINT)" + - "--url=$(CLOUDSCALE_API_URL)" + - "--log-level=info" + env: + - name: CSI_ENDPOINT + value: unix:///csi/csi.sock + - name: CLOUDSCALE_API_URL + value: https://api.cloudscale.ch/ + - name: CLOUDSCALE_MAX_CSI_VOLUMES_PER_NODE + value: "125" + - name: CLOUDSCALE_ACCESS_TOKEN + valueFrom: + secretKeyRef: + name: cloudscale + key: access-token + securityContext: + privileged: true + capabilities: + add: ["SYS_ADMIN"] + allowPrivilegeEscalation: true + volumeMounts: + - name: plugin-dir + mountPath: /csi + - name: pods-mount-dir + mountPath: /var/lib/kubelet + mountPropagation: "Bidirectional" + - name: device-dir + mountPath: /dev + - name: tmpfs + mountPath: /tmp + volumes: + - name: registration-dir + hostPath: + path: /var/lib/kubelet/plugins_registry/ + type: DirectoryOrCreate + - name: plugin-dir + hostPath: + path: /var/lib/kubelet/plugins/csi.cloudscale.ch + type: DirectoryOrCreate + - name: pods-mount-dir + hostPath: + path: /var/lib/kubelet + type: Directory + - name: device-dir + hostPath: + path: /dev + - name: tmpfs + emptyDir: + medium: Memory + --- + kind: StatefulSet + apiVersion: apps/v1 + metadata: + name: csi-cloudscale-controller + namespace: kube-system + spec: + serviceName: "csi-cloudscale" + selector: + matchLabels: + app: csi-cloudscale-controller + replicas: 1 + template: + metadata: + labels: + app: csi-cloudscale-controller + role: csi-cloudscale + spec: + hostNetwork: true + priorityClassName: system-cluster-critical + serviceAccount: csi-cloudscale-controller-sa + containers: + - name: csi-provisioner + image: "registry.k8s.io/sig-storage/csi-provisioner:v5.3.0" + imagePullPolicy: IfNotPresent + args: + - "--csi-address=$(ADDRESS)" + - "--default-fstype=ext4" + - "--v=5" + - "--feature-gates=Topology=false" + env: + - name: ADDRESS + value: /var/lib/csi/sockets/pluginproxy/csi.sock + volumeMounts: + - name: socket-dir + mountPath: /var/lib/csi/sockets/pluginproxy/ + - name: csi-attacher + image: "registry.k8s.io/sig-storage/csi-attacher:v4.10.0" + imagePullPolicy: IfNotPresent + args: + - "--csi-address=$(ADDRESS)" + - "--v=5" + env: + - name: ADDRESS + value: /var/lib/csi/sockets/pluginproxy/csi.sock + volumeMounts: + - name: socket-dir + mountPath: /var/lib/csi/sockets/pluginproxy/ + - name: csi-resizer + image: "registry.k8s.io/sig-storage/csi-resizer:v2.0.0" + args: + - "--csi-address=$(ADDRESS)" + - "--timeout=30s" + - "--v=5" + - "--handle-volume-inuse-error=false" + env: + - name: ADDRESS + value: /var/lib/csi/sockets/pluginproxy/csi.sock + imagePullPolicy: IfNotPresent + volumeMounts: + - name: socket-dir + mountPath: /var/lib/csi/sockets/pluginproxy/ + - name: csi-cloudscale-plugin + image: "quay.io/cloudscalech/cloudscale-csi-plugin:v4.0.0" + args: + - "--endpoint=$(CSI_ENDPOINT)" + - "--url=$(CLOUDSCALE_API_URL)" + - "--log-level=info" + env: + - name: CSI_ENDPOINT + value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock + - name: CLOUDSCALE_API_URL + value: https://api.cloudscale.ch/ + - name: CLOUDSCALE_ACCESS_TOKEN + valueFrom: + secretKeyRef: + name: cloudscale + key: access-token + imagePullPolicy: IfNotPresent + volumeMounts: + - name: socket-dir + mountPath: /var/lib/csi/sockets/pluginproxy/ + volumes: + - name: socket-dir + emptyDir: {} + --- + apiVersion: storage.k8s.io/v1 + kind: CSIDriver + metadata: + name: csi.cloudscale.ch + spec: + attachRequired: true + podInfoOnMount: true +--- +apiVersion: addons.cluster.x-k8s.io/v1beta2 +kind: ClusterResourceSet +metadata: + name: "csi-cloudscale" + namespace: "${NAMESPACE}" +spec: + strategy: ApplyOnce + clusterSelector: + matchLabels: + csi: cloudscale + resources: + - name: "csi-cloudscale" + kind: ConfigMap diff --git a/templates/addons/observability.yaml b/templates/addons/observability.yaml new file mode 100644 index 0000000..f54c693 --- /dev/null +++ b/templates/addons/observability.yaml @@ -0,0 +1,547 @@ +# Observability stack for long-running test clusters. +# +# Deploys: +# - Grafana Loki v3.7.2 (single binary, filesystem backend, PVC on cloudscale-volume-ssd) +# - Grafana Alloy v1.16.1 (DaemonSet collector: pod logs + journald + Kubernetes events) +# - Grafana v13.0.1 (ClusterIP, accessed via `kubectl port-forward`) +# +# Requires the `csi-cloudscale` addon (PVC needs the StorageClass it provides). +# Activate on a cluster by labelling it `observability: enabled`. For the PVC +# to bind, the cluster also needs `csi: cloudscale`. +# +# Configuration: +# LOKI_RETENTION_HOURS default 720h (30 days) +# LOKI_VOLUME_SIZE default 20Gi +# GRAFANA_ADMIN_PASSWORD default admin (fine for port-forward-only) +apiVersion: v1 +kind: ConfigMap +metadata: + name: "observability" + namespace: "${NAMESPACE}" +data: + observability.yaml: | + --- + apiVersion: v1 + kind: Namespace + metadata: + name: observability + --- + # ============================================================================ + # Loki + # ============================================================================ + apiVersion: v1 + kind: ConfigMap + metadata: + name: loki-config + namespace: observability + data: + loki.yaml: | + auth_enabled: false + server: + http_listen_port: 3100 + grpc_listen_port: 9095 + log_level: info + common: + path_prefix: /var/loki + storage: + filesystem: + chunks_directory: /var/loki/chunks + rules_directory: /var/loki/rules + replication_factor: 1 + ring: + kvstore: + store: inmemory + schema_config: + configs: + - from: 2024-01-01 + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: index_ + period: 24h + storage_config: + tsdb_shipper: + active_index_directory: /var/loki/tsdb-index + cache_location: /var/loki/tsdb-cache + filesystem: + directory: /var/loki/chunks + limits_config: + retention_period: ${LOKI_RETENTION_HOURS:=720h} + allow_structured_metadata: true + volume_enabled: true + ruler: + storage: + type: local + local: + directory: /var/loki/rules + rule_path: /var/loki/rules-tmp + ring: + kvstore: + store: inmemory + enable_api: true + compactor: + working_directory: /var/loki/compactor + delete_request_store: filesystem + retention_enabled: true + analytics: + reporting_enabled: false + --- + apiVersion: v1 + kind: Service + metadata: + name: loki + namespace: observability + labels: + app.kubernetes.io/name: loki + spec: + type: ClusterIP + selector: + app.kubernetes.io/name: loki + ports: + - name: http + port: 3100 + targetPort: 3100 + - name: grpc + port: 9095 + targetPort: 9095 + --- + apiVersion: apps/v1 + kind: StatefulSet + metadata: + name: loki + namespace: observability + labels: + app.kubernetes.io/name: loki + spec: + serviceName: loki + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: loki + template: + metadata: + labels: + app.kubernetes.io/name: loki + spec: + securityContext: + runAsNonRoot: true + runAsUser: 10001 + runAsGroup: 10001 + fsGroup: 10001 + containers: + - name: loki + image: grafana/loki:3.7.2 + imagePullPolicy: IfNotPresent + args: + - "-config.file=/etc/loki/loki.yaml" + ports: + - name: http + containerPort: 3100 + - name: grpc + containerPort: 9095 + readinessProbe: + httpGet: + path: /ready + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + volumeMounts: + - name: config + mountPath: /etc/loki + - name: storage + mountPath: /var/loki + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + memory: 1Gi + volumes: + - name: config + configMap: + name: loki-config + volumeClaimTemplates: + - metadata: + name: storage + spec: + accessModes: ["ReadWriteOnce"] + storageClassName: cloudscale-volume-ssd + resources: + requests: + storage: ${LOKI_VOLUME_SIZE:=20Gi} + --- + # ============================================================================ + # Alloy (collector) + # ============================================================================ + apiVersion: v1 + kind: ServiceAccount + metadata: + name: alloy + namespace: observability + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: alloy + rules: + - apiGroups: [""] + resources: ["nodes", "nodes/proxy", "nodes/metrics", "services", "endpoints", "pods", "pods/log", "events", "namespaces"] + verbs: ["get", "list", "watch"] + - apiGroups: ["discovery.k8s.io"] + resources: ["endpointslices"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["configmaps", "secrets"] + verbs: ["get", "list", "watch"] + - nonResourceURLs: ["/metrics"] + verbs: ["get"] + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: alloy + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: alloy + subjects: + - kind: ServiceAccount + name: alloy + namespace: observability + --- + apiVersion: v1 + kind: ConfigMap + metadata: + name: alloy-config + namespace: observability + data: + config.alloy: | + logging { + level = "info" + format = "logfmt" + } + + // -------- pod/container logs -------- + discovery.kubernetes "pods" { + role = "pod" + } + + // Rename __meta_* attributes to plain labels so they survive loki.write + // filtering (loki.write strips any label whose name starts with "__"). + discovery.relabel "pods" { + targets = discovery.kubernetes.pods.targets + + rule { + source_labels = ["__meta_kubernetes_namespace"] + target_label = "namespace" + } + rule { + source_labels = ["__meta_kubernetes_pod_name"] + target_label = "pod_name" + } + rule { + source_labels = ["__meta_kubernetes_pod_container_name"] + target_label = "container_name" + } + rule { + source_labels = ["__meta_kubernetes_pod_uid"] + target_label = "uid" + } + rule { + source_labels = ["__meta_kubernetes_pod_node_name"] + target_label = "node" + } + rule { + source_labels = ["__meta_kubernetes_pod_ip"] + target_label = "pod_ip" + } + + // Standard "app" label — common across most workloads + rule { + source_labels = ["__meta_kubernetes_pod_label_app"] + target_label = "app" + } + + // Kubernetes canonical labels (https://kubernetes.io/docs/concepts/overview/working-with-labels/) + rule { + source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_name"] + target_label = "app_kubernetes_io_name" + } + rule { + source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_instance"] + target_label = "app_kubernetes_io_instance" + } + rule { + source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_component"] + target_label = "app_kubernetes_io_component" + } + + // Controller kind and name (Deployment, StatefulSet, DaemonSet …) + rule { + source_labels = ["__meta_kubernetes_pod_controller_kind"] + target_label = "controller_kind" + } + rule { + source_labels = ["__meta_kubernetes_pod_controller_name"] + target_label = "controller_name" + } + } + + loki.source.kubernetes "pods" { + targets = discovery.relabel.pods.output + forward_to = [loki.write.default.receiver] + } + + // -------- systemd journal -------- + discovery.relabel "journal" { + targets = [] + rule { + source_labels = ["__journal__systemd_unit"] + target_label = "unit" + } + rule { + source_labels = ["__journal__hostname"] + target_label = "hostname" + } + } + + loki.source.journal "node" { + path = "/var/log/journal" + forward_to = [loki.write.default.receiver] + relabel_rules = discovery.relabel.journal.rules + labels = { + job = "systemd-journal", + node = sys.env("NODE_NAME"), + } + } + + // -------- Kubernetes events -------- + loki.source.kubernetes_events "events" { + forward_to = [loki.write.default.receiver] + log_format = "logfmt" + } + + // -------- sink -------- + loki.write "default" { + endpoint { + url = "http://loki.observability.svc.cluster.local:3100/loki/api/v1/push" + } + } + --- + apiVersion: apps/v1 + kind: DaemonSet + metadata: + name: alloy + namespace: observability + labels: + app.kubernetes.io/name: alloy + spec: + selector: + matchLabels: + app.kubernetes.io/name: alloy + template: + metadata: + labels: + app.kubernetes.io/name: alloy + spec: + serviceAccountName: alloy + hostPID: true + tolerations: + - operator: Exists + containers: + - name: alloy + image: grafana/alloy:v1.16.1 + imagePullPolicy: IfNotPresent + args: + - "run" + - "/etc/alloy/config.alloy" + - "--storage.path=/var/lib/alloy/data" + - "--server.http.listen-addr=0.0.0.0:12345" + ports: + - name: http + containerPort: 12345 + env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + securityContext: + privileged: true + runAsUser: 0 + volumeMounts: + - name: config + mountPath: /etc/alloy + - name: data + mountPath: /var/lib/alloy/data + - name: varlog + mountPath: /var/log + readOnly: true + - name: journal + mountPath: /var/log/journal + readOnly: true + - name: machine-id + mountPath: /etc/machine-id + readOnly: true + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + memory: 512Mi + volumes: + - name: config + configMap: + name: alloy-config + - name: data + emptyDir: {} + - name: varlog + hostPath: + path: /var/log + - name: journal + hostPath: + path: /var/log/journal + - name: machine-id + hostPath: + path: /etc/machine-id + --- + # ============================================================================ + # Grafana + # ============================================================================ + apiVersion: v1 + kind: ConfigMap + metadata: + name: grafana-datasources + namespace: observability + data: + datasources.yaml: | + apiVersion: 1 + datasources: + - name: Loki + type: loki + access: proxy + url: http://loki.observability.svc.cluster.local:3100 + isDefault: true + editable: false + jsonData: + maxLines: 5000 + --- + apiVersion: v1 + kind: Service + metadata: + name: grafana + namespace: observability + labels: + app.kubernetes.io/name: grafana + spec: + type: ClusterIP + selector: + app.kubernetes.io/name: grafana + ports: + - name: http + port: 3000 + targetPort: 3000 + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: grafana + namespace: observability + labels: + app.kubernetes.io/name: grafana + spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: grafana + template: + metadata: + labels: + app.kubernetes.io/name: grafana + spec: + securityContext: + runAsNonRoot: true + runAsUser: 472 + runAsGroup: 472 + fsGroup: 472 + containers: + - name: grafana + image: grafana/grafana:13.0.1 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 3000 + env: + - name: GF_SECURITY_ADMIN_USER + valueFrom: + secretKeyRef: + name: grafana-admin + key: admin-user + - name: GF_SECURITY_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: grafana-admin + key: admin-password + - name: GF_ANALYTICS_REPORTING_ENABLED + value: "false" + - name: GF_ANALYTICS_CHECK_FOR_UPDATES + value: "false" + - name: GF_AUTH_ANONYMOUS_ENABLED + value: "false" + readinessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + volumeMounts: + - name: storage + mountPath: /var/lib/grafana + - name: datasources + mountPath: /etc/grafana/provisioning/datasources + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + memory: 512Mi + volumes: + - name: storage + emptyDir: {} + - name: datasources + configMap: + name: grafana-datasources +--- +apiVersion: v1 +kind: Secret +metadata: + name: "observability-credentials" + namespace: "${NAMESPACE}" +type: addons.cluster.x-k8s.io/resource-set +stringData: + secret.yaml: | + apiVersion: v1 + kind: Secret + metadata: + name: grafana-admin + namespace: observability + type: Opaque + stringData: + admin-user: admin + admin-password: "${GRAFANA_ADMIN_PASSWORD:=admin}" +--- +apiVersion: addons.cluster.x-k8s.io/v1beta2 +kind: ClusterResourceSet +metadata: + name: "observability" + namespace: "${NAMESPACE}" +spec: + strategy: ApplyOnce + clusterSelector: + matchLabels: + observability: enabled + resources: + - name: "observability" + kind: ConfigMap + - name: "observability-credentials" + kind: Secret