We run ProxySQL on Kubernetes in front of MySQL, across several clusters. PMM 3 already monitors the MySQL servers. This is how we added the ProxySQL layer to it, and what to watch out for.
The pod
Three containers, two shared volumes:
| Container | Does |
|---|---|
fetch-secrets (init) | Logs into Azure Key Vault, writes every secret to /secrets/env.secret, downloads a CA bundle |
proxysql-server | Sources env.secret, renders proxysql.cnf with envsubst, starts ProxySQL |
pmm-client (sidecar) | Starts pmm-agent, registers with the PMM server, adds ProxySQL on 127.0.0.1:6032 |
Key management
We keep no credentials in Kubernetes Secrets or in Git. Everything comes from Key Vault at pod start, through the init container, using the pod’s workload identity:
| |
Three rules that came out of it:
- The PMM token is a Grafana service-account token, and it has to be Admin. Registering a node and adding a service are inventory writes, and PMM’s role matrix reserves those for Admin; the client install docs say the same. There is no narrower role for agents. So the token is treated as what it is: one per PMM server, an expiry date, stored only in Key Vault, read only by the init container, and rotated by updating the vault and rolling the pods. The vault secret is named after the PMM server, not the environment, so a cluster can be pointed at a different backend config without also being handed the wrong PMM token.
- The ProxySQL side needs no stored secret at all. The stats password is generated per pod and rendered into the
config:
stats_credentials="pmm:${PROXYSQL_PMM_MONITOR_PWD};". It only grants read access to the stats schema on the admin port. - The CA bundle comes from the init container too. The
percona/pmm-client:3image we pulled had no CA trust store at all.
The sidecar
| |
Cleanup: don’t register by pod name
The first version used ${HOSTNAME}-proxysql, the pod name. Every rollout replaces every pod, every new pod is a
new node and a new service in PMM, and the old ones stay behind. After a few rollouts one cluster
had 3 pods and 17 ProxySQL services in the inventory, and every dashboard drop-down was full of series that
each covered one rollout window.
The fix is above: name the registration after the Kubernetes node the pod runs on, which survives the pod.
pmm-admin config --force with the same node name replaces the previous pod’s registration
(node, services, agents) instead of adding one — that is what the flag is for: “remove Node with that name with all
dependent Services and Agents if one exist”. Removing an inventory entry does not delete the time series already
stored, so history is kept and one continuous series per node shows up in the dashboards.
The namespace and service name are in the string because more than one ProxySQL flavour may run on the same node. If your node names carry a long generated pool suffix, shorten them in the script — the name only has to be stable.
This works because we schedule ProxySQL with node taints and tolerations so that exactly one ProxySQL pod lands
on each node — the node is therefore a stable identity. If your pods can share a node or move between nodes, the
node name is not enough. Either use a StatefulSet and its ordinal pod name (proxysql-0, proxysql-1, …) with the
same --force pattern, or keep pod-named registrations and add a preStop hook that runs
pmm-admin remove proxysql <name> so a pod cleans up after itself.
Old pod-named entries have to be removed once. The Inventory page does it (Nodes tab, tick them, Delete, Force mode), or the API:
| |
After that the only thing that leaves a ghost is a Kubernetes node being replaced — one entry, rarely.
What broke on the way
Four things, in the order they surfaced. Each one only became visible after the previous one was fixed.
pmm-admin confighas no--server-username/--server-password. Those flags belong topmm-agent setup(and itsPMM_AGENT_SERVER_USERNAME/PMM_AGENT_SERVER_PASSWORDvariables). Withpmm-admin configthe credentials go in the URL:https://service_token:$TOKEN@host.envsubstonly substitutes exported variables. The stats password was sourced but not exported, so the config rendered asstats_credentials="pmm:;"and the sidecar failed with what looked like a wrong password.- Registered ≠ monitored. The inventory was fine and the dashboards were empty. Metrics are shipped by
vmagent, which pmm-agent starts as a child process; in our pods it did not inheritSSL_CERT_FILE, and the stock image has no CA store, so every push failed TLS. We build our own image withca-certificateson top ofpercona/pmm-client:3. - Editing the base pod template is a production rollout when auto-sync is on. Ours rolled every production pod on merge. No user-facing errors — ProxySQL’s connection pool reconnected and 5xx stayed at baseline — but measure it, don’t assume it, and consider turning auto-sync off for this kind of app in production.