Prometheus만 설치해도 호스트의 CPU·메모리·디스크·네트워크 메트릭이 자동으로 수집되는 것은 아니다. 리눅스 서버에서 이런 시스템 메트릭을 수집하려면 Node Exporter라는 작은 프로그램을 설치하고, Prometheus가 이를 스크레이프하도록(정해진 주기마다 찾아가 숫자를 받아 가도록) 설정해야 한다. 이 글은 공식 가이드(Node Exporter 공식 가이드)를 기반으로 최소 설치 과정을 정리한다.

이 글의 명령은 전부 리눅스 서버의 터미널에 한 줄씩 쳐서 실행한다. 코드 블록 위에 붙은 bash는 그 명령을 받아 실행하는 셸의 이름이다. 파일로 저장해 두는 코드가 아니다. 그리고 Prometheus는 이미 같은 서버에 systemd 서비스로 설치돼 돌고 있다고 보고 진행한다 — 뒤의 「Prometheus 연동」에서 systemctl restart prometheus로 재시작하는 것이 그 전제다.


Node Exporter가 필요한 이유

  • Prometheus 자체는 메트릭 저장소다. 스크레이프할 대상이 열어 둔 /metrics라는 엔드포인트(주소)에 HTTP로 요청을 보내, 거기 적힌 숫자를 받아 올 뿐이다. 호스트의 OS(리눅스)가 들고 있는 CPU·메모리 정보를 직접 읽으러 가지 않는다.
  • Node Exporter는 리눅스 호스트의 CPU·메모리·디스크·네트워크·파일시스템 정보를 읽어서, 자기 /metrics 주소에 「메트릭 이름과 숫자」를 한 줄에 하나씩 글자로 적어 내놓는다. 누가 그 주소를 요청하면 이 글자를 돌려준다. 이렇게 남이 가져갈 수 있게 내놓는 것을 「노출한다」고 부른다. 이런 역할의 프로그램을 익스포터라고 한다.
  • Prometheus가 Node Exporter를 스크레이프하면, node_cpu_seconds_total(CPU가 각 상태 — 쉬는 중, 일하는 중 등 — 로 보낸 시간을 초 단위로 계속 더해 온 합계), node_memory_MemTotal_bytes(전체 메모리 크기, 바이트) 같은 메트릭을 PromQL로 조회할 수 있다.

설치 과정

1) 바이너리 다운로드 및 설치

Node Exporter는 바이너리(내려받으면 바로 실행되는 실행 파일) 하나로 배포된다. 최신 릴리스(정식으로 공개된 버전)는 Prometheus 다운로드 페이지에서 확인한다. 아래는 1.7.0 기준 예시다. 다른 버전을 받을 때는 명령 안의 1.7.0(주소의 v1.7.0까지)을 전부 그 버전 숫자로 바꾼다.

cd /tmp
curl -LO https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz
tar xvfz node_exporter-1.7.0.linux-amd64.tar.gz
sudo mv node_exporter-1.7.0.linux-amd64/node_exporter /usr/local/bin/

한 줄씩 보면 이렇다. cd /tmp는 임시 파일을 두는 폴더로 이동하는 것이고, curl -LO <주소>는 그 주소의 파일을 현재 폴더에 내려받는다(curl. -L은 주소가 다른 곳으로 옮겨져 있으면 따라가라는 뜻, -O는 받은 파일을 주소에 있는 이름 그대로 저장하라는 뜻이다). tar xvfz <파일>은 .tar.gz로 압축된 파일을 풀고(x는 풀기, v는 푸는 과정을 화면에 보여주기, f는 뒤에 오는 것이 파일 이름이라는 표시, z는 gzip으로 풀라는 표시), 그러면 node_exporter-1.7.0.linux-amd64라는 폴더가 생긴다. sudo mv <경로> /usr/local/bin/은 그 폴더 안의 실행 파일 node_exporter를, 어디서든 실행 명령으로 바로 부를 수 있는 공용 폴더(/usr/local/bin)로 옮긴다. 이 폴더가 셸이 명령 이름을 받았을 때 실행 파일을 찾아보는 폴더 목록(PATH)에 들어 있어서, 경로 없이 node_exporter라고만 쳐도 실행된다. sudo는 관리자 권한으로 실행하라는 뜻이다. 이 줄에만 붙는 이유는 /usr/local/bin이 root 소유이고 root만 쓸 수 있는 폴더(권한 755)이기 때문이다. 앞의 curl·tar는 누구나 쓸 수 있는 /tmp에서 일해서 필요 없었다.

파일 이름의 linux-amd64는 「리눅스용, amd64 CPU용」이라는 뜻이다. CPU는 종류마다 알아듣는 명령이 다르다. 대부분의 PC와 서버는 x86 계열이고, 휴대폰이나 라즈베리파이 같은 작은 보드는 ARM 계열이다. amd64는 그중 x86 계열 64비트 CPU를 부르는 이름이다(x86_64라고도 쓴다). 바이너리는 CPU 종류마다 따로 나오므로, ARM 보드처럼 CPU 종류(아키텍처)가 다른 기계는 linux-arm64 등 맞는 파일을 받아야 한다. 내 기계가 어느 쪽인지는 uname -m으로 본다. 이 명령은 CPU 종류 이름을 찍는데, x86_64가 나오면 amd64 파일을 받으면 된다.

2) systemd 서비스 등록

터미널에서 node_exporter를 직접 실행하면 터미널을 닫을 때 같이 꺼진다. 터미널과 상관없이 뒤에서(백그라운드에서) 계속 돌게 하고, 서버를 다시 켜도 알아서 켜지게 하려면 systemd 서비스로 등록한다.

sudo tee /etc/systemd/system/node_exporter.service >/dev/null <<'EOF'
[Unit]
Description=Prometheus Node Exporter
After=network.target

[Service]
User=node-exp
Group=node-exp
Type=simple
ExecStart=/usr/local/bin/node_exporter --web.listen-address=:9100

[Install]
WantedBy=multi-user.target
EOF

한 줄씩 보면 이렇다. sudo tee <경로> >/dev/null <<'EOF' ... EOF는 EOF와 EOF 사이에 적은 여러 줄을 그대로 그 경로의 파일에 써 넣는 방법이다(히어독, heredoc) — tee는 받은 내용을 파일로 저장하는 명령이고, >/dev/null은 그 내용을 화면에는 다시 띄우지 않겠다는 뜻이다. 첫 EOF를 작은따옴표로 감싼 'EOF'는 「사이의 내용을 한 글자도 바꾸지 말고 그대로 써라」는 표시다. 따옴표가 없으면 셸이 내용 속의 $이름 같은 부분을 변수 값으로 바꿔 넣는다. 파일을 /etc/systemd/system/ 폴더에 두는 이유는, systemd가 서비스 설정 파일을 찾아 읽는 폴더 중 이곳이 관리자가 직접 만든 서비스를 두는 자리이기 때문이다. 파일 이름 node_exporter.service의 앞부분이 곧 서비스 이름이 되어, 뒤에서 systemctl ... node_exporter로 부른다.

그 안의 내용은 systemd가 읽는 서비스 설정 파일의 형식이다. [Unit]은 이 서비스가 무엇이고 언제 시작해도 되는지를 적는 자리로, After=network.target은 「네트워크가 올라온 뒤에 시작하라」는 뜻이다. [Service]는 실제로 어떻게 실행할지를 적는 자리다 — User·Group은 어느 계정과 그룹(uid · gid) 권한으로 돌릴지, Type=simple은 ExecStart에 적은 명령을 실행하면 그게 곧 서비스 본체라는 뜻, ExecStart는 실제로 실행할 명령이다(--web.listen-address=:9100은 9100번 포트에서 요청을 기다리다가, 누가 /metrics를 달라고 하면 메트릭을 돌려주라는 옵션. : 앞의 주소 자리를 비우면 이 서버가 가진 모든 주소에서 기다린다는 뜻이라, 같은 서버 안의 localhost:9100으로도, 다른 컴퓨터에서 서버 IP로 와도 받는다). [Install]은 enable(3단계에서 쓰는 「부팅 때 자동으로 켜기」 등록 명령)했을 때 언제 자동으로 켜질지를 적는 자리이고, WantedBy=multi-user.target은 「평소 리눅스가 다 켜진 상태」가 되면 같이 켜지라는 뜻이다.

User=node-exp의 node-exp 계정은 아직 없다. 3단계에서 만든다. 이 파일은 지금은 글자일 뿐이고 systemd가 이 계정으로 프로그램을 띄우는 것은 3단계의 시작 명령 때라서, 그 전에만 계정이 있으면 된다. 관리자 계정(root)이 아니라 따로 만든 계정으로 돌리는 것은, root는 권한 검사를 거의 다 통과하기 때문이다. 메트릭을 읽어 내놓기만 하는 프로그램에 그 권한을 줄 까닭이 없다.

3) 실행 계정 생성 및 서비스 시작

sudo useradd -rs /bin/false node-exp
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
sudo systemctl status node_exporter --no-pager

한 줄씩 보면 이렇다. useradd -rs /bin/false node-exp는 node-exp라는 계정을 만든다. -r은 사람이 아니라 프로그램을 돌리는 시스템 계정으로 만들라는 뜻이고(이 경우 홈 폴더를 만들지 않는다), -s /bin/false는 이 계정으로 로그인했을 때 뜨는 셸을 /bin/false로 정한다. /bin/false는 아무 일도 하지 않고 「실패」로 바로 끝나는 프로그램이라, 이 계정으로는 로그인해도 바로 끝나 버린다. 서비스를 돌리는 데만 쓰는 계정이기 때문이다.

systemctl daemon-reload는 systemd에게 서비스 설정 파일을 전부 다시 읽으라고 시킨다. 2단계에서 새 파일을 만들었으니, 이걸 해야 systemd가 node_exporter 서비스가 생긴 것을 안다. systemctl enable --now node_exporter의 enable은 2단계 파일의 [Install]대로 「부팅하면 자동으로 켜기」를 등록하는 것이고, enable만으로는 지금 당장 켜지지 않아서 --now를 붙여 지금도 켠다. systemctl status node_exporter는 그 서비스가 지금 어떤 상태인지와 최근 로그 몇 줄을 보여 준다. --no-pager는 결과가 길어도 한 화면씩 끊어 보여 주는 화면(페이저)에 넣지 말고 그대로 찍으라는 뜻이다.

정상이면 출력에 Active: active (running) since ...처럼 “active (running)”이 적힌 줄이 표시된다. failed가 보이면 시작하다 실패한 것이므로, 같은 출력 아래에 찍힌 최근 로그 줄에서 이유를 찾는다.

4) 방화벽 및 로컬 확인

  • 기본 포트: 9100/tcp (9100번 포트, TCP 방식의 연결이라는 표기다)
  • Prometheus가 이 서버가 아닌 다른 컴퓨터에 있다면, 이 서버의 방화벽이 9100/tcp로 들어오는 연결을 허용해야 그 Prometheus가 스크레이프할 수 있다. 방화벽을 여는 명령은 서버에 깔린 방화벽 도구마다 달라서 이 글에서는 다루지 않는다
  • 로컬(이 서버 안)에서 메트릭 확인:
    curl http://localhost:9100/metrics
    

    1단계의 curl과 같은 명령이지만 -O가 없다. 그러면 curl은 받은 내용을 파일로 저장하지 않고 화면에 그대로 찍는다. localhost는 이 컴퓨터 자신을 가리키므로, 이 명령은 「내 컴퓨터의 9100번 포트에 /metrics를 달라고 해서 화면에 찍어라」가 된다. Prometheus가 스크레이프할 때 받아 가는 것과 같은 글자다.

결과는 공식 가이드에 실린 예시처럼 이런 모양의 줄이 길게 이어진다.

# HELP go_gc_duration_seconds A summary of the GC invocation durations.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 3.8996e-05
go_gc_duration_seconds{quantile="0.25"} 4.5926e-05

#으로 시작하는 줄은 설명이고, 나머지는 한 줄에 「메트릭 이름{라벨} 숫자」 하나씩이다. 3.8996e-05는 3.8996 × 10⁻⁵, 곧 0.000038996이다. 이 예시 줄들이 무엇을 재는지는 몰라도 된다 — 여기서는 모양만 보면 된다. Node Exporter는 Go 언어로 만든 프로그램이라, 앞부분에는 이처럼 go_로 시작하는 Node Exporter 자기 자신(Go 실행 환경)에 대한 메트릭이 나온다. 그래서 node_로 시작하는 줄만 보려면 curl http://localhost:9100/metrics | grep "node_"처럼 grep으로 거른다(|는 앞 명령의 출력을 뒤 명령에 넘기는 파이프다). node_cpu_seconds_total, node_memory_MemTotal_bytes 등의 메트릭이 보이면 정상이다.


Prometheus 연동

1) prometheus.yml 스크레이프 설정 추가

prometheus.yml은 Prometheus의 설정 파일이다. Prometheus를 띄울 때 --config.file 옵션으로 지정한 파일이 이것이라, 위치는 Prometheus를 어떻게 설치했느냐에 따라 다르다. systemd 서비스로 설치했다면 systemctl cat prometheus로 서비스 설정 파일을 화면에 띄워, ExecStart 줄의 --config.file= 뒤에 적힌 경로를 보면 된다. 그 파일에 아래 내용을 넣는다. 파일에 scrape_configs:가 이미 있으면 그 줄을 한 번 더 쓰지 말고, 기존 목록 아래에 - job_name: "node"로 시작하는 덩어리만 같은 들여쓰기로 더한다.

scrape_configs:
  - job_name: "node"
    static_configs:
      - targets: ["localhost:9100"]

이 설정은 YAML 형식으로 적는다. scrape_configs는 Prometheus가 무엇을 스크레이프할지 적어 두는 목록이고, 그 안의 job_name은 이 수집 대상 묶음(job)에 붙이는 이름이다. Prometheus는 이 대상에서 가져온 모든 숫자에 job="node"라는 라벨을 자동으로 붙인다(뒤에서 볼 쿼리 up{job="node"}의 job="node"가 여기서 나온다). static_configs는 주소를 바뀌지 않는 값으로 직접 적어 두는 방식이고, targets는 실제로 스크레이프할 주소와 포트의 목록이다.

만약 원격 호스트(다른 컴퓨터)라면 ["192.168.1.100:9100"] 형식으로 그 컴퓨터의 IP를 지정한다. 이때는 「설치 과정」의 4)에서 본 대로 그 컴퓨터의 방화벽이 9100/tcp를 허용해야 한다.

2) Prometheus 재시작

sudo systemctl restart prometheus

Prometheus는 켜질 때 --config.file로 지정한 설정 파일을 읽는다. 그래서 파일을 고친 뒤 껐다 켜서(restart) 새 설정을 읽게 한다. 이 명령은 Prometheus가 prometheus라는 이름의 systemd 서비스로 설치돼 있을 때의 모양이다.

3) 스크레이프 상태 확인

Prometheus 웹 화면(공식 가이드 기준 http://localhost:9090, 다른 컴퓨터에서 보면 localhost 자리에 서버 주소)을 브라우저로 열고 Status → Targets에서 node job이 UP 상태인지 확인한다. UP은 Prometheus가 그 대상에 접속해 숫자를 받아 오는 데 성공하고 있다는 뜻이다. 또는 같은 웹 화면의 PromQL 입력창(http://localhost:9090/graph)에 PromQL 쿼리를 넣어 확인:

up{job="node"}

up은 Prometheus가 스크레이프할 때마다 대상마다 스스로 남기는 메트릭이다. 중괄호 안의 job="node"는 「job 라벨이 정확히 node인 것만 골라라」는 조건이다. 결과가 1이면 정상이다. up은 대상이 정상이고 접속되면 1, 스크레이프에 실패하면 0이다.


실무 쿼리 예시

Node Exporter가 정상 작동하면 아래 PromQL을 바로 쓸 수 있다. 각 식은 앞의 PromQL 입력창에 하나씩 넣는다.

CPU 사용률(%)

100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

메모리 사용률(%)

(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100

디스크 사용률(%)

(max by (instance,device) (node_filesystem_size_bytes{fstype!~"tmpfs|overlay"})
 - max by (instance,device) (node_filesystem_free_bytes{fstype!~"tmpfs|overlay"}))
 / max by (instance,device) (node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) * 100

네트워크 수신/송신 Bps

sum by (instance) (rate(node_network_receive_bytes_total{device!~"lo"}[5m]))
sum by (instance) (rate(node_network_transmit_bytes_total{device!~"lo"}[5m]))

네트워크 블록의 두 줄은 따로따로 넣는 별개의 식이다. 첫 줄이 수신, 둘째 줄이 송신이다.

네 식은 같은 재료를 쓴다. _total로 끝나는 메트릭은 카운터다. 줄지 않고 계속 더해지기만 하는 합계라서 값 자체로는 「지금 얼마나 바쁜지」를 알 수 없다. 그래서 rate(지표[5m])로 최근 5분 동안 그 지표가 초당 평균 얼마나 늘었는지를 구한다. 그다음 by (...)로 묶는다. avg by (instance) (...)는 인스턴스(스크레이프 대상 하나, 여기서는 Node Exporter가 도는 서버 한 대)별로 평균을 내고, sum by (instance) (...)는 인스턴스별로 더하고, max by (instance,device) (...)는 인스턴스와 장치의 조합별로 가장 큰 값 하나만 남긴다. 디스크 메트릭에는 device(장치) 말고도 mountpoint(그 장치가 붙은 폴더 경로)와 fstype(파일시스템 종류) 라벨이 붙어서, 같은 장치가 폴더 두 곳에 붙어 있으면 같은 장치의 줄이 두 개 나온다. max by (instance,device)로 장치마다 한 줄로 줄여 같은 디스크를 두 번 세지 않는다.

CPU 사용률 식은 먼저 mode="idle"인 시간의 비율(쉬고 있던 비율)을 구한 뒤 100 - (...)로 뒤집어서 「쉬지 않고 일한 비율」, 즉 CPU 사용률을 만든다. 쉰 시간이 곧 비율이 되는 이유는 이렇다. CPU 하나는 1초 동안 최대 1초만 쉴 수 있으므로, 「쉰 시간이 초당 얼마나 늘었나」는 0과 1 사이의 값, 곧 쉰 비율이다. 여기에 100을 곱해 퍼센트로 만든다. Node Exporter는 이 숫자를 CPU(코어)마다 따로 내놓으므로(멀티코어), avg by (instance)가 한 서버의 코어들을 평균 내서 서버 하나에 값 하나로 만든다.

메모리 사용률 식은 전체 메모리(MemTotal)에서 아직 쓸 수 있는 메모리(MemAvailable)를 빼 「이미 쓴 메모리」를 구하고, 그걸 전체로 나눠 백분율로 만든다.

디스크 사용률 식은 파일시스템의 전체 크기(node_filesystem_size_bytes)에서 남은 크기(node_filesystem_free_bytes)를 빼 쓴 만큼을 구하고, 역시 전체로 나눠 백분율로 만든다. fstype!~"tmpfs|overlay"는 진짜 디스크가 아닌 가짜 파일시스템은 빼고 본다는 뜻이다. !~는 「뒤의 정규식에 맞지 않는 것만 남겨라」, 정규식 안의 |는 「또는」이다. tmpfs는 내용이 디스크가 아니라 메모리에 있는 파일시스템이고, overlay는 다른 파일시스템 두 개를 겹쳐 하나처럼 보여 주는 파일시스템이다.

네트워크 식은 수신(receive)·송신(transmit) 바이트 수가 초당 얼마나 늘어나는지를 rate로 구해 그대로 Bps(초당 바이트)로 쓴다. device 라벨은 네트워크 장치 이름이고, device!~"lo"는 자기 자신과 통신하는 루프백 장치(lo)는 빼고 본다는 뜻이다.


흐름 한눈에 보기

아래 그림의 마지막 칸 Grafana는 Prometheus에 쌓인 숫자를 그래프로 그려 보여 주는 화면 도구이고, 대시보드는 그 그래프 여러 개를 한 화면에 모은 것이다. 이 글에서는 Grafana를 설치하지 않는다.

flowchart LR A[리눅스 호스트
Node Exporter :9100]:::exporter --> B[Prometheus
스크레이프 job node]:::prometheus B --> C[PromQL 쿼리
node_cpu_seconds_total]:::query C --> D[Grafana 대시보드
CPU/메모리/디스크]:::dashboard classDef exporter fill:#1e293b,stroke:#10b981,color:#e2e8f0; classDef prometheus fill:#0f172a,stroke:#22d3ee,color:#e0f2fe; classDef query fill:#1e293b,stroke:#f59e0b,color:#fef3c7; classDef dashboard fill:#1e293b,stroke:#ec4899,color:#fce7f3;

결론/팁

  • Prometheus는 메트릭 저장소일 뿐, 호스트 메트릭을 직접 수집하지 않는다. Node Exporter를 설치해야 node_* 메트릭을 쓸 수 있다.
  • systemd 서비스로 등록하면 재부팅 후에도 자동 실행된다.
  • 포트를 바꾸고 싶으면 --web.listen-address=:9101처럼 옵션을 주고, Prometheus 타깃도 동일 포트로 맞춘다.
  • 윈도우는 Node Exporter 대신 윈도우용 익스포터인 windows_exporter(wmi_exporter는 이 프로그램의 옛 이름이라, 지금은 windows_exporter만 기억하면 된다)를 설치하고 기본 포트 9182를 스크레이프한다.
  • 공식 가이드: Monitoring Linux host metrics with the Node Exporter

이제 Grafana에서 Node Exporter 메트릭을 패널(대시보드 안의 그래프 한 칸)로 만들어 CPU·메모리·디스크·네트워크 대시보드를 구성할 수 있다. 다음 단계로 cAdvisor(컨테이너 메트릭)와 Spring Boot Actuator(애플리케이션 메트릭) 연동을 이어가면 서버 · 컨테이너 · 애플리케이션을 모두 보는 풀스택 모니터링이 완성된다.


자신만의 철학을 만들어가는 중입니다.
최상단으로 이동했습니다!
확대 이미지

댓글남기기