Как работать с S3 через Python - Академия Selectel

Как работать с S3 через Python

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

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

Немного об S3

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

Подробнее про S3 и нюансы работы с ним — в статье «Что такое объектное хранилище S3».

Технология поддерживает любой тип данных. Вы можете хранить архивные логи, документы, фото и видео — все, что есть на вашем сайте. 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 в контекстном менеджере.
Выбор функции 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 ключа.
Окно добавления S3 ключа.
S3 ключи — Access key и Secret key.
Полученные ключи.

5. Ключи копируем и добавляем в проект. Чтобы обращаться к бакету, указываем endpoint_url — https://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 вы можете на сайте.

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