Файлы в проекте можно хранить разными способами: локально на компьютере, в базе данных или S3-хранилище (объектное хранилище). Последнее — одно из самых популярных решений. Оно отличается надежностью и масштабируемостью. Использовать S3 можно не только в личных целях, но и для решения бизнес-задач. Для специалиста навык работы с объектным хранилищем востребован. Он поможет быстрее дойти до следующего уровня в карьере.

Под катом расскажем о преимуществах S3, научимся загружать и получать файлы, сверстаем небольшой сайт с его использованием!

Используйте навигацию, если не хотите читать текст полностью:

Немного об S3
Переходим к практике
Создаем объектное хранилище
Тестируем код/a>
→ Создаем приватный контейнер

Заключение

Немного об S3


Если вы слышали, что S3-хранилище — только про Amazon и использование за границей, то это не так. Изначально компания AWS разработала технологию как API для простого доступа к объектам с помощью уникальных URL по HTTP или HTTPS. Однако на рынке уже давно представлены отечественные решения — например, объектное хранилище Selectel.

Технология поддерживает любой тип данных. Вы можете хранить архивные логи, документы, фото и видео — все, что есть на вашем сайте. S3 подходит и в качестве приватного хранилища данных пользователей.

Один из популярных сценариев использования S3 — бэкапирование. Здесь особую роль играет надежность: как только вы загружаете файл в хранилище, он перемещается сразу в три дата-центра. Следовательно, вы не потеряете свои данные даже в случае сбоя одного из них. Рассмотрим другие преимущества технологии.

  • Практически бесконечный объем хранилища — вы можете хранить вплоть до нескольких петабайт данных.
  • Автоматическое масштабирование. В случае резкого всплеска трафика ресурсы мгновенно пополнятся. Так можно параллельно работать с большим количеством пользователей.
  • Разграничение доступов. Для каждого контейнера можно задать список пользователей и команд с различными правами доступа — чтение или чтение и запись.



Переходим к практике


Напишем небольшой скрипт на Python. Позже вы сможете переиспользовать его в любых проектах. Скрипт подключается к объектному хранилищу и работает с файлами.

1. Для начала установим зависимости. Используем одну из самых популярных асинхронных библиотек для работы с S3 — aio-botocore.

pip install aiobotocore

Если вам интересен синхронный вариант, вы пишете на Django или Flask, используйте botocore. В случае с асинхронным кодом оставляйте aio-botocore. Реализация у них идентичная.

2. Далее создадим класс. Назвать его можно как угодно, в нашем случае — S3Client. Это будет универсальный код, не привязанный к конкретной реализации S3 или провайдеру.

class S3Client:
    def __init__(
            self,
            access_key: str,
            secret_key: str,
            endpoint_url: str,
            bucket_name: str,
    ):

Рассмотрим переменные, которые будем принимать при инициализации проекта:
  • ключ доступа,
  • Secret Key,
  • Access Key,
  • Endpoint URL (адрес нашего хранилища),
  • Bucket Name.

Среди переменных Secret — это некое «имя пользователя», а Access Key — пароль.

3. Создадим некоторую переменную Config (но вы можете назвать ее как угодно). Здесь дадим несколько ключей для подключения.

        self.config = {
            "aws_access_key_id": access_key,
            "aws_secret_access_key": secret_key,
            "endpoint_url": endpoint_url,
        }
        self.bucket_name = bucket_name
        self.session = get_session()

Не обращайте внимания, что переменные в библиотеке называются AWS (Amazon Web Services). Использовать их можно с любым хранилищем. Bucket Name сохраняем в переменной на всякий случай. Чтобы можно было работать с нашим хранилищем, добавим сессию get_session — ее импортируем из библиотеки aio-botocore, которую скачали ранее.

4. Создадим подключение Client. Здесь будем использовать уже знакомую get_session — в контекстном менеджере выбираем функцию Create Client.


Выбор функции Create Client в контекстном менеджере.

Имя задаем S3, раскрываем конфигурационный файл **self.config. Чтобы можно было использовать конструкцию get_client как контекстный менеджер, навесим декоратор asynccontextmanager. Он уже встроен в Python — импортируем из библиотеки context lib.

   @asynccontextmanager
    async def get_client(self):
        async with self.session.create_client("s3", **self.config) as client:
            yield client

5. Осталось сделать асинхронную функцию для загрузки файла. Принимать будем несколько параметров. Во-первых — адрес файла. Конечно, на практике вы вряд ли встретитесь с локальным хранением, но так как в нашей реализации нет фронтенда, клиентов и API, пока оставим так. Во-вторых — название, Object Name.

   async def upload_file(
            self,
            file_path: str,
    ):

6. Откроем контекстный менеджер. Здесь уже ничего не нужно передавать — весь конфиг мы прописали ранее, дальше можем переиспользовать get_client для загрузки, скачивания, удаления и прочих операций. Так как работаем с локальными файлами, естественно, их тоже нужно открывать через контекстный менеджер. Используем read binary mode, rb. Указываем бакет, а Object Name задаем сами. Бакет — это сущность для хранения объектов в S3.

       object_name = file_path.split("/")[-1]  # /users/artem/cat.jpg
        try:
            async with self.get_client() as client:
                with open(file_path, "rb") as file:
                    await client.put_object(
                        Bucket=self.bucket_name,
                        Key=object_name,
                        Body=file,
                    )


Но что значит put в client.put_object? Возможно, если вы пишете API, то уже сталкивались с этим понятием. Put – это изменение. Важно понимать, что в S3 все операции являются перезаписью. Если пишете новый файл, он добавится в систему. Если же файл уже существует и вы пишете put_object, он перезапишется новой версией. Исходный файл во втором сценарии будет утерян.

Создаем объектное хранилище


Пора создать объектное хранилище и получить ключи для авторизации.

1. В панели управления заходим во вкладку Объектное хранилище → Создать контейнер. Имя зададим test-public-bucket1, но вы можете выбрать любое другое. Тип можно выбрать приватный или публичный. Если вы хотите, чтобы ни у кого из интернета не было прямого доступа до ваших файлов — выбирайте приватный. Мы пока рассмотрим публичный.

2. Переходим к классу хранения. Если речь идет о бэкапах, архивных логах или данных, к которым вы редко возвращаетесь, но они очень ценны — выбирайте холодное хранение. Стандартное обычно выбирается для сервисов, где идет постоянная загрузка файлов, их просмотр и т. д. Нажимаем Создать контейнер.



Контейнер создается за несколько секунд. Но работать с ним пока можно только из браузера.

3. Чтобы взаимодействовать по API, следует создать сервисного пользователя. Переходим в Управление доступом → Сервисные пользователи → Добавить пользователя. Задаем имя пользователя и генерируем пароль, в поле Роль выбираем Администратор объектного хранилища. Выбираем наш проект из списка и нажимаем Добавить пользователя.


4. Получим ключи. Во вкладке Управление пользователями выбираем нужного и в поле S3 ключи нажимаем Добавить ключ. В окне Добавление S3 ключа выбираем наш проект и нажимаем Сгенерировать.


Окно добавления S3 ключа.


Полученные ключи.

5. Ключи копируем и добавляем в проект. Чтобы обращаться к бакету, указываем endpoint_url — s3.storage.selcloud.ru. Задаем bucket-name — в нашем случае это test-public-bucket1. Создаем асинхронную функцию main, в которой создаем объект хранилища S3Client. Загружаем файл test.txt и указываем относительный путь — await S3_client.upload_file(«test.txt»).

async def main():
    s3_client = S3Client(
        access_key="",
        secret_key="",
        endpoint_url="", 
        bucket_name="",
    )

    # Проверка, что мы можем загрузить, скачать и удалить файл
    await s3_client.upload_file("test.txt")
    await s3_client.get_file("test.txt", "text_local_file.txt")
    await s3_client.delete_file("test.txt")


if __name__ == "__main__":
    asyncio.run(main())


Тестируем код


Переходим к тестированию нашего кода — запускаем скрипт. Проверим, есть ли изменения в объектах. В контейнере должен отображаться новый файл. Здесь же можно получить ссылку на объект.





Помимо прочего, из панели управления вы можете проставить лимиты. С помощью них можно ограничить объем данных, которые загружаются в хранилище.


Раздел «Лимиты» во вкладке объектного хранилища.

Посмотрим, как загружать файлы из S3 на сайте. Будем использовать простейший одностраничный сайт, исключительно для проверки функций. Создаем файл index.html и выбираем пятую версию html. Положим сюда любой объект из хранилища, в качестве примера используем изображение и видеофайл.

<! doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" 
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div>
        <video src="https://445614ac-52e0-4209-bd5e-7efcc3ad83c3.selstorage.ru/jetski.mov" />
        <img src="https://326158a3-9309-4662-a565-ff0e15038960.selstorage.ru/2024-03-22%2011.03.30.jpg" />
        </div>
</body> 
</html>

Переходим на сайт. Видим, что файлы пришли из S3-хранилища. Видеофайл jetski.mov на 212 МБ присылается чанками (порционно). Это помогает S3 не сталкиваться с нагрузкой из-за большого трафика.


Панель администратора на созданном сайте.

Создаем приватный контейнер


Вернемся к выбору типа контейнера в панели управления. В этот раз создадим приватный контейнер с холодным классом хранения. Будем использовать его для бэкапов. Это хранилище, к которому мы также можем обращаться по API, например, через скрипт для резервного копирования. О том, как выбрать тип бэкапа — в обзоре.


Создание приватного контейнера с холодным хранением в панели управления.

Ключевое отличие от созданного ранее контейнера в том, что никто не сможет получить доступ к этому бакету. Здесь нет публичной ссылки и мы не можем расшарить файл, потому что это приватное хранилище. Но мы можем сгенерировать одноразовую ссылку и наложить на нее временные ограничения.


Настройки доступа к файлу в приватном контейнере.



Заключение


Мы поработали с приватными и публичными бакетами, поняли, как загружать и получать данные, как их отображать на сайте. Одно из важных преимуществ объектного хранилища, которое можно рассмотреть в заключение статьи — цена является его цена.

Допустим, у нас есть маркетплейс на 10 000 клиентов, где мы храним 1 ГБ карточек товаров в сжатом виде. Каждый клиент смотрит по 30 фотографий — суммарно уходит 10 ГБ исходящего трафика в месяц. При таких показателях стоимость будет около 24 ₽/мес.



Естественно, аренда объектного хранилища гораздо выгоднее, чем построение надежной и масштабируемой системы своими силами. В случае с бэкапами и холодным хранением цены, как правило, будут еще ниже. Рассчитать стоимость аренды объектного хранилища S3 вы можете на сайте.

Демонстрация работы S3-хранилища и другие подробности о его функциях — в видео
Автор: Артем Шумейко, автор канала на YouTube.

Комментарии (5)


  1. Andrey_Solomatin
    19.06.2024 09:45
    +2

    self.session = get_session()

    Этот код можно удалить. В сессию будет присвоен закрытый клиент.

    await s3_client.upload_file("test.txt")
    await s3_client.get_file("test.txt", "text_local_file.txt") await s3_client.delete_file("test.txt")

    Вы начали хорошо работать с контекстными менеджерами, но тут про них забыли.
    На каждую операцию будет новое подключение.


  1. skymal4ik
    19.06.2024 09:45
    +4

    Для тех, кто не любит облака и любит хранить и контролировать свои данные сам, может приглядеться к open source minio (https://min.io).

    Они не только позволяют гонять S3 совместимое хранилище (я лично использую докер), но и SDK для нескольких популярных языков, я использую для питона: https://min.io/docs/minio/linux/developers/python/minio-py.html


    1. Andrey_Solomatin
      19.06.2024 09:45

      Стоит подчернуть, что они S3 совместимы и можно использовать boto3. Другими словами переключаться между ними можно не меняя код. Можно использовать для тестирования.


  1. Flash_rnd
    19.06.2024 09:45

    Спасибо, хорошая статья

    А как можно загрузить в холодное обьектное хранилище 100млн. файлов и добиться хорошей скорости загрузки? При использовании django + celery у меня получилось 1000 файлов/мин.


  1. Flash_rnd
    19.06.2024 09:45

    Уже разобрался, добился скорости 4к/мин, но это дорого придётся в архивы упаковывать.