eb35419ad0
One consolidated search compromised on all three questions at once. It also
had a real defect: the Source selector carried `.+` as its All value, so
choosing "All" widened the query back to every stream in the cluster and the
scope preset only ever populated the picker. MongoDB and cert-manager showed up
in what was meant to be an application view.
Each query now carries both `job=~"${source:regex}"` and a literal job matcher,
so the selector actually restricts rather than widens.
The three kinds are subtracted from each other rather than merely filtered:
application logs exclude Keycloak's access-log and events loggers by name, the
Django container's NCSA lines by shape, and the Kong containers' stray access
lines by requiring the nginx date prefix.
Levels are read from the line rather than taken from Loki's detected_level.
That inference marks 78% of the Kong gateway output `unknown` even though every
one of those lines states [notice] or [warn], so filtering on it hid most of
the gateway's own logs. Each parser extracts the level and lowercases it, which
puts nginx's notice, Keycloak's INFO and LibreChat's info on one scale — and
removes the need for an `unknown` entry in the selector.
Applies move to server-side. Client-side apply stores a full copy of each
object in the last-applied-configuration annotation, capped at 256KB, and the
dashboard ConfigMap passed that at eight dashboards; the failure surfaced as
"metadata.annotations: Too long". Server-side apply tracks field ownership in
metadata instead, moving the ceiling to etcd's ~1MB.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
362 lines
18 KiB
Makefile
362 lines
18 KiB
Makefile
# =============================================================================
|
|
# infra-observability -- Grafana / Prometheus / Loki / Tempo stack (+ Alloy)
|
|
# Fully declarative: kustomize + kubectl. No imperative `helm install`.
|
|
#
|
|
# make -> help
|
|
# make deploy -> deploy everything
|
|
# make diag -> full diagnostics
|
|
# =============================================================================
|
|
|
|
SHELL := /bin/bash
|
|
.DEFAULT_GOAL := help
|
|
# Every target prints its own output, so commands are not echoed.
|
|
.SILENT:
|
|
|
|
NS ?= observability
|
|
CTX ?= orbstack
|
|
KUBECTL := kubectl --context=$(CTX)
|
|
K8S := k8s
|
|
DASHBOARDS := $(K8S)/grafana/dashboards
|
|
INGRESS_IP ?= 192.168.139.101
|
|
HOSTS := grafana.sttlab.local prometheus.sttlab.local alloy.sttlab.local
|
|
|
|
# Components backed by a PVC (this is also the flush order)
|
|
STATEFUL := prometheus loki tempo grafana
|
|
# Restartable workloads
|
|
WORKLOADS := deployment/prometheus deployment/loki deployment/tempo deployment/grafana daemonset/alloy
|
|
|
|
C_OK := \033[0;32m
|
|
C_WARN := \033[0;33m
|
|
C_ERR := \033[0;31m
|
|
C_HEAD := \033[1;36m
|
|
C_OFF := \033[0m
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Help
|
|
# -----------------------------------------------------------------------------
|
|
.PHONY: help
|
|
help: ## Show this help
|
|
printf "$(C_HEAD)infra-observability$(C_OFF) -- namespace $(C_OK)$(NS)$(C_OFF) / context $(C_OK)$(CTX)$(C_OFF)\n\n"
|
|
awk 'BEGIN {FS = ":.*?## "} \
|
|
/^[a-zA-Z0-9_-]+:.*?## / {printf " $(C_OK)%-24s$(C_OFF) %s\n", $$1, $$2} \
|
|
/^##@/ {printf "\n$(C_HEAD)%s$(C_OFF)\n", substr($$0, 5)}' $(MAKEFILE_LIST)
|
|
printf "\n"
|
|
|
|
##@ Deployment
|
|
|
|
.PHONY: deploy
|
|
deploy: check-context secret ## Deploy the whole stack (runtimes + dashboards)
|
|
# --server-side for the same reason as the dashboards target below.
|
|
$(KUBECTL) apply -k $(K8S) --server-side --force-conflicts
|
|
$(MAKE) --no-print-directory wait
|
|
|
|
.PHONY: deploy-runtime
|
|
deploy-runtime: check-context secret ## Deploy runtimes only (no dashboards)
|
|
$(KUBECTL) apply -f $(K8S)/namespace.yaml -f $(K8S)/secret.yaml
|
|
$(KUBECTL) apply -k $(K8S)/prometheus
|
|
$(KUBECTL) apply -k $(K8S)/loki
|
|
$(KUBECTL) apply -k $(K8S)/tempo
|
|
$(KUBECTL) apply -k $(K8S)/alloy
|
|
$(KUBECTL) apply -k $(K8S)/grafana
|
|
$(KUBECTL) apply -k $(K8S)/ingress
|
|
$(MAKE) --no-print-directory wait
|
|
|
|
.PHONY: prometheus loki tempo grafana alloy ingress
|
|
prometheus: check-context ## Deploy/update Prometheus only
|
|
$(KUBECTL) apply -k $(K8S)/prometheus
|
|
loki: check-context ## Deploy/update Loki only
|
|
$(KUBECTL) apply -k $(K8S)/loki
|
|
tempo: check-context ## Deploy/update Tempo only
|
|
$(KUBECTL) apply -k $(K8S)/tempo
|
|
grafana: check-context ## Deploy/update the Grafana runtime only (datasources included)
|
|
$(KUBECTL) apply -k $(K8S)/grafana
|
|
alloy: check-context ## Deploy/update Alloy only (log collection)
|
|
$(KUBECTL) apply -k $(K8S)/alloy
|
|
ingress: check-context ## Deploy/update the Ingresses and the certificate
|
|
$(KUBECTL) apply -k $(K8S)/ingress
|
|
|
|
.PHONY: secret
|
|
secret: ## Generate k8s/secret.yaml (random admin password) if absent
|
|
if [ ! -f $(K8S)/secret.yaml ]; then \
|
|
pw=$$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 20); \
|
|
sed -e '1s|.*|# Generated by `make secret` -- NOT versioned (see .gitignore).|' \
|
|
-e "s|<CHANGE_ME>|$$pw|" $(K8S)/secret.example.yaml > $(K8S)/secret.yaml; \
|
|
printf "$(C_OK)secret.yaml generated$(C_OFF) -- admin password: $$pw\n"; \
|
|
else \
|
|
printf "secret.yaml already present (use make grafana-password to read it)\n"; \
|
|
fi
|
|
|
|
##@ Dashboards (declarative, stable UIDs)
|
|
|
|
.PHONY: dashboards
|
|
dashboards: check-context dashboards-check ## Apply dashboards only (no Grafana restart)
|
|
# Server-side apply: client-side apply stores a full copy of the object in the
|
|
# kubectl.kubernetes.io/last-applied-configuration annotation, which is capped at
|
|
# 256KB. The dashboards passed that in aggregate. Server-side apply tracks field
|
|
# ownership in metadata instead, so the ceiling becomes etcd's ~1MB per object.
|
|
$(KUBECTL) apply -k $(DASHBOARDS) --server-side --force-conflicts
|
|
printf "$(C_OK)grafana-dashboards ConfigMap updated$(C_OFF) -- visible in Grafana within ~70s\n"
|
|
printf " (kubelet volume sync ~60s + provisioner poll 10s, no restart).\n"
|
|
printf " For immediate effect: $(C_WARN)make restart-grafana$(C_OFF)\n"
|
|
|
|
.PHONY: dashboards-sync
|
|
dashboards-sync: ## Regenerate the .json list in dashboards/kustomization.yaml
|
|
cd $(DASHBOARDS) && python3 -c "import glob,re,pathlib; \
|
|
p=pathlib.Path('kustomization.yaml'); s=p.read_text(); \
|
|
files=sorted(glob.glob('*.json')); \
|
|
block=''.join(' - %s\n' % f for f in files); \
|
|
s=re.sub(r'(# >>> dashboards.*?>>>\n).*?( # <<< dashboards <<<)', lambda m: m.group(1)+block+m.group(2), s, flags=re.S); \
|
|
p.write_text(s); print('%d dashboard(s) referenced' % len(files))"
|
|
$(MAKE) --no-print-directory dashboards-check
|
|
|
|
.PHONY: dashboards-check
|
|
dashboards-check: ## Validate the JSON files and UID uniqueness/stability
|
|
fail=0; \
|
|
for f in $(DASHBOARDS)/*.json; do \
|
|
jq -e . "$$f" >/dev/null 2>&1 || { printf "$(C_ERR)invalid JSON$(C_OFF): %s\n" "$$f"; fail=1; continue; }; \
|
|
uid=$$(jq -r '.uid // empty' "$$f"); \
|
|
[ -n "$$uid" ] || { printf "$(C_ERR)missing uid$(C_OFF): %s\n" "$$f"; fail=1; }; \
|
|
base=$$(basename "$$f" .json); \
|
|
[ "$$uid" = "$$base" ] || printf "$(C_WARN)filename != uid$(C_OFF): %s (uid=%s)\n" "$$f" "$$uid"; \
|
|
[ "$$(jq -r '.id // "null"' "$$f")" = "null" ] || printf "$(C_WARN)non-null id field$(C_OFF): %s (remove it)\n" "$$f"; \
|
|
grep -q -- "- $$base.json" $(DASHBOARDS)/kustomization.yaml || { printf "$(C_ERR)not referenced in kustomization.yaml$(C_OFF): %s (run make dashboards-sync)\n" "$$f"; fail=1; }; \
|
|
done; \
|
|
dup=$$(jq -r '.uid' $(DASHBOARDS)/*.json | sort | uniq -d); \
|
|
[ -z "$$dup" ] || { printf "$(C_ERR)duplicate UIDs$(C_OFF): %s\n" "$$dup"; fail=1; }; \
|
|
[ $$fail -eq 0 ] && printf "$(C_OK)dashboards OK$(C_OFF) (%s)\n" "$$(ls $(DASHBOARDS)/*.json | wc -l | tr -d ' ')"; \
|
|
exit $$fail
|
|
|
|
.PHONY: dashboards-list
|
|
dashboards-list: ## List the dashboards actually loaded by Grafana
|
|
auth="$$($(KUBECTL) -n $(NS) get secret observability-secrets -o jsonpath='{.data.GF_SECURITY_ADMIN_USER}' | base64 -d):$$($(KUBECTL) -n $(NS) get secret observability-secrets -o jsonpath='{.data.GF_SECURITY_ADMIN_PASSWORD}' | base64 -d)"; \
|
|
$(KUBECTL) -n $(NS) exec deployment/grafana -- curl -s -u "$$auth" 'http://localhost:3000/api/search?type=dash-db' \
|
|
| jq -r '.[] | "\(.uid)\t\(.folderTitle // "General")\t\(.title)"'
|
|
|
|
##@ Diagnostics
|
|
|
|
.PHONY: status
|
|
status: ## Overview (pods, services, PVCs, ingresses)
|
|
$(KUBECTL) -n $(NS) get pods -o wide
|
|
echo
|
|
$(KUBECTL) -n $(NS) get svc,pvc,ingress
|
|
|
|
.PHONY: diag
|
|
diag: ## Full diagnostics: state, PVCs, endpoints, certificate, events
|
|
printf "$(C_HEAD)== Pods ==$(C_OFF)\n"; $(KUBECTL) -n $(NS) get pods -o wide
|
|
printf "\n$(C_HEAD)== PVCs ==$(C_OFF)\n"; $(KUBECTL) -n $(NS) get pvc
|
|
printf "\n$(C_HEAD)== Endpoints ==$(C_OFF)\n"; $(KUBECTL) -n $(NS) get endpoints
|
|
printf "\n$(C_HEAD)== Ingresses ==$(C_OFF)\n"; $(KUBECTL) -n $(NS) get ingress
|
|
printf "\n$(C_HEAD)== Certificate ==$(C_OFF)\n";$(KUBECTL) -n $(NS) get certificate 2>/dev/null || echo "(cert-manager absent)"
|
|
printf "\n$(C_HEAD)== ConfigMaps ==$(C_OFF)\n";$(KUBECTL) -n $(NS) get cm
|
|
printf "\n$(C_HEAD)== Containers not ready ==$(C_OFF)\n"
|
|
$(KUBECTL) -n $(NS) get pods -o json | jq -r '.items[] | select(.status.containerStatuses != null) | .status.containerStatuses[] | select(.ready == false) | "\(.name)\trestarts=\(.restartCount)\t\(.state | keys[0]): \(.state[keys[0]].reason // "-")"' || true
|
|
printf "\n$(C_HEAD)== LibreChat scrape token ==$(C_OFF)\n"
|
|
$(MAKE) --no-print-directory check-metrics-token || true
|
|
printf "\n$(C_HEAD)== Last 15 events ==$(C_OFF)\n"
|
|
$(KUBECTL) -n $(NS) get events --sort-by=.lastTimestamp | tail -15
|
|
|
|
.PHONY: smoke
|
|
smoke: ## End-to-end checks: component health, datasources, dashboards, targets
|
|
# The Loki and Tempo images ship neither curl nor wget, so probes run from the
|
|
# Grafana pod, which has curl and sees the same Services as the datasources.
|
|
probe() { $(KUBECTL) -n $(NS) exec deployment/grafana -- curl -s --max-time 10 "$$1"; }; \
|
|
auth="$$($(KUBECTL) -n $(NS) get secret observability-secrets -o jsonpath='{.data.GF_SECURITY_ADMIN_USER}' | base64 -d):$$($(KUBECTL) -n $(NS) get secret observability-secrets -o jsonpath='{.data.GF_SECURITY_ADMIN_PASSWORD}' | base64 -d)"; \
|
|
printf "$(C_HEAD)== Component health ==$(C_OFF)\n"; \
|
|
printf " %-12s %s\n" "prometheus" "$$(probe http://prometheus:9090/-/ready)"; \
|
|
printf " %-12s %s\n" "loki" "$$(probe http://loki:3100/ready)"; \
|
|
printf " %-12s %s\n" "tempo" "$$(probe http://tempo:3200/ready)"; \
|
|
printf " %-12s %s\n" "grafana" "$$(probe http://localhost:3000/api/health | jq -c .)"; \
|
|
printf " %-12s %s\n" "alloy" "$$(probe http://alloy:12345/-/ready)"; \
|
|
printf "\n$(C_HEAD)== Grafana datasources ==$(C_OFF)\n"; \
|
|
for uid in prometheus loki tempo; do \
|
|
r=$$($(KUBECTL) -n $(NS) exec deployment/grafana -- curl -s -u "$$auth" -X POST "http://localhost:3000/api/datasources/uid/$$uid/health" | jq -r '"\(.status) \(.message)"'); \
|
|
printf " %-12s %s\n" "$$uid" "$$r"; \
|
|
done; \
|
|
printf "\n$(C_HEAD)== Provisioned dashboards ==$(C_OFF)\n"; \
|
|
$(KUBECTL) -n $(NS) exec deployment/grafana -- curl -s -u "$$auth" 'http://localhost:3000/api/search?type=dash-db' \
|
|
| jq -r '.[] | " \(.uid) \(.folderTitle // "General")/\(.title)"'; \
|
|
printf "\n$(C_HEAD)== Namespaces seen by Loki ==$(C_OFF)\n"; \
|
|
probe 'http://loki:3100/loki/api/v1/label/namespace/values' | jq -r '" " + (.data // [] | join(", "))'; \
|
|
printf "\n$(C_HEAD)== Prometheus targets ==$(C_OFF)\n"; \
|
|
probe http://prometheus:9090/api/v1/targets | jq -r '.data.activeTargets | group_by(.health)[] | " \(.[0].health)\t\(length)"'
|
|
|
|
.PHONY: targets
|
|
targets: ## List Prometheus scrape targets and their state
|
|
$(KUBECTL) -n $(NS) exec deployment/prometheus -- wget -qO- http://localhost:9090/api/v1/targets \
|
|
| jq -r '.data.activeTargets[] | "\(.health)\t\(.labels.job)\t\(.scrapeUrl)\t\(.lastError // "")"' | sort
|
|
|
|
.PHONY: check-metrics-token
|
|
check-metrics-token: ## Check the LibreChat token matches across both namespaces
|
|
a=$$($(KUBECTL) -n $(NS) get secret librechat-metrics -o jsonpath='{.data.token}' 2>/dev/null | base64 -d); \
|
|
b=$$($(KUBECTL) -n librechat get secret librechat-secrets -o jsonpath='{.data.METRICS_SECRET}' 2>/dev/null | base64 -d); \
|
|
if [ -z "$$a" ] || [ -z "$$b" ]; then \
|
|
printf "$(C_WARN)token not found$(C_OFF) (observability/librechat-metrics=%s, librechat/librechat-secrets=%s)\n" "$${a:+present}$${a:-absent}" "$${b:+present}$${b:-absent}"; \
|
|
elif [ "$$a" = "$$b" ]; then \
|
|
printf "$(C_OK)LibreChat token matches$(C_OFF) across observability and librechat\n"; \
|
|
else \
|
|
printf "$(C_ERR)DRIFT$(C_OFF): the two Secrets hold different values.\n"; \
|
|
printf " The librechat scrape will fall back to 401. Realign both copies.\n"; \
|
|
exit 1; \
|
|
fi
|
|
|
|
.PHONY: logs-prometheus logs-loki logs-tempo logs-grafana logs-alloy
|
|
logs-prometheus: ## Follow Prometheus logs
|
|
$(KUBECTL) -n $(NS) logs -f deployment/prometheus --tail=100
|
|
logs-loki: ## Follow Loki logs
|
|
$(KUBECTL) -n $(NS) logs -f deployment/loki --tail=100
|
|
logs-tempo: ## Follow Tempo logs
|
|
$(KUBECTL) -n $(NS) logs -f deployment/tempo --tail=100
|
|
logs-grafana: ## Follow Grafana logs
|
|
$(KUBECTL) -n $(NS) logs -f deployment/grafana --tail=100
|
|
logs-alloy: ## Follow Alloy logs
|
|
$(KUBECTL) -n $(NS) logs -f daemonset/alloy --tail=100
|
|
|
|
.PHONY: events
|
|
events: ## Namespace events, oldest to newest
|
|
$(KUBECTL) -n $(NS) get events --sort-by=.lastTimestamp
|
|
|
|
.PHONY: top
|
|
top: ## Pod CPU/memory usage
|
|
$(KUBECTL) -n $(NS) top pods 2>/dev/null || echo "metrics-server unavailable"
|
|
|
|
.PHONY: urls
|
|
urls: ## Show access URLs
|
|
printf " Grafana https://grafana.sttlab.local\n"
|
|
printf " Prometheus https://prometheus.sttlab.local\n"
|
|
printf " Alloy https://alloy.sttlab.local\n"
|
|
printf "\n In-cluster: http://prometheus:9090 - http://loki:3100 - http://tempo:3200\n"
|
|
printf " OTLP : tempo.$(NS).svc.cluster.local:4317 (gRPC) / :4318 (HTTP)\n"
|
|
|
|
.PHONY: hosts
|
|
hosts: ## Show the required /etc/hosts entries
|
|
printf "# Add to /etc/hosts:\n"
|
|
for h in $(HOSTS); do printf "$(INGRESS_IP)\t%s\n" "$$h"; done
|
|
|
|
.PHONY: grafana-password
|
|
grafana-password: ## Show the Grafana admin credentials
|
|
printf "user : %s\n" "$$($(KUBECTL) -n $(NS) get secret observability-secrets -o jsonpath='{.data.GF_SECURITY_ADMIN_USER}' | base64 -d)"
|
|
printf "password : %s\n" "$$($(KUBECTL) -n $(NS) get secret observability-secrets -o jsonpath='{.data.GF_SECURITY_ADMIN_PASSWORD}' | base64 -d)"
|
|
|
|
.PHONY: pf-grafana pf-prometheus pf-loki pf-tempo pf-alloy
|
|
pf-grafana: ## Port-forward Grafana on :3000
|
|
$(KUBECTL) -n $(NS) port-forward svc/grafana 3000:3000
|
|
pf-prometheus: ## Port-forward Prometheus on :9090
|
|
$(KUBECTL) -n $(NS) port-forward svc/prometheus 9090:9090
|
|
pf-loki: ## Port-forward Loki on :3100
|
|
$(KUBECTL) -n $(NS) port-forward svc/loki 3100:3100
|
|
pf-tempo: ## Port-forward Tempo on :3200
|
|
$(KUBECTL) -n $(NS) port-forward svc/tempo 3200:3200
|
|
pf-alloy: ## Port-forward the Alloy UI on :12345
|
|
$(KUBECTL) -n $(NS) port-forward svc/alloy 12345:12345
|
|
|
|
##@ Operations
|
|
|
|
.PHONY: wait
|
|
wait: ## Wait for every workload to become ready
|
|
for w in $(WORKLOADS); do \
|
|
printf " %-24s " "$$w"; \
|
|
$(KUBECTL) -n $(NS) rollout status $$w --timeout=300s 2>&1 | tail -1; \
|
|
done
|
|
|
|
.PHONY: restart
|
|
restart: ## Restart every workload
|
|
$(KUBECTL) -n $(NS) rollout restart $(WORKLOADS)
|
|
$(MAKE) --no-print-directory wait
|
|
|
|
.PHONY: restart-prometheus restart-loki restart-tempo restart-grafana restart-alloy
|
|
restart-prometheus: ## Restart Prometheus
|
|
$(KUBECTL) -n $(NS) rollout restart deployment/prometheus && $(KUBECTL) -n $(NS) rollout status deployment/prometheus
|
|
restart-loki: ## Restart Loki
|
|
$(KUBECTL) -n $(NS) rollout restart deployment/loki && $(KUBECTL) -n $(NS) rollout status deployment/loki
|
|
restart-tempo: ## Restart Tempo
|
|
$(KUBECTL) -n $(NS) rollout restart deployment/tempo && $(KUBECTL) -n $(NS) rollout status deployment/tempo
|
|
restart-grafana: ## Restart Grafana (dashboards picked up immediately)
|
|
$(KUBECTL) -n $(NS) rollout restart deployment/grafana && $(KUBECTL) -n $(NS) rollout status deployment/grafana
|
|
restart-alloy: ## Restart Alloy
|
|
$(KUBECTL) -n $(NS) rollout restart daemonset/alloy && $(KUBECTL) -n $(NS) rollout status daemonset/alloy
|
|
|
|
.PHONY: reload-prometheus
|
|
reload-prometheus: ## Hot-reload the Prometheus config (no restart)
|
|
$(KUBECTL) -n $(NS) exec deployment/prometheus -- wget -q --post-data='' -O- http://localhost:9090/-/reload \
|
|
&& printf "$(C_OK)Prometheus configuration reloaded$(C_OFF)\n"
|
|
|
|
##@ Persistence
|
|
|
|
.PHONY: pvc
|
|
pvc: ## PVC state and the volumes behind them
|
|
$(KUBECTL) -n $(NS) get pvc -o custom-columns='NAME:.metadata.name,STATUS:.status.phase,CAPACITY:.status.capacity.storage,SC:.spec.storageClassName,VOLUME:.spec.volumeName'
|
|
echo
|
|
$(KUBECTL) -n $(NS) get pods -o json | jq -r '.items[] | . as $$p | (.spec.volumes[]? | select(.persistentVolumeClaim) | "\($$p.metadata.name)\t-> \(.persistentVolumeClaim.claimName)")'
|
|
|
|
.PHONY: flush-pvc
|
|
flush-pvc: ## DESTRUCTIVE: wipe all persisted data (CONFIRM=yes)
|
|
$(call confirm,Wipe ALL data (metrics, logs, traces, Grafana))
|
|
for c in $(STATEFUL); do $(MAKE) --no-print-directory _flush COMP=$$c; done
|
|
$(MAKE) --no-print-directory wait
|
|
|
|
.PHONY: flush-prometheus flush-loki flush-tempo flush-grafana
|
|
flush-prometheus: ## DESTRUCTIVE: wipe the Prometheus TSDB (CONFIRM=yes)
|
|
$(call confirm,Wipe the Prometheus TSDB)
|
|
$(MAKE) --no-print-directory _flush COMP=prometheus
|
|
flush-loki: ## DESTRUCTIVE: wipe the Loki logs (CONFIRM=yes)
|
|
$(call confirm,Wipe the Loki logs)
|
|
$(MAKE) --no-print-directory _flush COMP=loki
|
|
flush-tempo: ## DESTRUCTIVE: wipe the Tempo traces (CONFIRM=yes)
|
|
$(call confirm,Wipe the Tempo traces)
|
|
$(MAKE) --no-print-directory _flush COMP=tempo
|
|
flush-grafana: ## DESTRUCTIVE: wipe the Grafana database (CONFIRM=yes)
|
|
$(call confirm,Wipe the Grafana database (users and prefs; dashboards are reprovisioned))
|
|
$(MAKE) --no-print-directory _flush COMP=grafana
|
|
|
|
# Internal target: scale down -> delete PVC -> recreate -> scale up.
|
|
.PHONY: _flush
|
|
_flush:
|
|
printf "$(C_WARN)flushing %s$(C_OFF)\n" "$(COMP)"
|
|
$(KUBECTL) -n $(NS) scale deployment/$(COMP) --replicas=0
|
|
$(KUBECTL) -n $(NS) wait --for=delete pod -l app=$(COMP) --timeout=120s 2>/dev/null || true
|
|
$(KUBECTL) -n $(NS) delete pvc $(COMP)-data --ignore-not-found --wait=true
|
|
$(KUBECTL) apply -k $(K8S)/$(COMP) --server-side --force-conflicts
|
|
$(KUBECTL) -n $(NS) scale deployment/$(COMP) --replicas=1
|
|
|
|
##@ Validation and teardown
|
|
|
|
.PHONY: build
|
|
build: ## Render the generated manifests to stdout
|
|
kustomize build $(K8S)
|
|
|
|
.PHONY: diff
|
|
diff: check-context ## Diff the manifests against the cluster state
|
|
$(KUBECTL) diff -k $(K8S) || true
|
|
|
|
.PHONY: validate
|
|
validate: dashboards-check ## Validate the manifests (build + server dry-run)
|
|
kustomize build $(K8S) >/dev/null && printf "$(C_OK)kustomize build OK$(C_OFF)\n"
|
|
$(KUBECTL) apply -k $(K8S) --server-side --force-conflicts --dry-run=server >/dev/null && printf "$(C_OK)server dry-run OK$(C_OFF)\n"
|
|
|
|
.PHONY: undeploy
|
|
undeploy: ## DESTRUCTIVE: delete the stack, PVCs kept (CONFIRM=yes)
|
|
$(call confirm,Delete the stack (PVCs are KEPT))
|
|
$(KUBECTL) delete -k $(K8S) --ignore-not-found
|
|
|
|
.PHONY: destroy
|
|
destroy: ## DESTRUCTIVE: delete the stack AND the PVCs (CONFIRM=yes)
|
|
$(call confirm,Delete the stack AND all data)
|
|
$(KUBECTL) delete -k $(K8S) --ignore-not-found
|
|
$(KUBECTL) -n $(NS) delete pvc --all --ignore-not-found
|
|
|
|
.PHONY: check-context
|
|
check-context:
|
|
cur=$$(kubectl config current-context); \
|
|
if [ "$$cur" != "$(CTX)" ]; then \
|
|
printf "$(C_ERR)current context '%s' != '$(CTX)'$(C_OFF) (commands explicitly target $(CTX))\n" "$$cur"; \
|
|
fi
|
|
|
|
# Guard rail for destructive targets: requires CONFIRM=yes
|
|
define confirm
|
|
@if [ "$(CONFIRM)" != "yes" ]; then \
|
|
printf "$(C_ERR)Destructive operation$(C_OFF): $(1)\n"; \
|
|
printf "Re-run with $(C_WARN)CONFIRM=yes$(C_OFF): make $(C_WARN)$@ CONFIRM=yes$(C_OFF)\n"; \
|
|
exit 1; \
|
|
fi
|
|
endef
|