用 Ansible 批量部署 Node Exporter,可以把“创建专用用户 → 安装固定版本程序 → 写入 systemd unit → daemon-reload → 启用并启动 → 验证 /metrics → 检查 Prometheus Targets”放进同一个 Playbook。题述 CSDN 文章把部署概括为“下载、建用户、写 systemd、启动一次编排”,但权限收敛和抓取端验收容易漏掉。
这条路径适用于使用 systemd 的 Linux 目标机。控制端需要能够通过 SSH 连接目标机,并具备 become 提权权限;Node Exporter 默认使用 9100 端口,Prometheus 再从自己的服务器主动拉取指标。
先把主流程跑通
先把主机写进 /etc/ansible/hosts,也可以使用自己的 inventory 文件并通过 -i 指定:
[node_exporter]
node-01 ansible_host=192.0.2.11
node-02 ansible_host=192.0.2.12
[node_exporter:vars]
ansible_user=deploy
ansible_become=true
ansible_become_method=sudo
不要把明文密码直接放进 inventory。deploy 用户需要提前配置好 SSH 密钥和受控的 sudo 权限;如果直接使用 root 登录,则可以不启用 become。
在 group_vars/node_exporter.yml 中固定发布参数:
node_exporter_version: "CHANGE_ME"
node_exporter_arch: "amd64"
node_exporter_source: "remote"
node_exporter_sha256: "CHANGE_ME"
node_exporter_listen_address: ":9100"
node_exporter_nologin_shell: "/sbin/nologin"
CHANGE_ME 必须替换为从 Node Exporter 官方 Release 核对过的版本号,以及对应架构压缩包的 SHA-256。资料中的 1.2.2、1.5.0、1.6.1 都只是历史示例,不能直接当作最新版。node_exporter_arch 应使用官方压缩包名称中的架构标识,例如 amd64 或 arm64。
下面的 Playbook 同时支持远程下载和本地安装包:
- name: Deploy Node Exporter
hosts: node_exporter
become: true
gather_facts: true
vars:
node_exporter_user: node_exporter
node_exporter_group: node_exporter
node_exporter_install_dir: /usr/local/node_exporter
node_exporter_archive_path: "/tmp/node_exporter-{{ node_exporter_version }}.linux-{{ node_exporter_arch }}.tar.gz"
node_exporter_unpack_dir: "/tmp/node_exporter-{{ node_exporter_version }}-{{ node_exporter_arch }}"
node_exporter_source: remote
node_exporter_version: CHANGE_ME
node_exporter_arch: amd64
node_exporter_sha256: CHANGE_ME
node_exporter_package_path: "files/node_exporter-{{ node_exporter_version }}.linux-{{ node_exporter_arch }}.tar.gz"
node_exporter_listen_address: ":9100"
node_exporter_nologin_shell: /sbin/nologin
node_exporter_health_url: http://127.0.0.1:9100/metrics
tasks:
- name: Validate release variables
ansible.builtin.assert:
that:
- node_exporter_version is match('^[0-9]+\.[0-9]+\.[0-9]+$')
- node_exporter_arch | length > 0
- node_exporter_source in ['remote', 'local']
- node_exporter_sha256 is match('^[0-9a-fA-F]{64}$')
- (node_exporter_source == 'remote') or (node_exporter_package_path | length > 0)
fail_msg: "Set a verified Node Exporter version, architecture and SHA-256."
- name: Ensure group exists
ansible.builtin.group:
name: "{{ node_exporter_group }}"
system: true
state: present
- name: Ensure user exists
ansible.builtin.user:
name: "{{ node_exporter_user }}"
group: "{{ node_exporter_group }}"
system: true
shell: "{{ node_exporter_nologin_shell }}"
create_home: false
state: present
- name: Ensure installation directory exists
ansible.builtin.file:
path: "{{ node_exporter_install_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Download release archive
ansible.builtin.get_url:
url: "https://github.com/prometheus/node_exporter/releases/download/v{{ node_exporter_version }}/node_exporter-{{ node_exporter_version }}.linux-{{ node_exporter_arch }}.tar.gz"
dest: "{{ node_exporter_archive_path }}"
checksum: "sha256:{{ node_exporter_sha256 }}"
mode: "0644"
when: node_exporter_source == 'remote'
- name: Copy release archive from controller
ansible.builtin.copy:
src: "{{ node_exporter_package_path }}"
dest: "{{ node_exporter_archive_path }}"
checksum: "sha256:{{ node_exporter_sha256 }}"
owner: root
group: root
mode: "0644"
when: node_exporter_source == 'local'
- name: Extract release archive
ansible.builtin.unarchive:
src: "{{ node_exporter_archive_path }}"
dest: "{{ node_exporter_unpack_dir }}"
extra_opts:
- --strip-components=1
owner: root
group: root
- name: Install node_exporter binary
ansible.builtin.copy:
src: "{{ node_exporter_unpack_dir }}/node_exporter"
dest: "{{ node_exporter_install_dir }}/node_exporter"
remote_src: true
owner: root
group: root
mode: "0755"
notify: Restart node exporter
- name: Install systemd unit
ansible.builtin.copy:
content: |
[Unit]
Description=Node Exporter
After=network.target
[Service]
Type=simple
User={{ node_exporter_user }}
Group={{ node_exporter_group }}
ExecStart={{ node_exporter_install_dir }}/node_exporter --web.listen-address={{ node_exporter_listen_address }}
Restart=on-failure
[Install]
WantedBy=multi-user.target
dest: /etc/systemd/system/node_exporter.service
owner: root
group: root
mode: "0644"
notify: Restart node exporter
- name: Enable and start node_exporter
ansible.builtin.systemd:
name: node_exporter
enabled: true
state: started
daemon_reload: true
- name: Apply pending restarts before health check
ansible.builtin.meta: flush_handlers
- name: Check local metrics endpoint
ansible.builtin.uri:
url: "{{ node_exporter_health_url }}"
status_code: 200
register: node_exporter_health
retries: 5
delay: 2
until: node_exporter_health is not failed
changed_when: false
handlers:
- name: Restart node exporter
ansible.builtin.systemd:
name: node_exporter
state: restarted
daemon_reload: true
node_exporter_source 为 remote 时,由目标机访问官方 Release;改成 local 后,node_exporter_package_path 指向控制端上的压缩包,目标机不再需要访问外网。
执行前先确认 Ansible 能看到目标组,再运行 Playbook:
ansible-inventory --graph
ansible-playbook node-exporter.yml
用户任务使用 state: present,程序和 unit 由文件模块判断内容变化,只有二进制或 unit 改变时才重启服务。因此再次执行应当收敛到相同状态,而不是重复创建用户或覆盖未变化的配置。
连不上目标机时先看 SSH 和清单
Ansible 通过 SSH 执行模块,主机分组、SSH 密钥和提权权限必须先分别确认:
ansible node_exporter -m raw -a "true"
ansible node_exporter -m ping
ansible node_exporter -b -m command -a "id"
第一条用于隔离 SSH 和网络问题;第二条验证远端 Python 与 Ansible 模块是否可用;第三条验证 become 能否真正取得 root 权限。如果 raw 已经连通而 ping 失败,常见原因是目标机缺少 Ansible 所需的 Python 环境,而不是 SSH 本身失败。
需要查看握手过程时再增加 -vvv:
ansible node_exporter -m ping -vvv
首次连接的主机密钥应通过可信渠道核对并存入 known_hosts,不要长期关闭主机密钥检查,也不要照抄旧案例里的明文 ansible_ssh_pass。
下载解压后先固定版本和权限
版本、架构和校验和都应来自同一次官方发布,不能只替换 URL 中的版本号,却继续使用旧压缩包的 SHA-256。已有案例分别使用过 1.2.2、1.5.0 等历史版本,这些数字只适合说明目录结构。
安装目录和二进制应由 root 持有:
ls -ld /usr/local /usr/local/node_exporter
ls -l /usr/local/node_exporter/node_exporter
专用用户只需要读取和执行权限,不需要写入程序目录。现有批量部署案例也采用“创建 prometheus 用户、放置程序、写入 systemd 服务”的组合。目录或二进制权限不正确时,systemd 日志通常会留下执行被拒绝的信息。
systemd 起不来时对照 unit 和日志
本文统一使用 /etc/systemd/system/node_exporter.service,避免和资料中出现的 /usr/lib/systemd/system 路径混用。渲染后的 unit 应类似下面这样:
[Unit]
Description=Node Exporter
After=network.target
[Service]
Type=simple
User=node_exporter
Group=node_exporter
ExecStart=/usr/local/node_exporter/node_exporter --web.listen-address=:9100
Restart=on-failure
[Install]
WantedBy=multi-user.target
User、Group、ExecStart 和监听端口都要与实际环境一致;ExecStart 必须使用绝对路径。修改 unit 后先重新加载,再检查 systemd 实际读取到的配置:
systemctl daemon-reload
systemctl enable --now node_exporter
systemctl is-active node_exporter
systemctl is-enabled node_exporter
systemctl status node_exporter --no-pager
systemctl cat node_exporter
systemctl show node_exporter -p User -p Group -p ExecStart
journalctl -u node_exporter -n 50 --no-pager
如果服务启动即退出,依次看用户是否存在、ExecStart 文件是否可执行、9100 是否已被占用:
/usr/local/node_exporter/node_exporter --version
ss -ltnp 'sport = :9100'
端口冲突时,要么停止占用进程,要么同时修改服务的 --web.listen-address 和 Prometheus 的抓取端口。只改其中一边,结果仍然会是连接失败。
本机有指标但 Prometheus 仍显示 DOWN
先在目标机本机检查接口:
curl -fsS http://127.0.0.1:9100/metrics >/dev/null
本机不通,问题仍在 Node Exporter 服务、监听地址或进程状态。本机能通,再从 Prometheus 所在服务器测试同一个目标:
curl -fsS http://192.0.2.10:9100/metrics >/dev/null
这一步失败,通常说明服务只监听了 127.0.0.1,或者 9100 被防火墙、安全组、路由策略拦住了。网络策略应只允许 Prometheus 服务器的源地址访问目标机的 TCP 9100,不要直接停用防火墙,也不要把 /metrics 暴露到公网。
本机和 Prometheus 服务器都能访问,但仍显示 DOWN,就转向 Prometheus 配置。静态目标可以这样写:
scrape_configs:
- job_name: node
static_configs:
- targets:
- 192.0.2.10:9100
这里的地址必须是 Prometheus 服务器能够访问到的目标地址,不能误写成 127.0.0.1。让 Prometheus 重新加载配置后,再查看 Targets 中对应条目的具体错误;常见原因包括目标地址写错、端口不一致、配置没有重新加载,或者 Prometheus 与目标机之间存在网络隔离。
Consul 自动发现时再确认一次真实抓取
如果依赖 Consul 自动发现,还需要注册服务、配置健康检查,并让 Prometheus 读取 Consul 中的目标列表。Consul 中能够查到节点,只说明服务发现层拿到了注册信息,不能代替最终的 Prometheus 抓取验证。
无论使用静态清单还是 Consul,最终都要确认 Prometheus 已生成目标、目标的抓取状态为 UP,并用 up 指标验收:
up{job="node"} == 1












暂无评论内容