feat: DB 백업 시스템 구축 (Phase 1,2,4)
- Phase 1: backup.conf.example + sam-db-backup.sh 백업 스크립트 - Phase 2: BackupCheckCommand + StatMonitorService.recordBackupFailure() - Phase 2: routes/console.php에 db:backup-check 05:00 스케줄 등록 - Phase 4: SlackNotificationService 생성 (웹훅 알림) - Phase 4: BackupCheckCommand/StatMonitorService에 Slack 알림 연동 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
115
app/Console/Commands/BackupCheckCommand.php
Normal file
115
app/Console/Commands/BackupCheckCommand.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\SlackNotificationService;
|
||||
use App\Services\Stats\StatMonitorService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class BackupCheckCommand extends Command
|
||||
{
|
||||
protected $signature = 'db:backup-check
|
||||
{--path= : 백업 경로 오버라이드}';
|
||||
|
||||
protected $description = 'DB 백업 상태를 확인하고 이상 시 알림을 생성합니다';
|
||||
|
||||
public function handle(StatMonitorService $monitorService): int
|
||||
{
|
||||
$this->info('DB 백업 상태 확인 시작...');
|
||||
|
||||
$statusFile = $this->option('path')
|
||||
? rtrim($this->option('path'), '/') . '/.backup_status'
|
||||
: env('BACKUP_STATUS_FILE', '/data/backup/mysql/.backup_status');
|
||||
|
||||
$errors = [];
|
||||
|
||||
// 1. 상태 파일 존재 여부
|
||||
if (! file_exists($statusFile)) {
|
||||
$errors[] = '백업 상태 파일 없음: ' . $statusFile;
|
||||
$this->reportErrors($monitorService, $errors);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$status = json_decode(file_get_contents($statusFile), true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$errors[] = '상태 파일 JSON 파싱 실패: ' . json_last_error_msg();
|
||||
$this->reportErrors($monitorService, $errors);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
// 2. last_run이 25시간 이내인지
|
||||
$lastRun = strtotime($status['last_run'] ?? '');
|
||||
if (! $lastRun || (time() - $lastRun) > 25 * 3600) {
|
||||
$lastRunStr = $status['last_run'] ?? 'unknown';
|
||||
$errors[] = "마지막 백업이 25시간 초과: {$lastRunStr}";
|
||||
}
|
||||
|
||||
// 3. status가 success인지
|
||||
if (($status['status'] ?? '') !== 'success') {
|
||||
$errors[] = '백업 상태 실패: ' . ($status['status'] ?? 'unknown');
|
||||
}
|
||||
|
||||
// 4. 각 DB 백업 파일 크기 검증
|
||||
$minSizes = [
|
||||
'sam' => (int) env('BACKUP_MIN_SIZE_SAM', 1048576),
|
||||
'sam_stat' => (int) env('BACKUP_MIN_SIZE_STAT', 102400),
|
||||
];
|
||||
|
||||
$databases = $status['databases'] ?? [];
|
||||
foreach ($minSizes as $dbName => $minSize) {
|
||||
if (! isset($databases[$dbName])) {
|
||||
$errors[] = "{$dbName} DB 백업 정보 없음";
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$sizeBytes = $databases[$dbName]['size_bytes'] ?? 0;
|
||||
if ($sizeBytes < $minSize) {
|
||||
$errors[] = "{$dbName} 백업 파일 크기 부족: {$sizeBytes} bytes (최소 {$minSize})";
|
||||
}
|
||||
}
|
||||
|
||||
// 결과 처리
|
||||
if (! empty($errors)) {
|
||||
$this->reportErrors($monitorService, $errors);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->info('✅ DB 백업 상태 정상');
|
||||
Log::info('db:backup-check 정상', [
|
||||
'last_run' => $status['last_run'],
|
||||
'databases' => array_keys($databases),
|
||||
]);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function reportErrors(StatMonitorService $monitorService, array $errors): void
|
||||
{
|
||||
$errorMessage = implode("\n", $errors);
|
||||
|
||||
$this->error('❌ DB 백업 이상 감지:');
|
||||
foreach ($errors as $error) {
|
||||
$this->error(" - {$error}");
|
||||
}
|
||||
|
||||
// stat_alerts에 기록
|
||||
$monitorService->recordBackupFailure(
|
||||
'[backup] DB 백업 이상 감지',
|
||||
$errorMessage
|
||||
);
|
||||
|
||||
// Slack 알림 전송
|
||||
app(SlackNotificationService::class)->sendBackupAlert(
|
||||
'DB 백업 이상 감지',
|
||||
$errorMessage
|
||||
);
|
||||
|
||||
Log::error('db:backup-check 실패', ['errors' => $errors]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user