From c450ef40821d9fa9485f91bb11fd6a3cc8b1b71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Fri, 22 May 2026 13:51:18 +0200 Subject: [PATCH] PMM-7 Dev env for mysql backups. --- dev/mysql-backup-compose/Dockerfile | 27 +++++ dev/mysql-backup-compose/Makefile | 62 +++++++++++ dev/mysql-backup-compose/README.md | 66 ++++++++++++ dev/mysql-backup-compose/docker-compose.yml | 35 ++++++ dev/mysql-backup-compose/mysql-init.sql | 7 ++ dev/mysql-backup-compose/register-mysql.sh | 102 ++++++++++++++++++ dev/mysql-backup-compose/start-mysqld.sh | 31 ++++++ .../supervisord-mysql.ini | 28 +++++ 8 files changed, 358 insertions(+) create mode 100644 dev/mysql-backup-compose/Dockerfile create mode 100644 dev/mysql-backup-compose/Makefile create mode 100644 dev/mysql-backup-compose/README.md create mode 100644 dev/mysql-backup-compose/docker-compose.yml create mode 100644 dev/mysql-backup-compose/mysql-init.sql create mode 100644 dev/mysql-backup-compose/register-mysql.sh create mode 100644 dev/mysql-backup-compose/start-mysqld.sh create mode 100644 dev/mysql-backup-compose/supervisord-mysql.ini diff --git a/dev/mysql-backup-compose/Dockerfile b/dev/mysql-backup-compose/Dockerfile new file mode 100644 index 00000000000..1324234387b --- /dev/null +++ b/dev/mysql-backup-compose/Dockerfile @@ -0,0 +1,27 @@ +ARG PMM_SERVER_IMAGE=perconalab/pmm-server-fb:PR-4368-377dfd9 +FROM ${PMM_SERVER_IMAGE} + +USER root + +ARG MYSQL_SERVER_RPM_VERSION=8.4.6-6.1.el9 +ARG XTRABACKUP_RPM_VERSION=8.4.0-4.1.el9 + +RUN dnf install -y epel-release https://repo.percona.com/yum/percona-release-latest.noarch.rpm \ + && percona-release setup ps84lts \ + && percona-release enable pxb-84-lts release \ + && percona-release enable tools release \ + && dnf install -y "percona-server-server-${MYSQL_SERVER_RPM_VERSION}" "percona-xtrabackup-84-${XTRABACKUP_RPM_VERSION}" qpress \ + && dnf clean all \ + && rm -rf /var/cache/dnf + +COPY mysql-init.sql /opt/mysql-backup/mysql-init.sql +COPY start-mysqld.sh /usr/local/bin/start-mysqld.sh +COPY register-mysql.sh /usr/local/bin/register-mysql.sh +COPY supervisord-mysql.ini /etc/supervisord.d/mysql-backup.ini + +RUN chmod +x /usr/local/bin/start-mysqld.sh /usr/local/bin/register-mysql.sh \ + && install -d -m 0750 -o pmm -g root /srv/mysql-data /srv/mysql \ + && rm -rf /var/lib/mysql \ + && ln -s /srv/mysql-data /var/lib/mysql + +USER 1000 diff --git a/dev/mysql-backup-compose/Makefile b/dev/mysql-backup-compose/Makefile new file mode 100644 index 00000000000..17bdea85615 --- /dev/null +++ b/dev/mysql-backup-compose/Makefile @@ -0,0 +1,62 @@ +# PMM Server + MySQL backup local development environment. + +COMPOSE_FILE ?= docker-compose.yml +CONTAINER ?= pmm-backup-server +WAIT_TIMEOUT ?= 300 + +.PHONY: help env-up env-up-rebuild env-down env-wait env-status env-logs env-register + +help: ## Display this help message + @echo "Please use \`make \` where is one of:" + @grep '^[a-zA-Z]' $(MAKEFILE_LIST) | \ + awk -F ':.*?## ' 'NF==2 {printf " %-20s%s\n", $$1, $$2}' + +env-up: ## Build (if needed) and start PMM Server with MySQL + XtraBackup + docker compose -f $(COMPOSE_FILE) up -d --build --wait --wait-timeout $(WAIT_TIMEOUT) + @$(MAKE) env-wait + @$(MAKE) env-status + +env-up-rebuild: ## Rebuild the server image from scratch and start + docker compose -f $(COMPOSE_FILE) build --no-cache + docker compose -f $(COMPOSE_FILE) up -d --wait --wait-timeout $(WAIT_TIMEOUT) + @$(MAKE) env-wait + @$(MAKE) env-status + +env-down: ## Stop containers and remove all data volumes + docker compose -f $(COMPOSE_FILE) down -v --remove-orphans + @for volume in $$(docker volume ls -q --filter label=com.docker.compose.project=pmm-mysql-backup); do \ + docker volume rm $$volume; \ + done + +env-wait: ## Wait until mysql-backup is registered in PMM + @echo "Waiting for mysql-backup service registration..." + @for i in $$(seq 1 $(WAIT_TIMEOUT)); do \ + if docker exec $(CONTAINER) /usr/local/percona/pmm/bin/pmm-admin list \ + --server-url="https://$${PMM_ADMIN_USER:-admin}:$${PMM_ADMIN_PASSWORD:-admin}@127.0.0.1:8443/" \ + --server-insecure-tls 2>/dev/null | grep -q 'mysql-backup'; then \ + echo "mysql-backup is registered"; \ + exit 0; \ + fi; \ + sleep 1; \ + done; \ + echo "Timed out waiting for mysql-backup registration"; \ + $(MAKE) env-logs; \ + exit 1 + +env-status: ## Show mysqld, agent, and registered services + @echo "=== supervisord programs ===" + @docker exec $(CONTAINER) supervisorctl status mysqld-backup-dev mysql-backup-register pmm-agent 2>/dev/null || true + @echo "=== mysqld ===" + @docker exec $(CONTAINER) sh -c 'mysqladmin ping -h 127.0.0.1 -uroot -p"$${MYSQL_ROOT_PASSWORD:-secret}" --silent' 2>/dev/null \ + && echo "mysqld: ready" || echo "mysqld: not ready" + @echo "=== pmm-admin ===" + @docker exec $(CONTAINER) /usr/local/percona/pmm/bin/pmm-admin status 2>/dev/null | head -15 || true + @docker exec $(CONTAINER) /usr/local/percona/pmm/bin/pmm-admin list \ + --server-url="https://$${PMM_ADMIN_USER:-admin}:$${PMM_ADMIN_PASSWORD:-admin}@127.0.0.1:8443/" \ + --server-insecure-tls 2>/dev/null || true + +env-logs: ## Tail registration and mysqld logs + docker exec $(CONTAINER) sh -c 'tail -n 50 /srv/logs/mysql-backup-register.log /srv/logs/mysqld-backup-dev.log 2>/dev/null' || true + +env-register: ## Re-run MySQL service registration manually + docker exec $(CONTAINER) /usr/local/bin/register-mysql.sh diff --git a/dev/mysql-backup-compose/README.md b/dev/mysql-backup-compose/README.md new file mode 100644 index 00000000000..f5cc23dbe0f --- /dev/null +++ b/dev/mysql-backup-compose/README.md @@ -0,0 +1,66 @@ +# PMM MySQL Backup Compose + +Single-container local setup: PMM Server with its **builtin pmm-agent**, plus MySQL and backup tools on the same host. + +The custom server image extends `perconalab/pmm-server-fb:PR-4368-377dfd9` and adds: + +- Percona Server for MySQL `8.4.6-6` (`mysqld`) +- `percona-xtrabackup-84` version `8.4.0-4` (`xtrabackup`, `xbcloud`, `xbstream`) +- `qpress` + +PMM checks backup software on the node where the agent runs. The server image already runs a local `pmm-agent` (supervisord, id `pmm-server`), so MySQL is registered on `127.0.0.1:3306` inside the same container. + +## Start + +From this directory: + +```sh +make env-up +``` + +Or from the repository root: + +```sh +make -C dev/mysql-backup-compose env-up +``` + +The default tested versions are: + +- PMM Server base image: `perconalab/pmm-server-fb:PR-4368-377dfd9` +- MySQL RPM: `percona-server-server-8.4.6-6.1.el9` +- XtraBackup RPM: `percona-xtrabackup-84-8.4.0-4.1.el9` + +Override with `PMM_SERVER_IMAGE`, `MYSQL_SERVER_RPM_VERSION`, and `XTRABACKUP_RPM_VERSION` if needed. + +PMM is available at with `admin` / `admin`. + +After startup, the `mysql-backup-register` supervisord job registers the MySQL service as `mysql-backup`. Registration can take 1–2 minutes after PMM Server becomes healthy (waits for `pmm-agent` and `mysqld`). + +Check status: + +```sh +make env-status +``` + +If `mysql-backup` is missing, inspect logs and re-register: + +```sh +make env-logs +make env-register +``` + +## Run A Backup From UI + +Create an S3 backup location in the PMM UI using your own S3-compatible storage, then start a physical MySQL backup for the `mysql-backup` service. + +## Stop + +```sh +make env-down +``` + +## Notes + +- MySQL datadir: `/srv/mysql-data` (inside the `pmm-data` volume). +- This is a local development setup, not a production deployment. +- MySQL backup jobs use S3 storage. Configure your S3 location manually in PMM before starting a backup. diff --git a/dev/mysql-backup-compose/docker-compose.yml b/dev/mysql-backup-compose/docker-compose.yml new file mode 100644 index 00000000000..c92e3ef4ae8 --- /dev/null +++ b/dev/mysql-backup-compose/docker-compose.yml @@ -0,0 +1,35 @@ +--- +name: pmm-mysql-backup + +services: + pmm-server: + build: + context: . + dockerfile: Dockerfile + args: + PMM_SERVER_IMAGE: ${PMM_SERVER_IMAGE:-perconalab/pmm-server-fb:PR-4368-377dfd9} + MYSQL_SERVER_RPM_VERSION: ${MYSQL_SERVER_RPM_VERSION:-8.4.6-6.1.el9} + XTRABACKUP_RPM_VERSION: ${XTRABACKUP_RPM_VERSION:-8.4.0-4.1.el9} + image: pmm-mysql-backup-server:local + platform: linux/amd64 + container_name: pmm-backup-server + hostname: pmm-server + healthcheck: + test: ["CMD", "curl", "-k", "-f", "https://127.0.0.1:8443/ping"] + interval: 5s + timeout: 5s + retries: 30 + start_period: 120s + environment: + GF_SECURITY_ADMIN_USER: ${PMM_ADMIN_USER:-admin} + GF_SECURITY_ADMIN_PASSWORD: ${PMM_ADMIN_PASSWORD:-admin} + PMM_ENABLE_BACKUP_MANAGEMENT: "true" + PMM_PUBLIC_ADDRESS: localhost + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-secret} + ports: + - "${PMM_PORT_HTTPS:-443}:8443" + volumes: + - pmm-data:/srv + +volumes: + pmm-data: diff --git a/dev/mysql-backup-compose/mysql-init.sql b/dev/mysql-backup-compose/mysql-init.sql new file mode 100644 index 00000000000..07e91d43dcc --- /dev/null +++ b/dev/mysql-backup-compose/mysql-init.sql @@ -0,0 +1,7 @@ +CREATE USER IF NOT EXISTS 'pmm'@'%' IDENTIFIED BY 'pmm'; + +GRANT SELECT, PROCESS, RELOAD, LOCK TABLES, REPLICATION CLIENT, BACKUP_ADMIN ON *.* TO 'pmm'@'%'; +GRANT SELECT ON performance_schema.* TO 'pmm'@'%'; + +ALTER USER 'pmm'@'%' WITH MAX_USER_CONNECTIONS 10; +FLUSH PRIVILEGES; diff --git a/dev/mysql-backup-compose/register-mysql.sh b/dev/mysql-backup-compose/register-mysql.sh new file mode 100644 index 00000000000..4b2e5b2c4b2 --- /dev/null +++ b/dev/mysql-backup-compose/register-mysql.sh @@ -0,0 +1,102 @@ +#!/bin/bash +set -euo pipefail + +PMM_ADMIN=/usr/local/percona/pmm/bin/pmm-admin +PMM_ADMIN_USER="${PMM_ADMIN_USER:-admin}" +PMM_ADMIN_PASSWORD="${PMM_ADMIN_PASSWORD:-admin}" +PMM_SERVER_URL="https://${PMM_ADMIN_USER}:${PMM_ADMIN_PASSWORD}@127.0.0.1:8443/" +MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD:-secret}" +STATE_DIR=/srv/mysql +LOG_PREFIX="[mysql-backup-register]" + +log() { + echo "${LOG_PREFIX} $*" +} + +wait_for_pmm_server() { + log "waiting for PMM Server..." + until curl -skf https://127.0.0.1:8443/ping >/dev/null 2>&1; do + sleep 2 + done +} + +wait_for_pmm_agent() { + log "waiting for pmm-agent..." + local attempt=0 + until "${PMM_ADMIN}" status 2>/dev/null | grep -q 'Connected'; do + attempt=$((attempt + 1)) + if [ "${attempt}" -ge 120 ]; then + log "pmm-agent did not connect within 4 minutes" + "${PMM_ADMIN}" status || true + return 1 + fi + sleep 2 + done +} + +wait_for_mysqld() { + log "waiting for mysqld..." + local attempt=0 + until mysqladmin ping -h 127.0.0.1 -uroot --silent 2>/dev/null \ + || mysqladmin ping -h 127.0.0.1 -uroot -p"${MYSQL_ROOT_PASSWORD}" --silent 2>/dev/null; do + attempt=$((attempt + 1)) + if [ "${attempt}" -ge 120 ]; then + log "mysqld did not become ready within 4 minutes" + return 1 + fi + sleep 2 + done +} + +bootstrap_mysql_users() { + if [ -f "${STATE_DIR}/.pmm-backup-initialized" ] && [ ! -f "${STATE_DIR}/.pmm-backup-root-password-set" ]; then + log "setting MySQL root password" + mysql -h 127.0.0.1 -uroot <<-EOSQL + ALTER USER 'root'@'localhost' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}'; + FLUSH PRIVILEGES; + EOSQL + touch "${STATE_DIR}/.pmm-backup-root-password-set" + fi + + if [ -f "${STATE_DIR}/.pmm-backup-root-password-set" ] && [ ! -f "${STATE_DIR}/.pmm-backup-user-created" ]; then + log "creating pmm MySQL user" + mysql -h 127.0.0.1 -uroot -p"${MYSQL_ROOT_PASSWORD}" < /opt/mysql-backup/mysql-init.sql + touch "${STATE_DIR}/.pmm-backup-user-created" + fi +} + +register_mysql_service() { + if "${PMM_ADMIN}" list --server-url="${PMM_SERVER_URL}" --server-insecure-tls 2>/dev/null | grep -q 'mysql-backup'; then + log "mysql-backup service already registered" + return 0 + fi + + log "registering mysql-backup service" + local attempt=0 + while [ "${attempt}" -lt 30 ]; do + if "${PMM_ADMIN}" add mysql mysql-backup 127.0.0.1:3306 \ + --server-url="${PMM_SERVER_URL}" \ + --server-insecure-tls \ + --username=pmm \ + --password=pmm \ + --query-source=perfschema \ + --cluster=backup-dev \ + --environment=dev; then + log "mysql-backup registered successfully" + return 0 + fi + attempt=$((attempt + 1)) + log "pmm-admin add failed, retry ${attempt}/30" + sleep 5 + done + + log "failed to register mysql-backup" + "${PMM_ADMIN}" list --server-url="${PMM_SERVER_URL}" --server-insecure-tls || true + return 1 +} + +wait_for_pmm_server +wait_for_pmm_agent +wait_for_mysqld +bootstrap_mysql_users +register_mysql_service diff --git a/dev/mysql-backup-compose/start-mysqld.sh b/dev/mysql-backup-compose/start-mysqld.sh new file mode 100644 index 00000000000..963c2dc9645 --- /dev/null +++ b/dev/mysql-backup-compose/start-mysqld.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -euo pipefail + +DATADIR=/var/lib/mysql +SOCKET=/srv/mysql/mysql.sock +ERROR_LOG=/srv/logs/mysqld-backup-dev.err +PID_FILE=/srv/mysql/mysqld.pid +STATE_DIR=/srv/mysql +PORT=3306 + +install -d -m 0750 /srv/mysql-data "${STATE_DIR}" + +if [ ! -d "${DATADIR}/mysql" ]; then + rm -rf "${DATADIR:?}/"* "${DATADIR}"/.[!.]* "${DATADIR}"/..?* 2>/dev/null || true + mysqld --initialize-insecure \ + --datadir="${DATADIR}" \ + --log-error="${ERROR_LOG}" + touch "${STATE_DIR}/.pmm-backup-initialized" +fi + +exec mysqld \ + --datadir="${DATADIR}" \ + --socket="${SOCKET}" \ + --log-error="${ERROR_LOG}" \ + --pid-file="${PID_FILE}" \ + --port="${PORT}" \ + --bind-address=127.0.0.1 \ + --server-id=1 \ + --log-bin=mysql-bin \ + --binlog-format=ROW \ + --performance-schema=ON diff --git a/dev/mysql-backup-compose/supervisord-mysql.ini b/dev/mysql-backup-compose/supervisord-mysql.ini new file mode 100644 index 00000000000..8050feafcdd --- /dev/null +++ b/dev/mysql-backup-compose/supervisord-mysql.ini @@ -0,0 +1,28 @@ +[program:mysqld-backup-dev] +priority = 3 +command = /usr/local/bin/start-mysqld.sh +autorestart = true +autostart = true +startretries = 10 +startsecs = 5 +stopsignal = INT +stopwaitsecs = 60 +environment = MYSQL_ROOT_PASSWORD="%(ENV_MYSQL_ROOT_PASSWORD)s" +stdout_logfile = /srv/logs/mysqld-backup-dev.log +stdout_logfile_maxbytes = 30MB +stdout_logfile_backups = 2 +redirect_stderr = true + +[program:mysql-backup-register] +priority = 20 +command = /usr/local/bin/register-mysql.sh +autorestart = unexpected +autostart = true +startretries = 5 +startsecs = 0 +exitcodes = 0 +environment = MYSQL_ROOT_PASSWORD="%(ENV_MYSQL_ROOT_PASSWORD)s" +stdout_logfile = /srv/logs/mysql-backup-register.log +stdout_logfile_maxbytes = 10MB +stdout_logfile_backups = 2 +redirect_stderr = true