Skip to content

Latest commit

 

History

History

web

README.md

🚀 StatusGuard - 分散死活監視システム

📋 概要

StatusGuardは高可用性を実現する分散型の死活監視プラットフォームです。複数のエージェントノードによる冗長化と多数決による合意アルゴリズムにより、単一障害点を排除し、信頼性の高い監視を提供します。

🏗️ システム構成

🖥️ コントローラーノード(Web UI/API)

  • 役割: 管理画面、設定管理、結果表示
  • 技術: AdonisJS + Inertia.js + Vue.js
  • データベース: PostgreSQL(マスターデータ)
  • 認証: GitHub OAuth

🔍 エージェントノード(監視実行)

  • 役割: 実際の死活監視処理を実行
  • 配置: 複数リージョン、複数プロバイダー対応
  • プロトコル: HTTP/HTTPS, TCP, ICMP Ping, DNS
  • レポート: JSON形式でコントローラーに送信

📊 合意メカニズム

  • 判定方式: 過半数による多数決(Byzantine Fault Tolerance)
  • タイムウィンドウ: 設定可能(デフォルト5分)
  • 最小エージェント数: 3ノード以上推奨
  • 分散アルゴリズム: Modified Raft Consensus

🛠️ 主要機能

📈 監視機能

  • HTTP/HTTPS監視: ステータスコード、レスポンス時間、コンテンツ検証
  • TCP監視: ポート接続性、レスポンス時間
  • ICMP監視: Ping到達性、パケットロス測定
  • DNS監視: 名前解決、DNSレコード検証
  • SSL証明書監視: 有効期限、証明書チェーン検証

🚨 アラート機能

  • 通知チャンネル: Slack, Discord, メール, Webhook
  • エスカレーション: 段階的通知設定
  • メンテナンス: 計画停止時の通知抑制
  • インシデント管理: 障害履歴、復旧時間記録

📊 可視化・レポート

  • リアルタイム監視ダッシュボード
  • SLA レポート: 稼働率、MTTR、MTBF
  • パフォーマンストレンド: レスポンス時間推移
  • 公開ステータスページ: カスタマイズ可能

🔧 管理機能

  • マルチテナント: プロジェクト単位の分離
  • チーム管理: ロールベースアクセス制御
  • API: RESTful API + Webhook
  • 設定テンプレート: 監視設定の一括適用

🌐 分散アーキテクチャ詳細

エージェント配置戦略

リージョン1: agent-tokyo-1, agent-tokyo-2
リージョン2: agent-oregon-1, agent-oregon-2  
リージョン3: agent-ireland-1, agent-ireland-2

合意プロセス

  1. 監視実行: 各エージェントが独立して監視実行
  2. 結果収集: 設定されたタイムウィンドウ内の結果を収集
  3. 多数決判定: Byzantine Fault Toleranceアルゴリズムで最終判定
  4. 状態更新: 合意結果をマスターDBに記録
  5. 通知実行: 状態変化時にアラート送信

耐障害性

  • ネットワーク分断: 各リージョンで独立動作可能
  • エージェント障害: 最小限必要数での継続監視
  • データベース冗長化: レプリケーション + 自動フェイルオーバー
  • ロードバランシング: 複数コントローラーノード対応

📋 技術仕様

フロントエンド

  • Vue.js 3 + TypeScript
  • Inertia.js (SPA風体験)
  • Tailwind CSS (レスポンシブデザイン)
  • Chart.js (グラフ表示)

バックエンド

  • AdonisJS 6 + TypeScript
  • Lucid ORM (PostgreSQL)
  • Bull Queue (非同期処理)
  • Redis (キャッシュ・セッション)

インフラ

  • Docker Compose (開発環境)
  • Kubernetes (本番環境)
  • PostgreSQL 15+ (プライマリDB)
  • Redis 7+ (キャッシュ・Queue)

監視エージェント

  • Node.js + TypeScript
  • Prometheus metrics準拠
  • gRPC通信 (コントローラー間)
  • Docker化対応

🧠 合意アルゴリズム詳細設計

🎯 アルゴリズム概要

StatusGuardでは、Modified Raftベースの合意アルゴリズムにByzantine Fault Tolerance要素を組み込んだハイブリッド型合意メカニズムを採用。

📊 合意プロセスフロー

Phase 1: 監視実行 (Monitor Execution)

interface MonitorCheck {
  checkId: string;           // UUID v4
  serviceId: string;
  agentId: string;
  timestamp: number;         // Unix timestamp
  result: {
    status: 'up' | 'down' | 'degraded';
    responseTime: number;    // ミリ秒
    statusCode?: number;     // HTTP用
    errorMessage?: string;
    metadata: Record<string, any>;
  };
  signature: string;         // HMAC-SHA256署名
}

Phase 2: 結果収集 (Result Collection)

  • タイムウィンドウ: 設定可能(30秒〜10分)
  • 収集条件: 各エージェントから最新1件ずつ
  • 最小必要数: ceil(totalAgents / 2) + 1
  • タイムアウト: タイムウィンドウの80%時点で強制判定

Phase 3: 合意判定 (Consensus Decision)

3-1. 署名検証フェーズ
function verifyAgentSignature(check: MonitorCheck): boolean {
  const payload = `${check.checkId}:${check.result.status}:${check.timestamp}`;
  const expectedSignature = hmacSha256(payload, agentSecretKey);
  return check.signature === expectedSignature;
}
3-2. 重み付き投票フェーズ
interface AgentWeight {
  agentId: string;
  reliability: number;      // 0.0-1.0 (過去の成功率)
  networkLatency: number;   // RTT (ms)
  region: string;
  weight: number;           // 計算後重み
}

function calculateWeight(agent: AgentWeight): number {
  const reliabilityFactor = agent.reliability;
  const latencyFactor = Math.max(0.1, 1 - (agent.networkLatency / 1000));
  const regionFactor = getRegionDiversityBonus(agent.region);
  
  return reliabilityFactor * latencyFactor * regionFactor;
}
3-3. Byzantine対応フェーズ
interface ConsensusVote {
  status: 'up' | 'down' | 'degraded';
  weight: number;
  confidence: number;       // 0.0-1.0
}

function byzantineConsensus(votes: ConsensusVote[]): ConsensusResult {
  // ビザンチン障害ノード検出
  const outliers = detectOutliers(votes);
  const validVotes = votes.filter(v => !outliers.includes(v));
  
  // 重み付き多数決
  const weightedResults = calculateWeightedMajority(validVotes);
  
  // 信頼度閾値チェック
  const finalResult = validateConfidenceThreshold(weightedResults);
  
  return finalResult;
}

🔧 実装アルゴリズム

Modified Raft Leader Election

class ConsensusCoordinator {
  private currentTerm: number = 0;
  private votedFor: string | null = null;
  private state: 'follower' | 'candidate' | 'leader' = 'follower';
  
  async electLeader(): Promise<string> {
    // 1. タームインクリメント
    this.currentTerm++;
    this.state = 'candidate';
    this.votedFor = this.agentId;
    
    // 2. 他ノードに投票要求
    const votes = await this.requestVotes();
    
    // 3. 過半数取得チェック
    if (votes.length > Math.floor(this.totalAgents / 2)) {
      this.state = 'leader';
      return this.agentId;
    }
    
    this.state = 'follower';
    return null;
  }
}

Consensus Decision Matrix

interface DecisionMatrix {
  up: { weight: number; confidence: number; agents: string[] };
  down: { weight: number; confidence: number; agents: string[] };
  degraded: { weight: number; confidence: number; agents: string[] };
}

function buildDecisionMatrix(checks: MonitorCheck[]): DecisionMatrix {
  const matrix: DecisionMatrix = {
    up: { weight: 0, confidence: 0, agents: [] },
    down: { weight: 0, confidence: 0, agents: [] },
    degraded: { weight: 0, confidence: 0, agents: [] }
  };
  
  for (const check of checks) {
    const agent = getAgentInfo(check.agentId);
    const weight = calculateWeight(agent);
    const confidence = calculateConfidence(check);
    
    matrix[check.result.status].weight += weight;
    matrix[check.result.status].confidence += confidence * weight;
    matrix[check.result.status].agents.push(check.agentId);
  }
  
  return matrix;
}

🛡️ 耐障害性メカニズム

Split-Brain Prevention

function preventSplitBrain(activeAgents: Agent[]): boolean {
  const totalWeight = activeAgents.reduce((sum, agent) => sum + agent.weight, 0);
  const requiredWeight = this.originalTotalWeight * 0.51; // 51%閾値
  
  return totalWeight >= requiredWeight;
}

Network Partition Handling

interface PartitionStrategy {
  strategy: 'majority' | 'regional' | 'manual';
  fallbackBehavior: 'conservative' | 'optimistic' | 'last_known';
  partitionTimeout: number; // 秒
}

function handleNetworkPartition(partition: Agent[]): ConsensusResult {
  if (partition.length < this.minimumQuorum) {
    // 保守的判定: 前回の状態を維持
    return { status: 'unknown', fallback: true };
  }
  
  // 部分的合意での判定継続
  return performPartialConsensus(partition);
}

📈 パフォーマンス最適化

Consensus Caching

interface ConsensusCache {
  serviceId: string;
  lastConsensus: ConsensusResult;
  timestamp: number;
  participatingAgents: string[];
  cacheValidityMs: number;
}

function getCachedConsensus(serviceId: string): ConsensusResult | null {
  const cached = this.consensusCache.get(serviceId);
  if (!cached) return null;
  
  const age = Date.now() - cached.timestamp;
  if (age > cached.cacheValidityMs) return null;
  
  return cached.lastConsensus;
}

Batched Processing

interface ConsensusBatch {
  batchId: string;
  services: string[];
  scheduledAt: number;
  maxBatchSize: number;
  processingTimeMs: number;
}

async function processBatchConsensus(batch: ConsensusBatch): Promise<void> {
  const promises = batch.services.map(serviceId => 
    this.performConsensus(serviceId)
  );
  
  await Promise.allSettled(promises);
}

🔍 デバッグ・監査機能

Consensus Audit Trail

interface ConsensusAudit {
  consensusId: string;
  serviceId: string;
  timestamp: number;
  participatingAgents: AgentVote[];
  decisionMatrix: DecisionMatrix;
  finalResult: ConsensusResult;
  processingTimeMs: number;
  byzantineFlags: string[];
}

🔧 BFT/Raft実装方式詳細

📡 通信プロトコル選択

1. gRPC (推奨) 🚀

// proto定義例
service ConsensusService {
  rpc RequestVote(VoteRequest) returns (VoteResponse);
  rpc AppendEntries(AppendEntriesRequest) returns (AppendEntriesResponse);
  rpc SubmitConsensus(ConsensusRequest) returns (ConsensusResponse);
  rpc HeartBeat(HeartBeatRequest) returns (HeartBeatResponse);
}

message VoteRequest {
  string term = 1;
  string candidate_id = 2;
  int64 last_log_index = 3;
  string last_log_term = 4;
  int64 timestamp = 5;
}

メリット:

  • 高性能: Protocol Buffersでバイナリシリアライゼーション
  • 型安全: 強い型チェック、自動コード生成
  • ストリーミング: リアルタイム通信に最適
  • 多言語対応: Node.js、Go、Rust等で統一

2. WebSocket (リアルタイム重視) ⚡

class ConsensusWebSocket {
  private connections: Map<string, WebSocket> = new Map();
  
  async broadcastVoteRequest(voteRequest: VoteRequest): Promise<VoteResponse[]> {
    const promises: Promise<VoteResponse>[] = [];
    
    for (const [agentId, ws] of this.connections) {
      if (ws.readyState === WebSocket.OPEN) {
        promises.push(this.sendVoteRequest(ws, voteRequest));
      }
    }
    
    return Promise.allSettled(promises);
  }
  
  private sendVoteRequest(ws: WebSocket, request: VoteRequest): Promise<VoteResponse> {
    return new Promise((resolve, reject) => {
      const messageId = generateUUID();
      const timeout = setTimeout(() => reject(new Error('Timeout')), 5000);
      
      this.pendingRequests.set(messageId, { resolve, reject, timeout });
      
      ws.send(JSON.stringify({
        type: 'VOTE_REQUEST',
        messageId,
        payload: request
      }));
    });
  }
}

3. HTTP/2 + Server-Sent Events (ハイブリッド) 🌐

// Raftリーダー選出用HTTP API
app.post('/consensus/vote', async (ctx) => {
  const voteRequest: VoteRequest = ctx.request.body;
  
  // 署名検証
  if (!verifySignature(voteRequest)) {
    return ctx.response.status(401).send({ error: 'Invalid signature' });
  }
  
  // Raftロジック
  const voteResponse = await this.raftNode.handleVoteRequest(voteRequest);
  
  return ctx.response.send(voteResponse);
});

// リアルタイム状態同期用SSE
app.get('/consensus/stream', (ctx) => {
  ctx.response.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });
  
  const stream = this.consensusEventStream.subscribe(ctx.response);
  
  ctx.request.on('close', () => {
    stream.unsubscribe();
  });
});

🏗️ 実装アーキテクチャパターン

Pattern 1: Pure P2P (Peer-to-Peer)

class RaftNode {
  private peers: Map<string, NodeConnection> = new Map();
  private state: NodeState = 'follower';
  private currentTerm = 0;
  private log: LogEntry[] = [];
  
  async startElection(): Promise<boolean> {
    this.state = 'candidate';
    this.currentTerm++;
    
    // 全ピアに並列投票要求
    const votePromises = Array.from(this.peers.values()).map(peer => 
      peer.requestVote({
        term: this.currentTerm,
        candidateId: this.nodeId,
        lastLogIndex: this.log.length - 1,
        lastLogTerm: this.getLastLogTerm()
      })
    );
    
    const votes = await Promise.allSettled(votePromises);
    const supportVotes = votes.filter(v => 
      v.status === 'fulfilled' && v.value.voteGranted
    ).length;
    
    return (supportVotes + 1) > Math.floor(this.peers.size / 2); // +1は自分の票
  }
}

Pattern 2: Coordinator-Based (コーディネーター経由)

class ConsensusCoordinator {
  private agents: Map<string, AgentConnection> = new Map();
  
  async performByzantineConsensus(serviceId: string): Promise<ConsensusResult> {
    // Phase 1: 監視結果収集
    const checkPromises = Array.from(this.agents.values()).map(agent =>
      agent.performMonitorCheck(serviceId)
    );
    
    const checks = await Promise.allSettled(checkPromises);
    const validChecks = checks
      .filter(c => c.status === 'fulfilled')
      .map(c => c.value)
      .filter(check => this.verifyCheckSignature(check));
    
    // Phase 2: Byzantine合意
    const consensus = this.calculateByzantineConsensus(validChecks);
    
    // Phase 3: 結果ブロードキャスト
    await this.broadcastConsensusResult(consensus);
    
    return consensus;
  }
}

⚡ 実装技術比較表

方式 レイテンシ 実装難易度 障害検出 スケーラビリティ
gRPC 🔥 極低 🟡 中 🔥 高精度 🔥 優秀
WebSocket 🔥 極低 🟢 低 🟡 中程度 🟡 中程度
HTTP/2 🟡 低 🟢 低 🟢 簡単 🟢 良好
TCP Socket 🔥 極低 🔴 高 🔥 高精度 🟡 中程度

🚀 StatusGuard実装推奨構成

メイン通信: gRPC

# 依存関係
npm install @grpc/grpc-js @grpc/proto-loader
npm install -D grpc-tools

バックアップ通信: HTTP/2

// フォールバック機能
class HybridConsensusTransport {
  async sendMessage(target: string, message: any): Promise<any> {
    try {
      // メイン: gRPC
      return await this.grpcClient.send(target, message);
    } catch (error) {
      // フォールバック: HTTP/2
      console.warn('gRPC failed, falling back to HTTP/2');
      return await this.httpClient.post(`http://${target}/consensus`, message);
    }
  }
}

リアルタイム通知: WebSocket

// UI更新用
class ConsensusEventBroadcaster {
  broadcast(event: ConsensusEvent): void {
    this.webSocketServer.clients.forEach(client => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({
          type: 'CONSENSUS_UPDATE',
          data: event
        }));
      }
    });
  }
}

📚 gRPC学習ロードマップ

🎯 学習課題 Level 1: Hello gRPC

目標: 基本的なgRPCサーバー・クライアント作成

# プロジェクト作成
mkdir grpc-learning
cd grpc-learning
npm init -y
npm install @grpc/grpc-js @grpc/proto-loader
npm install -D typescript @types/node ts-node

課題1-1: シンプルなGreeterサービス 🌟

// protos/greeter.proto
syntax = "proto3";

package greeter;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloResponse);
  rpc SayHelloStream (HelloRequest) returns (stream HelloResponse);
}

message HelloRequest {
  string name = 1;
  int32 age = 2;
}

message HelloResponse {
  string message = 1;
  int64 timestamp = 2;
}

課題1-2: TypeScriptサーバー実装

// server.ts
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import * as path from 'path';

const PROTO_PATH = path.join(__dirname, 'protos/greeter.proto');

const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true
});

const greeterProto = grpc.loadPackageDefinition(packageDefinition) as any;

// サービス実装
const greeterService = {
  sayHello: (call: any, callback: any) => {
    const { name, age } = call.request;
    callback(null, {
      message: `Hello ${name}! You are ${age} years old.`,
      timestamp: Date.now()
    });
  },

  sayHelloStream: (call: any) => {
    const { name, age } = call.request;
    let count = 0;
    
    const interval = setInterval(() => {
      call.write({
        message: `Stream message ${++count} for ${name}`,
        timestamp: Date.now()
      });
      
      if (count >= 5) {
        clearInterval(interval);
        call.end();
      }
    }, 1000);
  }
};

const server = new grpc.Server();
server.addService(greeterProto.greeter.Greeter.service, greeterService);

server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
  console.log('🚀 gRPC Server running on port 50051');
  server.start();
});

課題1-3: クライアント実装

// client.ts
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import * as path from 'path';

const PROTO_PATH = path.join(__dirname, 'protos/greeter.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true
});

const greeterProto = grpc.loadPackageDefinition(packageDefinition) as any;
const client = new greeterProto.greeter.Greeter('localhost:50051', grpc.credentials.createInsecure());

// 通常のRPC呼び出し
client.sayHello({ name: 'Alice', age: 25 }, (error: any, response: any) => {
  if (error) {
    console.error('Error:', error);
  } else {
    console.log('Response:', response);
  }
});

// ストリーミングRPC
const streamCall = client.sayHelloStream({ name: 'Bob', age: 30 });
streamCall.on('data', (response: any) => {
  console.log('Stream data:', response);
});
streamCall.on('end', () => {
  console.log('Stream ended');
});

🎯 学習課題 Level 2: ヘルスチェックAPI

目標: StatusGuard風の監視サービス作成

課題2-1: 監視サービスproto定義

// protos/monitor.proto
syntax = "proto3";

package monitor;

service MonitorService {
  rpc CheckHealth (HealthRequest) returns (HealthResponse);
  rpc StartMonitoring (MonitorRequest) returns (stream MonitorEvent);
  rpc RegisterAgent (AgentInfo) returns (AgentResponse);
}

message HealthRequest {
  string target_url = 1;
  int32 timeout_ms = 2;
  map<string, string> headers = 3;
}

message HealthResponse {
  bool is_healthy = 1;
  int32 response_time_ms = 2;
  int32 status_code = 3;
  string error_message = 4;
  int64 timestamp = 5;
}

message MonitorRequest {
  string service_id = 1;
  string target_url = 2;
  int32 interval_seconds = 3;
}

message MonitorEvent {
  string service_id = 1;
  HealthResponse health = 2;
  string event_type = 3; // "check_result", "service_down", "service_up"
}

message AgentInfo {
  string agent_id = 1;
  string region = 2;
  repeated string capabilities = 3;
}

message AgentResponse {
  bool success = 1;
  string message = 2;
}

課題2-2: 実装チャレンジ 🔥

  1. HTTP監視機能: axios等でURL監視
  2. ストリーミング: リアルタイム監視結果配信
  3. エラーハンドリング: gRPCエラーコード活用
  4. メタデータ: 認証ヘッダー等の追加

🎯 学習課題 Level 3: 合意アルゴリズム入門

目標: 簡単なRaftライクな投票システム

課題3-1: 投票サービス

// protos/consensus.proto
syntax = "proto3";

package consensus;

service ConsensusService {
  rpc RequestVote (VoteRequest) returns (VoteResponse);
  rpc SubmitProposal (Proposal) returns (ProposalResponse);
  rpc GetConsensusState (Empty) returns (ConsensusState);
}

message VoteRequest {
  string term = 1;
  string candidate_id = 2;
  string voter_id = 3;
}

message VoteResponse {
  bool vote_granted = 1;
  string current_term = 2;
  string message = 3;
}

message Proposal {
  string proposal_id = 1;
  string data = 2;
  string proposer_id = 3;
}

message ProposalResponse {
  bool accepted = 1;
  int32 vote_count = 2;
  int32 required_votes = 3;
}

message Empty {}

message ConsensusState {
  string current_leader = 1;
  string current_term = 2;
  int32 total_nodes = 3;
  repeated string active_nodes = 4;
}

🚀 実践Tips

デバッグのコツ 🐛

// gRPCエラーハンドリング
import { status } from '@grpc/grpc-js';

const handleGrpcError = (error: any) => {
  switch (error.code) {
    case status.UNAVAILABLE:
      console.log('🔴 Service unavailable');
      break;
    case status.DEADLINE_EXCEEDED:
      console.log('⏱️ Request timeout');
      break;
    case status.PERMISSION_DENIED:
      console.log('🚫 Permission denied');
      break;
    default:
      console.log('❌ Unknown error:', error.message);
  }
};

パフォーマンス測定 ⚡

// レスポンス時間測定
const measureGrpcCall = async (client: any, method: string, request: any) => {
  const start = Date.now();
  
  return new Promise((resolve, reject) => {
    client[method](request, (error: any, response: any) => {
      const duration = Date.now() - start;
      console.log(`📊 ${method} took ${duration}ms`);
      
      if (error) reject(error);
      else resolve(response);
    });
  });
};

📝 学習チェックリスト

  • Level 1: 基本的なgRPCサーバー・クライアント作成
  • Level 2: ストリーミングRPC実装
  • Level 3: エラーハンドリング完璧マスター
  • Level 4: メタデータとインターセプター理解
  • Level 5: パフォーマンス最適化とロードバランシング

🎓 次のステップ

Level 1〜3をクリアしたら、StatusGuardの実際の合意アルゴリズム実装に挑戦〜!💪✨