Хмарне сховище об'єктів: порівняння 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', // або власний домен через 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 під разову задачу.