(更新: 2026.03.05)

サーバー監視を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
  • psutil: CPU/メモリ/ディスク情報を取得するライブラリ
  • requests: Slack Webhook呼び出し用
  • 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行程度で十分だ。自分が必要な監視項目だけに絞ることで、アラートのノイズが少なく実用的な監視ができている。