GKE: values for the remaining homelab tools

Ports the rest of the homelab's stack: external-secrets, harbor, jenkins,
postgresql, redis, victoria-metrics-single, vmagent, node-exporter and
grafana. All nine verified with helm template.

Most are the homelab's values with storage moved from local-path to
standard-rwo and hostnames rebuilt on the reserved ingress IP. vmagent and
node-exporter are unchanged outright — everything in them is addressed by
cluster DNS, namespace or container port, none of which differs here.

Harbor is the substantial one. The homelab serves it over plain HTTP and
makes containerd accept that by hand-editing hosts.toml on the node; GKE
nodes are managed and replaced, so that edit cannot survive. Instead the
node pool was told at creation to trust a private CA for exactly this
hostname, and cert-manager now signs Harbor's certificate from that same
CA via an ingress-shim annotation. certSource is "secret" rather than the
chart's "auto", which would self-sign a certificate nothing trusts.
externalURL moves to https to match, since Harbor hands that URL to docker
clients and a mismatch surfaces as registry errors.

Jenkins drops secondaryingress, which exists in the homelab only to serve
its Tailscale hostname. Its plugin pins are carried over deliberately: each
fixes a failure whose symptom points somewhere else, above all the
kubernetes/kubernetes-client-api pairing, without which agents never come
online and builds hang at "Still waiting to schedule task".

Postgres and Redis keep the homelab's deliberately small memory settings.
Those were chosen for an 8GB node under pressure, and while this cluster
has room, a bigger cache buys nothing for a handful of small tools.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEsTefWWifp4ikvhHF5s6N
This commit is contained in:
Mukul Sharma
2026-09-12 15:01:49 +05:30
co-authored by Claude Opus 5
parent 63c243ce51
commit f83cedca65
9 changed files with 732 additions and 0 deletions
@@ -0,0 +1,42 @@
external-secrets:
# Same as the homelab's, which carries nothing cluster-specific: the
# controller is configured entirely by the ClusterSecretStore and
# ExternalSecret objects in devops-infra-argo-config-gcp, not by values.
#
# This is the piece every credential in the cluster hangs off — Harbor,
# Jenkins, Grafana and the pipeline all read their secrets from Vault
# through it, so it comes up before any of them.
#
# Two things must exist in Vault before the first ExternalSecret can sync,
# and neither is declarative: the KV v2 engine at secret/, and the
# Kubernetes auth method with a role bound to this controller's service
# account. Until then ExternalSecrets stay in a retry loop rather than
# failing outright.
#
# installCRDs defaults to true — kept, as on a fresh cluster there are no
# existing SecretStore/ExternalSecret CRs whose schema it could clobber.
#
# All three components default to unbounded resources; trimmed here for
# the same reason as everything else in this repo.
resources:
requests:
cpu: 25m
memory: 32Mi
limits:
memory: 128Mi
webhook:
resources:
requests:
cpu: 25m
memory: 32Mi
limits:
memory: 64Mi
certController:
resources:
requests:
cpu: 25m
memory: 32Mi
limits:
memory: 64Mi
@@ -0,0 +1,272 @@
grafana:
# From Vault via ExternalSecret (devops-infra-argo-config/secretstores/
# grafana-admin-credentials.yaml), same pattern as gitea/harbor/jenkins
# admin credentials elsewhere in this project — never a plaintext
# adminPassword in this file.
admin:
existingSecret: grafana-admin-credentials
userKey: username
passwordKey: password
persistence:
# 1Gi, not the chart's 10Gi default: this holds dashboards, folders and
# Grafana's own sqlite state, not metric data — VictoriaMetrics keeps
# that. Unlike the homelab's local-path, standard-rwo can be expanded
# later if that ever proves tight.
enabled: true
storageClassName: standard-rwo
size: 1Gi
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 384Mi
# Provisioned at boot, not clicked through in the UI — the same reason
# every other credential/config in this project is committed rather
# than set by hand: it survives a pod restart and a fresh install gets
# it automatically. VictoriaMetrics speaks Prometheus's own query API,
# so `type: prometheus` here is correct even though the URL is VM's —
# see this repo's victoria-metrics-single chart for why.
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: VictoriaMetrics
# Fixed uid, not left to auto-generate — the provisioned
# dashboard below references this datasource by uid, and an
# auto-generated one would only exist after Grafana's first
# boot, too late for a dashboard provisioned in the same boot.
uid: victoriametrics
type: prometheus
access: proxy
url: http://victoria-metrics-single-server.monitoring.svc.cluster.local:8428
isDefault: true
# Dashboard provisioning. Provisioned rather than built by hand in the
# UI for the same reason the datasource above is: it survives a pod
# restart (this deployment has no persistent Grafana database beyond
# the 1Gi PVC, and even with one, a fresh install should not start
# with an empty dashboard list) and a `git diff` shows what changed.
dashboardProviders:
dashboardproviders.yaml:
apiVersion: 1
providers:
- name: default
orgId: 1
folder: ""
type: file
disableDeletion: false
editable: true
options:
path: /var/lib/grafana/dashboards/default
# "Homelab Overview" — total Envoy/Contour RPS, per-namespace CPU and
# memory (the $namespace template variable filters every relevant
# panel), cluster-wide utilization against actual node capacity, and a
# total-resources row (cores/memory/pods/disk). Envoy and node-exporter
# metrics both required their own vmagent scrape job — see
# helm-overrides/.../vmagent/custom-values.yaml for why neither was
# reachable through the chart's own defaults in this cluster.
#
# Every panel except "Disk free" was run against the live deployment
# (vmui, over Tailscale) before being written in here — RPS, per-
# namespace CPU/memory, machine_cpu_cores/machine_memory_bytes all
# returned real data. "Disk free" depends on the node-exporter scrape
# job added alongside this same change, which had not been live yet to
# verify against — worth checking once this actually deploys, same as
# everything else in this repo that gets a `helm template` check but
# cannot get a live one before the first sync.
dashboards:
default:
homelab:
json: |
{
"title": "Homelab Overview",
"uid": "homelab-overview",
"schemaVersion": 39,
"editable": true,
"timezone": "browser",
"time": { "from": "now-1h", "to": "now" },
"refresh": "30s",
"templating": {
"list": [
{
"name": "namespace",
"type": "query",
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"query": "label_values(container_memory_working_set_bytes{container!=\"\", container!=\"POD\"}, namespace)",
"refresh": 2,
"multi": true,
"includeAll": true,
"current": { "selected": true, "text": "All", "value": "$__all" }
}
]
},
"panels": [
{ "type": "row", "title": "Ingress (Envoy / Contour)", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 100 },
{
"type": "stat", "title": "Total RPS", "id": 1,
"gridPos": { "h": 6, "w": 6, "x": 0, "y": 1 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [{ "expr": "sum(rate(envoy_http_downstream_rq_total{namespace=\"projectcontour\"}[5m]))", "legendFormat": "rps" }],
"fieldConfig": { "defaults": { "unit": "reqps", "decimals": 2 }, "overrides": [] },
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "graphMode": "area" }
},
{
"type": "stat", "title": "Active downstream connections", "id": 2,
"gridPos": { "h": 6, "w": 6, "x": 6, "y": 1 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [{ "expr": "sum(envoy_http_downstream_cx_active{namespace=\"projectcontour\"})", "legendFormat": "connections" }],
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] }
},
{
"type": "timeseries", "title": "Requests by response class", "id": 3,
"gridPos": { "h": 6, "w": 12, "x": 12, "y": 1 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [{
"expr": "sum by (envoy_response_code_class) (rate(envoy_http_downstream_rq_xx{namespace=\"projectcontour\"}[5m]))",
"legendFormat": "{{envoy_response_code_class}}xx"
}],
"fieldConfig": { "defaults": { "unit": "reqps" }, "overrides": [] },
"options": { "legend": { "displayMode": "list", "placement": "bottom" } }
},
{ "type": "row", "title": "Service level (by namespace)", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 7 }, "id": 101 },
{
"type": "timeseries", "title": "CPU usage by namespace", "id": 10,
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [{
"expr": "sum by (namespace) (rate(container_cpu_usage_seconds_total{namespace=~\"$namespace\", container!=\"\", container!=\"POD\"}[5m]))",
"legendFormat": "{{namespace}}"
}],
"fieldConfig": { "defaults": { "unit": "short", "custom": { "fillOpacity": 10, "stacking": { "mode": "normal" } } }, "overrides": [] },
"options": { "legend": { "displayMode": "table", "placement": "right", "calcs": ["mean", "max"] } }
},
{
"type": "timeseries", "title": "Memory usage by namespace", "id": 11,
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [{
"expr": "sum by (namespace) (container_memory_working_set_bytes{namespace=~\"$namespace\", container!=\"\", container!=\"POD\"})",
"legendFormat": "{{namespace}}"
}],
"fieldConfig": { "defaults": { "unit": "bytes", "custom": { "fillOpacity": 10, "stacking": { "mode": "normal" } } }, "overrides": [] },
"options": { "legend": { "displayMode": "table", "placement": "right", "calcs": ["mean", "max"] } }
},
{
"type": "table", "title": "Current usage per namespace", "id": 12,
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 16 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [
{ "expr": "sum by (namespace) (rate(container_cpu_usage_seconds_total{namespace=~\"$namespace\", container!=\"\", container!=\"POD\"}[5m]))", "format": "table", "instant": true, "refId": "A" },
{ "expr": "sum by (namespace) (container_memory_working_set_bytes{namespace=~\"$namespace\", container!=\"\", container!=\"POD\"})", "format": "table", "instant": true, "refId": "B" },
{ "expr": "count by (namespace) (count by (namespace, pod) (container_memory_working_set_bytes{namespace=~\"$namespace\", container!=\"\", container!=\"POD\"}))", "format": "table", "instant": true, "refId": "C" }
],
"transformations": [
{ "id": "merge", "options": {} },
{ "id": "organize", "options": {
"excludeByName": { "Time": true, "Time 1": true, "Time 2": true, "Time 3": true },
"renameByName": { "Value #A": "CPU (cores)", "Value #B": "Memory", "Value #C": "Pods" }
} }
],
"fieldConfig": { "defaults": {}, "overrides": [
{ "matcher": { "id": "byName", "options": "Memory" }, "properties": [{ "id": "unit", "value": "bytes" }] },
{ "matcher": { "id": "byName", "options": "CPU (cores)" }, "properties": [{ "id": "unit", "value": "short" }, { "id": "decimals", "value": 3 }] }
] }
},
{ "type": "row", "title": "Cluster utilization", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 24 }, "id": 102 },
{
"type": "gauge", "title": "CPU utilization", "id": 20,
"gridPos": { "h": 7, "w": 6, "x": 0, "y": 25 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [{ "expr": "100 * sum(rate(container_cpu_usage_seconds_total{container!=\"\", container!=\"POD\"}[5m])) / sum(machine_cpu_cores)" }],
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100,
"thresholds": { "mode": "absolute", "steps": [
{ "color": "green", "value": null }, { "color": "yellow", "value": 70 }, { "color": "red", "value": 90 }
] } }, "overrides": [] }
},
{
"type": "gauge", "title": "Memory utilization", "id": 21,
"gridPos": { "h": 7, "w": 6, "x": 6, "y": 25 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [{ "expr": "100 * sum(container_memory_working_set_bytes{container!=\"\", container!=\"POD\"}) / sum(machine_memory_bytes)" }],
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100,
"thresholds": { "mode": "absolute", "steps": [
{ "color": "green", "value": null }, { "color": "yellow", "value": 70 }, { "color": "red", "value": 90 }
] } }, "overrides": [] }
},
{
"type": "timeseries", "title": "Cluster CPU utilization over time", "id": 22,
"gridPos": { "h": 7, "w": 12, "x": 12, "y": 25 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [
{ "expr": "100 * sum(rate(container_cpu_usage_seconds_total{container!=\"\", container!=\"POD\"}[5m])) / sum(machine_cpu_cores)", "legendFormat": "CPU %" },
{ "expr": "100 * sum(container_memory_working_set_bytes{container!=\"\", container!=\"POD\"}) / sum(machine_memory_bytes)", "legendFormat": "Memory %" }
],
"fieldConfig": { "defaults": { "unit": "percent", "min": 0 }, "overrides": [] }
},
{ "type": "row", "title": "Total resources", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 32 }, "id": 103 },
{
"type": "stat", "title": "Node CPU capacity", "id": 30,
"gridPos": { "h": 5, "w": 4, "x": 0, "y": 33 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [{ "expr": "max(machine_cpu_cores)" }],
"fieldConfig": { "defaults": { "unit": "short", "displayName": "cores" }, "overrides": [] }
},
{
"type": "stat", "title": "Node memory capacity", "id": 31,
"gridPos": { "h": 5, "w": 4, "x": 4, "y": 33 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [{ "expr": "max(machine_memory_bytes)" }],
"fieldConfig": { "defaults": { "unit": "bytes" }, "overrides": [] }
},
{
"type": "stat", "title": "CPU used (cluster)", "id": 32,
"gridPos": { "h": 5, "w": 4, "x": 8, "y": 33 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [{ "expr": "sum(rate(container_cpu_usage_seconds_total{container!=\"\", container!=\"POD\"}[5m]))" }],
"fieldConfig": { "defaults": { "unit": "short", "displayName": "cores", "decimals": 2 }, "overrides": [] }
},
{
"type": "stat", "title": "Memory used (cluster)", "id": 33,
"gridPos": { "h": 5, "w": 4, "x": 12, "y": 33 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [{ "expr": "sum(container_memory_working_set_bytes{container!=\"\", container!=\"POD\"})" }],
"fieldConfig": { "defaults": { "unit": "bytes" }, "overrides": [] }
},
{
"type": "stat", "title": "Running pods", "id": 34,
"gridPos": { "h": 5, "w": 4, "x": 16, "y": 33 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [{ "expr": "count(count by (namespace, pod) (container_memory_working_set_bytes{container!=\"\", container!=\"POD\"}))" }],
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] }
},
{
"type": "stat", "title": "Disk free (root)", "id": 35,
"gridPos": { "h": 5, "w": 4, "x": 20, "y": 33 },
"datasource": { "type": "prometheus", "uid": "victoriametrics" },
"targets": [{ "expr": "node_filesystem_avail_bytes{mountpoint=\"/\"}" }],
"fieldConfig": { "defaults": { "unit": "bytes",
"thresholds": { "mode": "absolute", "steps": [
{ "color": "red", "value": null }, { "color": "yellow", "value": 5000000000 }, { "color": "green", "value": 15000000000 }
] } }, "overrides": [] }
}
]
}
ingress:
enabled: true
ingressClassName: contour
path: /
hosts:
- grafana.35.238.248.203.nip.io
@@ -0,0 +1,132 @@
harbor:
# GKE counterpart of helm-overrides/k8s-admin-prd-ase1/harbor, same chart
# (1.19.1), with one fundamental difference: this Harbor is served over
# real TLS, and it has to be.
#
# The homelab runs Harbor on plain HTTP and works around containerd's
# refusal to pull from an insecure registry by hand-editing
# /etc/containerd/certs.d/<host>/hosts.toml on the node. GKE nodes are
# managed and replaced, so that edit cannot survive. Instead the node pool
# is told, at creation, to trust a private CA for exactly this hostname,
# fetching it from Secret Manager (see toolshed-gke-infra's gke-cluster
# module). cert-manager signs Harbor's certificate from that same CA.
#
# Let's Encrypt is not an option: nip.io is not on the public suffix list,
# so every *.nip.io certificate on the internet shares one rate limit.
# A private CA is fine for image pulls, which is what matters, but
# browsers will warn on the Harbor UI. That is expected, not a fault.
expose:
type: ingress
tls:
enabled: true
# secret, not the chart's "auto": auto generates its own self-signed
# certificate, which the nodes have no reason to trust. This one is
# signed by the CA they were told to trust.
certSource: secret
secret:
secretName: harbor-tls
ingress:
hosts:
core: "harbor.35.238.248.203.nip.io"
className: contour
annotations:
# cert-manager's ingress-shim watches for this and creates the
# Certificate itself, writing the result into the secret named
# above. Nothing here ever touches a Certificate resource directly.
# The issuer is defined in devops-infra-argo-config-gcp's
# extra-manifests/registry-ca-clusterissuer.yaml.
cert-manager.io/cluster-issuer: registry-ca-issuer
# https, matching the ingress above. Harbor hands this URL to docker
# clients in its own API responses, so a mismatch here breaks pushes in
# ways that look like registry errors rather than configuration.
externalURL: "https://harbor.35.238.248.203.nip.io"
# From Vault through External Secrets — see
# secretstores/harbor-admin-credentials.yaml and Vault path
# secret/harbor/admin.
existingSecretAdminPassword: harbor-admin-credentials
existingSecretAdminPasswordKey: HARBOR_ADMIN_PASSWORD
# The one genuinely optional component. Harbor's database and redis are
# its own required internal state, not add-ons.
trivy:
enabled: false
persistence:
enabled: true
# Keeps the PVCs if the release is ever uninstalled: these hold the
# actual images.
resourcePolicy: "keep"
persistentVolumeClaim:
registry:
storageClass: standard-rwo
size: 5Gi
jobservice:
jobLog:
storageClass: standard-rwo
size: 1Gi
database:
storageClass: standard-rwo
size: 1Gi
redis:
storageClass: standard-rwo
size: 1Gi
portal:
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 256Mi
core:
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 512Mi
jobservice:
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 256Mi
registry:
registry:
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
controller:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 128Mi
database:
internal:
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 512Mi
redis:
internal:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 128Mi
@@ -0,0 +1,78 @@
jenkins:
# GKE counterpart of helm-overrides/k8s-admin-prd-ase1/jenkins, same chart
# (5.8.58). Dynamic Kubernetes build agents, so idle cost is the
# controller alone.
#
# Every plugin pin below is carried over deliberately, not copied
# blindly — each one fixes a failure that is not obvious from its symptom.
# See the homelab file for the full history.
controller:
image:
# The chart's own default plugin list requires core >= 2.504.3, while
# its default image tag is 2.504.2. An upstream inconsistency in the
# chart, not our configuration: bumping core is the fix, since the
# plugins involved (kubernetes above all) are what dynamic agents
# depend on.
tag: "2.504.3-jdk21"
# Helm replaces lists wholesale rather than merging, so this is the
# chart's full default list with one version corrected, plus two
# additions — not a hand-picked subset.
installPlugins:
# Pinned as a pair. kubernetes needs kubernetes-client-api >=
# 7.3.1-256.v788a_0b_787114; left unpinned it resolves to an older
# version at image-build time and every agent launch dies with
# NoSuchMethodError while constructing the client. The pods start
# fine, so it presents as builds hanging forever at "Still waiting to
# schedule task" rather than as a plugin problem.
- kubernetes:4437.v3a_18554d3f32
- kubernetes-client-api:7.3.1-256.v788a_0b_787114
- workflow-aggregator:608.v67378e9d3db_1
- git:5.7.0
# kubernetes/git/credentials need this version, though the chart's
# own default list pins an older one. Same class of upstream
# inconsistency as the image tag.
- configuration-as-code:2006.v001a_2ca_6b_574
# Not in the chart's default list at all — provides readYaml, which
# the shared library's loadConfig stage uses to parse each repo's
# config.yaml.
- pipeline-utility-steps:3.810.va_7672d206740
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
cpu: 500m
memory: 1Gi
admin:
# From Vault through External Secrets, never a plaintext password
# here. Requires secretstores/jenkins-admin-credentials.yaml to have
# synced, which in turn requires the Vault path secret/jenkins/admin.
existingSecret: jenkins-admin-credentials
userKey: jenkins-admin-user
passwordKey: jenkins-admin-password
ingress:
enabled: true
hostName: "jenkins.35.238.248.203.nip.io"
ingressClassName: contour
# The homelab also enables secondaryingress, purely to serve its
# Tailscale hostname — this chart's primary ingress supports only one
# host. There is one hostname here, so it stays off.
agent:
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
persistence:
enabled: true
storageClass: standard-rwo
size: 5Gi
@@ -0,0 +1,17 @@
prometheus-node-exporter:
# Unchanged from the homelab's — nothing here is cluster-specific. One
# DaemonSet pod per node on hostNetwork; three pods here rather than one.
#
# Host-level metrics are independent of which TSDB stores them, which is
# why this is its own release rather than a subchart of anything.
resources:
requests:
cpu: 20m
memory: 32Mi
limits:
memory: 64Mi
# The chart's Service carries prometheus.io/scrape by default. vmagent
# also targets these pods directly by container port, because that
# annotation-based path found nothing in the homelab — see the vmagent
# values for the detail.
@@ -0,0 +1,45 @@
# PostgreSQL for toolshed's control plane, on GKE.
#
# Its own namespace rather than toolshed's, so it is addressed over cluster
# DNS like any other platform component and outlives its first consumer:
#
# postgresql.postgres.svc.cluster.local:5432
#
# Hand-written chart, not Bitnami's: that registry has been unstable, and
# PostgreSQL ships no official chart.
#
# Credentials come from Vault through External Secrets — see
# secretstores/toolshed-postgres-credentials.yaml. The Secret must exist
# before this pod starts; without it the pod sits in
# CreateContainerConfigError, which does not explain itself.
fullnameOverride: postgresql
image:
repository: postgres
tag: "16-alpine"
pullPolicy: IfNotPresent
existingSecret: postgresql-credentials
database: toolshed
persistence:
enabled: true
storageClass: standard-rwo
size: 5Gi
config:
# Kept at the homelab's deliberately small values even though this
# cluster has room to spare: for a handful of control-plane tables it
# makes no measurable difference, and matching the homelab keeps one
# fewer variable between the two deployments. Raise it if a real query
# workload ever shows up here.
sharedBuffers: 32MB
maxConnections: "50"
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 256Mi
@@ -0,0 +1,45 @@
# Redis backing toolshed's managed cache add-on, on GKE.
#
# Its own namespace, same reasoning as postgresql:
#
# redis.redis.svc.cluster.local:6379
#
# Hand-written chart, not Bitnami's, for the same reason as postgresql.
#
# Read the chart's own values.yaml before changing anything about
# authentication. The absence of requirepass is deliberate and
# security-relevant, not an oversight: access is defined by an ACL file
# seeded from Vault through External Secrets
# (secretstores/toolshed-redis-credentials.yaml). The Secret must exist
# before this pod starts, or the init container cannot seed that file.
fullnameOverride: redis
image:
repository: redis
tag: "7-alpine"
pullPolicy: IfNotPresent
existingSecret: redis-credentials
persistence:
enabled: true
storageClass: standard-rwo
# Holds the ACL file and nothing else worth keeping, since snapshotting
# is off. 1Gi is already far more than needed.
size: 1Gi
config:
# Kept at the homelab's value. It was chosen there to fit an 8GB node
# under pressure, and while this cluster has room, a bigger cache buys
# nothing for a handful of small internal tools.
maxmemory: 48mb
maxmemoryPolicy: allkeys-lru
save: ""
resources:
requests:
cpu: 25m
memory: 32Mi
limits:
memory: 96Mi
@@ -0,0 +1,31 @@
victoria-metrics-single:
server:
# 7 days rather than the chart's month: this cluster exists to prove a
# pipeline, and every extra day is disk against the project's 250GB SSD
# quota, which every standard-rwo volume in the cluster shares.
retentionPeriod: "7d"
persistentVolume:
storageClassName: standard-rwo
# VictoriaMetrics' compression is why it replaced Prometheus here;
# this cluster's metric volume at 7 days fits well inside 3Gi. Unlike
# the homelab's local-path, this class can be expanded later.
size: 3Gi
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 512Mi
# vmui, VictoriaMetrics' built-in query UI, on the same pod and port —
# ad-hoc PromQL only, no saved dashboards; Grafana is the real UI. Free
# to expose, since it is not a separate component.
ingress:
enabled: true
ingressClassName: contour
hosts:
- name: vm.35.238.248.203.nip.io
path: ["/"]
port: http
@@ -0,0 +1,70 @@
victoria-metrics-agent:
# Unchanged from the homelab's, deliberately: everything here is
# addressed by cluster-internal Service DNS and namespace, none of which
# differs on GKE.
#
# The write path is what wires the two components together; the chart
# names do not imply it on their own.
remoteWrite:
- url: http://victoria-metrics-single-server.monitoring.svc.cluster.local:8428/api/v1/write
# config.scrape_configs stays at the chart's default, which already
# covers kubelet's cAdvisor endpoint and the prometheus.io/scrape
# annotation convention. extraScrapeConfigs is concatenated onto it
# rather than replacing it.
#
# Both jobs below exist because annotation-based discovery did not reach
# these targets in the homelab. Contour's Envoy carries no scrape
# annotation at all, and node-exporter's annotation sits on its Service,
# where the endpointslice discovery path found nothing. Targeting each by
# its fixed container port sidesteps both problems and is no less
# correct. Worth re-checking on this cluster rather than assuming the
# same gaps: if the defaults do find them here, these jobs are harmless
# duplicates, not errors.
extraScrapeConfigs:
- job_name: contour-envoy
kubernetes_sd_configs:
- role: pod
namespaces:
names: ["projectcontour"]
relabel_configs:
# 8002 is the official Contour chart's fixed metrics port for
# Envoy. /stats/prometheus is Envoy's own admin endpoint, not
# anything Contour-specific.
- action: keep
source_labels: [__meta_kubernetes_pod_container_port_number]
regex: "8002"
- target_label: __metrics_path__
replacement: /stats/prometheus
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_node_name]
target_label: node
- job_name: node-exporter
kubernetes_sd_configs:
- role: pod
namespaces:
names: ["monitoring"]
relabel_configs:
- action: keep
source_labels: [__meta_kubernetes_pod_container_port_number]
regex: "9100"
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_node_name]
target_label: node
resources:
requests:
cpu: 25m
memory: 64Mi
limits:
memory: 192Mi