이번에는 muabow.com/info에 라즈베리파이 하드웨어 대시보드를 붙였다.
목표는 단순했다.
- Go로 라즈베리파이의 현재 상태를 수집한다.
- 수집한 지표를 NATS에 발행한다.
- 웹 브라우저는 그 지표를 실시간으로 받아서 대시보드로 보여준다.
- 누구나 바로 열어볼 수 있는 공개 상태판으로 둔다.
즉 이번 작업은 Go + NATS + Server-Sent Events를 이용한 작은 실시간 관측 화면이다.
왜 Go로 만들었는가
시스템 상태를 읽는 작은 데몬은 Go와 잘 맞는다.
Go는 단일 바이너리로 빌드하기 쉽고, 라즈베리파이 같은 작은 서버에서도 부담이 적다.
또 /proc, systemctl, 파일시스템 상태 같은 리눅스 정보를 읽어 웹 서버로 내보내기에도 충분히 단순하다.
이번에는 라즈베리파이에 공식 Go tarball을 설치했다.
Go 1.26.3
linux/arm64
소스는 Go 프로젝트 디렉터리로 분리해 관리했다.
전체 구조
이번 대시보드의 흐름은 이렇다.
Go collector
-> NATS subject
-> Go web server subscriber
-> SSE /info/events
-> Browser dashboard
처음에는 Go 웹 서버가 직접 지표를 읽어도 됐지만, 일부러 NATS를 사이에 넣었다.
이유는 앞으로 확장하기 좋기 때문이다.
- 다른 앱도 같은 지표를 구독할 수 있다.
- 알림이나 로그 저장 같은 기능을 나중에 붙이기 쉽다.
- 대시보드와 수집 흐름을 느슨하게 분리할 수 있다.
수집하는 정보
현재 Go 앱은 아래 정보를 수집한다.
- CPU 사용률
- CPU 코어 수
- 메모리 사용량
- 디스크 사용량
- load average
- uptime
- 장치 온도
- 주요 서비스 상태
서비스 상태는 현재 아래 항목을 본다.
서비스 상태는 공개 서비스와 내부 앱 몇 개를 함께 본다.
리눅스 정보를 어떻게 읽는가
대부분의 값은 리눅스 표준 파일에서 읽는다.
CPU는 /proc/stat을 두 번 읽어 짧은 간격의 차이를 계산한다.
메모리는 /proc/meminfo에서 MemTotal, MemAvailable을 읽는다.
부하는 /proc/loadavg를 읽는다.
업타임은 /proc/uptime을 읽는다.
온도는 라즈베리파이에서 흔히 쓰는 아래 경로를 확인한다.
/sys/class/thermal/thermal_zone0/temp
/sys/class/hwmon/hwmon0/temp1_input
디스크는 Go의 syscall.Statfs로 / 파일시스템 상태를 읽는다.
NATS로 발행하는 방식
Go 앱은 2초마다 상태를 수집해서 NATS에 JSON으로 발행한다.
subject는 다음과 같다.
metrics subject
실제로 확인한 메시지는 이런 형태다.
{
"hostname": "muabow",
"os": "linux",
"arch": "arm64",
"cpu": {
"usagePercent": 0,
"cores": 4
},
"memory": {
"usedPercent": 16.26
},
"disk": {
"usedPercent": 16.81
},
"temperatureC": 52.35
}
NATS CLI로도 직접 구독 확인을 했다.
nats sub <metrics-subject> --count 1
메시지가 정상적으로 들어오는 것을 확인했다.
브라우저에는 어떻게 실시간으로 뿌리는가
브라우저에는 Server-Sent Events를 사용했다.
대시보드 페이지가 열리면 브라우저는 아래 경로에 연결한다.
/info/events
Go 서버는 NATS에서 받은 최신 지표를 SSE 이벤트로 흘려준다.
브라우저는 EventSource로 이 이벤트를 받아 화면 값을 갱신한다.
이 방식은 WebSocket보다 단순하고, 지금처럼 서버에서 브라우저로 상태를 계속 밀어주는 화면에 잘 맞는다.
지금은 공개 상태판으로 운영한다
처음에는 /info를 사진첩 관리자와 같은 비밀번호로 묶어두었다.
그런데 이 페이지는 관리자 패널보다는 사이트 안의 공개 기능에 가깝다.
그래서 지금은 로그인 없이 바로 열 수 있는 상태판으로 바꾸었다.
브라우저가 EventSource로 /info/events에 바로 연결해 실시간 값을 받도록 두었기 때문에, 홈의 기능 > 장치 정보 버튼을 누르면 곧바로 상태를 볼 수 있다.
nginx 연결
Go 앱은 라즈베리파이 내부에서만 열린다.
내부 루프백 주소의 앱 포트
nginx는 /info와 방문자 수 집계를 위한 /api/visits 요청을 Go 앱으로 프록시한다.
location /api/visits {
proxy_pass http://127.0.0.1:<internal-app-port>;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /info {
proxy_pass http://127.0.0.1:<internal-app-port>;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
}
proxy_buffering off는 SSE처럼 실시간으로 흘러가는 응답에 중요하다.
systemd 서비스
대시보드는 systemd 서비스로 등록했다.
dashboard.service
서비스는 nats.service 이후에 뜨도록 설정했다.
After=network-online.target nats.service
Wants=network-online.target nats.service
현재 상태는 enabled, active다.
확인 명령은 다음과 같다.
sudo systemctl status <dashboard-service>
sudo journalctl -u <dashboard-service> --no-pager -n 100
접속 주소
대시보드는 아래 주소에서 볼 수 있다.
https://muabow.com/info
이제는 로그인 없이 CPU, 메모리, 디스크, 온도, 서비스 상태가 실시간으로 갱신된다.
같은 Go 서비스는 사이트 footer에 붙인 방문자 수 API도 함께 맡고 있다.
https://muabow.com/api/visits
이번 구조의 장점
가장 마음에 드는 점은 구조가 작지만 확장 가능하다는 것이다.
- Go는 수집과 대시보드 서버를 담당한다.
- NATS는 지표 메시지의 통로가 된다.
- 브라우저는 SSE로 실시간 값을 받는다.
- nginx는
/info와/api/visits를 내부 앱으로 넘긴다.
지금은 하드웨어 대시보드지만, 나중에는 NATS에 흐르는 다른 이벤트도 같은 방식으로 보여줄 수 있다.
예를 들면:
- 사진 업로드 이벤트
- 글 배포 이벤트
- 백업 완료 이벤트
- 서비스 재시작 이벤트
이렇게 되면 /info는 단순 상태판을 넘어, 라즈베리파이의 작은 관제 화면이 될 수 있다.
정리
이번 작업으로 라즈베리파이에 실시간 하드웨어 대시보드가 생겼다.
핵심은 단순하다.
- Go 설치
- 장치 상태 수집 앱 작성
- NATS에 지표 발행
- 브라우저에 SSE로 실시간 전달
/info에 nginx 프록시 연결- 공개 상태판으로 정리하고 사이트 기능과 연결
웹사이트가 조금씩 정적 페이지를 넘어, 작은 개인 서버 운영 도구로 자라나는 중이다.