サーバー監視をPythonスクリプトで自作してみた話
VPSでWordPressを動かし始めてから、「サーバーの状態を常に把握したい」と思うようになった。
DatadogやNew Relicは高機能だが、個人のVPS一台のために月額料金を払うのは気が引ける。Zabbixは高機能すぎて構築が大変。Prometheus + Grafanaも同様だ。
だったら、必要な監視だけPythonで自作してSlackに通知すればいい。そう決めて作ったのが今回紹介するスクリプトだ。
監視したい項目
シンプルに3点に絞った。
1. CPU使用率: 80%以上が5分続いたらアラート
2. メモリ使用率: 85%以上でアラート
3. ディスク使用率: 90%以上でアラート
これだけでも、プロセスが暴走していたり、ディスクが満杯でWordPressが壊れたりするのを事前に検知できる。
依存パッケージ
pip install psutil requests python-dotenv
スクリプト全体
#!/usr/bin/env python3
"""
server_monitor.py - サーバー基本監視 + Slack通知
"""
import os
import time
import logging
from datetime import datetime
from typing import Optional
import psutil
import requests
from dotenv import load_dotenv
load_dotenv()
# 設定
SLACK_WEBHOOK_URL = os.environ['SLACK_WEBHOOK_URL']
CHECK_INTERVAL_SEC = 60 # チェック間隔(秒)
CPU_THRESHOLD = 80.0 # CPU警告しきい値(%)
MEMORY_THRESHOLD = 85.0 # メモリ警告しきい値(%)
DISK_THRESHOLD = 90.0 # ディスク警告しきい値(%)
CPU_SUSTAINED_COUNT = 5 # CPU高負荷が何回連続でアラートを出すか
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s'
)
logger = logging.getLogger(__name__)
def get_cpu_usage() -> float:
"""CPU使用率(%)を返す。interval=1でより正確な値を取得。"""
return psutil.cpu_percent(interval=1)
def get_memory_usage() -> dict:
"""メモリ使用状況を返す。"""
mem = psutil.virtual_memory()
return {
'total_gb': mem.total / (1024 ** 3),
'used_gb': mem.used / (1024 ** 3),
'percent': mem.percent,
}
def get_disk_usage(path: str = '/') -> dict:
"""ディスク使用状況を返す。"""
disk = psutil.disk_usage(path)
return {
'total_gb': disk.total / (1024 ** 3),
'used_gb': disk.used / (1024 ** 3),
'percent': disk.percent,
}
def send_slack_alert(message: str, level: str = 'warning') -> bool:
"""Slack Webhookにアラートを送信する。"""
emoji = {'warning': ':warning:', 'critical': ':rotating_light:', 'ok': ':white_check_mark:'}
color = {'warning': '#FFA500', 'critical': '#FF0000', 'ok': '#36a64f'}
payload = {
'attachments': [{
'color': color.get(level, '#FFA500'),
'text': f"{emoji.get(level, '')} {message}",
'footer': f"server-monitor | {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
}]
}
try:
res = requests.post(SLACK_WEBHOOK_URL, json=payload, timeout=10)
res.raise_for_status()
return True
except requests.RequestException as e:
logger.error(f"Slack通知失敗: {e}")
return False
def format_alert_message(metric: str, value: float, threshold: float, unit: str = '%') -> str:
return f"[{metric}] 現在値: {value:.1f}{unit} (しきい値: {threshold:.0f}{unit})"
def monitor_loop():
"""監視ループのメインロジック。"""
cpu_high_count = 0
last_disk_alert_time: Optional[float] = None
last_memory_alert_time: Optional[float] = None
alert_cooldown_sec = 1800 # 同じアラートを30分以内に再送しない
logger.info("サーバー監視を開始します")
send_slack_alert("サーバー監視を開始しました", level='ok')
while True:
now = time.time()
# CPU監視
cpu = get_cpu_usage()
if cpu >= CPU_THRESHOLD:
cpu_high_count += 1
logger.warning(f"CPU高負荷: {cpu:.1f}% ({cpu_high_count}回連続)")
if cpu_high_count >= CPU_SUSTAINED_COUNT:
send_slack_alert(
format_alert_message('CPU使用率', cpu, CPU_THRESHOLD),
level='critical'
)
cpu_high_count = 0 # アラート送信後リセット
else:
if cpu_high_count > 0:
logger.info(f"CPU正常に戻りました: {cpu:.1f}%")
cpu_high_count = 0
# メモリ監視
mem = get_memory_usage()
if mem['percent'] >= MEMORY_THRESHOLD:
if last_memory_alert_time is None or (now - last_memory_alert_time) > alert_cooldown_sec:
msg = (
format_alert_message('メモリ使用率', mem['percent'], MEMORY_THRESHOLD)
+ f"\n使用量: {mem['used_gb']:.1f}GB / {mem['total_gb']:.1f}GB"
)
send_slack_alert(msg, level='warning')
last_memory_alert_time = now
logger.warning(f"メモリ高負荷: {mem['percent']:.1f}%")
# ディスク監視
disk = get_disk_usage('/')
if disk['percent'] >= DISK_THRESHOLD:
if last_disk_alert_time is None or (now - last_disk_alert_time) > alert_cooldown_sec:
msg = (
format_alert_message('ディスク使用率', disk['percent'], DISK_THRESHOLD)
+ f"\n使用量: {disk['used_gb']:.1f}GB / {disk['total_gb']:.1f}GB"
)
send_slack_alert(msg, level='critical')
last_disk_alert_time = now
logger.warning(f"ディスク残量少: {disk['percent']:.1f}%")
logger.info(
f"CPU: {cpu:.1f}% | "
f"Memory: {mem['percent']:.1f}% | "
f"Disk: {disk['percent']:.1f}%"
)
time.sleep(CHECK_INTERVAL_SEC)
if __name__ == '__main__':
monitor_loop()
環境変数の設定
# .env
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXX/YYY/ZZZ
Slack Webhookは、SlackのApp設定で「Incoming Webhooks」を有効にして取得する。
systemdで常駐化
スクリプトを常時起動し、サーバー再起動後も自動で立ち上がるようにする。
systemdのサービスファイル(server-monitor.service)を以下の内容で作成する:
[Unit]
Description=Server Monitor
After=network.target
[Service]
Type=simple
User=nakano
WorkingDirectory=/home/nakano/server-monitor
ExecStart=/home/nakano/server-monitor/venv/bin/python server_monitor.py
Restart=always
RestartSec=10
EnvironmentFile=/home/nakano/server-monitor/.env
[Install]
WantedBy=multi-user.target
$ sudo systemctl daemon-reload
$ sudo systemctl enable server-monitor
$ sudo systemctl start server-monitor
$ sudo systemctl status server-monitor
運用してみた感想
よかった点
本当に必要なアラートだけが来る。DatadogのデフォルトアラートはNoisy(うるさい)で、重要でない通知も大量に来る。自作だと完全にコントロールできる。
ディスク満杯を事前に検知できた。WordPressのアクセスログが膨れ上がりかけていたのをアラートで気づき、ログローテーションを設定した。監視がなければ確実に気づかなかった。
課題
プロセス監視がない。NginxやPHP-FPMが死んでいてもこのスクリプトでは検知できない。psutil.process_iter() でプロセスの存在確認を追加するのが次のステップだ。
HTTPレスポンス監視がない。サーバー自体は生きていてもWordPressが503を返しているケースは、外からHTTPリクエストを飛ばして確認する必要がある。
まとめ
psutil + requests + systemdの組み合わせで、個人VPS向けのシンプルな監視システムを自作できた。
高機能な監視ツールは不要で、「異常があればSlackに通知」というだけであればPython 50行程度で十分だ。自分が必要な監視項目だけに絞ることで、アラートのノイズが少なく実用的な監視ができている。