Ansible批量部署 Node Exporter 抓取成功

用 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
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容