1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

月額100円の外形監視サービスをFastAPI+Stripe+Cloudflare Tunnelで自作した話

1
Posted at

はじめに

自宅サーバーで運用しているサービスが落ちても気づけない、という問題を抱えていました。

既存の監視サービスは多機能すぎて設定が複雑だったり、無料プランに制限が多かったりします。
「URLを入れるだけで監視できて、月100円で使える」ものが欲しくて、自作しました。

その名も Nafusaba Monitor です。


作ったもの

Nafusaba Monitor は、個人サイト・WordPress・自宅サーバー向けのシンプルな外形監視サービスです。

主な機能

  • HTTP/HTTPS 疎通監視(5分間隔)
  • SSL証明書の有効期限監視(30日前から段階的に通知)
  • 障害・復旧をメールで通知(変化時のみ、再通知なし)
  • 誤報防止フィルタ(2回連続失敗+外部疎通確認)
  • 登録・ログイン不要
  • 月額100円のみ

あえて実装しなかったもの

  • ユーザー登録・ログイン機能
  • Slack/Discord通知
  • ステータスページ
  • スマホアプリ

「シンプルに使えること」を最優先にしました。


技術スタック

役割 技術
バックエンド FastAPI + Uvicorn
データベース PostgreSQL 16
監視処理 APScheduler + httpx
決済 Stripe(PaymentIntent + 3Dセキュア)
メール 内部 Postfix(SMTPリレー)
公開 Cloudflare Tunnel
インフラ Docker Compose on Proxmox LXC

設計のポイント

誤報防止ロジック

自宅サーバーあるあるですが、監視サーバー自身がインターネット障害になっているのに通知が飛んでくるという問題があります。

これを防ぐために、障害メールの送信条件を3つに絞りました。

障害メールを送る条件(3つすべて必要)

1. 監視サーバー自身が外部に接続できている
   (Google・Cloudflareに到達可能)

2. 監視対象サイトへの接続が2回連続で失敗

3. 前回の監視結果が正常(UP)だった
async def _check_monitor(monitor: Monitor):
    # まず外部疎通確認
    internet_ok = await check_external_connectivity()
    if not internet_ok:
        return  # 自サーバーの障害なので通知しない

    result = await check_url(monitor.url)

    if not result.ok:
        monitor.consecutive_failures += 1
        # 2回連続失敗 かつ 前回がUPの場合のみ通知
        if monitor.consecutive_failures >= 2 and monitor.current_state == "UP":
            send_down_alert(monitor.email, ...)
    else:
        monitor.consecutive_successes += 1
        # 2回連続成功 かつ 前回がDOWNの場合のみ復旧通知
        if monitor.consecutive_successes >= 2 and monitor.current_state == "DOWN":
            send_recovery_alert(monitor.email, ...)

Cloudflare Tunnel で公開

自宅サーバーなのでグローバルIPが動的です。Let's Encrypt + Nginxという選択肢もありましたが、Cloudflare Tunnelを使うと:

  • ポート開放不要
  • SSL証明書の管理不要
  • DDNSの管理不要

設定はCloudflareダッシュボードでPublic Hostnameに web:8000 を指定するだけです。

# docker-compose.yml
services:
  web:
    build: .
    env_file: .env

  cloudflared:
    image: cloudflare/cloudflared:latest
    command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}
    depends_on:
      - web

Stripe で決済

個人サービスでの決済実装には Stripe を選びました。理由は:

  • 開業届なしで個人でも本番利用可能
  • 3Dセキュアが自動対応
  • PaymentIntent方式でカード情報がサーバーに触れない
def create_payment_intent(months: int, description: str):
    intent = stripe.PaymentIntent.create(
        amount=100 * months,  # 月額100円
        currency="jpy",
        description=description,
        automatic_payment_methods={"enabled": True},
    )
    return intent.client_secret

フロントエンドでStripe Elementsを使うことで、3Dセキュア認証も自動で処理されます。

SSRF防止

URLを受け取って外部にリクエストするサービスなので、内部ネットワークへのアクセスを防ぐ必要があります。

BLOCKED_NETWORKS = [
    ipaddress.ip_network("10.0.0.0/8"),
    ipaddress.ip_network("172.16.0.0/12"),
    ipaddress.ip_network("192.168.0.0/16"),
    ipaddress.ip_network("127.0.0.0/8"),
    ipaddress.ip_network("169.254.0.0/16"),  # AWSメタデータ等
]

def validate_url_for_monitoring(url: str) -> None:
    hostname = urlparse(url).hostname
    infos = socket.getaddrinfo(hostname, None)
    for info in infos:
        ip = ipaddress.ip_address(info[4][0])
        for network in BLOCKED_NETWORKS:
            if ip in network:
                raise HTTPException(400, "プライベートIPへのアクセスは登録できません。")

DNS解決後のIPを検証することで、DNSリバインディング攻撃も防いでいます。


申し込みフローの設計

「登録不要」を実現するために、メールアドレスの認証をトークンで代替しています。

① URL・メール入力 → 疎通テスト(必須)
② 確認メール送信(itsdangeroused 署名付きトークン、1時間有効)
③ メール内リンクをクリック → 決済ページへリダイレクト
④ Stripe決済(3Dセキュア)
⑤ 監視ID発行(MON-XXXX-XXXX形式)・監視開始

アカウントを作らなくてもメール認証で本人確認ができるため、ユーザーの離脱ポイントを減らせます。


ハマったポイント

Proxmox LXC で Docker を動かす

非特権コンテナのデフォルト設定ではDockerが起動しませんでした。

# /etc/pve/lxc/<CTID>.conf に追記
features: keyctl=1,nesting=1

nesting=1 がないとDockerのネームスペース機能が使えません。

データベースURLの @ エンコード

パスワードに @ が含まれると、URLパーサーがホスト名を誤認識します。

# NG
DATABASE_URL=postgresql+asyncpg://user:pass@word@db:5432/monitor

# OK(@をURLエンコード)
DATABASE_URL=postgresql+asyncpg://user:pass%40word@db:5432/monitor

app/static ディレクトリの存在確認

FastAPIのStaticFilesは起動時にディレクトリの存在を確認します。ディレクトリがないと起動クラッシュします。

mkdir -p app/static/css app/static/js

Dockerfileに含めるか、docker-compose.ymlのvolumes設定前に作成が必要です。


使い方

申し込み

  1. kanshi.nafusaba.com/register にアクセス
  2. 監視対象URLを入力して疎通テストを実行(必須)
  3. 通知先メールアドレスと監視期間を入力
  4. 注意事項に同意して確認メールを送信
  5. メール内のリンクからStripeで決済(月額100円〜)
  6. 監視ID(MON-XXXX-XXXX形式)が発行されて即時開始

通知のタイミング

障害発生時:サイトダウンをメールで即通知
復旧時:復旧をメールで通知
SSL期限:30日前・14日前・7日前・3日前・当日に通知
監視期限:期限7日前に延長案内を送信

障害が継続中でも再通知しません。変化があったときだけ届くので、アラート疲れがありません。

期間延長

kanshi.nafusaba.com/extend から監視IDと登録URLで延長できます。自動更新はないため、必要なときだけ延長する運用ができます。


料金

プラン 料金
1ヶ月 ¥100
3ヶ月 ¥300
12ヶ月 ¥1,200

1監視対象あたりの料金です。自動更新なし、返金不可。
1ヵ月~12ヵ月まで選択可能です。


おわりに

自宅サーバー趣味勢として「自分が欲しいものを作る」を実践したら、思ったより実用的なサービスになりました。

FastAPI + Stripe + Cloudflare Tunnelの組み合わせは、個人開発でマネタイズまで持っていくのにちょうどいいスタックだと感じています。特にCloudflare Tunnelは自宅サーバー公開の複雑さを大幅に下げてくれました。
これからもっといいサービスにしていければと思います。

同じように「自宅サーバーを監視したい」という方にぜひ使ってみてください。

▼ Nafusaba Monitor
https://kanshi.nafusaba.com


Nafusabaは自宅サーバーリソースのレンタルやネットワーク構築をしている個人ブランドです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?