Файлы — картинки, документы, бэкапы, статику сайта — в облаке не держат на диске виртуальной машины. Для этого есть объектное хранилище: отдельный сервис, который хранит сколько угодно файлов, доступен по сети и не требует своего сервера. В GCP это Cloud Storage. Разберём основы; более общий разбор модели, применимый к любому объектному хранилищу, — в разделе Object storage (S3).
Чем выше класс, тем дешевле хранить и дороже достать: у каждого холодного класса есть минимальный оплачиваемый срок и плата за извлечение, поэтому переносят по тому, как данные читают на самом деле, а не по календарю.
Бакет, объект, ключ
Файлы на диске машины кончились, а при её пересоздании и вовсе пропали — ровно от этого уходят в объектное хранилище, где данные живут отдельно от машин. Привычных папок и файлов там нет, модель другая:
- Бакет (bucket) — контейнер верхнего уровня, куда складывают объекты. Имя бакета глобально уникально.
- Объект — сам файл вместе с метаданными.
- Ключ (имя объекта) — строка-идентификатор, например
images/2026/logo.png. Слэши выглядят как папки, но это просто часть имени: настоящей иерархии нет, пространство плоское.
Доступ — только через HTTP API, не как к диску, и тут нужна точность. Читать объект можно и по частям: HTTP умеет запросить диапазон байт, и именно так вычитывают кусок большого файла или отдают видео. А вот менять его по частям нельзя: дописать в конец или поправить пару байт внутри не получится — любая правка означает перезапись объекта целиком (и новую версию, если включено версионирование). Именно эта упрощённая модель и позволяет хранилищу масштабироваться до огромных объёмов.
Большие файлы: возобновляемая загрузка
Про запись объекта сказано, что он пишется целиком, и для файла на пару мегабайт этого достаточно. Для большого файла есть два механизма, без которых на нестабильной сети ничего не работает.
Возобновляемая загрузка. Клиент начинает сессию загрузки, получает её адрес и отправляет файл частями. Прервалась сеть — можно спросить, сколько байт уже принято, и продолжить с этого места, а не начинать заново. Клиентские библиотеки включают этот режим сами для файлов больше порога (обычно несколько мегабайт), и это одна из причин пользоваться библиотекой, а не собирать запросы руками.
var blobInfo = BlobInfo.newBuilder(bucket, "reports/big.csv").build();
try (var writer = storage.writer(blobInfo)) { // возобновляемая сессия
writer.write(ByteBuffer.wrap(chunk)); // частями
}
w := storage.Bucket(bucket).Object("reports/big.csv").NewWriter(ctx) // возобновляемая сессия
w.ChunkSize = 8 << 20 // частями по 8 МБ
if _, err := w.Write(chunk); err != nil {
return err
}
return w.Close()
const stream = bucket.file('reports/big.csv').createWriteStream({ resumable: true, chunkSize: 8 * 1024 * 1024 }); // возобновляемая сессия, частями
stream.end(chunk);
blob = bucket.blob("reports/big.csv")
blob.chunk_size = 8 * 1024 * 1024 # частями по 8 МБ — включает возобновляемую сессию
blob.upload_from_file(source)
Два практических момента: сессия загрузки живёт ограниченное время (порядка недели), и её адрес — это секрет: кто его знает, может дописать в ваш объект.
Составные объекты. Второй приём для очень больших файлов: загрузить части как отдельные объекты параллельно, а потом одной операцией склеить их в один. Это ускоряет загрузку в разы (части идут одновременно) и удобно, когда файл собирается из нескольких источников.
gcloud storage objects compose gs://b/part-1 gs://b/part-2 gs://b/full.csv
Оговорки: у склейки есть предел на число частей за одну операцию (порядка тридцати, но операции можно повторять по дереву), и после склейки части удаляют отдельно — иначе платите за них.
И общее правило: выгрузка больших файлов тоже частями (по диапазонам байт) — это и быстрее, и позволяет продолжить после обрыва. Библиотеки делают это сами, если пользоваться их высокоуровневыми методами, а не читать поток целиком в память.
Классы хранения
Одни данные читают постоянно, другие лежат годами «на всякий случай». Держать всё одинаково — переплачивать. Поэтому у объектов есть класс хранения:
- Standard — для частого доступа: дороже за хранение, дёшево читать;
- Nearline — для доступа примерно раз в месяц;
- Coldline — примерно раз в квартал;
- Archive — для архива, к которому почти не обращаются: копейки за хранение, доступ дороже.
Решение принимают по деньгам, а не по частоте самой по себе: холодный класс экономит на хранении, но каждое чтение из него стоит дороже, а начиная с Nearline есть минимальный срок хранения, за который платят целиком. Если объект читают чаще, чем раз в месяц, экономия на хранении съедается платой за чтение. Важная деталь GCP: во всех классах данные доступны сразу (без «разморозки»), отличается только цена хранения и чтения. Логика простая: горячие данные — в Standard, холодные (старые логи, бэкапы) — в дешёвых классах. Вручную это делать необязательно: lifecycle-правила сами переносят объекты в холодные классы по возрасту и удаляют совсем старые.
Только у дешёвых классов есть два счётчика, из-за которых экономия иногда превращается в переплату:
- минимальный оплачиваемый срок хранения: Nearline — 30 дней, Coldline — 90, Archive — 365. Удалили объект или перезаписали его раньше — заплатите так, как будто он пролежал весь срок;
- плата за извлечение: в холодных классах деньги берут ещё и за сам факт чтения, за каждый гигабайт.
Поэтому правила переноса пишут под то, как данные читают на самом деле. Если логи месячной давности у вас регулярно поднимают для разбора, отправка их в Archive на третий день сделает счёт больше, а не меньше.
И про удаление. Новые бакеты Google сегодня создаёт с включённым мягким удалением (soft delete): удалённые и перезаписанные объекты не пропадают сразу, а лежат в скрытом состоянии — по умолчанию 7 дней — и их можно вернуть. То же и с самим бакетом. Это спасает ровно от той аварии, которая случается чаще прочих: скрипт залил не те файлы поверх нужных. Но 7 дней — это 7 дней, и версионирование это не заменяет: нужна история версий — включают именно его.
Signed URL и доступ
Бакет по умолчанию приватный (доступ по Cloud IAM). Чтобы дать временную ссылку на приватный объект — «скачать отчёт в течение часа» — используют signed URL: подписанную ссылку с ограниченным сроком, без раздачи постоянного доступа. Это аналог presigned URL в S3.
Работает это в обе стороны, и вторая сторона не менее полезна: подписанную ссылку выдают и на загрузку (метод PUT). Её отдают браузеру или мобильному приложению, и файл едет прямо в бакет, минуя ваш сервис, — не нужно гонять гигабайты через свой сервис и упираться в лимиты на размер запроса.
Одна деталь, о которую спотыкаются все: чтобы подписать ссылку, нужен закрытый ключ. Либо это JSON-ключ сервисного аккаунта — то, от чего мы как раз уходим, — либо, правильный путь, право подписывать от имени сервисного аккаунта через API (iam.serviceAccounts.signBlob, оно же роль Service Account Token Creator). Совсем «без ключей» подписать не получится ни у кого, и именно это чаще всего ломает первый заход: код работает на машине разработчика с ключом и падает в облаке без него.
Публичный доступ (объекты по прямой ссылке) включают осознанно — так раздают статику сайта. Управлять доступом рекомендуется единообразно на уровне бакета (uniform bucket-level access), а не по каждому объекту.
Типы размещения
Важное решение — где хранить данные и сколько копий:
- региональный бакет — данные в одном регионе, копии по его зонам; дёшево, переживает падение зоны;
- двухрегиональный (dual-region) и мультирегиональный (multi-region) — копии в нескольких регионах; выше доступность и защита от аварии региона, но дороже.
Выбор — баланс цены, задержки и того, какой сбой вы хотите пережить.
Из чего складывается стоимость
Счёт складывается из трёх частей: хранение (за гигабайты в месяц, зависит от класса — архив в разы дешевле Standard), операции (за количество чтений/записей) и исходящий трафик (за данные наружу). Самая коварная статья — трафик: на раздаче популярной статики растёт незаметно. Помогает Cloud CDN перед бакетом: он кэширует данные ближе к пользователю, снижает задержку и разгружает источник. Только трафик от этого не исчезает — он переезжает в тариф CDN: вы платите за отдачу из кэша (за гигабайт обычно дешевле, чем прямая отдача из бакета) и отдельно за наполнение кэша, когда CDN идёт за файлом в бакет. Дешевле — да, бесплатно — нет.
Где это применяется
Cloud Storage — стандартное место для файлового: пользовательские загрузки, картинки и видео, статика фронтенда, резервные копии, экспорты и логи. Приложение обычно не отдаёт файлы через себя, а кладёт их в бакет и раздаёт ссылками — это разгружает сервис.
Типичные ошибки новичков:
- держат файлы на диске машины — теряют их при пересоздании и не масштабируются;
- раздают всё через приложение вместо ссылок или CDN — грузят сервис и переплачивают за трафик;
- делают бакет публичным по ошибке — открывают приватные данные;
- не настраивают lifecycle — платят за Standard даже за архив.
Глубже: единый доступ на уровне бакета и старые списки ACLрасширенное
Половина инструкций в интернете объясняет доступ через списки управления доступом (ACL) на отдельных объектах. Это устаревший путь, и в новых бакетах он не работает.
У бакета есть настройка единого доступа на уровне бакета (uniform bucket-level access). Когда она включена — а в новых бакетах она включена по умолчанию и через 90 дней становится необратимой — списки на объектах отключаются полностью: доступ определяют только права уровня платформы, назначенные на бакет или выше.
Что это меняет на практике:
- Права выдают на бакет, а не на объект: роли вида «читатель объектов бакета» для служебной учётной записи. Это проще и лучше поддаётся аудиту.
- Инструкции с
gsutil acl setи «сделать объект публичным» не работают — и это не ошибка, а свойство настройки. Симптом: команда отвечает отказом со словами про единый доступ. - Публичная раздача одного файла делается иначе: либо подписанная ссылка с ограниченным сроком, либо публичный доступ на весь бакет (осознанно и обычно нет), либо раздача через сеть доставки содержимого.
Отдельный случай, когда списки на объектах всё-таки нужны — очень разные права у разных объектов в одном бакете. Правильный ответ почти всегда другой: разложить объекты по разным бакетам с разными правами. Бакеты бесплатны, а точечные права на объекты — источник ошибок, которые никто не может проверить.
Глубже: как не сделать бакет публичным случайнорасширенное
В списке типичных ошибок есть публичный бакет, и у этой ошибки есть механизм, который делает её невозможной: предотвращение публичного доступа (public access prevention).
Работает он на двух уровнях:
На бакете — настройка «запрещено», и тогда никакие права, выданные на бакет, не сделают его доступным для всех: попытка выдать доступ «всем пользователям интернета» просто отвергается.
На организации или папке — ограничение (storage.publicAccessPrevention), которое применяет это ко всем бакетам домена, включая будущие. Это правильный уровень: тогда ошибка невозможна у всех, а не только там, где кто-то не забыл.
gcloud storage buckets update gs://orders-reports --public-access-prevention
gcloud resource-manager org-policies enable-enforce \
storage.publicAccessPrevention --organization=123456789012
Как после этого раздавать файлы наружу: подписанные ссылки с коротким сроком (о них раздел выше) или сеть доставки содержимого с доступом к бакету по служебной учётной записи. Оба способа не требуют публичного бакета вовсе, а значит запрет можно включить навсегда.
И проверка, которую стоит сделать один раз на существующем контуре: пройтись по бакетам и посмотреть, нет ли у кого-то доступа «всем пользователям» — в консоли такие бакеты помечаются явно, а в командной строке видны в политике доступа.
Глубже: блокировка хранения: копии, которые нельзя удалитьрасширенное
Механизм, на который опирается вся тема резервных копий: политика хранения (retention policy) и её блокировка (bucket lock).
Политика хранения говорит: объекты в этом бакете нельзя удалять и изменять, пока не пройдёт заданный срок. Это действует на всех, включая владельца.
Блокировка политики делает её необратимой: после блокировки срок можно только увеличить, а снять политику или уменьшить срок нельзя никому и никогда.
gcloud storage buckets update gs://acme-backups --retention-period=30d
gcloud storage buckets update gs://acme-backups --lock-retention-period # необратимо
Зачем это нужно именно для копий: копия, которую можно удалить теми же правами, которыми удалили данные, копией не является. Злоумышленник, получивший права администратора, или ошибочный скрипт уничтожают и данные, и резерв одним движением. Заблокированная политика делает это невозможным физически.
Три практических правила. Срок берут по требованию (обычно 30 дней для операционных копий, годы — для того, что требует регулятор). Блокировать стоит осознанно: после блокировки бакет нельзя удалить, пока не истечёт срок у всех объектов, — то есть за ошибку в настройке вы будете платить за хранение весь срок. И рядом с политикой включают версионирование объектов: оно защищает от перезаписи, а политика — от удаления; вместе они закрывают обе беды.
Глубже: порядки ценрасширенное
Разговор про «копейки за хранение» стоит перевести в числа (ориентиры для европейских регионов, точные значения — в калькуляторе):
| Что | Порядок цены |
|---|---|
| Стандартный класс, хранение | около 2 центов за гигабайт в месяц |
| Nearline (доступ раз в месяц) | примерно вдвое дешевле хранения, плюс плата за чтение |
| Coldline (раз в квартал) | примерно в четыре раза дешевле стандартного |
| Archive (раз в год) | примерно в десять раз дешевле стандартного |
| Исходящий трафик в интернет | порядка 8–12 центов за гигабайт |
| Обращение к бакету из того же региона | обычно бесплатно |
| Операции записи и списка | порядка 5 долларов за миллион |
| Операции чтения | порядка 40 центов за миллион |
Что из этого важно на практике. Отдать гигабайт пользователю дороже, чем хранить его четыре месяца — то есть в счёте за хранилище обычно преобладает трафик, а не хранение. Холодные классы дешевле кратно, но за чтение из них платят отдельно и есть минимальный срок хранения (удалили раньше — заплатили как за минимальный срок), поэтому перекладывают туда то, что действительно не читают. И операции записи в десять раз дороже чтения — значит, миллион мелких файлов дороже, чем один большой, не только в хранении, но и в записи.
Практический вывод для расчёта: считают три строки отдельно — хранение, трафик и операции. Почти всегда выясняется, что оптимизировать надо трафик (сетью доставки содержимого) и число операций (укрупнением файлов), а не класс хранения.
Коротко
- Бакет с глобально уникальным именем, объект с метаданными и ключ, где слэши это часть имени; читать можно по диапазону байт, писать целиком или возобновляемой сессией.
- Большие файлы грузят возобновляемой сессией с продолжением после обрыва или частями как отдельными объектами со склейкой; адрес сессии это секрет, а части после склейки удаляют.
- Классы Standard, Nearline, Coldline и Archive различаются кратно, но у холодных есть минимальный срок 30, 90 и 365 дней и плата за извлечение; правила переноса пишут под реальное чтение, мягкое удаление включено на семь дней.
- Бакет приватный, временную ссылку на скачивание и загрузку даёт signed URL, подписанный правом служебной учётной записи, а не JSON-ключом.
- Единый доступ на уровне бакета включён по умолчанию и становится необратимым через 90 дней: права выдают на бакет, ACL на объектах не работают, разные права это разные бакеты.
- Предотвращение публичного доступа ставят на бакет или на всю организацию, а наружу раздают подписанными ссылками или через сеть доставки.
- Политика хранения с блокировкой делает копии неудаляемыми для всех, включая владельца; после блокировки срок можно только увеличить, а бакет нельзя удалить до истечения.
- Региональный бакет переживает зону, двух- и мультирегиональный переживают регион дороже; в счёте преобладает трафик наружу, а не хранение, операции записи в десять раз дороже чтения.
Что почитать дальше
- Объектные хранилища — общая модель S3-подобных хранилищ.
- Подключение SDK — как читать и писать объекты из кода.
- Отказоустойчивость и восстановление в GCP — блокировка хранения как защита копий.
- Оптимизация затрат в GCP — классы хранения и трафик в счёте.