Add VictoriaMetrics observability stack + sync catalog for monitoring test

- VM stack 10 charts: victoria-metrics-cluster/auth, victoria-logs-cluster,
  victoria-metrics-agent/alert, opentelemetry-collector, kube-state-metrics,
  prometheus-node-exporter, alertmanager, perses (JWT/OIDC, Infisical-ready)
- ArgoCD ApplicationSet (syncWave) + per-chart dip-values overlays
- doc/victoria-metrics-architecture.md, define-chart-resources updates
- includes pending working-tree changes (mlflow, kubeflow, apisix, CLAUDE.md)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
wbsong111
2026-06-25 11:10:51 +09:00
parent a55427730e
commit 6290322f1b
514 changed files with 68103 additions and 40 deletions
@@ -0,0 +1,31 @@
{{- if and .Values.config.database.file .Values.config.database.sql }}
{{ fail "[ERROR] 'config.database' must be set as file or SQL, and those options are mutually exclusive." }}
{{ end }}
{{- if and .Values.persistence.enabled (eq .Values.persistence.storageClass "") }}
{{ fail "[ERROR] 'persistencen.storageClass' must be set." }}
{{ end }}
{{- define "perses.validateDatabaseConfig" -}}
{{- if and .Values.config.database.file .Values.config.database.sql }}
{{- fail "[ERROR] Both 'config.database.file' and 'config.database.sql' cannot be set at the same time. Please configure only one of them." }}
{{- end }}
{{- $sqlConfig := .Values.config.database.sql }}
{{- if $sqlConfig }}
{{- $tlsConfig := $sqlConfig.tls_config }}
{{- if and $tlsConfig (kindIs "map" $tlsConfig) }}
{{- if or (hasKey $tlsConfig "ca_file") (hasKey $tlsConfig "cert_file") (hasKey $tlsConfig "key_file") (hasKey $tlsConfig "server_name") (hasKey $tlsConfig "insecure_skip_verify") (hasKey $tlsConfig "min_version") (hasKey $tlsConfig "max_version") }}
{{- fail "[ERROR] Legacy SQL TLS keys detected under 'config.database.sql.tls_config'. Use camelCase keys: ca_file->caFile, cert_file->certFile, key_file->keyFile, server_name->serverName, insecure_skip_verify->insecureSkipVerify, min_version->minVersion, max_version->maxVersion." }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.datasources }}
#################################################################################
###### WARNING: The 'datasources' configuration field is deprecated #####
###### and will be removed in the future version. #####
###### Please migrate to using the 'sidecar' configuration #####
###### for provisioning datasources. #####
#################################################################################
{{- end }}
@@ -0,0 +1,49 @@
{{- if .Values.sidecar.enabled }}
{{- $nsConfig := dict "customEnv" false }}
{{- range .Values.sidecar.extraEnvVars }}
{{- if eq .name "NAMESPACE" }}
{{- $_ := set $nsConfig "customEnv" true }}
{{- end }}
{{- end }}
{{- $hasCustomNamespaceEnv := $nsConfig.customEnv }}
{{- $clusterScope := or .Values.sidecar.allNamespaces $hasCustomNamespaceEnv }}
{{- $roleKind := ternary "ClusterRole" "Role" $clusterScope }}
{{- $bindingKind := ternary "ClusterRoleBinding" "RoleBinding" $clusterScope }}
{{- $roleName := ternary (printf "%s-clusterrole" (include "perses.fullname" .)) (printf "%s-role" (include "perses.fullname" .)) $clusterScope }}
{{- $bindingName := ternary (printf "%s-clusterrolebinding" (include "perses.fullname" .)) (printf "%s-rolebinding" (include "perses.fullname" .)) $clusterScope }}
apiVersion: rbac.authorization.k8s.io/v1
kind: {{ $roleKind }}
metadata:
name: {{ $roleName }}
{{- if not $clusterScope }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
{{- include "perses.labels" . | nindent 4 }}
app.kubernetes.io/component: iam
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "watch", "list"]
{{- if .Values.sidecar.enableSecretAccess }}
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: {{ $bindingKind }}
metadata:
name: {{ $bindingName }}
{{- if not $clusterScope }}
namespace: {{ .Release.Namespace }}
{{- end }}
roleRef:
kind: {{ $roleKind }}
name: {{ $roleName }}
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: {{ include "perses.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}
@@ -0,0 +1,23 @@
{{- $shouldCreate := (include "perses.shouldCreateEnvVarsSecret" . | trim) }}
{{- if eq $shouldCreate "true" }}
{{- $mergedEnvVars := include "perses.mergedEnvVars" . | fromYamlArray }}
{{- if $mergedEnvVars }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "perses.envVarsSecretName" . }}
labels:
{{- include "perses.labels" . | nindent 4 }}
app.kubernetes.io/component: configuration
{{- with .Values.config.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
data:
{{- range $mergedEnvVars }}
{{- if and (kindIs "map" .) (hasKey . "value") }}
{{ .name | kebabcase }}: {{ .value | b64enc }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,17 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "perses.fullname" . }}
labels:
{{- include "perses.labels" . | nindent 4 }}
app.kubernetes.io/component: configuration
{{- with .Values.config.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
data:
config.yaml: |-
{{ $config := unset .Values.config "annotations" }}
{{- with $config }}
{{- toYaml . | nindent 4 }}
{{- end }}
@@ -0,0 +1,14 @@
{{- if .Values.datasources }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "perses.fullname" . }}-datasources
labels:
{{- include "perses.labels" . | nindent 4 }}
app.kubernetes.io/component: configuration
data:
{{- range $.Values.datasources }}
{{ .metadata.name | lower }}.json: |-
{{- tpl (toJson .) $ | nindent 4 }}
{{- end }}
{{- end }}
@@ -0,0 +1,218 @@
{{- include "perses.validateDatabaseConfig" . }}
{{- if .Values.config.database.sql }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "perses.fullname" . }}
labels:
{{- include "perses.labels" . | nindent 4 }}
app.kubernetes.io/component: workload
{{- with .Values.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
{{- include "perses.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "perses.selectorLabels" . | nindent 8 }}
annotations:
{{- if .Values.datasources }}
checksum/config: {{ include (print $.Template.BasePath "/datasources.yaml") . | sha256sum }}
{{- else }}
checksum/config: {{ include (print $.Template.BasePath "/config.yaml") . | sha256sum }}
{{- end }}
spec:
serviceAccountName: {{ include "perses.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.persistence.securityContext | nindent 8 }}
containers:
{{- if .Values.sidecar.enabled }}
{{- $nsConfig := dict "customEnv" false }}
{{- range .Values.sidecar.extraEnvVars }}
{{- if eq .name "NAMESPACE" }}
{{- $_ := set $nsConfig "customEnv" true }}
{{- end }}
{{- end }}
{{- $hasCustomNamespaceEnv := $nsConfig.customEnv }}
- name: {{ .Chart.Name }}-provisioning-sidecar
image: "{{ .Values.sidecar.image.registry | default .Values.image.registry }}/{{ .Values.sidecar.image.repository }}:{{ .Values.sidecar.image.tag }}"
{{- with .Values.sidecar.securityContext }}
securityContext:
{{- toYaml . | nindent 10 }}
{{- end }}
volumeMounts:
- name: provisioning
mountPath: {{ .Values.config.provisioning.folders | first }}
env:
- name: LABEL
value: {{ .Values.sidecar.label }}
- name: LABEL_VALUE
value: {{ .Values.sidecar.labelValue | quote }}
- name: FOLDER
value: {{ .Values.config.provisioning.folders | first }}
- name: HEALTH_PORT
value: "{{ .Values.sidecar.healthPort }}"
{{- if and (not $hasCustomNamespaceEnv) .Values.sidecar.allNamespaces }}
- name: NAMESPACE
value: ALL
{{- end }}
{{- if .Values.sidecar.enableSecretAccess}}
- name: RESOURCE
value: both
{{- end }}
{{- with .Values.sidecar.extraEnvVars -}}
{{ toYaml . | nindent 10 }}
{{- end }}
{{- end }}
- name: {{ .Chart.Name }}
image: "{{ .Values.image.registry }}/{{ .Values.image.name }}:{{ .Values.image.version | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
- --config=/etc/perses/config/config.yaml
- --web.listen-address=:{{ .Values.service.targetPort }}
- --web.hide-port=false
- --web.telemetry-path={{ .Values.config.api_prefix }}/metrics
- --log.level={{ .Values.logLevel }}
- --log.method-trace=true
{{- range $key, $value := .Values.extraArgs }}
{{- if $value }}
- --{{ $key }}={{ tpl ($value | toString) $ }}
{{- else }}
- --{{ $key }}
{{- end }}
{{- end }}
{{- $mergedEnvVars := include "perses.mergedEnvVars" . | fromYamlArray }}
{{- if $mergedEnvVars }}
env:
{{- range $mergedEnvVars }}
{{- if and (kindIs "map" .) (hasKey . "name") }}
- name: {{ .name }}
valueFrom:
secretKeyRef:
name: {{ include "perses.envVarsSecretName" $ }}
key: {{ .name | kebabcase }}
{{- end }}
{{- end }}
{{- end }}
volumeMounts:
{{- if .Values.volumeMounts }}
{{- tpl (toYaml .Values.volumeMounts | nindent 10) . }}
{{- end }}
- name: config
mountPath: "/etc/perses/config"
- mountPath: "/etc/perses/storage"
name: storage
{{- if .Values.tls.enabled }}
{{- if .Values.tls.caCert.enabled }}
- name: ca-cert
mountPath: {{ .Values.tls.caCert.mountPath }}
readOnly: true
{{- end }}
{{- if .Values.tls.clientCert.enabled }}
- name: client-cert
mountPath: {{ .Values.tls.clientCert.mountPath }}
readOnly: true
{{- end }}
{{- end }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort}}
readinessProbe:
httpGet:
path: {{ .Values.config.api_prefix }}/api/v1/health
port: http
scheme: HTTP
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
successThreshold: {{ .Values.readinessProbe.successThreshold }}
failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
livenessProbe:
httpGet:
path: {{ .Values.config.api_prefix }}/api/v1/health
port: http
scheme: HTTP
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
successThreshold: {{ .Values.livenessProbe.successThreshold }}
failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 10 }}
{{- end }}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumes:
{{- if .Values.volumes }}
{{- tpl (toYaml .Values.volumes | nindent 8) . }}
{{- end }}
- name: config
configMap:
defaultMode: 420
name: {{ include "perses.fullname" . }}
- name: storage
emptyDir: {}
{{- if .Values.sidecar.enabled }}
- name: provisioning
emptyDir: {}
{{- end }}
{{- if .Values.tls.enabled }}
{{- if .Values.tls.caCert.enabled }}
- name: ca-cert
secret:
secretName: {{ .Values.tls.caCert.secretName | default (printf "%s-tls" (include "perses.fullname" .)) }}
defaultMode: 420
{{- end }}
{{- if .Values.tls.clientCert.enabled }}
- name: client-cert
secret:
secretName: {{ .Values.tls.clientCert.secretName | default (printf "%s-tls" (include "perses.fullname" .)) }}
defaultMode: 420
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if or .Values.affinity .Values.podAntiAffinity }}
affinity:
{{- end }}
{{- with .Values.affinity }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if eq .Values.podAntiAffinity "hard" }}
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: {{ .Values.podAntiAffinityTopologyKey }}
labelSelector:
matchExpressions:
- {key: app.kubernetes.io/name, operator: In, values: [{{ template "perses.name" . }}]}
{{- else if eq .Values.podAntiAffinity "soft" }}
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: {{ .Values.podAntiAffinityTopologyKey }}
labelSelector:
matchExpressions:
- {key: app.kubernetes.io/name, operator: In, values: [{{ template "perses.name" . }}]}
{{- end }}
{{- with .Values.topologySpreadConstraints }}
topologySpreadConstraints:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
@@ -0,0 +1,4 @@
{{ range .Values.extraObjects }}
---
{{ tpl (toYaml .) $ }}
{{ end }}
@@ -0,0 +1,42 @@
{{- if and .Values.gateway.enabled .Values.gateway.createGateway }}
{{- $fullName := include "perses.fullname" . -}}
{{- $gatewayName := default (printf "%s-gateway" $fullName) .Values.gateway.name -}}
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: {{ $gatewayName }}
{{- if .Values.gateway.namespace }}
namespace: {{ .Values.gateway.namespace }}
{{- end }}
labels:
{{- include "perses.labels" . | nindent 4 }}
app.kubernetes.io/component: networking
{{- with .Values.gateway.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.gateway.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
gatewayClassName: {{ required "gateway.gatewayClassName is required when creating a Gateway" .Values.gateway.gatewayClassName }}
{{- if .Values.gateway.listeners }}
listeners:
{{- range .Values.gateway.listeners }}
- name: {{ .name }}
port: {{ .port }}
protocol: {{ .protocol }}
{{- if .hostname }}
hostname: {{ .hostname | quote }}
{{- end }}
{{- if .tls }}
tls:
{{- toYaml .tls | nindent 8 }}
{{- end }}
{{- if .allowedRoutes }}
allowedRoutes:
{{- toYaml .allowedRoutes | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,30 @@
{{- if and .Values.sidecar.enabled .Values.sidecar.globalAdminUsers .Values.config.security.enable_auth }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "perses.fullname" . }}-admin-bootstrap
labels:
{{- include "perses.labels" . | nindent 4 }}
{{ .Values.sidecar.label }}: {{ .Values.sidecar.labelValue | quote }}
data:
global-admin-role.yaml: |
kind: GlobalRole
metadata:
name: global-admin
spec:
permissions:
- actions: ["*"]
scopes: ["*"]
global-admin-binding.yaml: |
kind: GlobalRoleBinding
metadata:
name: global-admin-binding
spec:
role: global-admin
subjects:
{{- range .Values.sidecar.globalAdminUsers }}
- kind: User
name: {{ . | quote }}
{{- end }}
{{- end }}
@@ -0,0 +1,90 @@
{{/* ========================================================================
Configuration helpers
========================================================================= */}}
{{/*
Return env vars generated from authentication providers for client_id/client_secret.
We include entries even when the value is empty so they can be sourced from an external Secret.
NOTE: When using external secrets (secret.create=false or envVarsExternalSecretName),
this should return empty to avoid duplicate secret creation.
*/}}
{{- define "perses.authProviderEnvVars" -}}
{{- $result := list }}
{{- $usingExternalSecret := or (and .Values.secret (not (default true .Values.secret.create))) .Values.envVarsExternalSecretName }}
{{- if not $usingExternalSecret }}
{{- $auth := .Values.config.security.authentication }}
{{- if and .Values.config.security.enable_auth $auth }}
{{- range $i, $provider := $auth.providers.oidc }}
{{- $result = append $result (dict "name" (printf "PERSES_SECURITY_AUTHENTICATION_PROVIDERS_OIDC_%d_CLIENT_ID" $i) "value" ($provider.client_id | default "")) }}
{{- $result = append $result (dict "name" (printf "PERSES_SECURITY_AUTHENTICATION_PROVIDERS_OIDC_%d_CLIENT_SECRET" $i) "value" ($provider.client_secret | default "")) }}
{{- end }}
{{- range $i, $provider := $auth.providers.oauth }}
{{- $result = append $result (dict "name" (printf "PERSES_SECURITY_AUTHENTICATION_PROVIDERS_OAUTH_%d_CLIENT_ID" $i) "value" ($provider.client_id | default "")) }}
{{- $result = append $result (dict "name" (printf "PERSES_SECURITY_AUTHENTICATION_PROVIDERS_OAUTH_%d_CLIENT_SECRET" $i) "value" ($provider.client_secret | default "")) }}
{{- end }}
{{- end }}
{{- end }}
{{- toYaml $result }}
{{- end }}
{{/*
Merge user-provided envVars with auto-generated auth provider env vars.
Auto-generated env vars are only added if not already provided by the user.
*/}}
{{- define "perses.mergedEnvVars" -}}
{{- $autoRaw := include "perses.authProviderEnvVars" . }}
{{- $auto := $autoRaw | fromYamlArray | default (list) }}
{{- $userRaw := .Values.envVars | default (list) }}
{{- $user := (kindIs "slice" $userRaw | ternary $userRaw (list $userRaw)) }}
{{- /* Build a map of user-provided env var names for deduplication */ -}}
{{- $userEnvNames := dict }}
{{- range $user }}
{{- if and (kindIs "map" .) (hasKey . "name") }}
{{- $_ := set $userEnvNames .name true }}
{{- end }}
{{- end }}
{{- /* Only add auto-generated env vars if not already provided by user */ -}}
{{- $filteredAuto := list }}
{{- range $auto }}
{{- if and (kindIs "map" .) (hasKey . "name") }}
{{- if not (hasKey $userEnvNames .name) }}
{{- $filteredAuto = append $filteredAuto . }}
{{- end }}
{{- end }}
{{- end }}
{{- $merged := concat $user $filteredAuto }}
{{- if $merged }}
{{- toYaml $merged }}
{{- else }}
[]
{{- end }}
{{- end }}
{{/*
Resolve env vars Secret name (generated or custom override).
*/}}
{{- define "perses.envVarsSecretName" -}}
{{- $name := "" }}
{{- if and .Values.secret .Values.secret.name }}
{{- $name = .Values.secret.name }}
{{- else if .Values.envVarsExternalSecretName }}
{{- $name = .Values.envVarsExternalSecretName }}
{{- else }}
{{- $name = (include "perses.fullname" .) }}
{{- end }}
{{- $name }}
{{- end }}
{{/*
Should we create the env vars Secret?
*/}}
{{- define "perses.shouldCreateEnvVarsSecret" -}}
{{- $create := true }}
{{- if hasKey .Values "secret" }}
{{- if hasKey .Values.secret "create" }}
{{- $create = .Values.secret.create }}
{{- end }}
{{- end }}
{{- $shouldCreate := and $create (not .Values.envVarsExternalSecretName) }}
{{- if $shouldCreate }}true{{- else }}false{{- end }}
{{- end }}
@@ -0,0 +1,75 @@
{{/* ========================================================================
Naming & identity helpers
========================================================================= */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "perses.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "perses.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "perses.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "perses.labels" -}}
helm.sh/chart: {{ include "perses.chart" . }}
{{ include "perses.selectorLabels" . }}
app.kubernetes.io/version: {{ default .Chart.AppVersion .Values.image.version }}
app.kubernetes.io/part-of: {{ include "perses.name" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- if not (empty .Values.additionalLabels) }}
{{ toYaml .Values.additionalLabels }}
{{- end }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "perses.selectorLabels" -}}
app.kubernetes.io/name: {{ include "perses.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "perses.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "perses.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Render an internal DNS endpoint for the Perses service.
*/}}
{{- define "perses.dns" -}}
http://{{ include "perses.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.targetPort }}
{{- end -}}
@@ -0,0 +1,81 @@
{{- if .Values.gateway.enabled }}
{{- $fullName := include "perses.fullname" . -}}
{{- $gatewayName := default (printf "%s-gateway" $fullName) .Values.gateway.name -}}
{{- $routeName := default (printf "%s-http-route" $fullName) .Values.gateway.httpRoute.name -}}
{{- $svcPort := .Values.service.port -}}
{{- $defaultParentRef := dict "name" $gatewayName -}}
{{- if .Values.gateway.namespace }}
{{- $_ := set $defaultParentRef "namespace" .Values.gateway.namespace -}}
{{- end }}
{{- $parentRefs := .Values.gateway.httpRoute.parentRefs }}
{{- if and (empty $parentRefs) .Values.gateway.createGateway }}
{{- $parentRefs = (list $defaultParentRef) }}
{{- end }}
{{- if empty $parentRefs }}
{{- fail "gateway.httpRoute.parentRefs must be set when gateway.enabled is true and gateway.createGateway is false" }}
{{- end }}
{{- $rules := .Values.gateway.httpRoute.rules }}
{{- if empty $rules }}
{{- $rules = list (dict "matches" (list (dict "path" (dict "type" "PathPrefix" "value" "/"))) "backendRefs" (list (dict "name" $fullName "port" $svcPort))) }}
{{- end }}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ $routeName }}
labels:
{{- include "perses.labels" . | nindent 4 }}
app.kubernetes.io/component: networking
{{- with .Values.gateway.httpRoute.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.gateway.httpRoute.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
parentRefs:
{{- range $parentRefs }}
- name: {{ .name }}
{{- if .namespace }}
namespace: {{ .namespace }}
{{- end }}
{{- if .sectionName }}
sectionName: {{ .sectionName }}
{{- end }}
{{- end }}
{{- if .Values.gateway.httpRoute.hostnames }}
hostnames:
{{- range .Values.gateway.httpRoute.hostnames }}
- {{ . | quote }}
{{- end }}
{{- end }}
rules:
{{- range $rules }}
- {{- if .matches }}
matches:
{{- toYaml .matches | nindent 8 }}
{{- end }}
{{- if .filters }}
filters:
{{- toYaml .filters | nindent 8 }}
{{- end }}
{{- $backendRefs := .backendRefs | default (list (dict "name" $fullName "port" $svcPort)) }}
backendRefs:
{{- range $backendRefs }}
- name: {{ .name | default $fullName }}
{{- if .namespace }}
namespace: {{ .namespace }}
{{- end }}
{{- if .kind }}
kind: {{ .kind }}
{{- end }}
{{- if .group }}
group: {{ .group }}
{{- end }}
port: {{ .port | default $svcPort }}
{{- if .weight }}
weight: {{ .weight }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,43 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "perses.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "perses.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.ingressClassName }}
ingressClassName: {{ .Values.ingress.ingressClassName }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,24 @@
{{- if .Values.persistence.enabled }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: {{ include "perses.fullname" . }}
labels:
{{- include "perses.labels" . | nindent 4 }}
app.kubernetes.io/component: storage
{{- with .Values.persistence.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.persistence.storageClass }}
storageClassName: {{ .Values.persistence.storageClass }}
{{- end }}
accessModes:
{{- range .Values.persistence.accessModes }}
- {{ . | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.persistence.size | quote }}
{{- end }}
@@ -0,0 +1,43 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "perses.fullname" . }}
labels:
{{- include "perses.labels" . | nindent 4 }}
app.kubernetes.io/component: networking
{{- with .Values.service.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if (or (eq .Values.service.type "ClusterIP") (empty .Values.service.type)) }}
type: ClusterIP
{{- with .Values.service.clusterIP }}
clusterIP: {{ . }}
{{- end }}
{{- else if eq .Values.service.type "LoadBalancer" }}
type: {{ .Values.service.type }}
{{- with .Values.service.loadBalancerIP }}
loadBalancerIP: {{ . }}
{{- end }}
{{- with .Values.service.loadBalancerSourceRanges }}
loadBalancerSourceRanges:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- else }}
type: {{ .Values.service.type }}
{{- end }}
ports:
- name: {{ .Values.service.portName }}
port: {{ .Values.service.port }}
protocol: TCP
targetPort: {{ .Values.service.targetPort }}
{{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }}
nodePort: {{ .Values.service.nodePort }}
{{- end }}
selector:
{{- include "perses.selectorLabels" . | nindent 4 }}
@@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "perses.serviceAccountName" . }}
labels:
{{- include "perses.labels" . | nindent 4 }}
app.kubernetes.io/component: iam
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
@@ -0,0 +1,24 @@
{{- if .Values.serviceMonitor.selfMonitor -}}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "perses.fullname" . }}
labels:
{{- include "perses.labels" . | nindent 4 }}
{{- with .Values.serviceMonitor.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
matchLabels:
{{- with .Values.serviceMonitor.selector.matchLabels }}
{{- toYaml . | nindent 6 }}
{{- end }}
namespaceSelector:
matchNames:
- {{ .Release.Namespace}}
endpoints:
- port: {{ .Values.service.portName }}
interval: {{ .Values.serviceMonitor.interval }}
path: {{ .Values.config.api_prefix }}/metrics
{{- end -}}
@@ -0,0 +1,275 @@
{{- include "perses.validateDatabaseConfig" . }}
{{- if .Values.config.database.file }}
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "perses.fullname" . }}
labels:
{{- include "perses.labels" . | nindent 4 }}
app.kubernetes.io/component: workload
{{- with .Values.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
replicas: {{ .Values.replicas }}
serviceName: {{ include "perses.fullname" . }}-headless
selector:
matchLabels:
{{- include "perses.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "perses.selectorLabels" . | nindent 8 }}
annotations:
{{- if .Values.datasources }}
checksum/config: {{ include (print $.Template.BasePath "/datasources.yaml") . | sha256sum }}
{{- else }}
checksum/config: {{ include (print $.Template.BasePath "/config.yaml") . | sha256sum }}
{{- end }}
spec:
serviceAccountName: {{ include "perses.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.persistence.securityContext | nindent 8 }}
containers:
{{- if .Values.sidecar.enabled }}
{{- $nsConfig := dict "customEnv" false }}
{{- range .Values.sidecar.extraEnvVars }}
{{- if eq .name "NAMESPACE" }}
{{- $_ := set $nsConfig "customEnv" true }}
{{- end }}
{{- end }}
{{- $hasCustomNamespaceEnv := $nsConfig.customEnv }}
- name: {{ .Chart.Name }}-provisioning-sidecar
image: "{{ .Values.sidecar.image.registry | default .Values.image.registry }}/{{ .Values.sidecar.image.repository }}:{{ .Values.sidecar.image.tag }}"
{{- with .Values.sidecar.securityContext }}
securityContext:
{{- toYaml . | nindent 10 }}
{{- end }}
volumeMounts:
- name: provisioning
mountPath: {{ .Values.config.provisioning.folders | first }}
env:
- name: LABEL
value: {{ .Values.sidecar.label }}
- name: LABEL_VALUE
value: {{ .Values.sidecar.labelValue | quote }}
- name: FOLDER
value: {{ .Values.config.provisioning.folders | first }}
- name: HEALTH_PORT
value: "{{ .Values.sidecar.healthPort }}"
{{- if and (not $hasCustomNamespaceEnv) .Values.sidecar.allNamespaces }}
- name: NAMESPACE
value: ALL
{{- end }}
{{- if .Values.sidecar.enableSecretAccess}}
- name: RESOURCE
value: both
{{- end }}
{{- with .Values.sidecar.extraEnvVars -}}
{{ toYaml . | nindent 10 }}
{{- end }}
{{- end }}
- name: {{ .Chart.Name }}
image: "{{ .Values.image.registry }}/{{ .Values.image.name }}:{{ .Values.image.version | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
- --config=/etc/perses/config/config.yaml
- --web.listen-address=:{{ .Values.service.targetPort }}
- --web.hide-port=false
- --web.telemetry-path={{ .Values.config.api_prefix }}/metrics
- --log.level={{ .Values.logLevel }}
- --log.method-trace=true
{{- range $key, $value := .Values.extraArgs }}
{{- if $value }}
- --{{ $key }}={{ tpl ($value | toString) $ }}
{{- else }}
- --{{ $key }}
{{- end }}
{{- end }}
{{- $mergedEnvVars := include "perses.mergedEnvVars" . | fromYamlArray }}
{{- if $mergedEnvVars }}
env:
{{- range $mergedEnvVars }}
{{- if and (kindIs "map" .) (hasKey . "name") }}
- name: {{ .name }}
valueFrom:
secretKeyRef:
name: {{ include "perses.envVarsSecretName" $ }}
key: {{ .name | kebabcase }}
{{- end }}
{{- end }}
{{- end }}
volumeMounts:
{{- if .Values.volumeMounts }}
{{- tpl (toYaml .Values.volumeMounts | nindent 10) . }}
{{- end }}
{{- if .Values.ociArtifacts }}
- name: {{ .Values.ociArtifacts.name }}
mountPath: {{ .Values.ociArtifacts.mountPath }}
{{- if .Values.ociArtifacts.subPath }}
subPath: {{ .Values.ociArtifacts.subPath }}
{{- end }}
{{- end }}
- name: config
mountPath: "/etc/perses/config"
{{- if .Values.config.database.file }}
- name: data
mountPath: {{ .Values.config.database.file.folder }}
{{- end }}
{{- if .Values.datasources }}
- name: datasources
mountPath: /etc/perses/datasources
{{- end }}
{{- if or .Values.sidecar.enabled .Values.provisioningPersistence.enabled }}
- name: provisioning
mountPath: {{ .Values.config.provisioning.folders | first }}
{{- end }}
{{- if .Values.tls.enabled }}
{{- if .Values.tls.caCert.enabled }}
- name: ca-cert
mountPath: {{ .Values.tls.caCert.mountPath }}
readOnly: true
{{- end }}
{{- if .Values.tls.clientCert.enabled }}
- name: client-cert
mountPath: {{ .Values.tls.clientCert.mountPath }}
readOnly: true
{{- end }}
{{- end }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
readinessProbe:
httpGet:
path: {{ .Values.config.api_prefix }}/api/v1/health
port: http
scheme: HTTP
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
successThreshold: {{ .Values.readinessProbe.successThreshold }}
failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
livenessProbe:
httpGet:
path: {{ .Values.config.api_prefix }}/api/v1/health
port: http
scheme: HTTP
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
successThreshold: {{ .Values.livenessProbe.successThreshold }}
failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 10 }}
{{- end }}
volumes:
{{- if .Values.volumes }}
{{- tpl (toYaml .Values.volumes | nindent 8) . }}
{{- end }}
{{- if .Values.ociArtifacts }}
- name: {{ .Values.ociArtifacts.name }}
image:
reference: {{ .Values.ociArtifacts.image.reference }}
{{- if .Values.ociArtifacts.image.pullPolicy }}
pullPolicy: {{ .Values.ociArtifacts.image.pullPolicy }}
{{- end }}
{{- end }}
{{- if .Values.config.database.file }}
- name: data
{{- if .Values.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ include "perses.fullname" . }}
{{- else }}
emptyDir: {}
{{- end }}
{{- end }}
- name: config
configMap:
defaultMode: 420
name: {{ include "perses.fullname" . }}
{{- if .Values.datasources }}
- name: datasources
configMap:
name: {{ include "perses.fullname" . }}-datasources
{{- end }}
{{- if and .Values.sidecar.enabled (not .Values.provisioningPersistence.enabled) }}
- name: provisioning
emptyDir: {}
{{- end }}
{{- if .Values.tls.enabled }}
{{- if .Values.tls.caCert.enabled }}
- name: ca-cert
secret:
secretName: {{ .Values.tls.caCert.secretName | default (printf "%s-tls" (include "perses.fullname" .)) }}
defaultMode: 420
{{- end }}
{{- if .Values.tls.clientCert.enabled }}
- name: client-cert
secret:
secretName: {{ .Values.tls.clientCert.secretName | default (printf "%s-tls" (include "perses.fullname" .)) }}
defaultMode: 420
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if or .Values.affinity .Values.podAntiAffinity }}
affinity:
{{- end }}
{{- with .Values.affinity }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if eq .Values.podAntiAffinity "hard" }}
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: {{ .Values.podAntiAffinityTopologyKey }}
labelSelector:
matchExpressions:
- {key: app.kubernetes.io/name, operator: In, values: [{{ template "perses.name" . }}]}
{{- else if eq .Values.podAntiAffinity "soft" }}
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: {{ .Values.podAntiAffinityTopologyKey }}
labelSelector:
matchExpressions:
- {key: app.kubernetes.io/name, operator: In, values: [{{ template "perses.name" . }}]}
{{- end }}
{{- with .Values.topologySpreadConstraints }}
topologySpreadConstraints:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.provisioningPersistence.enabled }}
volumeClaimTemplates:
- metadata:
name: provisioning
{{- with .Values.provisioningPersistence.labels }}
labels:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.provisioningPersistence.annotations }}
annotations:
{{- toYaml . | nindent 10 }}
{{- end }}
spec:
accessModes:
{{- range .Values.provisioningPersistence.accessModes }}
- {{ . | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.provisioningPersistence.size | quote }}
{{- if .Values.provisioningPersistence.storageClass }}
storageClassName: {{ .Values.provisioningPersistence.storageClass | quote }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,75 @@
{{- if .Values.testFramework.enabled -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-test
namespace: {{ .Release.Namespace }}
labels:
type: helm-chart-test
{{- include "perses.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded"
data:
run.sh: |-
#!/usr/bin/env bats
load "/usr/lib/bats/bats-detik/utils"
load "/usr/lib/bats/bats-detik/detik"
DETIK_CLIENT_NAME="kubectl"
@test "Verify that there is a service named {{ include "perses.fullname" . }}" {
verify "there is 1 service named '^{{ include "perses.fullname" . }}$'"
}
@test "Verify that there is a configmap named {{ include "perses.fullname" . }}" {
verify "there is 1 configmap named '^{{ include "perses.fullname" . }}$'"
}
{{- if .Values.config.database.file }}
@test "Verify successful statefulset and running status of the {{ .Release.Name }} pod" {
verify "there is 1 statefulset named '{{ include "perses.fullname" . }}'"
try "at most 3 times every 20s to get pods named '{{ include "perses.fullname" . }}.*' and verify that '.status.phase' is 'running'"
}
{{- end -}}
{{- if .Values.config.database.sql }}
@test "Verify successful deployment and running status of the {{ .Release.Name }} pod" {
verify "there is 1 deployment named '{{ include "perses.fullname" . }}'"
try "at most 3 times every 20s to get pods named '{{ include "perses.fullname" . }}.*' and verify that '.status.phase' is 'running'"
}
{{- end -}}
{{- if .Values.persistence.enabled }}
@test "Verify successful creation and bound status of {{ include "perses.fullname" . }} persistent volume claims" {
try "at most 3 times every 5s to get persistentvolumeclaims named '{{ include "perses.fullname" . }}.*' and verify that '.status.phase' is 'Bound'"
}
{{- end -}}
{{- if .Values.ingress.enabled }}
@test "Verify successful creation of ingress resource" {
verify "there is 1 ingress named '^{{ include "perses.fullname" . }}$'"
}
{{- end -}}
{{- if and .Values.gateway.enabled .Values.gateway.createGateway }}
@test "Verify successful creation of gateway resource" {
verify "there is 1 gateways.gateway.networking.k8s.io named '^{{ default (printf "%s-gateway" (include "perses.fullname" .)) .Values.gateway.name }}$'"
}
{{- end -}}
{{- if .Values.gateway.enabled }}
@test "Verify successful creation of HTTPRoute resource" {
verify "there is 1 httproutes.gateway.networking.k8s.io named '^{{ default (printf "%s-http-route" (include "perses.fullname" .)) .Values.gateway.httpRoute.name }}$'"
}
{{- end -}}
{{ if .Values.serviceMonitor.selfMonitor }}
@test "Verify succesful creation of ServiceMonitor" {
verify "there is 1 servicemonitor named '^{{ include "perses.fullname" . }}$'"
}
{{ end }}
{{- end -}}
@@ -0,0 +1,57 @@
{{- if .Values.testFramework.enabled -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Release.Name }}-test
namespace: {{ .Release.Namespace }}
labels:
type: helm-chart-test
{{- include "perses.labels" . | nindent 4 }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ .Release.Name }}-test
namespace: {{ .Release.Namespace }}
labels:
type: helm-chart-test
{{- include "perses.labels" . | nindent 4 }}
rules:
- apiGroups: ["apps"]
resources: ["statefulsets","deployments"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods", "persistentvolumeclaims", "services", "configmaps"]
verbs: ["get", "list"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list"]
{{- if .Values.gateway.enabled }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways", "httproutes"]
verbs: ["get", "list"]
{{- end }}
{{- if .Values.serviceMonitor.selfMonitor }}
- apiGroups: ["monitoring.coreos.com"]
resources: ["servicemonitors"]
verbs: ["get", "list"]
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ .Release.Name }}-test
namespace: {{ .Release.Namespace }}
labels:
type: helm-chart-test
{{- include "perses.labels" . | nindent 4 }}
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}-test
namespace: {{ .Release.Namespace }}
roleRef:
kind: Role
name: {{ .Release.Name }}-test
apiGroup: rbac.authorization.k8s.io
---
{{- end -}}
@@ -0,0 +1,28 @@
{{- if .Values.testFramework.enabled -}}
apiVersion: v1
kind: Pod
metadata:
name: {{ .Release.Name }}-test
namespace: {{ .Release.Namespace }}
labels:
type: helm-chart-test
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded"
spec:
serviceAccountName: {{ .Release.Name }}-test
containers:
- name: bats-test
image: "{{ .Values.testFramework.image.registry}}/{{ .Values.testFramework.image.repository}}:{{ .Values.testFramework.image.tag }}"
imagePullPolicy: {{ .Values.testFramework.image.pullPolicy }}
command: ["bats", "-t", "/tests/run.sh"]
volumeMounts:
- name: tests
mountPath: /tests
readOnly: true
volumes:
- name: tests
configMap:
name: {{ .Release.Name }}-test
restartPolicy: Never
{{- end -}}