0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Hybrid License System Day 25: 運用とモニタリング

Last updated at Posted at 2025-12-24

🎄 科学と神々株式会社 アドベントカレンダー 2025

Hybrid License System Day 25: 運用とモニタリング

統合・デプロイ編 (5/5) - 最終日!


🎉 はじめに

Day 25、ついに最終日です!運用とモニタリングについて学び、Hybrid License Systemを本番環境で安定稼働させる方法を理解しましょう。

25日間の集大成として、Prometheus統合、Grafanaダッシュボード、ログ集約、アラート設定を実装します。


📊 モニタリングの重要性

なぜモニタリングが必要なのか?

問題発生 → 検知 → 原因特定 → 対処 → 再発防止
     ↑                                    ↓
     └────────────────────────────────────┘
           モニタリングによる継続的改善

モニタリングの目的:

  1. 障害の早期検知: ユーザーが気づく前に問題を発見
  2. パフォーマンス最適化: ボトルネックの特定と改善
  3. 容量計画: スケーリングのタイミング判断
  4. SLA遵守: サービスレベル目標の達成確認

🔍 Four Golden Signals

Googleが提唱する、モニタリングで重視すべき4つの指標:

1. Latency(レイテンシー)

リクエストからレスポンスまでの時間

// Express.jsミドルウェアでレイテンシー計測
app.use((req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    const duration = Date.now() - start;

    // Prometheusメトリクス送信
    httpRequestDuration.observe(
      { method: req.method, route: req.route?.path, status_code: res.statusCode },
      duration / 1000  // 秒単位
    );

    console.log({
      method: req.method,
      path: req.path,
      statusCode: res.statusCode,
      duration: `${duration}ms`
    });
  });

  next();
});

目標値:

  • p50 (中央値): <25ms
  • p95: <50ms
  • p99: <100ms

2. Traffic(トラフィック)

システムへのリクエスト量

// リクエストカウンター
const httpRequestsTotal = new Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status_code']
});

app.use((req, res, next) => {
  res.on('finish', () => {
    httpRequestsTotal.inc({
      method: req.method,
      route: req.route?.path || req.path,
      status_code: res.statusCode
    });
  });

  next();
});

監視項目:

  • 1秒あたりのリクエスト数(RPS)
  • エンドポイント別のトラフィック分布
  • ピーク時間帯の特定

3. Errors(エラー)

失敗したリクエストの割合

// エラーレートカウンター
const httpErrorsTotal = new Counter({
  name: 'http_errors_total',
  help: 'Total number of HTTP errors',
  labelNames: ['method', 'route', 'status_code']
});

app.use((req, res, next) => {
  res.on('finish', () => {
    if (res.statusCode >= 400) {
      httpErrorsTotal.inc({
        method: req.method,
        route: req.route?.path || req.path,
        status_code: res.statusCode
      });
    }
  });

  next();
});

目標値:

  • エラーレート: <0.1%(1000リクエストに1回以下)
  • 4xxエラー: クライアント起因(バリデーション改善)
  • 5xxエラー: サーバー起因(即座に対処)

4. Saturation(飽和度)

システムリソースの使用率

// CPU・メモリ使用率
const processMemoryUsage = new Gauge({
  name: 'process_memory_usage_bytes',
  help: 'Memory usage in bytes'
});

const processCpuUsage = new Gauge({
  name: 'process_cpu_usage_percent',
  help: 'CPU usage percentage'
});

setInterval(() => {
  const memUsage = process.memoryUsage();
  processMemoryUsage.set(memUsage.heapUsed);

  const cpuUsage = process.cpuUsage();
  processCpuUsage.set(
    (cpuUsage.user + cpuUsage.system) / 1000000  // マイクロ秒を秒に変換
  );
}, 10000);  // 10秒ごと

目標値:

  • CPU使用率: <70%
  • メモリ使用率: <80%
  • ディスクI/O: <80%

📈 Prometheus統合

Prometheusとは?

Prometheusは、オープンソースの監視・アラートシステムです。時系列データベースを内蔵し、メトリクス収集とクエリが可能です。

Auth Serviceへのプロメテウス統合

// auth-service/src/standalone.js
const promClient = require('prom-client');

// デフォルトメトリクス収集(CPU、メモリなど)
promClient.collectDefaultMetrics({
  timeout: 5000,
  prefix: 'auth_service_'
});

// カスタムメトリクス定義
const httpRequestDuration = new promClient.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.001, 0.01, 0.1, 0.5, 1, 2.5, 5, 10]
});

const licenseActivations = new promClient.Counter({
  name: 'license_activations_total',
  help: 'Total number of license activations',
  labelNames: ['plan', 'status']
});

const licenseValidations = new promClient.Counter({
  name: 'license_validations_total',
  help: 'Total number of license validations',
  labelNames: ['status']
});

// メトリクスエンドポイント
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', promClient.register.contentType);
  res.end(await promClient.register.metrics());
});

// ライセンスアクティベーションでメトリクス記録
app.post('/activate', async (req, res) => {
  const start = Date.now();

  try {
    const result = await authService.activate(email, password, clientId);

    licenseActivations.inc({ plan: result.license.plan, status: 'success' });
    httpRequestDuration.observe(
      { method: 'POST', route: '/activate', status_code: 200 },
      (Date.now() - start) / 1000
    );

    res.status(200).json(result);
  } catch (error) {
    licenseActivations.inc({ plan: 'unknown', status: 'failure' });
    httpRequestDuration.observe(
      { method: 'POST', route: '/activate', status_code: 401 },
      (Date.now() - start) / 1000
    );

    res.status(401).json({ error: error.message });
  }
});

Prometheus設定ファイル

# prometheus.yml
global:
  scrape_interval: 15s      # 15秒ごとにメトリクス収集
  evaluation_interval: 15s   # 15秒ごとにルール評価

scrape_configs:
  # API Gateway
  - job_name: 'api-gateway'
    static_configs:
      - targets: ['api-gateway:3000']
    metrics_path: '/metrics'

  # Auth Service
  - job_name: 'auth-service'
    static_configs:
      - targets: ['auth-service:3001']
    metrics_path: '/metrics'

  # Admin Service
  - job_name: 'admin-service'
    static_configs:
      - targets: ['admin-service:3002']
    metrics_path: '/metrics'

# アラートルール
rule_files:
  - '/etc/prometheus/alerts.yml'

# Alertmanager設定
alerting:
  alertmanagers:
    - static_configs:
        - targets: ['alertmanager:9093']

アラートルール

# alerts.yml
groups:
  - name: license_system_alerts
    interval: 30s
    rules:
      # 高いエラーレート
      - alert: HighErrorRate
        expr: rate(http_errors_total[5m]) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High error rate detected"
          description: "Error rate is {{ $value | humanizePercentage }} over the last 5 minutes"

      # 高いレスポンスタイム
      - alert: HighLatency
        expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High latency detected"
          description: "p99 latency is {{ $value }}s over the last 5 minutes"

      # サービスダウン
      - alert: ServiceDown
        expr: up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Service {{ $labels.job }} is down"
          description: "Service {{ $labels.job }} has been down for more than 1 minute"

      # 高いメモリ使用率
      - alert: HighMemoryUsage
        expr: (process_memory_usage_bytes / process_memory_limit_bytes) > 0.9
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High memory usage detected"
          description: "Memory usage is {{ $value | humanizePercentage }} of the limit"

📊 Grafanaダッシュボード

Grafanaとは?

Grafanaは、Prometheusなどのデータソースからメトリクスを可視化するダッシュボードツールです。

ダッシュボード構成例

{
  "dashboard": {
    "title": "Hybrid License System - Overview",
    "panels": [
      {
        "title": "Request Rate (RPS)",
        "targets": [
          {
            "expr": "rate(http_requests_total[1m])",
            "legendFormat": "{{job}} - {{method}} {{route}}"
          }
        ],
        "type": "graph"
      },
      {
        "title": "Response Time (p95, p99)",
        "targets": [
          {
            "expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))",
            "legendFormat": "p95"
          },
          {
            "expr": "histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))",
            "legendFormat": "p99"
          }
        ],
        "type": "graph"
      },
      {
        "title": "Error Rate",
        "targets": [
          {
            "expr": "rate(http_errors_total[5m]) / rate(http_requests_total[5m])",
            "legendFormat": "{{job}}"
          }
        ],
        "type": "graph"
      },
      {
        "title": "License Activations",
        "targets": [
          {
            "expr": "license_activations_total",
            "legendFormat": "{{plan}} - {{status}}"
          }
        ],
        "type": "graph"
      },
      {
        "title": "Memory Usage",
        "targets": [
          {
            "expr": "process_memory_usage_bytes",
            "legendFormat": "{{job}}"
          }
        ],
        "type": "graph"
      }
    ]
  }
}

📝 ログ集約(ELKスタック)

ELKスタックとは?

ELK = Elasticsearch + Logstash + Kibana

  1. Elasticsearch: ログデータの保存・検索エンジン
  2. Logstash: ログ収集・変換・転送
  3. Kibana: ログ可視化・分析UI

構造化ログの実装

// auth-service/src/logger.js
const winston = require('winston');

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: {
    service: 'auth-service',
    version: '1.0.0'
  },
  transports: [
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple()
      )
    }),
    new winston.transports.File({
      filename: '/var/log/auth-service/error.log',
      level: 'error'
    }),
    new winston.transports.File({
      filename: '/var/log/auth-service/combined.log'
    })
  ]
});

// ログ出力例
logger.info('License activation started', {
  email: 'user@example.com',
  clientId: 'client-123',
  plan: 'free'
});

logger.error('License activation failed', {
  email: 'user@example.com',
  clientId: 'client-123',
  error: error.message,
  stack: error.stack
});

Logstash設定

# logstash.conf
input {
  file {
    path => "/var/log/*/combined.log"
    codec => "json"
  }
}

filter {
  if [level] == "error" {
    mutate {
      add_tag => ["error"]
    }
  }
}

output {
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    index => "license-system-%{+YYYY.MM.dd}"
  }

  # 重要なエラーは即座にアラート
  if "error" in [tags] and [service] == "auth-service" {
    email {
      to => "ops@example.com"
      subject => "Auth Service Error"
      body => "%{message}"
    }
  }
}

🚨 アラート設定

Alertmanager設定

# alertmanager.yml
global:
  smtp_smarthost: 'smtp.gmail.com:587'
  smtp_from: 'alerts@example.com'
  smtp_auth_username: 'alerts@example.com'
  smtp_auth_password: 'password'

route:
  receiver: 'team-email'
  group_by: ['alertname', 'severity']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 1h

  routes:
    - match:
        severity: critical
      receiver: 'pagerduty'

    - match:
        severity: warning
      receiver: 'team-slack'

receivers:
  - name: 'team-email'
    email_configs:
      - to: 'team@example.com'

  - name: 'team-slack'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/...'
        channel: '#alerts'
        title: '{{ .GroupLabels.alertname }}'
        text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'

  - name: 'pagerduty'
    pagerduty_configs:
      - service_key: 'your-pagerduty-key'

🎓 運用ベストプラクティス

1. 定期的なヘルスチェック

# cronで5分ごとにヘルスチェック
*/5 * * * * curl -f http://localhost:3000/health || echo "API Gateway is down!" | mail -s "Alert" ops@example.com

2. 自動バックアップ

# 毎日深夜にデータベースバックアップ
0 0 * * * docker run --rm -v hybrid_database-data:/data -v /backup:/backup alpine tar czf /backup/db-$(date +\%Y\%m\%d).tar.gz /data

3. ログローテーション

# /etc/logrotate.d/license-system
/var/log/license-system/*.log {
  daily
  rotate 30
  compress
  delaycompress
  notifempty
  create 0640 nodejs nodejs
  sharedscripts
  postrotate
    docker-compose restart
  endscript
}

4. セキュリティアップデート

# 週次でセキュリティアップデート
0 2 * * 0 docker-compose pull && docker-compose up -d

🎉 25日間の総まとめ

完成したシステム

✅ API Gateway (Express.js)
   - ルーティング・プロキシ
   - レート制限
   - CORS・セキュリティヘッダー

✅ Auth Service (Node.js)
   - ライセンスアクティベーション・検証
   - JWT生成・検証
   - BCryptパスワードハッシング

✅ Admin Service (Express.js + React)
   - 管理ダッシュボード
   - ユーザー・ライセンス管理
   - 統計API

✅ インフラ
   - Docker Compose設定
   - Prometheus/Grafana監視
   - ELKログ集約
   - アラート設定

学んだスキル

  1. マイクロサービスアーキテクチャ - 設計原則とパターン
  2. API Gateway実装 - Express.jsでのルーティング・セキュリティ
  3. 認証・認可 - JWT/BCryptの実装
  4. データベース設計 - SQLite + better-sqlite3
  5. Docker/Kubernetes - コンテナ化とオーケストレーション
  6. テスト - 統合テスト・E2Eテスト
  7. CI/CD - 自動化パイプライン
  8. 運用・監視 - Prometheus/Grafana/ELK

🚀 今後の展開

短期(1-2週間)

  • CORS テスト修正
  • Dockerイメージの最適化
  • CI/CD パイプライン構築

中期(1-2ヶ月)

  • Kubernetes対応
  • 管理ダッシュボードUI完成
  • WebAssemblyクライアント実装

長期(3-6ヶ月)

  • マルチリージョン対応
  • リアルタイム通知(WebSocket)
  • オフラインライセンス検証

🙏 おわりに

25日間のアドベントカレンダー、お疲れさまでした!

マイクロサービスアーキテクチャによる商用グレードのライセンス認証システムを、設計から実装、デプロイ、運用まで一通り学びました。

このシステムは実際の本番環境でも使用できる品質を目指して設計されています。ぜひご自身のプロジェクトに応用してみてください。

Happy Coding! 🎉


🔗 関連リンク


全25日間、ありがとうございました!🎄

Copyright © 2025 Gods & Golem, Inc. All rights reserved.

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?