December 5, 2025
Бэкап бакета S3 YandexCloud в локальное хранилище с помощью Veeam + PowerShell скриптов
Этот метод обходит ограничения Veeam, создавая локальную копию S3 данных, которую Veeam уже может бэкапить.
Полная архитектура решения
┌─────────────┐ скачивание ┌─────────────┐ бэкап ┌─────────────┐
│ Yandex S3 │─────────────▶│ Локальная │─────────▶│ Veeam │
│ бакет │ AWS CLI │ папка │ Veeam │ Repository │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
└────────────────────────────┼───────────────────────┘
PowerShell
Pre-job ScriptШаг 1: Подготовка окружения
1.1 Установка AWS CLI на Windows
# Скачать и установить AWS CLI # https://aws.amazon.com/cli/ # Проверить установку aws --version # Настройка профиля Yandex Cloud aws configure set aws_access_key_id YOUR_KEY --profile yandex aws configure set aws_secret_access_key YOUR_SECRET --profile yandex aws configure set region ru-central1 --profile yandex aws configure set s3.endpoint_url https://storage.yandexcloud.net --profile yandex
1.2 Создание структуры папок
# PowerShell - создать папки New-Item -Path "C:\Backup\S3-Temp" -ItemType Directory -Force New-Item -Path "C:\Backup\S3-Logs" -ItemType Directory -Force New-Item -Path "C:\Scripts\Veeam" -ItemType Directory -Force
Шаг 2: Создание PowerShell скрипта
Полный скрипт с обработкой ошибок и логированием
# S3-Download-PreScript.ps1
# Сохранить в: C:\Scripts\Veeam\S3-Download-PreScript.ps1
param(
[string]$JobName = $(throw "Job name is required"),
[string]$BackupSource = $(throw "Backup source is required")
)
# Конфигурация
$Config = @{
BucketName = "ваш-бакет"
LocalPath = "C:\Backup\S3-Temp"
LogPath = "C:\Backup\S3-Logs\$(Get-Date -Format 'yyyy-MM-dd')-sync.log"
MaxRetries = 3
RetryDelay = 30 # секунды
AWSProfile = "yandex"
ExcludePatterns = @("*.tmp", "*.temp", "*.log", "Thumbs.db")
}
# Функция логирования
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$LogMessage = "$Timestamp [$Level] $Message"
Add-Content -Path $Config.LogPath -Value $LogMessage
Write-Host $LogMessage
}
# Функция скачивания с повторами
function Sync-S3Bucket {
param([int]$RetryCount = 0)
try {
Write-Log "Начало синхронизации S3 бакета: $($Config.BucketName)"
Write-Log "Целевая папка: $($Config.LocalPath)"
# Формирование команды AWS CLI
$ExcludeArgs = $Config.ExcludePatterns | ForEach-Object { "--exclude `"$_`"" }
$ExcludeString = $ExcludeArgs -join " "
$Command = "aws s3 sync s3://$($Config.BucketName) `"$($Config.LocalPath)`" " +
"--endpoint-url https://storage.yandexcloud.net " +
"--profile $($Config.AWSProfile) " +
"--no-progress " +
$ExcludeString
Write-Log "Выполнение команды: $Command"
# Выполнение команды
$Result = Invoke-Expression $Command
# Проверка результата
if ($LASTEXITCODE -eq 0) {
Write-Log "Синхронизация успешно завершена"
# Сбор статистики
$FileCount = (Get-ChildItem -Path $Config.LocalPath -Recurse -File | Measure-Object).Count
$TotalSize = (Get-ChildItem -Path $Config.LocalPath -Recurse -File | Measure-Object -Property Length -Sum).Sum
$SizeMB = [math]::Round($TotalSize / 1MB, 2)
Write-Log "Статистика: $FileCount файлов, $SizeMB MB"
return $true
} else {
throw "AWS CLI вернул ошибку: $LASTEXITCODE"
}
}
catch {
Write-Log "Ошибка при синхронизации: $_" -Level "ERROR"
if ($RetryCount -lt $Config.MaxRetries) {
Write-Log "Повторная попытка через $($Config.RetryDelay) секунд (попытка $($RetryCount + 1) из $($Config.MaxRetries))"
Start-Sleep -Seconds $Config.RetryDelay
return Sync-S3Bucket -RetryCount ($RetryCount + 1)
} else {
Write-Log "Превышено максимальное количество попыток" -Level "ERROR"
return $false
}
}
}
# Функция очистки старых файлов
function Cleanup-OldFiles {
param([int]$DaysToKeep = 7)
try {
Write-Log "Очистка файлов старше $DaysToKeep дней"
$CutoffDate = (Get-Date).AddDays(-$DaysToKeep)
$OldFiles = Get-ChildItem -Path $Config.LocalPath -Recurse -File |
Where-Object { $_.LastWriteTime -lt $CutoffDate }
$DeletedCount = 0
foreach ($File in $OldFiles) {
try {
Remove-Item -Path $File.FullName -Force
$DeletedCount++
}
catch {
Write-Log "Не удалось удалить файл $($File.FullName): $_" -Level "WARNING"
}
}
Write-Log "Удалено файлов: $DeletedCount"
return $true
}
catch {
Write-Log "Ошибка при очистке: $_" -Level "ERROR"
return $false
}
}
# Функция проверки свободного места
function Check-DiskSpace {
param([string]$Path, [long]$RequiredSpaceMB = 1024)
try {
$Drive = (Get-Item $Path).Root.Name
$DriveInfo = Get-PSDrive -Name $Drive.Substring(0,1)
$FreeSpaceMB = [math]::Round($DriveInfo.Free / 1MB, 2)
$RequiredSpaceFormatted = [math]::Round($RequiredSpaceMB, 2)
Write-Log "Свободное место на диске $Drive : $FreeSpaceMB MB"
if ($FreeSpaceMB -lt $RequiredSpaceMB) {
Write-Log "ВНИМАНИЕ: Свободного места ($FreeSpaceMB MB) меньше требуемого ($RequiredSpaceFormatted MB)" -Level "WARNING"
return $false
}
return $true
}
catch {
Write-Log "Ошибка при проверке места на диске: $_" -Level "ERROR"
return $false
}
}
# Основной поток выполнения
try {
Write-Log "========================================"
Write-Log "Начало выполнения pre-job скрипта"
Write-Log "Job Name: $JobName"
Write-Log "Backup Source: $BackupSource"
Write-Log "========================================"
# 1. Проверка свободного места (минимум 10GB)
if (-not (Check-DiskSpace -Path $Config.LocalPath -RequiredSpaceMB 10240)) {
throw "Недостаточно свободного места на диске"
}
# 2. Синхронизация S3 бакета
$SyncResult = Sync-S3Bucket
if (-not $SyncResult) {
throw "Синхронизация S3 не удалась"
}
# 3. Очистка старых файлов
Cleanup-OldFiles -DaysToKeep 7
# 4. Проверка, что файлы доступны для Veeam
$TestFileCount = (Get-ChildItem -Path $Config.LocalPath -File -Recurse | Select-Object -First 10).Count
if ($TestFileCount -eq 0) {
Write-Log "ВНИМАНИЕ: В целевой папке нет файлов" -Level "WARNING"
}
Write-Log "Pre-job скрипт успешно завершен"
exit 0
}
catch {
Write-Log "Критическая ошибка в pre-job скрипте: $_" -Level "ERROR"
exit 1
}Шаг 3: Создание задания Veeam Backup Job
3.1 Настройка через Veeam Backup & Replication Console
1. Открыть Veeam Console
2. Backup Job → File Share → Microsoft Windows
3. Имя задания: "Yandex-S3-Backup"
4. На вкладке "Files and Folders":
- Добавить → C:\Backup\S3-Temp
- Исключить: *.tmp, *.log (дополнительно к скрипту)
5. На вкладке "Storage":
- Repository: выберите локальный репозиторий
- Retention policy: 30 restore points
- Compression: Optimal
- Deduplication: Enable (если есть)
6. На вкладке "Advanced":
- Ссылка на скрипты → Scripts
- Add pre-job script
- Путь: C:\Scripts\Veeam\S3-Download-PreScript.ps1
- Аргументы: -JobName "{JobName}" -BackupSource "{Source}"
7. На вкладке "Schedule":
- Enable schedule
- Daily at 02:00 AM
- Days: Monday-Friday3.2 Параметры скрипта в Veeam
# Veeam передает параметры в скрипт:
# {JobName} - имя задания Veeam
# {Source} - источник бэкапа
# {Target} - целевой репозиторий
# {ResultDir} - директория с результатами
# В нашем скрипте используем:
- JobName "{JobName}"
- BackupSource "{Source}"Шаг 4: Дополнительные скрипты
Post-job скрипт для очистки
# S3-Cleanup-PostScript.ps1
# Выполняется ПОСЛЕ успешного бэкапа Veeam
param(
[string]$JobName,
[string]$BackupSource,
[string]$ResultDir
)
$Config = @{
LocalPath = "C:\Backup\S3-Temp"
LogPath = "C:\Backup\S3-Logs\$(Get-Date -Format 'yyyy-MM-dd')-cleanup.log"
DaysToKeepInTemp = 3
}
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$LogMessage = "$Timestamp [$Level] $Message"
Add-Content -Path $Config.LogPath -Value $LogMessage
}
try {
Write-Log "Начало post-job очистки"
Write-Log "Задание: $JobName"
# Очистка временных файлов старше N дней
$CutoffDate = (Get-Date).AddDays(-$Config.DaysToKeepInTemp)
$FilesToDelete = Get-ChildItem -Path $Config.LocalPath -Recurse -File |
Where-Object { $_.LastWriteTime -lt $CutoffDate }
$DeletedCount = 0
$FailedCount = 0
foreach ($File in $FilesToDelete) {
try {
Remove-Item -Path $File.FullName -Force
$DeletedCount++
}
catch {
Write-Log "Не удалось удалить $($File.FullName): $_" -Level "WARNING"
$FailedCount++
}
}
Write-Log "Удалено файлов: $DeletedCount, не удалось: $FailedCount"
# Отправка отчета (опционально)
if (Test-Path "$PSScriptRoot\Send-EmailReport.ps1") {
& "$PSScriptRoot\Send-EmailReport.ps1" -JobName $JobName -Deleted $DeletedCount
}
Write-Log "Post-job скрипт завершен"
exit 0
}
catch {
Write-Log "Ошибка в post-job скрипте: $_" -Level "ERROR"
exit 1
}Скрипт для уведомлений
# Send-EmailReport.ps1
param(
[string]$JobName,
[int]$FilesDownloaded = 0,
[int]$FilesDeleted = 0
)
$SmtpServer = "smtp.yandex.ru"
$SmtpPort = 587
$From = "backup@yourdomain.com"
$To = "admin@yourdomain.com"
$Credential = Get-Credential
$Body = @"
Отчет о бэкапе S3 бакета
Задание: $JobName
Время: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
Статус: УСПЕШНО
Статистика:
- Файлов скачано из S3: $FilesDownloaded
- Файлов очищено: $FilesDeleted
- Дисковое пространство: $((Get-PSDrive C).Free / 1GB) GB свободно
Лог файл: C:\Backup\S3-Logs\$(Get-Date -Format 'yyyy-MM-dd')-sync.log
"@
Send-MailMessage `
-From $From `
-To $To `
-Subject "[Backup] S3 Backup Report - $JobName" `
-Body $Body `
-SmtpServer $SmtpServer `
-Port $SmtpPort `
-UseSsl `
-Credential $CredentialШаг 5: Настройка прав и безопасности
5.1 Сервисная учетная запись
# Создание учетной записи для Veeam $Password = ConvertTo-SecureString "ComplexPassword123!" -AsPlainText -Force New-LocalUser -Name "VeeamS3Backup" -Password $Password -FullName "Veeam S3 Backup Service" -Description "Service account for S3 backups" # Добавление в группу Add-LocalGroupMember -Group "Administrators" -Member "VeeamS3Backup" # Настройка Veeam для использования этой учетной записи # В свойствах задания Veeam → Advanced → Account
5.2 Безопасное хранение credentials
# Вариант 1: Windows Credential Manager cmdkey /add:storage.yandexcloud.net /user:access_key /pass:secret_key # Вариант 2: Зашифрованный файл $Credential = Get-Credential $Credential | Export-Clixml -Path "C:\Secure\aws-creds.xml" # В скрипте используем: $Credential = Import-Clixml -Path "C:\Secure\aws-creds.xml"
Шаг 6: Мониторинг и отчетность
Скрипт для проверки статуса
# Check-BackupStatus.ps1
$VeeamServer = "localhost"
$JobName = "Yandex-S3-Backup"
# Получение статуса из Veeam
# (Требуется Veeam PowerShell Module)
Import-Module Veeam.Backup.PowerShell
$Job = Get-VBRJob -Name $JobName
$LastSession = $Job.FindLastSession()
$Status = @{
JobName = $Job.Name
LastRun = $LastSession.CreationTime
Result = $LastSession.Result
ProcessedFiles = $LastSession.GetTaskSessions().Progress.ProcessedObjects
ReadBytes = [math]::Round($LastSession.Progress.ReadSize / 1GB, 2)
TransferredBytes = [math]::Round($LastSession.Progress.TransferedSize / 1GB, 2)
}
ConvertTo-Json $StatusИнтеграция с системным мониторингом
# Для Zabbix/PRTG
$HealthFile = "C:\Backup\S3-Temp\.health"
$LastSync = (Get-ChildItem $HealthFile -ErrorAction SilentlyContinue).LastWriteTime
if ((Get-Date) - $LastSync -gt [TimeSpan]::FromHours(24)) {
Write-Host "0" # Проблема
} else {
Write-Host "1" # OK
}Шаг 7: Оптимизация производительности
7.1 Настройка AWS CLI
# C:\Users\Username\.aws\config
[profile yandex]
region = ru-central1
s3 =
max_concurrent_requests = 20
max_queue_size = 10000
multipart_threshold = 64MB
multipart_chunksize = 16MB7.2 Оптимизация Veeam
В настройках задания Veeam: - Storage → Advanced → Performance: * Compression level: Optimal * Storage optimization: LAN target * Enable inline deduplication: Yes - Processing → Advanced → Advanced settings: * Enable CBT: Yes * Changed block tracking: Enable - Maintenance: * Perform backup files health check: Monthly * Defragment and compact full backup file: Weekly
Проблемы и решения
Проблема 1: Таймауты при скачивании
# В скрипте добавляем параметры:
$Command = "aws s3 sync ... " +
"--cli-read-timeout 600 " +
"--cli-connect-timeout 300 " +
"--cli-read-timeout 0 "Проблема 2: Нехватка места на диске
# Автоматическая очистка по квоте
$QuotaGB = 100
$CurrentSizeGB = [math]::Round((Get-ChildItem $Config.LocalPath -Recurse | Measure-Object -Property Length -Sum).Sum / 1GB, 2)
if ($CurrentSizeGB -gt $QuotaGB) {
# Удаляем самые старые файлы
Get-ChildItem $Config.LocalPath -Recurse -File |
Sort-Object LastWriteTime |
Select-Object -First 100 |
Remove-Item -Force
}Проблема 3: Конфликты блокировок файлов
# Проверка на заблокированные файлы
$LockedFiles = Get-ChildItem $Config.LocalPath -Recurse -File |
Where-Object {
try { [IO.File]::Open($_.FullName, 'Open', 'Read', 'None') }
catch { $true }
}
if ($LockedFiles) {
Write-Log "Найдены заблокированные файлы: $($LockedFiles.Count)" -Level "WARNING"
}Полная автоматизация через Task Scheduler
Если нужно независимое от Veeam расписание:
# Создание задачи в планировщике
$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"C:\Scripts\Veeam\S3-Sync-Standalone.ps1`""
$Trigger = New-ScheduledTaskTrigger -Daily -At 1:30AM
$Principal = New-ScheduledTaskPrincipal -UserId "VeeamS3Backup" -LogonType Password -RunLevel Highest
$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName "S3-Sync-PreVeeam" `
-Action $Action `
-Trigger $Trigger `
-Principal $Principal `
-Settings $SettingsИтоговая структура файлов
C:\
├── Scripts\
│ └── Veeam\
│ ├── S3-Download-PreScript.ps1 # Основной скрипт
│ ├── S3-Cleanup-PostScript.ps1 # Скрипт очистки
│ ├── Send-EmailReport.ps1 # Уведомления
│ └── Check-BackupStatus.ps1 # Мониторинг
├── Backup\
│ ├── S3-Temp\ # Временная копия S3
│ │ ├── file1.jpg
│ │ └── folder\
│ ├── S3-Logs\ # Логи синхронизации
│ │ ├── 2024-01-15-sync.log
│ │ └── 2024-01-15-cleanup.log
│ └── VeeamRepository\ # Репозиторий Veeam
└── Secure\
└── aws-creds.xml # Зашифрованные ключиПреимущества и недостатки этого подхода
✅ Преимущества:
- Использует существующие инвестиции в Veeam
- Централизованное управление и мониторинг
- Дедупликация и компрессия Veeam
- Retention policies Veeam
- Интеграция с другими системами бэкапа
❌ Недостатки:
- Двойное хранение данных (временная копия)
- Сложность настройки
- Зависимость от AWS CLI
- Нет инкрементального скачивания S3→Local
- Требует места для временной копии
📊 Когда использовать:
- Уже есть Veeam Enterprise Plus
- Нужна интеграция с существующей инфраструктурой
- Требуется дедупликация для экономии места
- Важен централизованный мониторинг