From 70ad6c02df9192d22f3398a392c52555a20e551d Mon Sep 17 00:00:00 2001 From: Masaki Yatsu Date: Mon, 8 Sep 2025 23:39:41 +0900 Subject: [PATCH] feat(metabase): add metabase --- justfile | 1 + metabase/.gitignore | 2 + metabase/justfile | 122 ++++++++++++++++++ ...abase-config-external-secret.gomplate.yaml | 18 +++ metabase/metabase-values.gomplate.yaml | 84 ++++++++++++ 5 files changed, 227 insertions(+) create mode 100644 metabase/.gitignore create mode 100644 metabase/justfile create mode 100644 metabase/metabase-config-external-secret.gomplate.yaml create mode 100644 metabase/metabase-values.gomplate.yaml diff --git a/justfile b/justfile index 6a02370..872f7c7 100644 --- a/justfile +++ b/justfile @@ -12,6 +12,7 @@ mod keycloak mod jupyterhub mod k8s mod longhorn +mod metabase mod minio mod postgres mod utils diff --git a/metabase/.gitignore b/metabase/.gitignore new file mode 100644 index 0000000..c630f17 --- /dev/null +++ b/metabase/.gitignore @@ -0,0 +1,2 @@ +metabase-values.yaml +metabase-config-external-secret.yaml diff --git a/metabase/justfile b/metabase/justfile new file mode 100644 index 0000000..fab79db --- /dev/null +++ b/metabase/justfile @@ -0,0 +1,122 @@ +set fallback := true + +export METABASE_NAMESPACE := env("METABASE_NAMESPACE", "metabase") +export METABASE_CHART_VERSION := env("METABASE_CHART_VERSION", "2.22.0") +export METABASE_HOST := env("METABASE_HOST", "") +export EXTERNAL_SECRETS_NAMESPACE := env("EXTERNAL_SECRETS_NAMESPACE", "external-secrets") +export K8S_VAULT_NAMESPACE := env("K8S_VAULT_NAMESPACE", "vault") + +[private] +default: + @just --list --unsorted --list-submodules + +# Add Helm repository +add-helm-repo: + helm repo add pmint93 https://pmint93.github.io/helm-charts + helm repo update + +# Remove Helm repository +remove-helm-repo: + helm repo remove pmint93 + +# Create Metabase namespace +create-namespace: + @kubectl get namespace ${METABASE_NAMESPACE} &>/dev/null || \ + kubectl create namespace ${METABASE_NAMESPACE} + +# Delete Metabase namespace +delete-namespace: + @kubectl delete namespace ${METABASE_NAMESPACE} --ignore-not-found + +# Create Metabase session secret +create-session-secret: + #!/bin/bash + set -euo pipefail + session_secret=$(just utils::random-password) + + if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then + echo "External Secrets Operator detected. Storing session secret in Vault..." + just vault::put metabase/config session="${session_secret}" + + kubectl delete secret metabase-config -n ${METABASE_NAMESPACE} --ignore-not-found + kubectl delete externalsecret metabase-config -n ${METABASE_NAMESPACE} --ignore-not-found + + # gomplate -f metabase-config-external-secret.gomplate.yaml | kubectl apply -f - + gomplate -f metabase-config-external-secret.gomplate.yaml \ + -o metabase-config-external-secret.yaml + kubectl apply -f metabase-config-external-secret.yaml + + echo "Waiting for ExternalSecret to sync..." + kubectl wait --for=condition=Ready externalsecret/metabase-config \ + -n ${METABASE_NAMESPACE} --timeout=60s + else + echo "External Secrets Operator not found. Creating secret directly..." + kubectl delete secret metabase-config -n ${METABASE_NAMESPACE} --ignore-not-found + kubectl create secret generic metabase-config -n ${METABASE_NAMESPACE} \ + --from-literal=session="${session_secret}" + + if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null; then + just vault::put metabase/config session="${session_secret}" + fi + fi + +# Create Metabase database secret +create-database-secret: + #!/bin/bash + set -euo pipefail + if kubectl get secret database-config -n ${METABASE_NAMESPACE} &>/dev/null; then + kubectl delete secret database-config -n ${METABASE_NAMESPACE} + fi + kubectl create secret generic database-config -n ${METABASE_NAMESPACE} \ + --from-literal=host=postgres-cluster-rw.postgres \ + --from-literal=port=5432 \ + --from-literal=user=$(just postgres::admin-username) \ + --from-literal=password=$(just postgres::admin-password) \ + --from-literal=database=metabase + +# Delete Metabase database secret +delete-database-secret: + @kubectl delete secret database-config -n ${METABASE_NAMESPACE} --ignore-not-found + + +# Install Metabase +install: + #!/bin/bash + set -euo pipefail + export METABASE_HOST=${METABASE_HOST:-} + while [ -z "${METABASE_HOST}" ]; do + METABASE_HOST=$( + gum input --prompt="Metabase host (FQDN): " --width=100 \ + --placeholder="e.g., metabase.example.com" + ) + done + + just create-namespace + just postgres::create-db metabase + just create-database-secret + just create-session-secret + + just add-helm-repo + gomplate -f metabase-values.gomplate.yaml -o metabase-values.yaml + + helm upgrade --cleanup-on-fail --install metabase pmint93/metabase \ + --version ${METABASE_CHART_VERSION} -n ${METABASE_NAMESPACE} --wait \ + -f metabase-values.yaml + +# Uninstall Metabase +uninstall delete-db='true': + #!/bin/bash + set -euo pipefail + helm uninstall metabase -n ${METABASE_NAMESPACE} --ignore-not-found --wait + kubectl delete secret metabase-config -n ${METABASE_NAMESPACE} --ignore-not-found + kubectl delete externalsecret metabase-config -n ${METABASE_NAMESPACE} --ignore-not-found + just delete-database-secret + just delete-namespace + if [ "{{ delete-db }}" = "true" ]; then + just postgres::delete-db metabase + fi + + # Clean up Vault entries if present + if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null; then + just vault::delete metabase/config || true + fi diff --git a/metabase/metabase-config-external-secret.gomplate.yaml b/metabase/metabase-config-external-secret.gomplate.yaml new file mode 100644 index 0000000..aa62733 --- /dev/null +++ b/metabase/metabase-config-external-secret.gomplate.yaml @@ -0,0 +1,18 @@ +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: metabase-config + namespace: {{ .Env.METABASE_NAMESPACE }} +spec: + refreshInterval: 1h + secretStoreRef: + name: vault-secret-store + kind: ClusterSecretStore + target: + name: metabase-config + creationPolicy: Owner + data: + - secretKey: session + remoteRef: + key: metabase/config + property: session \ No newline at end of file diff --git a/metabase/metabase-values.gomplate.yaml b/metabase/metabase-values.gomplate.yaml new file mode 100644 index 0000000..de9c60d --- /dev/null +++ b/metabase/metabase-values.gomplate.yaml @@ -0,0 +1,84 @@ +# Metabase Helm Chart Values +# https://github.com/pmint93/helm-charts/tree/master/charts/metabase + +image: + repository: metabase/metabase + tag: v0.51.1 + +# Use PostgreSQL for production +database: + type: postgres + host: postgres-cluster-rw.postgres + port: 5432 + dbname: metabase + existingSecret: database-config + existingSecretUsernameKey: user + existingSecretPasswordKey: password + +ingress: + enabled: true + className: traefik + annotations: + kubernetes.io/ingress.class: traefik + traefik.ingress.kubernetes.io/router.entrypoints: websecure + hosts: + - {{ .Env.METABASE_HOST }} + tls: + - hosts: + - {{ .Env.METABASE_HOST }} + +# Session configuration +session: + existingSecret: metabase-config + existingSecretKey: session + +# Basic configuration +extraEnv: + - name: MB_APPLICATION_NAME + value: "Metabase Analytics" + - name: MB_ENABLE_PUBLIC_SHARING + value: "true" + - name: MB_ENABLE_EMBEDDING + value: "true" + +# Resource limits +resources: + limits: + memory: 2Gi + cpu: 1000m + requests: + memory: 1Gi + cpu: 500m + +# Security context +securityContext: + runAsUser: 2000 + runAsGroup: 2000 + runAsNonRoot: true + +# Pod security context +podSecurityContext: + fsGroup: 2000 + +# Service account +serviceAccount: + create: true + +# Log4j2 configuration to reduce verbose logging +log4j2XML: | + + + + + + + + + + + + + + + + \ No newline at end of file