Облачное хранилище объектов: сравнение S3-провайдеров
Объектное хранилище (Object Storage) — оптимальный способ хранить статику, медиафайлы, бэкапы и ML-датасеты. Все провайдеры используют совместимый с S3 API, что упрощает смену провайдера.
Сравнение S3-провайдеров
| Провайдер | Цена (GB/мес) | Трафик исходящий | Регион |
|---|---|---|---|
| Hetzner Object Storage | €0.0049 | €0.010/GB (первые 1TB бесплатно) | Германия, Финляндия |
| Backblaze B2 | $0.006 | $0.01/GB | США, ЕС |
| Cloudflare R2 | $0.015 | БЕСПЛАТНО | Глобально |
| DigitalOcean Spaces | $0.020 | $0.01/GB (первые 1TB бесплатно) | США, ЕС, Азия |
| AWS S3 | $0.023 | $0.09/GB | Глобально |
Cloudflare R2: бесплатный исходящий трафик
# R2 через rclone
[cloudflare-r2]
type = s3
provider = Cloudflare
access_key_id = YOUR_R2_ACCESS_KEY
secret_access_key = YOUR_R2_SECRET_KEY
endpoint = https://ACCOUNT_ID.r2.cloudflarestorage.com
region = autoПодключение к WordPress (WP Offload Media)
# wp-config.php
define('AS3CF_SETTINGS', serialize([
'provider' => 's3',
'access-key-id' => 'YOUR_KEY',
'secret-access-key' => 'YOUR_SECRET',
'bucket' => 'my-wp-media',
'region' => 'eu-central-1',
'domain' => 'path', // или custom domain через CDN
]));PHP: загрузка файлов в S3
use Aws\S3\S3Client;
$s3 = new S3Client([
'version' => 'latest',
'region' => 'eu-central-1',
'endpoint' => 'https://YOUR_ENDPOINT',
'credentials' => ['key' => 'KEY', 'secret' => 'SECRET'],
'use_path_style_endpoint' => true,
]);
// Загрузить файл
$s3->putObject([
'Bucket' => 'my-bucket',
'Key' => 'uploads/' . basename($_FILES['file']['name']),
'SourceFile' => $_FILES['file']['tmp_name'],
'ACL' => 'public-read',
'ContentType' => $_FILES['file']['type'],
]);
// Получить публичный URL
$url = $s3->getObjectUrl('my-bucket', 'uploads/photo.jpg');Разбор реальных ошибок S3 API
Два сообщения об ошибках встречаются чаще всего у новичков в объектных хранилищах, и оба не связаны с сетью или квотами.
SignatureDoesNotMatch
<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message></Error>Классические причины: неверный secret key (лишний пробел при копировании), рассинхронизация системных часов сервера более чем на 15 минут, либо не выставлен use_path_style_endpoint => true для провайдеров, которые не поддерживают virtual-hosted style (MinIO, Hetzner). Проверьте время командой timedatectl и синхронизируйте через chronyd или ntpd.
AccessControlListNotSupported
<Error><Code>AccessControlListNotSupported</Code><Message>The bucket does not allow ACLs</Message></Error>С апреля 2023 года новые бакеты AWS S3 создаются с Object Ownership Bucket owner enforced, который полностью отключает ACL. Код с 'ACL' => 'public-read' в putObject начинает падать с этой ошибкой. Решение — убрать параметр ACL и открыть доступ через bucket policy в JSON вместо ACL на уровне объекта.
Когда объектное хранилище не подходит
- Нужна POSIX-семантика с блокировками файлов и атомарной дозаписью — S3 это не файловая система, а хранилище неизменяемых объектов целиком.
- Приложению нужна низкая задержка для блочного доступа базы данных — используйте локальный NVMe или сетевой блочный диск, а не объектное API.
- Хранится огромное количество очень мелких файлов с высокой частотой запросов — биллинг за операции (PUT/GET) начинает превышать биллинг за объём, разумнее объединять файлы или ставить CDN-кэш перед бакетом.
- Нужна запись в реальном времени с минимальной задержкой (потоковое видео, телеметрия) — объектное хранилище проектировалось для батчевой записи, а не для потока.
- Регуляторные требования обязывают физический контроль над носителем — тогда разворачивайте собственный MinIO на своём железе вместо облачного провайдера.
Проверка результата
# Загрузить тестовый файл и сверить контрольную сумму
aws s3 cp test.txt s3://my-bucket/test.txt --endpoint-url https://YOUR_ENDPOINT
aws s3api head-object --bucket my-bucket --key test.txt --endpoint-url https://YOUR_ENDPOINT
# Проверить публичный доступ, если бакет должен быть открыт
curl -I https://my-bucket.YOUR_ENDPOINT/test.txt
# Сравнить локальный и удалённый MD5
md5sum test.txt
aws s3api head-object --bucket my-bucket --key test.txt --query ETag --endpoint-url https://YOUR_ENDPOINTКод ответа 403 на публичный curl -I почти всегда означает, что bucket policy не разрешает анонимное чтение, а не проблему с ключами доступа — ключи вообще не нужны для чтения публичного объекта. Если объектное хранилище используется для бэкапов, логичнее гонять их через rclone, а не писать код на PHP под разовую задачу.