S3-совместимое хранилище из Go: три библиотеки и цена выбора
aws-sdk-go-v2, minio-go или самописный клиент через HTTP — у каждого свой вес в бинарнике, свой стиль ретраев и свои сюрпризы с эндпоинтами. Разбираем, что выбрать под задачу.

Вы подключаете к Go-сервису объектное хранилище: не обязательно AWS S3, а часто Yandex Object Storage, VK Cloud, MinIO или Cloudflare R2. Все они говорят на диалекте протокола S3, но диалекте — с оговорками. Очевидный выбор «возьму официальный AWS SDK» тянет за собой десятки транзитивных зависимостей и настройку, заточенную под Amazon. А минималистичный minio-go внезапно оказывается удобнее для загрузки файлов, но скромнее по покрытию API. Разберём три реальных варианта и цену каждого.
Речь про Go 1.22+, aws-sdk-go-v2 (модули с версиями от 2023–2024 годов) и minio-go/v7. Точные версии модулей всегда сверяйте в go.mod и в релизах на GitHub — API v2 у AWS стабилизировался, но отдельные под-модули (например, feature/s3/manager) обновляются отдельно.
Задача и что в ней ломается
Типичный набор операций: загрузить файл (часто большой, потоком), скачать, получить presigned URL для отдачи клиенту напрямую, удалить, перечислить объекты по префиксу. Кажется, что любая библиотека это умеет — и умеет. Проблема не в наличии методов, а в трёх деталях, на которых спотыкаются почти все.
- Path-style против virtual-hosted-style. AWS по умолчанию строит адрес как
bucket.s3.amazonaws.com. Многие совместимые хранилища и особенно MinIO в дефолтной конфигурации ждутendpoint/bucket. Если не переключить стиль, получите ошибки резолвинга DNS или 403. - Регион. У сторонних хранилищ региона в смысле AWS может не быть, но SDK требует непустое значение. Часто прокатывает любая строка вроде
ru-central1илиus-east-1, но подпись запроса считается с учётом региона — расхождение даётSignatureDoesNotMatch. - Кастомный endpoint. В v1 SDK его задавали одним полем. В v2 механизм менялся, и старые примеры из интернета уже не компилируются.
Вариант 1. aws-sdk-go-v2 — полный официальный SDK
Официальный SDK от Amazon. Покрывает весь S3 API, включая экзотику вроде multipart с ручным управлением частями, S3 Select и объектное блокирование. Модульный: подтягиваете только service/s3, а не весь AWS.
Задание кастомного эндпоинта в v2 делается через BaseEndpoint в опциях клиента (этот способ пришёл на смену устаревшему EndpointResolver). Точную сигнатуру опций сверяйте в документации модуля service/s3 — она зависит от версии.
package main
import (
"context"
"log"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func newClient(ctx context.Context) (*s3.Client, error) {
// Читаем креды из переменных окружения или задаём явно.
creds := credentials.NewStaticCredentialsProvider("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY", "")
cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion("ru-central1"), // непустой регион обязателен
config.WithCredentialsProvider(creds),
)
if err != nil {
return nil, err
}
client := s3.NewFromConfig(cfg, func(o *s3.Options) {
o.BaseEndpoint = aws.String("https://storage.yandexcloud.net")
o.UsePathStyle = true // для многих совместимых хранилищ
})
return client, nil
}
func main() {
ctx := context.Background()
client, err := newClient(ctx)
if err != nil {
log.Fatal(err)
}
_ = client
}
Для больших файлов не гоняйте PutObject вручную — берите feature/s3/manager, там есть Uploader с автоматическим multipart и параллельными частями. Скачивание потоком — через Downloader.
Чем платите: самый тяжёлый по зависимостям вариант и самый многословный код. Обработка ошибок через типизированные API-ошибки (errors.As на конкретные типы вроде *types.NoSuchKey) — мощно, но требует привыкания.
Вариант 2. minio-go/v7 — лёгкий клиент под загрузку файлов
Клиент от разработчиков MinIO, но работает с любым S3-совместимым хранилищем, включая сам AWS. Заметно компактнее по API: инициализация в одну структуру, загрузка файла — один вызов.
package main
import (
"context"
"log"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
func main() {
ctx := context.Background()
client, err := minio.New("storage.yandexcloud.net", &minio.Options{
Creds: credentials.NewStaticV4("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY", ""),
Secure: true, // https
Region: "ru-central1",
})
if err != nil {
log.Fatal(err)
}
// Загрузка файла с диска: библиотека сама решит про multipart.
info, err := client.FPutObject(ctx, "my-bucket", "docs/report.pdf",
"/tmp/report.pdf", minio.PutObjectOptions{ContentType: "application/pdf"})
if err != nil {
log.Fatal(err)
}
log.Printf("загружено %d байт, etag %s", info.Size, info.ETag)
}
Стиль адресации minio-go подбирает автоматически по эндпоинту, что снимает часть боли с path-style. Presigned URL, стриминг через GetObject (возвращает io.ReadCloser), листинг через канал ListObjects — всё лаконично.
Если сервис делает в основном «залей файл — отдай ссылку — удали», minio-go пишется в два-три раза короче официального SDK и тянет ощутимо меньше зависимостей. За полноту API вы при этом почти не платите — редкие операции всё равно закрыты.
Чем платите: покрытие S3 API уже, чем у AWS SDK. Экзотические возможности (тонкая работа с версионированием, отдельные ACL-сценарии, S3 Select) может не оказаться или они реализованы иначе. Проверяйте наличие нужного метода до того, как завяжете архитектуру.
Вариант 3. Свой HTTP-клиент с ручной подписью V4
Крайний случай: вы делаете одну-две операции, не хотите ни одной внешней зависимости и готовы реализовать подпись AWS Signature Version 4 вручную. Технически возможно и иногда оправдано в жёстко ограниченном окружении или в embedded-сценарии.
На практике это ловушка. Подпись V4 — многошаговый алгоритм: канонический запрос, строка для подписи, производный ключ через цепочку HMAC-SHA256. Ошибка в порядке заголовков или в кодировании пути даёт SignatureDoesNotMatch без подсказки, где именно. Presigned URL, retry на 5xx, обработка редиректов от хранилища — всё это придётся написать самому. Полноценный код тут не приводим: если сомневаетесь, что реализуете V4 без ошибок, не реализуйте — возьмите готовую библиотеку.
Разумная граница: свой клиент оправдан, только если ограничение на зависимости жёстче, чем стоимость поддержки собственной подписи.
Сравнение
| Критерий | aws-sdk-go-v2 | minio-go/v7 | Свой HTTP-клиент |
|---|---|---|---|
| Зависимости | Много (модульно) | Умеренно | Ноль внешних |
| Покрытие S3 API | Полное | Основное, без части экзотики | Ровно то, что напишете |
| Многословность кода | Высокая | Низкая | Очень высокая |
| Multipart из коробки | Да (s3/manager) | Да (авто в PutObject) | Пишете сами |
| Presigned URL | Да | Да | Пишете сами |
| Подпись V4 | Встроена | Встроена | Ваша ответственность |
| Риск SignatureDoesNotMatch по своей вине | Низкий | Низкий | Высокий |
Когда что выбирать
- aws-sdk-go-v2 — если работаете с настоящим AWS S3, нужны редкие возможности API, или сервис и так завязан на другие AWS-модули. Тогда единый стиль SDK перевешивает вес зависимостей.
- minio-go/v7 — если хранилище стороннее (Yandex, VK, R2, self-hosted MinIO) и задачи типовые: загрузка, отдача, presigned-ссылки. Меньше кода, меньше шансов ошибиться с path-style.
- Свой клиент — почти никогда. Только при жёстком запрете на внешние зависимости и одной-двух простых операциях.
Что ломается чаще всего
SignatureDoesNotMatch— почти всегда несовпадение региона в конфиге и в подписи, либо пробелы/спецсимволы в ключе объекта, закодированные не так, как ждёт хранилище.403 Forbiddenпри живых кредах — часто неверный стиль адресации: включите или выключите path-style под ваше хранилище.- Утечка соединений при стриминге:
GetObjectвозвращает поток, который обязан быть закрыт. ПропущенныйClose()копит зависшие соединения под нагрузкой. - Таймауты на больших файлах: дефолтный HTTP-клиент может обрывать долгую загрузку. Настраивайте таймауты транспорта и используйте multipart, а не один гигантский PutObject.
Что делать дальше
Определитесь с хранилищем и проверьте, какой стиль адресации оно требует — это первое, что сломается на новой инсталляции. Оберните выбранную библиотеку в собственный интерфейс из четырёх-пяти методов (put, get, delete, list, presign): тогда смена библиотеки или хранилища не расползётся по всему коду. В проде мерьте долю ретраев на 5xx и время до первого байта при скачивании — по ним видно проблемы с сетью до хранилища раньше, чем на них пожалуются пользователи.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: aws-sdk-go-v2 service/s3 — документация модуля, AWS Signature Version 4 — спецификация подписи
Частые вопросы
Работает ли aws-sdk-go-v2 не с AWS, а со сторонним хранилищем?
У меня другая версия aws-sdk-go-v2, и BaseEndpoint не находится
Что делать, если получаю SignatureDoesNotMatch?
Стоит ли писать свою подпись V4, чтобы не тянуть зависимости?
Как грузить файлы больше нескольких сотен мегабайт?
minio-go подходит только для MinIO?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.