Right-size CPU requests and stage controlled Odroid rebalancing

This commit is contained in:
feedc0de committed 2026-10-31 23:52:50 +01:00
1 parent c4f2f82f29
commit bbabf768ec
23 files changed
+249 -17

No files matched your search

+4 -1
View File
@@ -49,7 +49,7 @@ server:
minAvailable: 1
resources:
requests:
cpu: 500m
cpu: 150m
memory: 1Gi
limits:
# Deliberately leave CPU unlimited so login bursts can use idle cores.
@@ -91,6 +91,9 @@ server:
startupProbe:
timeoutSeconds: 10
worker:
resources:
requests:
cpu: 150m
enabled: true
livenessProbe:
timeoutSeconds: 10
+4
View File
@@ -20,3 +20,7 @@ ingress:
- hosts:
- brunner.ninja
secretName: brunner-ninja-tls
resources:
requests:
cpu: 175m
+5
View File
@@ -23,3 +23,8 @@ certificate:
secretName: coturn
dnsName: brunner.ninja
issuerName: letsencrypt-dns
resources:
requests:
cpu: 50m
limits:
cpu:
+24
View File
@@ -0,0 +1,24 @@
# Odroid CPU rebalancing
The upstream Descheduler chart runs every five minutes. `values.yaml` is the
policy; the bootstrap Application selects its chart version.
The policy uses Metrics Server CPU usage: nodes below 40% are possible
receivers and nodes above 75% are possible eviction sources. Kubernetes still
chooses replacement placement. It does not live-migrate processes or guarantee
that a replacement lands on a different node.
Each cycle permits at most two evictions overall, one per node and namespace.
Pods must be at least one hour old, belong to a controller with at least two
replicas, have a PodDisruptionBudget, have no PVC, and fit another eligible
node. Only the six Odroids are eligible. Storage, networking, monitoring,
certificate management, and GitOps namespaces are excluded. EmptyDir alone is
not a protection for these replicated stateless workloads.
Start with `cmdOptions.dry-run: true`, inspect Job logs, then remove that option
to allow evictions. To stop balancing, set `suspend: true` in values and let Argo
sync. Existing running Jobs must finish before suspension takes full effect.
Review after changes to workload mix; do not remove PVC and infrastructure
protections to force a more even CPU graph. CPU requests determine whether a
replacement can fit even when observed utilization is low.
+58
View File
@@ -0,0 +1,58 @@
schedule: "*/5 * * * *"
concurrencyPolicy: Forbid
activeDeadlineSeconds: 120
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 2
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: null
memory: 256Mi
# Observe the first cycle before enabling evictions.
cmdOptions:
dry-run: true
v: 3
deschedulerPolicy:
maxNoOfPodsToEvictPerNode: 1
maxNoOfPodsToEvictPerNamespace: 1
maxNoOfPodsToEvictTotal: 2
metricsProviders:
- source: KubernetesMetrics
profiles:
- name: odroid-cpu-balance
pluginConfig:
- name: DefaultEvictor
args:
nodeFit: true
minPodAge: 1h
minReplicas: 2
nodeSelector: "kubernetes.io/hostname in (odroid-h3-00,odroid-h3-01,odroid-h3-02,odroid-h3-03,odroid-h3-04,odroid-h3-05)"
podProtections:
defaultDisabled:
- PodsWithLocalStorage
extraEnabled:
- PodsWithPVC
- PodsWithoutPDB
- name: LowNodeUtilization
args:
metricsUtilization:
source: KubernetesMetrics
thresholds:
cpu: 40
targetThresholds:
cpu: 75
evictableNamespaces:
exclude:
- kube-system
- rook-ceph
- argocd
- elastic
- prometheus
- metallb-system
- cert-manager
plugins:
balance:
enabled:
- LowNodeUtilization
+2 -2
View File
@@ -1,5 +1,5 @@
managedNamespaces:
- elastic
- elastic
installCRDs: true
createClusterScopedResources: true
@@ -9,7 +9,7 @@ resources:
cpu: 100m
memory: 256Mi
limits:
cpu: 1
cpu: null
memory: 1Gi
config:
+1 -1
View File
@@ -98,7 +98,7 @@ spec:
protocol: UDP
resources:
requests:
cpu: 500m
cpu: 75m
memory: 1Gi
limits:
cpu: "2"
+2 -3
View File
@@ -47,10 +47,9 @@ spec:
- name: elasticsearch
resources:
requests:
cpu: "2"
cpu: 500m
memory: 12Gi
limits:
cpu: "4"
memory: 12Gi
volumeClaimTemplates:
- metadata:
@@ -119,7 +118,7 @@ spec:
- name: kibana
resources:
requests:
cpu: 500m
cpu: 100m
memory: 4Gi
limits:
cpu: "2"
+4
View File
@@ -82,3 +82,7 @@ grafana.ini:
security:
allow_embedding: true
x_frame_options: ""
resources:
requests:
cpu: 75m
+4
View File
@@ -20,3 +20,7 @@ ingress:
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: default-authentik@kubernetescrd
resources:
requests:
cpu: 175m
+2 -2
View File
@@ -15,10 +15,10 @@ env:
value: Europe/Vienna
resources:
requests:
cpu: 500m
cpu: 100m
memory: 2Gi
limits:
cpu: "2"
cpu:
memory: 16Gi
ingress:
enabled: true
+3 -3
View File
@@ -49,7 +49,7 @@ mastodon:
replicas: 1
resources:
requests:
cpu: 250m
cpu: 50m
memory: 768Mi
limits:
cpu: "2"
@@ -70,7 +70,7 @@ mastodon:
- fasp
resources:
requests:
cpu: 250m
cpu: 50m
memory: 768Mi
limits:
cpu: "2"
@@ -83,7 +83,7 @@ mastodon:
workers: 1
resources:
requests:
cpu: 100m
cpu: 25m
memory: 256Mi
limits:
cpu: "1"
+5
View File
@@ -4,3 +4,8 @@ serviceMonitor:
enabled: true
labels:
release: prometheus
resources:
requests:
cpu: 175m
limits:
cpu:
+1 -1
View File
@@ -44,5 +44,5 @@ affinity:
values: [arschrock]
resources:
requests:
cpu: 100m
cpu: 50m
memory: 1Gi
+1 -1
View File
@@ -7,7 +7,7 @@ ingress:
traefik.ingress.kubernetes.io/router.middlewares: default-authentik@kubernetescrd
resources:
requests:
cpu: 100m
cpu: 35m
memory: 512Mi
limits:
memory: 2Gi
+3
View File
@@ -77,6 +77,9 @@ prometheus:
kubernetes.io/ingress.class: "traefik"
traefik.ingress.kubernetes.io/router.entrypoints: websecure
prometheusSpec:
resources:
requests:
cpu: 200m
retention: "15d"
storageSpec:
volumeClaimTemplate:
+1 -1
View File
@@ -13,5 +13,5 @@ ingress:
traefik.ingress.kubernetes.io/router.middlewares: default-authentik@kubernetescrd
resources:
requests:
cpu: 100m
cpu: 25m
memory: 256Mi
+1 -1
View File
@@ -11,7 +11,7 @@ podDisruptionBudget:
minAvailable: 1
resources:
requests:
cpu: 500m
cpu: 700m
memory: 1Gi
affinity:
podAntiAffinity:
+1 -1
View File
@@ -20,7 +20,7 @@ readinessProbe:
timeoutSeconds: 5
resources:
requests:
cpu: 100m
cpu: 75m
memory: 256Mi
ephemeral-storage: 128Mi
limits: