# ansible / monitoring / prometheus

Install Node Exporter without touching the host by hand

One playbook fetches the release, creates a locked-down service account, wires up systemd, and leaves you with a metrics endpoint — reproducible on every box you run it against.

$ systemctl status node_exporter
Unitnode_exporter.service
Loadedloaded (/etc/systemd/system/node_exporter.service; enabled)
Activeactive (running)
Usernode_exporter
Listen0.0.0.0:9100
v1.8.1
release
/sbin/nologin
shell
7 tasks
playbook
# overview

Why this playbook holds up

Built around three properties that matter for anything running unattended on a server: idempotency, isolation, and portability. Every task uses a native Ansible module — no shell one-liners to babysit, no drift between runs. Run it once or run it a hundred times; the end state doesn't change.

idempotent

Safe to re-run

Tasks check current state before acting, so a second run against an already-configured host changes nothing.

portable

No distro assumptions

Works against any systemd-based Linux host — swap hosts: localhost for an inventory group and it scales the same way.

# playbook

The full task list

node_exporter.ymlYAML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
---
- name: Install node exporter
  hosts: localhost
  become: yes
  vars:
    node_exporter_version: "1.8.1"
  tasks:
    - name: Download and extract node exporter
      unarchive:
        src: "https://github.com/prometheus/node_exporter/releases/download/v{{ node_exporter_version }}/node_exporter-{{ node_exporter_version }}.linux-amd64.tar.gz"
        dest: /tmp
        remote_src: yes

    - name: Copy node exporter binary
      copy:
        src: "/tmp/node_exporter-{{ node_exporter_version }}.linux-amd64/node_exporter"
        dest: /usr/local/bin/node_exporter
        mode: '0755'
        remote_src: yes

    - name: Create node exporter user
      user:
        name: node_exporter
        shell: /sbin/nologin
        system: yes
        create_home: no

    - name: Create systemd service file
      copy:
        dest: /etc/systemd/system/node_exporter.service
        content: |
          [Unit]
          Description=Node Exporter
          After=network.target
          [Service]
          User=node_exporter
          ExecStart=/usr/local/bin/node_exporter
          [Install]
          WantedBy=multi-user.target

    - name: Reload systemd
      command: systemctl daemon-reload

    - name: Enable and start node exporter
      service:
        name: node_exporter
        state: started
        enabled: yes

Run order

  • fetch

    Download and extract the release into /tmp.

    unarchive, remote_src
  • place

    Move the binary to /usr/local/bin and mark it executable.

    copy, mode 0755
  • isolate

    Create a no-login system account to run the service as.

    user, /sbin/nologin
  • wire

    Write the unit file and register it with systemd.

    copy + daemon-reload
  • run

    Enable on boot and start the service now.

    service, enabled + started
# verify

Confirm it's actually up

Two checks are enough: is the systemd unit reporting active, and is the metrics endpoint answering. If both check out, Prometheus can scrape the host.

terminal
$ systemctl status node_exporter
● node_exporter.service - Node Exporter
   Active: active (running) since Wed 2026-07-08
 
$ curl -s localhost:9100/metrics | head -n 3
# HELP go_gc_duration_seconds A summary of the pause duration.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 1.7e-05
 
$
# notes

Lessons from running this in production

WARN

YAML is indentation-sensitive — use spaces consistently, never tabs, or the parser fails before a single task runs.

INFO

Centralize the version in vars so a bump is a one-line change, not a find-and-replace across the file.

OK

Every task here is idempotent by construction — safe to re-run against a host that's already configured.