欢迎光临
我们一直在努力

用 Shell 脚本自动部署 mysqld_exporter 并接入 Prometheus 远程抓取

MySQL 实例一多,mysqld_exporter 的部署就成了体力活。下载二进制、建监控专用账号、写 .my.cnf、配 systemd、改 prometheus.yml、reload——一套流程走下来十几分钟,还容易在某一台上漏掉一步,结果就是 Grafana 上某个实例的曲线突然断了,回头再补。

这篇文章想解决的就是这个重复配置问题:写一个 Shell 脚本,在一台新的 MySQL 机器上执行一次,就把 exporter 装好、跑起来,并且让 Prometheus 能远程抓到它。下面讲清楚每一步为什么这么做,以及远程抓取和本机抓取的区别在哪。

先明确整体结构

在动手前,先把数据流画清楚,不然后面配参数容易乱。

+—————-+ +——————+ +————-+
| MySQL 实例 | <— | mysqld_exporter | <— | Prometheus |
| (3306) | 查询 | (9104) | 拉取 | (9090) |
+—————-+ +——————+ +————-+
^ ^
| |
监控专用账号 systemd 常驻
(PROCESS, REPLICATION) 监听 9104

关键点:exporter 和 Prometheus 不一定在同一台机器上。很多人第一次配的时候把两者装一起,直接用 localhost:9104 抓,一旦拆开就发现抓不到——原因通常是 exporter 默认只监听本地,或者防火墙没放行。脚本要把这个场景考虑进去。

环境与版本说明

我下面用的版本组合是:

组件版本说明
MySQL 8.0.x 5.7 也适用,账号语法略有差异
mysqld_exporter 0.15.x 官方 release 二进制
Prometheus 2.4x 支持 file_sd
系统 CentOS 7 / Rocky 9 systemd 管理

mysqld_exporter 的版本迭代不算快,但 0.15 之后对 MySQL 8 的默认认证方式支持更完整。如果你用的是更老的版本,遇到 caching_sha2_password 认证失败,多半是版本太旧,建议先升级 exporter 本身,而不是去改 MySQL 认证插件。

【注意】下面脚本里的下载地址、文件名我写的是当时实际可用的形式。官方 release 的命名规则偶尔会调整,你执行前最好先去 GitHub releases 页面确认一下当前文件名,这一点我没法替你保证长期有效。

第一步:MySQL 侧的准备

exporter 要采集信息,必须有一个能连上 MySQL 的账号。这个账号不需要写权限,但需要几个特定权限。

CREATE USER 'exporter'@'%' IDENTIFIED BY 'StrongPasswordHere' WITH MAX_USER_CONNECTIONS 3;
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'%';
FLUSH PRIVILEGES;

几个权限的作用:

  • PROCESS:读 SHOW PROCESSLIST、SHOW ENGINE INNODB STATUS 这类信息,没有它很多指标是空的。
  • REPLICATION CLIENT:读主从复制状态,用于 mysql_slave_status_* 系列指标。
  • SELECT:部分采集项需要查表。

WITH MAX_USER_CONNECTIONS 3 是我习惯加的限制。exporter 默认会开一个连接池,如果实例很多、又没限制,监控账号可能占掉不少连接数。给个上限更稳妥。

【踩坑提醒】'exporter'@'%' 里的 % 表示允许从任意主机连。如果你的 exporter 和 MySQL 在同一台机器,建议改成 'exporter'@'localhost',减少暴露面。反过来,如果 exporter 部署在独立机器上,那就必须用 % 或者具体 IP。

第二步:写部署脚本

第二步:写部署脚本

脚本我按"幂等"的思路写——重复执行不会出乱子。核心逻辑是:检查是否已安装,没有就下载;生成配置文件;写 systemd unit;启动。

#!/bin/bash
set -euo pipefail

# ===== 可配置项 =====
EXPORTER_VERSION="0.15.1"
EXPORTER_USER="mysqld_exporter"
INSTALL_DIR="/usr/local/bin"
CONFIG_DIR="/etc/mysqld_exporter"
LISTEN_ADDR="0.0.0.0:9104"

# MySQL 连接信息(生产环境建议从外部传入,不要硬编码)
MYSQL_HOST="${MYSQL_HOST:-127.0.0.1}"
MYSQL_PORT="${MYSQL_PORT:-3306}"
MYSQL_USER="${MYSQL_USER:-exporter}"
MYSQL_PASS="${MYSQL_PASS😕请通过环境变量 MYSQL_PASS 传入密码}"

ARCH="linux-amd64"
PKG="mysqld_exporter-${EXPORTER_VERSION}.${ARCH}"
URL="https://github.com/prometheus/mysqld_exporter/releases/download/v${EXPORTER_VERSION}/${PKG}.tar.gz"

# ===== 1. 创建专用系统用户(幂等)=====
if ! id "$EXPORTER_USER" &>/dev/null; then
useradd –no-create-home –shell /sbin/nologin "$EXPORTER_USER"
echo "[+] 已创建用户 $EXPORTER_USER"
fi

# ===== 2. 下载并安装二进制 =====
if [ ! -x "${INSTALL_DIR}/mysqld_exporter" ]; then
cd /tmp
curl -fsSL -o "${PKG}.tar.gz" "$URL"
tar xzf "${PKG}.tar.gz"
install -m 0755 "${PKG}/mysqld_exporter" "${INSTALL_DIR}/"
rm -rf "${PKG}" "${PKG}.tar.gz"
echo "[+] 已安装 mysqld_exporter ${EXPORTER_VERSION}"
else
echo "[=] mysqld_exporter 已存在,跳过安装"
fi

# ===== 3. 生成 .my.cnf =====
mkdir -p "$CONFIG_DIR"
cat > "${CONFIG_DIR}/.my.cnf" <<EOF
[client]
host=${MYSQL_HOST}
port=${MYSQL_PORT}
user=${MYSQL_USER}
password=${MYSQL_PASS}
EOF

chown -R "${EXPORTER_USER}:${EXPORTER_USER}" "$CONFIG_DIR"
chmod 600 "${CONFIG_DIR}/.my.cnf"
echo "[+] 已写入 ${CONFIG_DIR}/.my.cnf"

这里有两个细节值得说。

第一,set -euo pipefail 不能省。下载失败、解压失败、权限不足,脚本应该立刻停,而不是继续往下走。否则可能出现"二进制没装好但 systemd 已经启动"的假成功。

第二,密码通过环境变量传入,而不是写在脚本里。${MYSQL_PASS:?…} 这种写法在变量为空时会直接报错退出,避免生成一个空密码的配置文件。

第三步:systemd unit

第三步:systemd unit

# ===== 4. 写 systemd unit =====
cat > /etc/systemd/system/mysqld_exporter.service <<EOF
[Unit]
Description=Prometheus MySQL Exporter
After=network-online.target
Wants=network-online.target

[Service]
User=${EXPORTER_USER}
Group=${EXPORTER_USER}
Type=simple
ExecStart=${INSTALL_DIR}/mysqld_exporter \\\\
–config.my-cnf=${CONFIG_DIR}/.my.cnf \\\\
–web.listen-address=${LISTEN_ADDR}
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable –now mysqld_exporter
echo "[+] mysqld_exporter 已启动"

–web.listen-address 默认是 :9104,等价于监听所有网卡的 9104 端口。我显式写成 0.0.0.0:9104 是为了让远程 Prometheus 能抓到。如果你的安全策略不允许 exporter 对外暴露,那这里应该写 127.0.0.1:9104,然后用 SSH 隧道或者反向代理来做远程访问——但那已经不是"远程抓取"的常规做法了,多数场景下还是直接监听内网 IP。

【关键结论】远程抓取能成立的前提是:exporter 监听在 Prometheus 能访问到的地址上,且中间没有防火墙拦截。这两点缺一不可,脚本只能保证前者。

第四步:让 Prometheus 知道新目标

第四步:让 Prometheus 知道新目标

这是最容易重复劳动的一步。传统做法是直接编辑 prometheus.yml:

scrape_configs:
job_name: 'mysql'
static_configs:
targets:
'10.0.0.11:9104'
'10.0.0.12:9104'

每加一台机器改一次文件、reload 一次,机器一多就很烦。

更省事的方案是用 file_sd(基于文件的服务发现)。Prometheus 会监听一个目录下的 JSON/YAML 文件,文件变了自动生效,不需要 reload。

scrape_configs:
job_name: 'mysql'
file_sd_configs:
files:
'/etc/prometheus/targets/mysql/*.json'
refresh_interval: 30s

然后在 /etc/prometheus/targets/mysql/ 下放一个 JSON:

[
{
"targets": ["10.0.0.11:9104"],
"labels": {
"env": "prod",
"role": "mysql"
}
}
]

这样部署脚本最后一步就可以直接往这个目录写文件。不过要注意,Prometheus 服务器和 MySQL 服务器通常不是同一台,脚本没法直接写远端目录。两种处理方式:

  • 脚本生成 JSON 片段,输出到 stdout,由你(或另一个同步脚本)汇总到 Prometheus 机器上;
  • 用配置管理工具(Ansible 等)统一推送。
  • 我倾向于第一种:部署脚本保持"只负责本机"的职责,同步这件事交给更上层的流程。脚本末尾加一段:

    # ===== 5. 输出 file_sd 片段 =====
    LOCAL_IP=$(hostname -I | awk '{print $1}')
    cat <<EOF

    [+] 请将以下内容加入 Prometheus 的 file_sd 目录:

    [
    {
    "targets": ["${LOCAL_IP}:9104"],
    "labels": {
    "env": "prod",
    "instance_name": "$(hostname)"
    }
    }
    ]

    EOF

    hostname -I 取本机第一个 IP,多数情况够用;如果机器有多网卡,这里可能需要手动指定,脚本里我没做自动判断,因为哪种网卡该用取决于你的网络规划。

    验证:别只看端口通不通

    验证:别只看端口通不通

    部署完,很多人第一反应是 curl localhost:9104/metrics。这个能验证 exporter 起来了,但不能验证 Prometheus 真能抓到。

    分三层验证:

  • exporter 自身是否正常
  • curl -s http://localhost:9104/metrics | grep mysql_up

    看到 mysql_up 1 说明 exporter 成功连上了 MySQL。如果是 0,说明账号或网络有问题,先去看 journalctl -u mysqld_exporter 的报错。

  • 远程能否访问
  • 在 Prometheus 机器上:

    curl -s http://10.0.0.11:9104/metrics | head

    如果超时,检查 exporter 机器的防火墙(firewall-cmd –list-ports 或 iptables -L),以及 exporter 是否真的监听了 0.0.0.0 而不是 127.0.0.1:

    ss -tlnp | grep 9104

  • Prometheus 是否识别到目标
  • 打开 Prometheus 的 /targets 页面,找到 mysql 这个 job,看 target 的状态是 UP 还是 DOWN。DOWN 的话,页面上会直接给出错误信息,比猜要快得多。

    几个实际会遇到的坑

    认证插件不匹配。MySQL 8 默认用 caching_sha2_password,老版本的 mysqld_exporter(0.12 及更早)连不上。解决办法是升级 exporter,而不是把监控账号改成 mysql_native_password——后者会降低安全性,不划算。

    TLS 连接。如果 MySQL 强制要求 SSL,exporter 需要在 .my.cnf 里加 ssl-mode=REQUIRED 之类的参数。这一点具体写法取决于 exporter 版本对 MySQL 客户端参数的透传程度,我没有在强制 TLS 的环境里完整验证过,建议先查对应版本的 release notes。

    多实例采集。mysqld_exporter 支持通过 –collector.multi-target 之类的参数(不同版本参数名有差异)在请求时动态指定目标,这样一台 exporter 可以服务多个 MySQL。但配置方式和我上面写的单实例模式不一样,如果你的实例数量很大,值得单独研究这个模式,能省掉每台 MySQL 机器都装一个 exporter 的成本。

    权限给多了。有些教程直接给 ALL PRIVILEGES,没必要。PROCESS、REPLICATION CLIENT、SELECT 三个够用了。监控账号是最容易被忽视的权限泄漏点,能少给就少给。

    关于脚本本身的一点取舍

    这个脚本我做了一个明确的取舍:不做远端 Prometheus 配置的自动写入。原因很简单,exporter 部署和 Prometheus 配置分属两个不同的生命周期——exporter 可能被重装、迁移,而 Prometheus 的抓取配置需要集中管理。把两者耦合在一个脚本里,短期看省事,长期看会变成"为什么 Prometheus 配置被某台 MySQL 机器改了"这种难查的问题。

    如果你确实想要全自动,更合理的方向是:部署脚本只负责本机 exporter,另外用一个统一的注册流程(可以是 Ansible,也可以是一个接收 HTTP 请求的小服务)来管理 file_sd 目录。这样职责清晰,也方便做审计。

    脚本里的 EXPORTER_VERSION、LISTEN_ADDR 这些我都做成了变量,实际用的时候可以直接改成从命令行参数或环境变量读取,扩展起来不难。真正花时间的不是脚本本身,而是把"远程抓取需要哪些前提"这件事想清楚——监听地址、防火墙、账号权限、file_sd 的同步方式,这四个点理顺了,剩下的都是模板代码。

    赞(0)
    未经允许不得转载:171主机测评 » 用 Shell 脚本自动部署 mysqld_exporter 并接入 Prometheus 远程抓取
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址