Install¶
The provider is installed with the Helm chart in charts/bnerd-database-provider, from the
repository checked out at a release tag. Landscapes consume tagged versions only.
Prerequisites¶
- Kubernetes 1.30 or later (the chart uses
admissionregistration.k8s.io/v1ValidatingAdmissionPolicies and the kubeletsleeppreStop action; on older clusters setdeletionProtection.enabled=falseandpreStopSleepSeconds=0). - A Kubernetes cluster that can reach the shards' primary Services (the provider runs DDL there, never through pgBouncer).
- A shard admin Secret per shard (see Add a shard).
- A TLS certificate for the API (below). It is required: the chart refuses to render without
tls.secretName, and the provider exits at start without a certificate. The API never serves plain HTTP. - The Prometheus Operator CRDs only if you enable
metrics.serviceMonitororprometheusRule.
1. Create the serving certificate¶
The certificate is self-issued: one CA per DB cluster, which consumers pin. The server certificate's
SAN must cover the private load-balancer IP consumers use and the in-cluster DNS name of the
provider Service. The Service is named <release>-bnerd-database-provider (or just <release> if the
release name already contains bnerd-database-provider); check with kubectl -n <namespace> get svc
after the first install, or render it first with helm template.
# CA (keep ca.key offline; consumers only need ca.crt)
openssl req -x509 -newkey rsa:4096 -nodes -keyout ca.key -out ca.crt \
-days 3650 -subj "/CN=database-provider CA"
# server key + CSR
openssl req -newkey rsa:2048 -nodes -keyout tls.key -out server.csr \
-subj "/CN=database-provider"
# sign: SAN = LB IP + in-cluster DNS name
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out tls.crt -days 825 -extfile <(printf '%s\n' \
'subjectAltName=IP:<LB-IP>,DNS:<service>.<namespace>.svc' \
'extendedKeyUsage=serverAuth' 'basicConstraints=CA:FALSE')
# check
openssl x509 -in tls.crt -noout -ext subjectAltName
openssl verify -CAfile ca.crt tls.crt
Create the Secret in the release namespace (in a GitOps landscape: as a SOPS-encrypted manifest of the
same shape). It must be a kubernetes.io/tls style Secret with the keys tls.crt and tls.key:
Hand ca.crt to each consumer (it is public). Never put ca.key or tls.key into chat, logs or a
shared channel.
The provider re-reads the certificate files when they change (checked on TLS handshakes, at most every 10 seconds, after the kubelet has synced the updated Secret into the pod); a renewal needs no restart. A reload that fails keeps serving the last good certificate and is logged.
2. Apply the CRDs¶
The CRDs are in the chart's crds/ directory. Helm installs them on helm install only and never
upgrades them. Apply them explicitly, before the first install and on every upgrade, before the new
image rolls out:
(With Flux: ship the crds/ directory as its own Kustomization that the HelmRelease depends on.)
Skipping this on an upgrade leaves the API server pruning new fields the new image writes.
3. Install the chart¶
helm upgrade --install db-provider charts/bnerd-database-provider \
--namespace <namespace> --create-namespace \
--set tls.secretName=<tls-secret-name> \
--set 'shardSecrets[0].namespace=<namespace-of-shard-admin-secrets>' \
--set 'shardSecrets[0].names={<admin-secret-of-shard01>,<admin-secret-of-shard02>}'
Values you will usually set (defaults in values.yaml):
| Value | Default | Meaning |
|---|---|---|
tls.secretName |
(none, required) | Secret with tls.crt / tls.key |
replicas |
2 |
API is served by every replica; reconciles run on the leader only |
leaderElection.enabled |
true |
must stay true when replicas > 1; the chart fails to render otherwise |
deletion.gracePeriod |
168h |
soft-delete grace period before DROP; below 24h the provider refuses to start unless deletion.allowShortGracePeriod: true |
connectionLimit.default |
20 |
role connection limit when the request has no sizeClass |
sizeClasses |
small: 20, medium: 50 |
sizeClass to connection limit per role |
shardSecrets |
[{namespace: databases, names: [pg-shard01-pguser-provisioner]}] |
shard admin Secrets outside the release namespace; the provider gets get on exactly these names (no list/watch) |
deletionProtection.enabled, .breakGlassGroups |
true, [bnerd:db-provider-break-glass] |
admission policy refusing DELETE on Databases (and on Shards that still hold databases); see hold versus soft delete |
networkPolicy.enabled |
true |
NetworkPolicy, see below |
podDisruptionBudget.enabled |
true |
minAvailable: 1 (only with replicas > 1); replicas spread across nodes by a preferred anti-affinity unless affinity is set |
terminationGracePeriodSeconds, preStopSleepSeconds |
30, 5 |
graceful shutdown |
logLevel |
info |
info or debug |
listen.port |
8443 |
TLS API port |
metrics.port / health.port |
8080 / 8081 |
plain-HTTP metrics and probes |
metrics.serviceMonitor.enabled |
false |
render a ServiceMonitor |
prometheusRule.enabled, .cluster, .jobSelector |
off | see Monitoring |
The chart sets the environment variable PROVIDER_NAMESPACE from the pod's namespace. It is the
release namespace that scopes everything the provider manages: Database objects, their
credentials Secrets, consumer token Secrets, and the Secrets cache. The provider exits at start if it is
unset. PROVIDER_CONFIG (mounted ConfigMap) and PROVIDER_TLS_DIR are set by the chart.
RBAC is namespaced: Secrets are accessible in the release namespace (credentials and consumer token
Secrets, cached there) and, outside it, only get on the admin Secrets named in shardSecrets. Those are
read uncached by name; the provider never lists or watches another namespace's Secrets (the Percona namespace
also holds replication certificates, backup keys and superuser credentials). There is no cluster-wide Secret
access.
Every Consumer needs a maxDatabases quota (why); there is no
default.
Network policy¶
With networkPolicy.enabled (default) the provider pods accept only:
- the API port from
networkPolicy.apiFrom(default192.168.0.0/16, the private network the consumers and the private DB load balancer use). Check the source address the pods actually see: if the load balancer or kube-proxy SNATs to node addresses (externalTrafficPolicy: Cluster), add the node CIDR, or/readyzand the API time out from outside; - the metrics port from
networkPolicy.metricsFrom(default: any pod in namespacemonitoring);
and may only reach DNS (port 53), the kube-apiserver (networkPolicy.apiServerCIDRs, ports 443/6443; tighten to
the API server address where known) and the shards' admin Services in networkPolicy.shardNamespaces on
networkPolicy.shardPorts (default databases, 5432). Kubelet health probes come from the node and are
allowed by common CNIs (Calico, Cilium) without a rule; otherwise list the node CIDR in
networkPolicy.healthFrom.
4. Verify¶
kubectl -n <namespace> rollout status deploy/<release>-bnerd-database-provider
kubectl -n <namespace> get pods
# from a host that can reach the LB / Service, with the CA you generated
curl --cacert ca.crt https://<LB-IP>:8443/readyz
/healthz and /readyz need no token and answer {"status":"ok"}; every response carries
X-Provider-Version. Then create Shard and Consumer objects
(next page).
Upgrading¶
kubectl apply -f charts/bnerd-database-provider/crds/(see above; not done by Helm).helm upgradewith the new chart. ReadCHANGES.mdfor the target version first.- Check
/readyzand that allShardobjects areReady.