操作
バグ #756
未完了【開発計画】インフラヘルパー Phase 3 - コア機能追加(ログ・バックアップ・通知)
ステータス:
新規
優先度:
高め
担当者:
-
開始日:
2025-06-26
期日:
進捗率:
0%
予定工数:
説明
🎯 Phase 3: コア機能追加(機能拡張)¶
概要¶
インフラヘルパーサービスに不足している主要機能(ログ管理、バックアップ、通知)を実装する。
対応期限¶
2025年8月15日(6週間)
実装タスク¶
📊 3-1. ログ管理機能¶
APIエンドポイント設計¶
// ログ取得
GET /api/v1/logs/containers/:containerName
Query params:
- lines: 取得行数(デフォルト: 100)
- since: 開始時刻(ISO 8601)
- until: 終了時刻(ISO 8601)
- follow: リアルタイム追跡(boolean)
// ログ検索
POST /api/v1/logs/search
Body:
- query: 検索クエリ
- containers: 対象コンテナ配列
- dateRange: { start, end }
- severity: ['error', 'warn', 'info']
// ログダウンロード
GET /api/v1/logs/download/:containerName
Query params:
- format: 'txt' | 'json' | 'csv'
- compress: boolean
実装詳細¶
// backend/services/logService.js
class LogService {
async getContainerLogs(containerName, options) {
const container = docker.getContainer(containerName);
const stream = await container.logs({
stdout: true,
stderr: true,
since: options.since || 0,
timestamps: true,
tail: options.lines || 100
});
return this.parseLogStream(stream);
}
async searchLogs(searchParams) {
// Elasticsearchまたは内部検索実装
const results = [];
for (const containerName of searchParams.containers) {
const logs = await this.getContainerLogs(containerName, {
since: searchParams.dateRange.start
});
const filtered = logs.filter(log =>
log.message.includes(searchParams.query) &&
(!searchParams.severity || searchParams.severity.includes(log.level))
);
results.push(...filtered);
}
return results;
}
streamLogs(containerName, ws) {
const container = docker.getContainer(containerName);
container.logs({
stdout: true,
stderr: true,
follow: true,
timestamps: true
}, (err, stream) => {
stream.on('data', chunk => {
ws.send(JSON.stringify({
type: 'log',
container: containerName,
data: chunk.toString()
}));
});
});
}
}
フロントエンド実装¶
// LogViewer Component
function LogViewer() {
const [logs, setLogs] = useState([]);
const [filter, setFilter] = useState('');
const [following, setFollowing] = useState(false);
useEffect(() => {
if (following) {
const ws = new WebSocket(`${WS_URL}/logs/${containerName}`);
ws.onmessage = (event) => {
const log = JSON.parse(event.data);
setLogs(prev => [...prev.slice(-999), log]);
};
return () => ws.close();
}
}, [following, containerName]);
return (
<div className="bg-gray-900 text-green-400 p-4 rounded-lg font-mono">
<div className="flex justify-between mb-4">
<input
type="text"
placeholder="Filter logs..."
value={filter}
onChange={(e) => setFilter(e.target.value)}
className="bg-gray-800 px-3 py-1 rounded"
/>
<button
onClick={() => setFollowing(!following)}
className={`px-4 py-1 rounded ${following ? 'bg-red-600' : 'bg-green-600'}`}
>
{following ? 'Stop Following' : 'Follow Logs'}
</button>
</div>
<div className="h-96 overflow-y-auto">
{logs
.filter(log => log.message.includes(filter))
.map((log, i) => (
<div key={i} className={`py-1 ${log.level === 'error' ? 'text-red-400' : ''}`}>
<span className="text-gray-500">{log.timestamp}</span> {log.message}
</div>
))}
</div>
</div>
);
}
💾 3-2. バックアップ・復旧機能¶
APIエンドポイント設計¶
// バックアップ作成
POST /api/v1/backup/create
Body:
- type: 'full' | 'incremental' | 'selective'
- targets: ['containers', 'volumes', 'configs', 'databases']
- compression: boolean
- encryption: boolean
// バックアップ一覧
GET /api/v1/backup/list
Query params:
- page, limit, sort
// バックアップ復旧
POST /api/v1/backup/restore/:backupId
Body:
- targets: 復旧対象の選択
- options: { stopContainers: boolean, overwrite: boolean }
// バックアップスケジュール
POST /api/v1/backup/schedule
Body:
- cron: "0 2 * * *"
- type: 'full' | 'incremental'
- retention: { days: 30 }
実装詳細¶
// backend/services/backupService.js
class BackupService {
async createBackup(options) {
const backupId = uuidv4();
const backupPath = `/backups/${backupId}`;
// メタデータ作成
const metadata = {
id: backupId,
timestamp: new Date(),
type: options.type,
targets: options.targets,
status: 'in_progress'
};
// バックアップ実行
const tasks = [];
if (options.targets.includes('containers')) {
tasks.push(this.backupContainers(backupPath));
}
if (options.targets.includes('volumes')) {
tasks.push(this.backupVolumes(backupPath));
}
if (options.targets.includes('databases')) {
tasks.push(this.backupDatabases(backupPath));
}
await Promise.all(tasks);
// 圧縮・暗号化
if (options.compression) {
await this.compressBackup(backupPath);
}
if (options.encryption) {
await this.encryptBackup(backupPath);
}
metadata.status = 'completed';
metadata.size = await this.getBackupSize(backupPath);
return metadata;
}
async backupVolumes(backupPath) {
const volumes = await docker.listVolumes();
for (const volume of volumes.Volumes) {
await execAsync(`docker run --rm -v ${volume.Name}:/data -v ${backupPath}:/backup alpine tar -czf /backup/${volume.Name}.tar.gz /data`);
}
}
async scheduleBackup(schedule) {
// cron-jobとして登録
cron.schedule(schedule.cron, async () => {
await this.createBackup({
type: schedule.type,
targets: schedule.targets || ['containers', 'volumes', 'configs']
});
// 古いバックアップの削除
await this.cleanupOldBackups(schedule.retention);
});
}
}
🔔 3-3. 通知・アラート機能¶
APIエンドポイント設計¶
// 通知チャンネル設定
POST /api/v1/notifications/channels
Body:
- type: 'email' | 'slack' | 'webhook' | 'line'
- config: { /* チャンネル固有の設定 */ }
// アラートルール設定
POST /api/v1/alerts/rules
Body:
- name: "高CPU使用率"
- condition: {
metric: "cpu_usage",
operator: ">",
threshold: 80,
duration: "5m"
}
- actions: ['notify', 'restart', 'scale']
- channels: ['channelId1', 'channelId2']
// アラート履歴
GET /api/v1/alerts/history
Query params:
- status: 'active' | 'resolved' | 'acknowledged'
- severity: 'critical' | 'warning' | 'info'
- from, to: 日付範囲
実装詳細¶
// backend/services/alertService.js
class AlertService {
constructor() {
this.rules = new Map();
this.channels = new Map();
this.activeAlerts = new Map();
}
async evaluateRules() {
for (const [ruleId, rule] of this.rules) {
const value = await this.getMetricValue(rule.condition.metric);
const triggered = this.evaluateCondition(value, rule.condition);
if (triggered && !this.activeAlerts.has(ruleId)) {
await this.triggerAlert(rule);
} else if (!triggered && this.activeAlerts.has(ruleId)) {
await this.resolveAlert(ruleId);
}
}
}
async triggerAlert(rule) {
const alert = {
id: uuidv4(),
ruleId: rule.id,
name: rule.name,
severity: rule.severity,
triggeredAt: new Date(),
status: 'active'
};
this.activeAlerts.set(rule.id, alert);
// 通知送信
for (const channelId of rule.channels) {
const channel = this.channels.get(channelId);
await this.sendNotification(channel, alert);
}
// 自動アクション実行
if (rule.actions.includes('restart')) {
await this.executeAction('restart', rule.target);
}
}
async sendNotification(channel, alert) {
switch (channel.type) {
case 'slack':
await this.sendSlackNotification(channel.config, alert);
break;
case 'email':
await this.sendEmailNotification(channel.config, alert);
break;
case 'line':
await this.sendLineNotification(channel.config, alert);
break;
case 'webhook':
await this.sendWebhookNotification(channel.config, alert);
break;
}
}
}
// 5分ごとにルール評価
setInterval(() => {
alertService.evaluateRules();
}, 5 * 60 * 1000);
フロントエンド実装¶
// Alert Rules Manager
function AlertRulesManager() {
const [rules, setRules] = useState([]);
const [showCreateModal, setShowCreateModal] = useState(false);
return (
<div className="space-y-4">
<div className="flex justify-between items-center">
<h2 className="text-2xl font-bold">Alert Rules</h2>
<button
onClick={() => setShowCreateModal(true)}
className="bg-line-green text-white px-4 py-2 rounded-lg"
>
Create Rule
</button>
</div>
<div className="grid gap-4">
{rules.map(rule => (
<div key={rule.id} className="bg-white p-4 rounded-lg shadow">
<div className="flex justify-between items-start">
<div>
<h3 className="font-semibold">{rule.name}</h3>
<p className="text-gray-600">
{rule.condition.metric} {rule.condition.operator} {rule.condition.threshold}
</p>
</div>
<div className="flex gap-2">
<StatusBadge status={rule.status} />
<button className="text-red-600">Delete</button>
</div>
</div>
</div>
))}
</div>
</div>
);
}
データベース設計¶
-- alerts table
CREATE TABLE alerts (
id TEXT PRIMARY KEY,
rule_id TEXT NOT NULL,
name TEXT NOT NULL,
severity TEXT NOT NULL,
status TEXT NOT NULL,
triggered_at TIMESTAMP NOT NULL,
resolved_at TIMESTAMP,
acknowledged_at TIMESTAMP,
acknowledged_by TEXT,
metadata JSON
);
-- backup_history table
CREATE TABLE backup_history (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
status TEXT NOT NULL,
started_at TIMESTAMP NOT NULL,
completed_at TIMESTAMP,
size_bytes INTEGER,
targets JSON,
error TEXT,
metadata JSON
);
成果物¶
- 完全実装されたログ管理システム
- バックアップ・復旧システム
- 通知・アラートシステム
- 統合テスト済みのコード
- APIドキュメント
- ユーザーマニュアル
表示するデータがありません
操作