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:
co-authored by
Claude Opus 5
parent
63c243ce51
commit
f83cedca65
@@ -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
|
||||||
Reference in New Issue
Block a user