Cloud Storage: объектное хранилище

Разбираемся, куда в облаке кладут файлы: что такое бакет и объект, чем Nearline отличается от Archive, сколько это стоит на самом деле и почему одна галочка «сделать публичным» иногда обходится в тысячи долларов.

Cloud Storage — объектное хранилище Google Cloud: вы кладёте файл целиком, получаете на него адрес вида gs://my-bucket/photos/cat.jpg и платите за реально занятые гигабайты и за операции, а не за сервер и не за выделенный диск.

Зачем нужен отдельный сервис для файлов

Типичный путь новичка: поднять виртуальную машину, примонтировать диск и складывать туда аватарки пользователей. Работает — ровно до первой проблемы, и проблем будет четыре.

  • Диск живёт в одной зоне. Зона недоступна — файлы недоступны вместе с ней.
  • Диск конечен. Кончилось место — надо руками расширять, останавливать сервис, следить.
  • Диск не умеет отдавать файлы в интернет. Нужен nginx, TLS-сертификат, кэш, защита от перегрузки.
  • Вы платите за выделенный объём, а не за занятый. Диск на 500 ГБ, забитый на 30 ГБ, стоит как диск на 500 ГБ.

Cloud Storage закрывает все четыре пункта. Данные автоматически размазаны по нескольким зонам, объём безграничен (буквально: вы просто пишете дальше), любой объект можно отдать по HTTPS напрямую, а счёт растёт ровно от того, сколько байт лежит и сколько раз их трогали. Это «диск, у которого нет размера» — и именно так его стоит воспринимать.

Цена вопроса — ограничения. Объект нельзя дописать в середину. Хотите поменять один байт — перезаписываете весь объект. Поэтому базу данных или файл лога, в который постоянно пишут, в Cloud Storage не положишь: это хранилище для «готовых» блобов — картинок, бэкапов, архивов, CSV-выгрузок, артефактов сборки, датасетов.

Бакеты и объекты

Бакет (bucket, «ведро») — контейнер верхнего уровня. Внутри лежат объекты — сами файлы плюс их метаданные (Content-Type, кастомные заголовки, класс хранения). Два факта, о которые спотыкаются все:

  1. Имя бакета глобально уникально во всём Google Cloud. Не в вашем проекте — во всём мире. Бакет images занят лет пятнадцать назад. Поэтому в имя принято зашивать домен или проект: codechick-prod-uploads.
  2. Внутри бакета нет папок. Пространство имён плоское. Объект называется 2026/07/report.pdf целиком — слеши это просто символы в имени. Консоль рисует «папки», потому что группирует объекты по общему префиксу, но физически никаких директорий нет. Отсюда следствие: «переименовать папку» = скопировать и удалить каждый объект.
# создаём бакет: имя, регион, uniform-доступ (см. ниже), класс по умолчанию
gcloud storage buckets create gs://codechick-prod-uploads \
  --location=europe-west1 \
  --default-storage-class=STANDARD \
  --uniform-bucket-level-access

# кладём файл и смотрим, что получилось
gcloud storage cp ./cat.jpg gs://codechick-prod-uploads/photos/cat.jpg
gcloud storage ls --long gs://codechick-prod-uploads/photos/

Результат:

Creating gs://codechick-prod-uploads/...
Copying file://./cat.jpg to gs://codechick-prod-uploads/photos/cat.jpg
  Completed files 1/1 | 184.2kiB/184.2kiB

  188612  2026-07-14T09:12:44Z  gs://codechick-prod-uploads/photos/cat.jpg
TOTAL: 1 objects, 188612 bytes

Локация: одна из главных развилок

При создании бакета вы выбираете локацию, и потом её не изменить. Вариантов три:

ТипПримерКогда брать
Regioneurope-west1Дефолт. Данные рядом с вашими серверами, дёшево, задержка минимальна.
Dual-regioneur4Нужна устойчивость к падению целого региона при быстрой записи.
Multi-regionEU, USРаздача контента по всему континенту (сайт, картинки). Дороже за гигабайт.

Правило простое: если бакет обслуживает приложение, ставьте тот же регион, где живёт приложение. Иначе каждый запрос к файлу пойдёт через полконтинента, а трафик между регионами вам ещё и посчитают.

Классы хранения и во что это обходится

Класс хранения — это компромисс «дешевле хранить / дороже читать». Данные всегда доступны мгновенно (никаких «разморозок», как у ленточных архивов), но чем холоднее класс, тем больше вы заплатите за сам факт чтения и тем дольше обязаны хранить объект.

КлассХранение, за ГБ/месПлата за чтение (retrieval)Минимальный срокДля чего
Standard≈ $0,020нетнетВсё, что читают регулярно: аватарки, статика, активные данные
Nearline≈ $0,010≈ $0,01/ГБ30 днейЧитают примерно раз в месяц: свежие бэкапы
Coldline≈ $0,004≈ $0,02/ГБ90 днейЧитают раз в квартал: старые бэкапы, архив логов
Archive≈ $0,0012≈ $0,05/ГБ365 дней«Положил и забыл»: юридический архив, долгое хранение по регламенту

Цифры — порядок величин для обычного региона; точные суммы всегда сверяйте с калькулятором Google Cloud, они меняются. Важнее принцип: Archive в 16 раз дешевле Standard за хранение, но чтение терабайта из Archive стоит примерно $50. Прочитаете такой архив дважды в месяц — и «экономия» превратится в перерасход.

Помимо хранения и retrieval в счёте есть ещё два пункта, про которые постоянно забывают:

  • Операции. Class A (запись, листинг) — порядка $0,005 за 1000 операций, Class B (чтение) — около $0,0004 за 1000. Миллион мелких файлов по 2 КБ обойдётся дороже, чем один архив на 2 ГБ, — не размером, а количеством операций.
  • Исходящий трафик (egress). Отдача файла в интернет — примерно $0,12 за ГБ. Хранение картинки стоит копейки; раздача этой картинки миллиону человек — уже нет.

Бесплатный лимит: 5 ГБ Standard в месяц (только в регионах us-east1, us-west1, us-central1), 5000 операций Class A, 50 000 Class B и 100 ГБ исходящего трафика из Северной Америки. Для учебных экспериментов этого хватает с запасом — но обратите внимание, что бесплатны только US-регионы.

Lifecycle: автоматическое охлаждение

Руками перекладывать объекты между классами никто не будет. Для этого есть правила жизненного цикла — JSON, который Cloud Storage применяет сам раз в сутки.

{
  "lifecycle": {
    "rule": [
      {
        "action": { "type": "SetStorageClass", "storageClass": "NEARLINE" },
        "condition": { "age": 30, "matchesStorageClass": ["STANDARD"] }
      },
      {
        "action": { "type": "SetStorageClass", "storageClass": "COLDLINE" },
        "condition": { "age": 90, "matchesStorageClass": ["NEARLINE"] }
      },
      {
        "action": { "type": "Delete" },
        "condition": { "age": 400 }
      }
    ]
  }
}
gcloud storage buckets update gs://codechick-prod-uploads \
  --lifecycle-file=lifecycle.json

Через месяц бэкап сам уедет в Nearline, через три — в Coldline, через 400 дней исчезнет. Это одна из самых окупаемых десятиминутных настроек в облаке.

Публичный доступ и почему он опасен

Сделать объект доступным всему интернету — одна команда. Именно поэтому это самая частая причина утечек данных в облаках вообще: кто-то «на пять минут для теста» открыл бакет и забыл.

# ОПАСНО: выдаём роль чтения специальному принципалу allUsers = «весь интернет»
gcloud storage buckets add-iam-policy-binding gs://codechick-prod-uploads \
  --member=allUsers \
  --role=roles/storage.objectViewer

После этого любой объект в бакете читается по ссылке https://storage.googleapis.com/codechick-prod-uploads/photos/cat.jpg — без авторизации, без ограничений, кем угодно. Три последствия, о которых стоит знать заранее:

  1. Утечка. Публичный бакет находят автоматические сканеры за часы. Если рядом с картинками лежал db-dump.sql — он утёк.
  2. Счёт. Egress платный. Публичный бакет с тяжёлыми файлами могут «выкачивать» днями — и заплатите за это вы. Известный сценарий: файл попал на популярный форум, к утру счёт на несколько тысяч долларов.
  3. Листинг. Роль objectViewer на бакете позволяет ещё и перечислить объекты. То есть посторонний видит не только тот файл, ссылку на который ему дали, а вообще все.

Что делать вместо этого:

  • Держите на бакете Public Access Prevention (--public-access-prevention). Тогда Google просто запретит выдать доступ allUsers, даже если кто-то попробует. На всех бакетах с пользовательскими данными это должно быть включено по умолчанию.
  • Включайте Uniform bucket-level access: права задаются только через IAM на бакет, без древних ACL на каждый объект. Меньше способов ошибиться.
  • Нужен временный доступ к одному файлу — выдавайте signed URL: ссылку с подписью и сроком жизни. Бакет остаётся приватным.
# ссылка живёт 15 минут, бакет при этом закрыт
gcloud storage sign-url gs://codechick-prod-uploads/reports/q2.pdf \
  --duration=15m \
  --impersonate-service-account=uploader@my-project.iam.gserviceaccount.com

Публичный бакет оправдан ровно в одном случае: это заведомо публичная статика — картинки сайта, css, дистрибутивы. И даже тогда перед ним стоит поставить Cloud CDN, чтобы не платить за каждый байт egress по полной.

Загрузка: gcloud storage и gsutil

Исторически с Cloud Storage работали через gsutil. Сегодня Google рекомендует gcloud storage — та же логика, но заметно быстрее на больших объёмах (лучше распараллеливает). Старые команды продолжают работать, в статьях и на StackOverflow вам будут попадаться обе.

Задачаgcloud storagegsutil (старое)
Копироватьgcloud storage cp a.txt gs://b/gsutil cp a.txt gs://b/
Синхронизировать папкуgcloud storage rsync ./dist gs://b/dist --recursivegsutil -m rsync -r ./dist gs://b/dist
Удалитьgcloud storage rm gs://b/old.loggsutil rm gs://b/old.log
Размер бакетаgcloud storage du --summarize gs://bgsutil du -s gs://b

Из кода работают через SDK. Обратите внимание: клиент сам умеет докачивать большие файлы кусками (resumable upload), поэтому загрузку десятигигабайтного архива не надо изобретать вручную.

from google.cloud import storage

client = storage.Client()
bucket = client.bucket("codechick-prod-uploads")

# загрузка файла; для больших объектов клиент сам режет на чанки
blob = bucket.blob("reports/q2.pdf")
blob.cache_control = "public, max-age=3600"
blob.upload_from_filename("q2.pdf")

# сразу кладём в холодный класс — платить за хранение будем меньше
cold = bucket.blob("archive/2024.tar.gz")
cold.storage_class = "COLDLINE"
cold.upload_from_filename("2024.tar.gz")

print(blob.public_url)  # ссылка сработает, только если объект публичный

Как это работает

Под капотом Cloud Storage — не «файловая система в облаке», а распределённый key-value поверх Colossus, внутренней файловой системы Google. Имя объекта — это ключ. Данные разбиваются на куски, куски дублируются по разным машинам и (для multi-region) по разным дата-центрам, а метаданные лежат в глобальной базе с сильной согласованностью.

Из этой архитектуры следует всё, что вы видите снаружи:

  • Запись атомарна и согласована. Как только загрузка завершилась, любой следующий read вернёт новую версию — «а вдруг реплика отстала» здесь не бывает.
  • Нет частичной записи. Изменить байт в середине нельзя, потому что объект — неделимая единица с контрольной суммой. Отсюда же версионирование: включите --versioning, и «перезапись» будет складывать старую версию рядом, а не затирать.
  • Листинг — дорогая операция. Он идёт по отсортированному индексу ключей. Перечислить бакет с 50 млн объектов — долго и платно, поэтому никогда не проектируйте логику приложения вокруг «а давайте просто пролистаем бакет».
  • Класс хранения — свойство объекта, а не бакета. Класс бакета — лишь значение по умолчанию для новых объектов. В одном бакете спокойно лежат и Standard, и Archive.

Частые ошибки

  • Положить часто читаемые данные в Archive. Классика «оптимизации»: увидели цену $0,0012 и переложили туда всё. Каждое чтение теперь стоит $0,05/ГБ — счёт вырос, а не упал. Холодные классы — только для данных, к которым реально не обращаются.
  • Забыть про минимальный срок хранения. Удалили объект из Nearline через 3 дня — вам всё равно выставят счёт за 30 дней (early deletion fee). В Archive — за целый год. Записывать во временный бакет с классом Archive — верный способ платить за мусор двенадцать месяцев.
  • Включить versioning и не настроить lifecycle. Приложение каждый час перезаписывает один и тот же файл; старые версии копятся молча. Через полгода бакет весит в сотни раз больше, чем показывает листинг. Обязательно добавляйте правило с numNewerVersions.
  • Публичный бакет «на время отладки». Отладка кончается, публичность остаётся. Включайте Public Access Prevention заранее — так вы физически не сможете совершить эту ошибку.
  • Раздавать тяжёлую статику напрямую из бакета. Работает, но за каждый гигабайт egress вы платите по полной. Cloud CDN перед бакетом сокращает этот счёт в разы.
  • Миллионы мелких объектов. Логи по одной строке в файл — это миллионы Class A операций. Пакуйте в архивы или пишите крупными блоками.
  • Бакет в другом регионе, чем приложение. Тихо добавляет и задержку, и межрегиональный трафик в счёт.

Итоги

  • Cloud Storage — хранилище целых объектов: положить, прочитать, перезаписать, удалить. Не файловая система и не база данных.
  • Имя бакета уникально во всём мире, папок внутри нет — только префиксы в именах объектов.
  • Класс хранения — сделка «дешевле хранить / дороже читать» плюс минимальный срок хранения. Standard для активного, Nearline/Coldline/Archive — для того, что реально не читают.
  • В счёте четыре строки: хранение, операции, retrieval и egress. Про две последние забывают чаще всего.
  • Бесплатно: 5 ГБ Standard в US-регионах, 5000 Class A и 50 000 Class B операций в месяц.
  • allUsers означает «весь интернет». Включайте Public Access Prevention и Uniform bucket-level access, а временный доступ выдавайте signed URL.
  • Lifecycle-правила — десять минут работы, которые годами экономят деньги.
Проверьте себя
1. Вы храните ежедневные бэкапы, которые реально скачиваете примерно раз в квартал при инцидентах. Какой класс хранения разумнее выбрать?
AStandard — всегда безопаснее
BColdline — дешёвое хранение, а плату за чтение вы заплатите редко
CArchive — он самый дешёвый, значит лучший
DNearline, потому что бэкапы всегда кладут в Nearline
2. Что произойдёт, если выдать роль roles/storage.objectViewer принципалу allUsers на бакете?
AДоступ получат только пользователи вашей организации в Google Cloud
BНичего не изменится, пока вы не сделаете публичным каждый объект отдельно
CЛюбой человек в интернете сможет читать объекты бакета и перечислять их список, а исходящий трафик оплатите вы
DОбъекты станут доступны на чтение, но исходящий трафик Google в этом случае не тарифицирует