115 lines
3.6 KiB
PHP
115 lines
3.6 KiB
PHP
|
|
<?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]);
|
||
|
|
}
|
||
|
|
}
|