pdbmux: deploy the merging PuppetDB proxy in-cluster (#275)

## Why

During the VM -> k8s Puppet migration, two PuppetDBs coexist and nodes move
between them as they migrate. `node-lookup` (and `pblastreport`) need a single,
consistent PuppetDB v4 view spanning both. `pdbmux` is a small merging proxy
that provides exactly that. Per the all-in-kubernetes estate direction it runs
as an in-cluster service, not a per-VM systemd unit.

pdbmux now lives in its own repository (https://git.unkin.net/unkin/pdbmux) —
split out of the earlier node-lookup prototype — and is released as a container
image on its own `v*` tags.

## Changes

- Add `apps/base/pdbmux/` (namespace, configmap, deployment, service, gateway,
  httproute), modeled directly on the encapi app.
- Deployment: 2 replicas, image `git.unkin.net/unkin/pdbmux:v0.1.0`, port 8080,
  `/healthz` liveness + readiness, config via `PDBMUX_*` env from a ConfigMap.
- Backends: `old=http://puppetdbapi.service.consul:8080`,
  `new=http://puppetdb.puppet.svc.cluster.local:8080` (in-cluster, verified
  against `apps/base/puppet/service_puppetdb.yaml` port `pdb-http`/8080 — the
  in-cluster address is preferred over the external gateway). `new` is
  primary/prefer, merge = freshness.
- Expose over HTTPS at `pdbmux.k8s.syd1.au.unkin.net` via a `traefik-internal`
  Gateway (cert-manager `vault-issuer`, external-dns), plain-HTTP backend on a
  port-80 Service — same shape as the puppetdb/encapi gateways — so
  VM/workstation `node-lookup` can reach it.
- Add `apps/overlays/au-syd1/pdbmux/` and wire pdbmux into the platform
  ApplicationSet (`apps/overlays/*/pdbmux`) and the platform AppProject
  (`pdbmux` namespace destination), exactly as encapi is wired.

No new woodpecker ServiceAccount is required: the pdbmux image push uses the
`docker-buildx` plugin against the Gitea registry with the `default` SA (same as
encapi), not artifactapi.

## Verification

- `kubectl kustomize apps/overlays/au-syd1/pdbmux` builds clean (image resolves
  to `git.unkin.net/unkin/pdbmux:v0.1.0`).
- ApplicationSet + AppProject YAML validated.

## Merge gates

1. The pdbmux repo initial-content PR
   (unkin/pdbmux#1) must merge first.
2. `v0.1.0` must then be tagged on the pdbmux repo so the image
   `git.unkin.net/unkin/pdbmux:v0.1.0` is built and pushed by that repo`s
   `.woodpecker/docker.yaml`.
3. Then merge this PR. (If the first release tag differs from `v0.1.0`, update
   the image tag in `apps/base/pdbmux/deployment.yaml` to match before merging.)

---------

Co-authored-by: benvin <neotheo@gmail.com>
Reviewed-on: #275
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
This commit was merged in pull request #275.
This commit is contained in:
2026-07-24 23:40:56 +10:00
committed by BenVincent
parent 4580a5f6a4
commit 6d7cdd59e3
10 changed files with 210 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: pdbmux-env
namespace: pdbmux
data:
PDBMUX_LISTEN: ":8080"
# Two PuppetDB backends merged during the VM -> k8s migration:
# old = legacy Consul-registered puppetdbapi (reachable from pods via the
# Consul DNS the puppet workloads already use)
# new = the in-cluster k8s PuppetDB (plain HTTP on 8080; in-cluster address
# is preferred over the external gateway to avoid a hairpin).
PDBMUX_BACKENDS: "old=http://puppetdbapi.service.consul:8080,new=http://puppetdb.puppet.svc.cluster.local:8080"
# "new" (the k8s PuppetDB) is the primary for non-merged pass-through and the
# preferred backend for ties / static-merge fallback.
PDBMUX_PRIMARY: "new"
PDBMUX_PREFER: "new"
PDBMUX_MERGE: "freshness"
+63
View File
@@ -0,0 +1,63 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: pdbmux
namespace: pdbmux
annotations:
reloader.stakater.com/auto: "true"
spec:
replicas: 2
selector:
matchLabels:
app: pdbmux
strategy:
rollingUpdate:
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
labels:
app: pdbmux
spec:
automountServiceAccountToken: false
containers:
- name: pdbmux
# Image is published by the pdbmux repo's .woodpecker/docker.yaml on
# a v* tag. It only exists after that tag is cut (see PR merge gates).
image: git.unkin.net/unkin/pdbmux:v0.1.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: http
protocol: TCP
envFrom:
# PDBMUX_LISTEN / PDBMUX_BACKENDS / PDBMUX_PRIMARY / PDBMUX_PREFER /
# PDBMUX_MERGE
- configMapRef:
name: pdbmux-env
optional: false
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 15
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 256Mi
restartPolicy: Always
+37
View File
@@ -0,0 +1,37 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
labels:
traefik.io/instance: internal
annotations:
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: pdbmux.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
external-dns.alpha.kubernetes.io/hostname: pdbmux.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.4
name: pdbmux
namespace: pdbmux
spec:
gatewayClassName: traefik-internal
listeners:
- allowedRoutes:
namespaces:
from: Same
hostname: pdbmux.k8s.syd1.au.unkin.net
name: http
port: 80
protocol: HTTP
- allowedRoutes:
namespaces:
from: Same
hostname: pdbmux.k8s.syd1.au.unkin.net
name: https
port: 443
protocol: HTTPS
tls:
certificateRefs:
- group: ""
kind: Secret
name: pdbmux-tls
mode: Terminate
+49
View File
@@ -0,0 +1,49 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: pdbmux-http-redirect
namespace: pdbmux
spec:
hostnames:
- pdbmux.k8s.syd1.au.unkin.net
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: pdbmux
sectionName: http
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: pdbmux
namespace: pdbmux
spec:
hostnames:
- pdbmux.k8s.syd1.au.unkin.net
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: pdbmux
sectionName: https
rules:
- backendRefs:
- group: ""
kind: Service
name: pdbmux
port: 80
weight: 1
matches:
- path:
type: PathPrefix
value: /
+11
View File
@@ -0,0 +1,11 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- configmap.yaml
- deployment.yaml
- service.yaml
- gateway.yaml
- httproute.yaml
+5
View File
@@ -0,0 +1,5 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: pdbmux
+17
View File
@@ -0,0 +1,17 @@
---
apiVersion: v1
kind: Service
metadata:
name: pdbmux
namespace: pdbmux
spec:
internalTrafficPolicy: Cluster
ports:
- name: http
port: 80
protocol: TCP
targetPort: http
selector:
app: pdbmux
sessionAffinity: None
type: ClusterIP
@@ -0,0 +1,6 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../../base/pdbmux
+1
View File
@@ -29,6 +29,7 @@ spec:
- path: apps/overlays/*/jfrog
- path: apps/overlays/*/kanidm
- path: apps/overlays/*/node-feature-discovery
- path: apps/overlays/*/pdbmux
- path: apps/overlays/*/priority-classes
- path: apps/overlays/*/puppet
- path: apps/overlays/*/purelb
+2
View File
@@ -39,6 +39,8 @@ spec:
server: https://kubernetes.default.svc
- namespace: 'node-feature-discovery'
server: https://kubernetes.default.svc
- namespace: 'pdbmux'
server: https://kubernetes.default.svc
- namespace: 'priority-classes'
server: https://kubernetes.default.svc
- namespace: 'purelb'