Как работать с объектным хранилищем на Python

y-_yhmbt8bfypauuqb7ziw-nray.png


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

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

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

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

Немного об S3


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

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

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

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


oc2p7egxfswgdcnzdsqi16jqtlq.png

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


Напишем небольшой скрипт на 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.

nj7biqpmjs6qrbxbz4a35bmsquk.png


Выбор функции 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. Переходим к классу хранения. Если речь идет о бэкапах, архивных логах или данных, к которым вы редко возвращаетесь, но они очень ценны — выбирайте холодное хранение. Стандартное обычно выбирается для сервисов, где идет постоянная загрузка файлов, их просмотр и т. д. Нажимаем Создать контейнер.

jbvrzxdkphu2gnnid9tprl9kluq.png

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

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

jiyloypvvy0grs-_ytawddfgvgg.png


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

rmqclmu_qo2jjoxripds6ib0cxo.png


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

l7-fbnqg0n-zjsw9zs6ij3qnbco.png


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

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())

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


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

qnmwy73583gb89dv6qcfpvlikc0.png
aejxixjri7zvhxbhfdcnzz-lkqy.png

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

wuo76zqbwhfabgtsdsdoimsutr4.png


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

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




    
    
    
    Document


    


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

8to3_juwhbrswgdplykttazny40.png


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

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


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

jbvrzxdkphu2gnnid9tprl9kluq.png


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

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

6oxwrolrvkdwxstvgz2vgv4cese.png


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

flq76khxip_thhtr5zffjaony5a.png

Заключение


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

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

lbdx1cbbn7oavcxwqgxwmkei8fg.png

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

Демонстрация работы S3-хранилища и другие подробности о его функциях — в видео

Автор: Артем Шумейко, автор канала на YouTube.

© Habrahabr.ru