prole/knoe/core/ops/monitoring.py
chrisfu eb9430df9d fix(gitlab,infra): ARM64 RPi service cluster – GitLab deploy in gitlab ns on gandalf
Namespace & routing
- milestones.py: GitOpsMilestone now resolves namespace from
  gitops.gitlab_namespace (new) → Global.GITLAB_NAMESPACE → 'gitlab'
  hardcoded; never falls through to gitops.namespace (was 'gitea')
- prole.cfg: add gitops.gitlab_namespace=gitlab + GITLAB_NAMESPACE=gitlab
- init_gitlab.sh: NAMESPACE defaults to gitlab, NODE_SELECTOR blanked so
  only gitaly+minio are node-pinned; GITOPS_NAMESPACE fallback removed

GitLab on ARM64 RPi (16 KB kernel pages)
- init_gitlab.sh: DaemonSet compiles jemalloc-5.3.0 with --with-lg-page=14
  (glibc/Ubuntu) on every node; LD_PRELOAD injected per Ruby component
- Minio: quay.io 2022 image (ARM64); configure init container replaced
  with ARM64 alpine that writes credential files; MINIO_ROOT_USER/PASSWORD
  injected directly into main container env via secretKeyRef
- Minio buckets auto-created post-deploy (registry, lfs, artifacts, etc.)
- webservice/sidekiq: replicaCount=1, reduced memory (1500M/800M),
  liveness probe initialDelaySeconds=3600 (Rails loads 25-40min on RPi)
- allowedHosts set as flat string list (chart 9.x default is list-of-maps
  which breaks URI initializer in 7_gitlab_http.rb)
- gitaly+minio always pinned to gandalf (local PV); other workloads spread

Storage
- Static PVs created for gitaly (50Gi) + minio (10Gi) on synology d005
- Synology dirs created before PVs; bucket creation idempotent

Redis (shared for GitLab KAS)
- init_redis.sh: persistence disabled (no dynamic provisioner); Redis used
  as pub/sub broker only

Infrastructure / pi.prole.org
- Removed pi.prole.org from [k3s_agents] – dedicated pihole node, OOM
- host_vars: k3s_enabled=false, k3s_state=absent (storage preserved)
- New playbook: infrastructure/playbooks/disable_pi_k3s.yml (drain + disable)
- monitoring.py: node-exporter DaemonSet excludes pi.prole.org
- init_monitoring.sh: pi.prole.org excluded from node-exporter affinity
- kong-deployment.yaml: affinity rule prevents scheduling on pi (pihole owns 80/443)

Co-authored-by: Junie <junie@jetbrains.com>
2026-04-02 20:15:11 -07:00

156 lines
4.6 KiB
Python

from __future__ import annotations
import os
import tempfile
from ._services_common import _LogFn, _detect_mode, _helm, _kubectl, _log, _namespace, _to_bool
def _monitoring_namespace(namespace: str | None, env: dict | None) -> str:
if env:
explicit = str(env.get("MONITORING_NAMESPACE") or "").strip()
if explicit:
return explicit
return _namespace(namespace, env, default="monitoring")
def _release_name(env: dict | None) -> str:
return str((env or {}).get("MONITORING_RELEASE") or "prometheus")
def initialize(
*,
namespace: str | None = None,
env: dict | None = None,
log: _LogFn | None = None,
mode: str | None = None,
) -> None:
update(namespace=namespace, env=env, log=log, mode=mode)
def start(
*,
namespace: str | None = None,
env: dict | None = None,
log: _LogFn | None = None,
mode: str | None = None,
) -> None:
update(namespace=namespace, env=env, log=log, mode=mode)
def update(
*,
namespace: str | None = None,
env: dict | None = None,
log: _LogFn | None = None,
mode: str | None = None,
) -> None:
_detect_mode(mode, env) # mode retained for parity with other owners
ns = _monitoring_namespace(namespace, env)
release = _release_name(env)
chart = str((env or {}).get("MONITORING_CHART") or "prometheus-community/kube-prometheus-stack")
grafana_password = str((env or {}).get("GRAFANA_ADMIN_PASSWORD") or "prole")
_log(log, "[MONITORING] Ensuring helm repos")
_helm(["repo", "add", "prometheus-community", "https://prometheus-community.github.io/helm-charts"], env=env)
_helm(["repo", "update"], env=env)
_kubectl(["create", "namespace", ns], env=env, timeout=60)
# Nodes with broken kubelet (e.g. pi.prole.org returning 502) must be excluded
# from the node-exporter DaemonSet so helm --wait can succeed.
excluded_nodes = str((env or {}).get("MONITORING_NODE_EXPORTER_EXCLUDE_NODES") or "pi.prole.org")
excluded_list = [n.strip() for n in excluded_nodes.split(",") if n.strip()]
values_yaml = "prometheus-node-exporter:\n"
if excluded_list:
values_yaml += (
" affinity:\n"
" nodeAffinity:\n"
" requiredDuringSchedulingIgnoredDuringExecution:\n"
" nodeSelectorTerms:\n"
" - matchExpressions:\n"
" - key: kubernetes.io/hostname\n"
" operator: NotIn\n"
" values:\n"
)
for node in excluded_list:
values_yaml += f" - {node}\n"
tmp_values = tempfile.NamedTemporaryFile(
mode="w", suffix=".yaml", prefix="monitoring-values-", delete=False
)
try:
tmp_values.write(values_yaml)
tmp_values.flush()
tmp_values.close()
_log(log, f"[MONITORING] Deploying {release} in namespace {ns}")
_helm(
[
"upgrade",
"--install",
release,
chart,
"--namespace",
ns,
"--set",
"grafana.adminUser=admin",
"--set",
f"grafana.adminPassword={grafana_password}",
"-f",
tmp_values.name,
"--wait",
],
env=env,
timeout=600,
check=True,
)
finally:
os.unlink(tmp_values.name)
def restart(
*,
namespace: str | None = None,
env: dict | None = None,
log: _LogFn | None = None,
) -> None:
ns = _monitoring_namespace(namespace, env)
_log(log, f"[MONITORING] Restarting grafana deployment in namespace {ns}")
_kubectl(["-n", ns, "rollout", "restart", "deployment/prometheus-grafana"], env=env, timeout=180)
def stop(
*,
namespace: str | None = None,
env: dict | None = None,
log: _LogFn | None = None,
) -> None:
ns = _monitoring_namespace(namespace, env)
release = _release_name(env)
_log(log, f"[MONITORING] Uninstalling release {release} in namespace {ns}")
_helm(["uninstall", release, "--namespace", ns], env=env, timeout=180)
def status(
*,
namespace: str | None = None,
env: dict | None = None,
) -> bool:
ns = _monitoring_namespace(namespace, env)
dep = _kubectl(
[
"-n",
ns,
"get",
"deployment",
"prometheus-grafana",
"-o",
"jsonpath={.status.readyReplicas}",
],
env=env,
timeout=20,
)
return _to_bool(dep.returncode == 0 and (dep.stdout or "0").strip() not in {"", "0"})