📖

Инструкция пользователя Облака УВА Advanced

Проекты в Облаке УВА Advanced

Страница с описаниями проектов доступна с виртуальных машин всех отделов. Выполните проброс локального порта аналогично тому, как описано здесь:

ssh -L 5555:10.0.4.62:5555 <your-username>@<ECS-external-IP>
  • -L 5555 — произвольно выбранный порт, на котором страница будет доступна локально.
  • 10.0.4.62:5555 — эти данные должны остаться без изменения.
  • <your-username>@<ECS-external-IP> замените на свой логин и внешний IP виртуальной машины отдела.

После проброса, в браузере:

http://localhost:5555

Митапы Облака УВА Advanced

ДатаНазвание
29.08.2025Митап №1: Презентация Облака УВА Advanced
01.12.2025Митап №2: GigaChat

Правила пользования Облаком УВА Advanced

  1. Сбор персональных данных с использованием облачных ресурсов УВА запрещен. Перед началом работы, пожалуйста, изучите 152-ФЗ.
  1. Использование Облака УВА не связано с:
    • с тестированием продуктовых гипотез;
    • проведением маркетинговых исследований, исследований UX (user experience) и клиентских путей, социологических исследований;
    • поддержкой/продажей существующих продуктов или реализацией run-процессов;
    • выводом продуктов на внешней рынок;
    • договорными отношениями с внешними клиентами (физ. лица, юр. лица) с принятием обязательств перед клиентами по качеству или доступности цифровых сервисов. Не предусмотрена обработка информации выше К4.
    • обработкой персональных данных клиентов. В целях обеспечения соблюдения требований Федерального закона от 27.07.2006 г. №152-ФЗ «О персональных данных» в Облаке УВА запрещено осуществлять сбор, хранение, использование, обработку, передачу и распространение персональных данных субъектов персональных данных.
  1. Не отключайте опцию подключения к виртуальной машине с правами root. Нарушение этого правила может привести к блокировке вашей машины.
  1. Администраторам придерживаться шаблона при создании учетной записи на виртуальной машине: {имя}-{табельный номер}. Пример: eimeredelin@sberbank.ru eimeredelin-2042974.
  1. Не храните в базах данных и объектном хранилище информацию, не предназначенную для широкой аудитории. Пользование сервером PostgreSQL и бакетами в Облаке УВА организовано по принципу
    🤝

    все видят всех и могут выгружать всё

  1. Давайте сущностям осмысленные имена. В названиях однотипных придерживайтесь шаблона/структуры. При создании базы данных заполняйте поле Комментарий. Пожалуйста, думайте об удобстве использования материалов вашими коллегами.
  1. Вопросы по работе сервиса, предложения и обнаруженные ошибки, пожалуйста, направляйте по адресам:

О персональных данных

Персональные данные (ПДн) — это любая информация, относящаяся к прямо или косвенно определенному или определяемому физическому лицу (п.1 ст.3 Федерального закона от 27.07.2006 №152-ФЗ «О персональных данных», далее — Закон о ПДн).

Для того чтобы загрузить и обрабатывать данные, содержащие ПДн, требуется правовое основание. Перечень правовых оснований для обычных и специальных категорий ПДн приведен в ч.1 ст.6, ч.2, 2.1 ст.10 Закона о ПДн. Основным (хотя и не единственным) правовым основанием является согласие на обработку ПДн. Сбор и обработка ПДн без правовых оснований влечет риск.

На что нужно обратить внимание при обработке внешних данных:

  1. Обезличивание ПДн — действия, в результате которых становится невозможным без использования дополнительной информации определить принадлежность ПДн конкретному лицу.
  1. Если из датасета будет удалена только часть «наиболее очевидных» ПДн (ФИО, номер телефона и т.п.), либо такие данные будут заменены идентификаторами, но останется иная информация, прямо или косвенно связанная с гражданином, существует вероятность признания таких действий обезличиванием ПДн. В ряде случаев обезличенные ПДн остаются персональными, и при их обработке по-прежнему необходимо соблюдать требования закона.
  1. Использование изображения гражданина (в т.ч. фотографии, видеозаписи) допускается с согласия этого гражданина. Тот факт, что изображение опубликовано в открытом доступе, еще не означает, что можно свободно загружать и использовать изображение без согласия гражданина.

    Не требуется получать согласие в следующих случаях:

    • изображение используется в государственных, общественных или иных публичных интересах;
    • изображение получено при съемке, которая проводится в местах, открытых для свободного посещения, или на публичных мероприятиях, за исключением случаев, когда такое изображение является основным объектом использования;
    • гражданин позировал за плату;
    • обстоятельства размещения изображения в Интернете могут говорить о согласии гражданина на использование изображения (например, если это предусмотрено условиями пользования сайтом, на котором размещено изображение).

    В иных случаях использование изображения гражданина без его согласия влечет риск.

  1. Частная жизнь — это область жизнедеятельности человека, которая касается только его и не подлежит контролю со стороны общества и государства, если не носит противоправный характер.

    При этом исчерпывающее определение перечня сведений, однозначно относящихся/не относящихся к сведениям, составляющим частную жизнь лица, невозможно.
    В частную жизнь включаются, в частности, сведения о происхождении, о месте пребывания или жительства, о личной и семейной жизни, о профессиональной и деловой деятельности.

    Не допускаются без согласия гражданина сбор, хранение, распространение и использование любой информации о его частной жизни. Согласие не требуется в случаях, когда указанные действия осуществляются в государственных, общественных или иных публичных интересах, а также в случаях, если информация о частной жизни гражданина ранее стала общедоступной, либо была раскрыта самим гражданином или по его воле (п.1 ст.152.2 ГК РФ).

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

  1. Нарушением тайны телефонных переговоров является, в частности, незаконный доступ к информации о входящих и об исходящих сигналах соединения между абонентами или абонентскими устройствами пользователей связи (дате, времени, продолжительности соединений, номерах абонентов, других данных, позволяющих идентифицировать абонентов — п.4 Постановления Пленума Верховного Суда РФ от 25.12.2018 №46).

При возникновении вопросов с определением категории информации для обработки в Облаке УВА можно обратиться к коллегам из SOC (ЮЗБ).

Порядок получения доступов

Порядок получения доступов — учетных данных к сервисам в Облаке УВА.

Есть пользователь (сотрудник отдела), есть администратор отдела — тоже сотрудник отдела, но с функцией "старосты", есть администратор Облака УВА.

  1. Чтобы получить свои персональные учетные данные, сотрудник/заявитель пишет админу своего отдела.
  1. Админ отдела пишет админу Облака УВА: создать доступы для табельный номер заявителя. Табельный номер есть в профиле сотрудника в Пульсе. Если заявителю требуется sudo, админ отдела указывает это. По умолчанию sudo не будет.
  1. Админ Облака УВА создает для заявителя доступы к виртуальной машине отдела, серверу PostgreSQL и объектному хранилищу.
  1. Админ Облака УВА передает заявителю учетные данные из п.3 с копией письма админу отдела.
  1. Заявитель подключается к сервисам, пользуясь данной Инструкцией и своими учетными данными.

Администраторы отделов

Вертикаль/ОтделФИО
ОАОПДемков Владимир Валерьевич
ОАОПГорянский Илья Андреевич
ОАПСОКотенев Алексей Алексеевич
ОАПСОЛюбимцев Игорь Юрьевич
ОАРБЧечнев Алексей Александрович
ОАРБДейнеко Максим Андреевич
ОАИТМеределин Евгений Игоревич
ОАИТМахмутов Радик Гумерович
ОАОФРХертек Ясемин Орлан-ооловна
ОАОФРУльяненков Тимофей Владимирович
CDSАбрашкина Анна Александровна
CDSБатарчук Егор Игоревич

Комплект учетных данных

Виртуальная машина

  1. Внешний IP для подключения по SSH. В примере Подключение к виртуальной машине: 188.72.107.76.
  1. Внутренний IP в Облаке УВА.
  1. Имя пользователя и пароль. Пользователь в примере: user-demo.

Сервер PostgreSQL

  1. Имя сервера PostgreSQL и его IP-адрес. В примере Подключение к серверу PostgreSQL: postgres-demo, 10.0.14.19.
  1. Имя пользователя и пароль. Пользователь в примере: user-demo.

Объектное хранилище

  1. Имя IAM-пользователя и пароль для доступа к сервису Object Storage Service через консоль платформы Advanced Cloud.ru. Используется в п.5 Подключение к объектному хранилищу. Метод двухфакторной аутентификации (2FA) — Virtual MFA Device, см. раздел Подключить приложение одноразовых паролей.
    ℹ️

    консольный доступ работает с локальной машины и из сигмы

  1. Access key, secret key, endpoint и region (programmatic access — программный доступ) для управления объектами в бакете с помощью клиентов/интерфейсов объектных хранилищ и библиотек. Используется в пп.1-4 Подключение к объектному хранилищу.
    ℹ️

    программный доступ работает с локальной машины (GUI, CLI) и виртуальной машины (CLI)

  1. Имя созданного для отдела бакета. В примере: user-demo-bucket.

Подключение к виртуальной машине

  1. Сгенерируйте пароль (passphrase) для SSH-ключа и сохраните его в надежном месте. Пожалуйста, не пренебрегайте созданием passphrase — он понадобится для подключения к серверу PostgreSQL. Например, создайте буквенно-цифровую последовательность длиной 50 символов этим генератором.
  1. Сгенерируйте SSH-ключ на локальной машине:
    $ ssh-keygen
    Generating public/private ed25519 key pair.
    Enter file in which to save the key (/c/Users/user/.ssh/id_ed25519):
    Enter passphrase for "/c/Users/user/.ssh/id_ed25519" (empty for no passphrase):
    Enter same passphrase again:
    Your identification has been saved in /c/Users/user/.ssh/id_ed25519
    Your public key has been saved in /c/Users/user/.ssh/id_ed25519.pub
    The key fingerprint is:
    SHA256:apfCa4esUZko7sJjHEwkE2eMpvrRUQlucdrf6RXZ9Y0 user@AsusVivobook
    The key's randomart image is:
    +--[ED25519 256]--+
    |.+o o...        .|
    |+=.. =o      o oo|
    |=.  +..     o E o|
    |.. ... + . . .   |
    |+ ....+ S o .    |
    |.+...o . o .     |
    |o.o...=.o .      |
    |.*.  o++.        |
    |..o .o..         |
    +----[SHA256]-----+

  1. Скопируйте публичную часть ключа, id_ed25519.pub, с локальной машины на виртуальную. Измените путь до ключа на локальной машине на свой.
    $ scp C:/Users/user/.ssh/id_ed25519.pub user-demo@188.72.107.76:/home/user-demo/.ssh/authorized_keys
    user-demo@188.72.107.76's password:
    id_ed25519.pub                                          100%   99     7.3KB/s   00:00

  1. Подключитесь к виртуальной машине по SSH:
    $ ssh user-demo@188.72.107.76
    Enter passphrase for key '/c/Users/user/.ssh/id_ed25519':
    Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-71-generic x86_64)
    
     * Documentation:  https://help.ubuntu.com
     * Management:     https://landscape.canonical.com
     * Support:        https://ubuntu.com/pro
    
     System information as of Wed Jul 30 02:20:05 PM MSK 2025
    
      System load:  0.0               Processes:             113
      Usage of /:   8.6% of 39.26GB   Users logged in:       0
      Memory usage: 10%               IPv4 address for eth0: 10.0.14.15
      Swap usage:   0%
    
    
    Expanded Security Maintenance for Applications is not enabled.
    
    0 updates can be applied immediately.
    
    Enable ESM Apps to receive additional future security updates.
    See https://ubuntu.com/esm or run: sudo pro status
    
    
    
            Welcome to Elastic Cloud Service

    Еще проще с Termius:

    1. Создайте хост с подключением по паролю:

    1. Создайте ключ:

    1. Отправьте ключ на хост:

    1. Уберите из профиля хоста пароль и подключайтесь по ключу:

    1. В платной версии есть синхронизация зашифрованного хранилища (vault) между десктоп- и мобильным приложением:

  1. Доступ к виртуальной машине из сигмы.

    Закрепите Elastic Cloud Server на панели быстрого доступа:

    Найдите в списке машину вашего отдела и подключитесь к терминалу:

    ⚠️

    Завершите сессию командой exit перед закрытием вкладки браузера.

Подключение к серверу PostgreSQL

Виртуальная машина как безопасная точка доступа к серверу PostgreSQL

  1. Установите PostgreSQL на локальную машину, включая предлагаемый во время установки pgAdmin.

  1. Выполните в pgAdmin первое подключение к серверу по SSH, следуя подсказкам на скриншотах ниже.

  1. Вход при последующих подключениях:

  1. Создайте базу данных и схему (schema) внутри нее. Внутри схемы вы сможете создавать таблицы и другие сущности. Скриншот ниже показывает пример ошибки, которую получит другой пользователь при попытке изменить вашу таблицу.

  1. Быть владельцем сущности не всегда означает право изменять ее: владелец может делегировать право, хотя сам при этом может им активно и не обладать. Чтобы писать в таблицы, например, с помощью SQLAlchemy, выполните небольшую донастройку по примеру на скришотах ниже. Откройте свойства принадлежащих вам, т.е. созданных вами, сущностей (БД, схема, таблица и т.д.) и дайте самому себе или коллегам необходимые права. Во вкладке Security задаются права на уже созданные сущности, в Default Privileges — на те, которые будут созданы внутри редактируемой сущности.

  1. Если вы используете SQLAlchemy ORM, не забудьте указать имя схемы. Таблица в примере, reviews, живет на схеме bankiru. Если не указать это явно, SQLAlchemy будет искать/писать таблицу на дефолтной для постгреса схеме public, к которой сейчас у пользователей доступа нет, и это приведет к InsufficientPermissionException. Для установки схемы воспользуйтесь одним из двух способов:
    • определите схему в метаданных базового класса Base, если схему нужно распространить на все дочерние модели;
    • переопределите схему в дандер-атрибуте __table_args__ конкретной модели, если схема задается для той или иной таблицы индивидуально.
    from sqlalchemy import MetaData
    from sqlalchemy.orm import declarative_base
    
    
    Base = declarative_base(metadata=MetaData(schema="bankiru"))
    
    
    class Review(Base):
        __tablename__ = "reviews"
        # __table_args__ = {"schema": "bankiru"}
    	  
    	  # ...

Подключение к объектному хранилищу

📌

пп.1-4 — с локальной машины, п.5 — с локальной машины и из сигмы

  1. Установите OBS Browser Plus на локальную машину.
  1. Выполните вход, следуя подсказкам на скриншоте. Используйте данные из п.2 Доступ к объектному хранилищу.

  1. Найдите по имени созданный для вас бакет.

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

  1. Подключение через консоль платформы Advanced Cloud.ru. Используйте имя пользователя и пароль из п.1 Доступ к объектному хранилищу.

    Двухфакторная аутентификация — ваша почта в сигме:

    Или через Меню:

  1. Узнайте из материалов митапа о создании из бакета статического сайта.

О подходах к наделению правами

Обратите внимание на разницу в подходах к наделению правами между сервером postgres и объектным хранилищем.

На сервере postgres все учетные записи изначально являются равноправными, read-only, и не привязаны к отделам, т.е. нет такого понятия как “командное пространство” и с точки зрения прав каждый сам по себе. Например, вы создали базу данных — значит, вы ее владелец и можете наделять коллег теми или иными дополнительными правами. Симметрично: чтобы получить права сверх чтения на сущность, созданную не вами, вы должны запросить их у владельца. Узнать владельца и отредактировать права можно в свойствах сущности.

В объектном хранилище Облака УВА аналог командного пространства существует — это бакет. Получая доступ к бакету отдела, вы можете писать в него свои объекты и удалять любые объекты ваших коллег по отделу. Но, разумеется, вы не можете изменять содержимое бакетов других отделов — только просматривать и скачивать.

  • сервер PostgreSQL: нет командного пространства, владелец наделяет правами на объект;
  • объектное хранилище: есть командное пространство — бакет, политика бакета регулирует права на объект.

Дополнительные материалы/Что дальше

  • создайте SSH-ключ на виртуальной машине и скопируйте его публичную часть в свою учетную запись на GitHub;
  • узнайте, как устанавливать python-пакеты и управлять проектами с помощью замечательного менеджера uv;
  • узнайте, как подключиться к вашим базам данных на сервере PostgreSQL с помощью SQLAlchemy;
  • узнайте, как управлять запуском вашего приложения с помощью systemd;
  • узнайте, как управлять контейнерами и подами с помощью Podman.

Пример простого сервиса: banki.ru

ВертикальОАИТ (Северо-Западный банк)
АвторМеределин Евгений Игоревич
ТемаЖалобы и негативные отзывы клиентов на работу банков
Старт1 января 2025 г.
Период обновлениясутки
Типтаблица
Полядата публикации, текст жалобы, ссылка на жалобу, населенный пункт автора, банк, банковская услуга
Доступные форматыcsv, json, parquet, xlsx
ИнструментыFastAPI, Pydantic, SQLAlchemy, aiobotocore, Gradio, Pydantic AI, Cloud.ru Foundation Models
Репозиторий APIGitHub
Репозиторий Gradio UIGitHub
Swagger API10.0.4.62:1706/docs
Gradio UI10.0.4.62:17060
Сервер PostgreSQLbankiru db — bankiru schema — reviews table
Объектное хранилище (Object Storage)oait-bucket — bankiru_reviews_db_backup.parquet

Обновление от 12.01.2026

  • Сервис получил интерфейс Gradio, работающий на 10.0.4.62:17060. Примеры:
  • Чтобы суммаризировать жалобы моделями сервиса Cloud.ru Foundation Models из выпадающего списка Cloud model, создайте API-ключ как описано здесь, и вставьте его в поле Cloud API key. Для скачивания файла с выборкой в формате Format ключ не требуется.
  • В выпадающем списке Bank топ-50 банков по количеству жалоб за 2025 г. в алфавитном порядке. Если оставить поле Bank пустым, оно не будет ограничивать результирующую выборку, что равносильно выбору всех банков, которых намного больше, чем 50. Такая же логика работает для Product и Location.
  • В выпадающем списке Product — банковские услуги из рубрикатора banki.ru: сначала для физлиц, затем для юрлиц. Оба блока оканчиваются опцией Другое.
  • В выпадающем списке Location административные центры субъектов РФ.

Получение данных по API на личной машине

Доступ к API базы данных bankiru предоставлен виртуальным машинам всех отделов. Скачивайте данные на локальную машину, подключаясь к API через вашу виртуальную машину по SSH-туннелю:

ssh -L 1706:10.0.4.62:1706 <your-username>@<ECS-external-IP>
  • -L 1706 — произвольно выбранный порт, на котором приложение будет доступно локально.
  • 10.0.4.62:1706 — эти данные должны остаться без изменения.
  • <your-username>@<ECS-external-IP> замените на свои логин и внешний IP виртуальной машины.

Пример запроса — данные за лето 2025 г. в xlsx:

http://localhost:1706/reviews?startDate=20250601&endDate=20250831&outputFormat=xlsx

Прекратить проброс порта: Ctrl+C или Ctrl+D.

Еще проще с Termius: создайте профиль проброса порта и подключайтесь в один клик:

Получение данных по API в сигме

  1. Подключение к виртуальной машине в консоли Cloud.ru Advanced — см. п.5 раздела Подключение к виртуальной машине.
  1. На всех машинах в Облаке УВА установлен HTTPie. Пример запроса тот же — данные за лето 2025 г. в xlsx:
    $ http 10.0.4.62:1706/reviews \
    > startDate==20250601 endDate==20250831 outputFormat==xlsx \
    > --unsorted
    • http (или https, если требуется) — утилита HTTPie;
    • метод не указан, по умолчанию GET для запроса без тела;
    • 10.0.4.62:1706/reviews — внутренний IP-адрес машины ОАИТ, порт, на котором работает приложение и эндпоинт;
    • startDate, endDate, outputFormat — query-параметры запроса;
    • --unsorted — опция для вывода полей в ответе в порядке их следования в модели данных (а не в алфавитном).
    ⚠️

    Завершите сессию командой exit перед закрытием вкладки браузера.

  1. Укороченная ссылка от spoo.me в сигме не откроется, а оригинальная весьма длинная. Поэтому смотрим имя файла в поле filename и забираем из бакета ОАИТ:

Запуск проекта в Облаке УВА Advanced

В проекте используются все услуги Облака УВА Advanced, доступные на момент написания в августе 2025: виртуальная машина, сервер PostgreSQL и объектное хранилище. Пожалуйста, проходите этот раздел после выполнения инструкций выше.

Сервис сбора и хранения негативных отзывов/жалоб с banki.ru состоит из двух частей:

  1. Парсер. В начале наступившего дня собирает данные за истекшие сутки и отправляет их в API базы данных.
  1. API базы данных. Публикует пришедшие от парсера новые записи в базе данных. После каждого коммита (добавления или удаления записей) делает резервную копию базы данных в формате parquet и перезаписывает предыдущую версию в бакете объектного хранилища. Принимает запрос на получение из БД выборки в заданном формате с фильтрацией по интервалу дат, банкам, банковским услугам, городам авторов жалоб. Выборка предоставляется в ответе в виде прямой ссылки на скачивание файла из бакета объектного хранилища. Возможна суммаризация жалоб при наличии API-ключа, совместимого с Cloud.ru Foundation Models.

Рассмотрим разработку и запуск API как отдельного проекта. Ход работы с парсером аналогичен.

План действий. Разработка API ведется на [локальной] машине с Windows. Площадка запуска и постоянной работы — виртуальная машина с Ubuntu. Мы подготовим проект на локальной машине, создадим для него репозиторий на GitHub, склонируем репозиторий на виртуальную машину, запустим API и посмотрим на его работу.

Инструменты: uv, GitHub CLI, systemd, HTTPie CLI, Pydantic Logfire.

В примере используется SSH-клиент Termius с двумя профилями: PowerShell и bash из поставки Git для Windows (C:\Program Files\Git\bin\bash.exe). Если не указано особо, команда выполняется в bash.

  1. Установка uv в Windows. В PowerShell:
    powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
    Downloading uv 0.8.9 (x86_64-pc-windows-msvc)  Installing to C:\Users\user\.local\bin
      uv.exe
      uvx.exe
      uvw.exe
    everything's installed!
    uv --version
    uv 0.8.9 (68c0bf8a2 2025-08-11)

  1. Создание проекта с помощью uv:
    /d/git
    $ uv init bankiru-dbapi
    Initialized project `bankiru-dbapi` at `D:\git\bankiru-dbapi`

  1. Создание виртуального окружения, установка зависимостей. Изучите документацию uv, если вашему проекту требуется какая-то конкретная версия интерпретатора и чтобы дать виртуальному окружению имя, отличное от .venv по умолчанию.
    /d/git
    $ cd bankiru-dbapi/
    /d/git/bankiru-dbapi (main)
    $ uv add aiobotocore alembic environs fastapi[standard] logfire[fastapi] pandas psycopg[binary,pool] py-spoo-url pyarrow sqlalchemy
    Using CPython 3.13.2
    Creating virtual environment at: .venv
    Resolved 104 packages in 814ms
    Installed 102 packages in 3.93s
     + aiobotocore==2.24.0
     + aiohappyeyeballs==2.6.1
     + aiohttp==3.12.15
     ...
     + wrapt==1.17.3
     + yarl==1.20.1
     + zipp==3.23.0

    pyproject.toml:

    /d/git/bankiru-dbapi (main)
    $ cat pyproject.toml
    [project]
    name = "bankiru-dbapi"
    version = "0.1.0"
    description = "Banki.ru Claims and Negative Reviews Database API"
    readme = "README.md"
    requires-python = ">=3.13"
    dependencies = [
        "aiobotocore>=2.24.0",
        "alembic>=1.16.4",
        "environs>=14.3.0",
        "fastapi[standard]>=0.116.1",
        "logfire[fastapi]>=4.3.3",
        "pandas>=2.3.1",
        "psycopg[binary,pool]>=3.2.9",
        "py-spoo-url>=0.0.6",
        "pyarrow>=21.0.0",
        "sqlalchemy>=2.0.43",
    ]

    Заполним поле description. В проекте есть alembic, но создание системы миграций здесь не рассматривается. Для чтения переменных окружения используется environs. Обратите также внимание на замечательную библиотеку dataclass-wizard.

  1. Мы поработали и написали в корневой папке проекта несколько py-модулей и/или пакетов. Также мы добавим:
    • файл с переменными окружения .env ;
    • файл .env.example, чтобы использующие код знали, какие переменные окружения он ожидает;
    • файл .gitignore, исключающий из отслеживания .env и .venv ;
    • юнит systemd bankiru-dbapi.service для запуска API на виртуальной машине.
    /d/git/bankiru-dbapi (main)
    $ ls -a -1
    .
    ..
    .env
    .env.example
    .git
    .gitignore
    .python-version
    .venv
    README.md
    alembic
    alembic.ini
    bankiru-dbapi.service
    botocore_client.py
    database.py
    handlers.py
    logfire_auto_tracing.py
    main.py
    models.py
    pyproject.toml
    schemas.py
    uv.lock

  1. .env.example :
    /d/git/bankiru-dbapi (main)
    $ cat .env.example
    API_TOKEN=
    LOGFIRE_TOKEN=
    POSTGRES_URL=postgresql+psycopg://<username>:<password>@<ip>/<db_name>
    
    AWS_REQUEST_CHECKSUM_CALCULATION=when_required
    AWS_RESPONSE_CHECKSUM_VALIDATION=when_required
    OBS_BUCKET=
    OBS_ACCESS_KEY=
    OBS_SECRET_KEY=
    OBS_REGION=
    OBS_ENDPOINT=
    
    ECS_PRIVATE_IP=
    ECS_PORT=

  1. bankiru-dbapi.service :
    /d/git/bankiru-dbapi (main)
    $ cat bankiru-dbapi.service
    [Unit]
    Description=bankiru-dbapi
    After=syslog.target
    After=network.target
    
    [Service]
    Type=simple
    User=evm
    WorkingDirectory=/home/evm/bankiru-dbapi
    ExecStart=/home/evm/bankiru-dbapi/.venv/bin/python /home/evm/bankiru-dbapi/logfire_auto_tracing.py
    Restart=always
    
    [Install]
    WantedBy=multi-user.target

  1. Выпуск токена доступа/аутентификации personal access token (PAT) на GitHub здесь. Требование к токену: The minimum required scopes are repo, read:org, admin:public_key.

  1. Установка GitHub CLI в Windows: скачать msi или с помощью winget.
    /d/git/bankiru-dbapi (main)
    $ winget install --id GitHub.cli
    Found GitHub CLI [GitHub.cli] Version 2.76.2
    This application is licensed to you by its owner.
    Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
    Downloading https://github.com/cli/cli/releases/download/v2.76.2/gh_2.76.2_windows_amd64.msi
      ██████████████████████████████  17.4 MB / 17.4 MB
    Successfully verified installer hash
    Starting package install...
    The installer will request to run as administrator. Expect a prompt.
    Successfully installed
    /d/git/bankiru-dbapi (main)
    $ gh --version
    gh version 2.76.2 (2025-07-30)
    https://github.com/cli/cli/releases/tag/v2.76.2

  1. Аутентификация в GitHub CLI с использованием токена из п.7. Публичная часть SSH-ключа будет скопирована и появится здесь с назначенным ему именем.
    /d/git/bankiru-dbapi (main)
    $ gh auth login
    ? Where do you use GitHub? GitHub.com                           
    ? What is your preferred protocol for Git operations on this host? SSH                                  
    ? Upload your SSH public key to your GitHub account? C:\Users\user\.ssh\id_ed25519.pub    
    ? Title for your SSH key: (GitHub CLI) ...
    
    ? Title for your SSH key: ...
    ? How would you like to authenticate GitHub CLI? Paste an authentication token        
    Tip: you can generate a Personal Access Token here https://github.com/settings/tokens
    The minimum required scopes are 'repo', 'read:org', 'admin:public_key'.
    ? Paste your authentication token: ****************************************
    - gh config set -h github.com git_protocol ssh
    ✓ Configured git protocol
    ✓ Uploaded the SSH key to your GitHub account: C:\Users\user\.ssh\id_ed25519.pub
    ✓ Logged in as EvgenyMeredelin

    Токен аутентификации хранится безопасно:

    /d/git/bankiru-dbapi (main)
    $ gh auth status
    github.com
      ✓ Logged in to github.com account EvgenyMeredelin (keyring)
      - Active account: true
      - Git operations protocol: ssh
      - Token: ghp_************************************
      - Token scopes: ...

  1. Коммит проекта. Но перед этим добавим описание в README.md.
    /d/git/bankiru-dbapi (main)
    $ git add .
    /d/git/bankiru-dbapi (main)
    $ git commit -m "First commit"
    [main (root-commit) 3dbd33a] First commit
     19 files changed, 2808 insertions(+)
     create mode 100644 .env.example
     create mode 100644 .gitignore
     create mode 100644 .python-version
     create mode 100644 README.md
     create mode 100644 alembic.ini
     create mode 100644 alembic/README
     create mode 100644 alembic/env.py
     create mode 100644 alembic/script.py.mako
     create mode 100644 alembic/versions/45600a192211_initial_migration.py
     create mode 100644 bankiru-dbapi.service
     create mode 100644 botocore_client.py
     create mode 100644 database.py
     create mode 100644 handlers.py
     create mode 100644 logfire_auto_tracing.py
     create mode 100644 main.py
     create mode 100644 models.py
     create mode 100644 pyproject.toml
     create mode 100644 schemas.py
     create mode 100644 uv.lock

  1. Создание репозитория и пуш коммита в него. Источник — текущая папка, поэтому репозиторий получит ее имя: bankiru-dbapi.
    /d/git/bankiru-dbapi (main)
    $ gh repo create --source=. --private --push
    ✓ Created repository EvgenyMeredelin/bankiru-dbapi on github.com
      https://github.com/EvgenyMeredelin/bankiru-dbapi
    ✓ Added remote git@github.com:EvgenyMeredelin/bankiru-dbapi.git
    Enter passphrase for key '/c/Users/user/.ssh/id_ed25519':
    Enumerating objects: 23, done.
    Counting objects: 100% (23/23), done.
    Delta compression using up to 8 threads
    Compressing objects: 100% (21/21), done.
    Writing objects: 100% (23/23), 99.42 KiB | 1.33 MiB/s, done.
    Total 23 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
    remote: Resolving deltas: 100% (1/1), done.
    To github.com:EvgenyMeredelin/bankiru-dbapi.git
     * [new branch]      HEAD -> main
    branch 'main' set up to track 'origin/main'.
    ✓ Pushed commits to git@github.com:EvgenyMeredelin/bankiru-dbapi.git

  1. Чтение переменных окружения и подключение к виртуальной машине:
    /d/git/bankiru-dbapi (main)
    $ . .env
    /d/git/bankiru-dbapi (main)
    $ ssh evm@<ECS-public-IP>
    Enter passphrase for key '/c/Users/user/.ssh/id_ed25519':
    Welcome to Ubuntu 24.04.3 LTS (GNU/Linux 6.8.0-71-generic x86_64)
    
     * Documentation:  https://help.ubuntu.com
     * Management:     https://landscape.canonical.com
     * Support:        https://ubuntu.com/pro
     
    ...

  1. Установка uv в Ubuntu:
    evm@ecs-evm:~$ curl -LsSf https://astral.sh/uv/install.sh | sh
    downloading uv 0.8.9 x86_64-unknown-linux-gnu
    no checksums to verify
    installing to /home/evm/.local/bin
      uv
      uvx
    everything's installed!
    evm@ecs-evm:~$ uv --version
    uv 0.8.9

  1. По причине несовершенства механизма хранения гитом секретов в Linux мы не будем устанавливать GitHub CLI в Ubuntu. Создадим SSH-ключ по пп.1-2 Подключение к виртуальной машине — с той лишь разницей, что это нужно сделать не на локальной машине, а на виртуальной. Скопируем руками публичную часть ключа и создадим новый ключ в разделе SSH keys здесь.
    evm@ecs-evm:~$ cat .ssh/id_ed25519.pub
    ssh-ed25519 ...

  1. Клонирование репозитория на виртуальную машину. Ссылку, соответствующую протоколу, в том числе SSH, можно скопировать со страницы репозитория (зеленая кнопка Code).
    evm@ecs-evm:~$ git clone git@github.com:EvgenyMeredelin/bankiru-dbapi.git
    Cloning into 'bankiru-dbapi'...
    remote: Enumerating objects: 23, done.
    remote: Counting objects: 100% (23/23), done.
    remote: Compressing objects: 100% (20/20), done.
    remote: Total 23 (delta 1), reused 23 (delta 1), pack-reused 0 (from 0)
    Receiving objects: 100% (23/23), 99.42 KiB | 530.00 KiB/s, done.
    Resolving deltas: 100% (1/1), done.

  1. (Вос)создание виртуального окружения:
    evm@ecs-evm:~$ cd bankiru-dbapi/
    evm@ecs-evm:~/bankiru-dbapi$ uv sync
    Using CPython 3.13.5
    Creating virtual environment at: .venv
    Resolved 104 packages in 26ms
    Prepared 46 packages in 2.63s
    Installed 102 packages in 620ms
     + aiobotocore==2.24.0
     + aiohappyeyeballs==2.6.1
     + aiohttp==3.12.15
     ...
     + wrapt==1.17.3
     + yarl==1.20.1
     + zipp==3.23.0

  1. Откроем второй терминал и скопируем файл с переменными окружения .env с локальной машины на виртуальную:
    /d/git/bankiru-dbapi (main)
    $ . .env
    /d/git/bankiru-dbapi (main)
    $ scp .env evm@<ECS-public-IP>:/home/evm/bankiru-dbapi/
    Enter passphrase for key '/c/Users/user/.ssh/id_ed25519':
    .env                                                    100%  704    50.7KB/s   00:00

    Теперь весь проект на месте:

    evm@ecs-evm:~/bankiru-dbapi$ ls -a -1
    .
    ..
    alembic
    alembic.ini
    bankiru-dbapi.service
    botocore_client.py
    database.py
    .env
    .env.example
    .git
    .gitignore
    handlers.py
    logfire_auto_tracing.py
    main.py
    models.py
    pyproject.toml
    .python-version
    README.md
    schemas.py
    uv.lock
    .venv

  1. Скопируем подготовленный в п.6 юнит systemd и запустим его:
    evm@ecs-evm:~/bankiru-dbapi$ sudo cp bankiru-dbapi.service /etc/systemd/system
    evm@ecs-evm:~/bankiru-dbapi$ sudo systemctl daemon-reload
    evm@ecs-evm:~/bankiru-dbapi$ sudo systemctl enable bankiru-dbapi.service
    Created symlink /etc/systemd/system/multi-user.target.wants/bankiru-dbapi.service → /etc/systemd/system/bankiru-dbapi.service.
    evm@ecs-evm:~/bankiru-dbapi$ sudo systemctl start bankiru-dbapi.service

    Юнит работает:

    evm@ecs-evm:~$ sudo systemctl status bankiru-dbapi.service
    ● bankiru-dbapi.service - bankiru-dbapi
         Loaded: loaded (/etc/systemd/system/bankiru-dbapi.service; enabled; preset: enabled)
         Active: active (running) since Wed 2025-08-13 21:17:43 MSK; 4s ago
       Main PID: 34936 (python)
          Tasks: 11 (limit: 4144)
         Memory: 124.8M (peak: 125.2M)
            CPU: 1.849s
         CGroup: /system.slice/bankiru-dbapi.service
                 └─34936 /home/evm/bankiru-dbapi/.venv/bin/python /home/evm/bankiru-dbapi/logfire_auto_tracing.py
    
    Aug 13 21:17:43 ecs-evm systemd[1]: Started bankiru-dbapi.service - bankiru-dbapi.
    Aug 13 21:17:43 ecs-evm python[34936]: Logfire project URL: https://logfire-us.pydantic.dev/...
    Aug 13 21:17:44 ecs-evm python[34936]: INFO:     Started server process [34936]
    Aug 13 21:17:44 ecs-evm python[34936]: INFO:     Waiting for application startup.
    Aug 13 21:17:44 ecs-evm python[34936]: INFO:     Application startup complete.
    Aug 13 21:17:44 ecs-evm python[34936]: INFO:     Uvicorn running on http://... (Press CTRL+C to quit)

    Выход: :q. Число запускаемых юнитов не ограничено. Главное чтобы на запускаемый юнитами ваш код хватало ресурсов машины.

  1. Итак, API готов к работе. Парсер поднимается аналогично. Мы создали на сервере базу данных, и парсер уже насобирал в нее данные. Установим на локальной машине Chocolatey и HTTPie CLI. В PowerShell:

    Run Get-ExecutionPolicy. If it returns Restricted, then run Set-ExecutionPolicy AllSigned or Set-ExecutionPolicy Bypass -Scope Process.

    Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
    choco --version
    2.5.0
    choco install httpie
    Chocolatey v2.5.0
    Installing the following packages:
    httpie
    By installing, you accept licenses for the packages.
    Downloading package from source 'https://community.chocolatey.org/api/v2/'
    Progress: Downloading httpie 3.2.2... 100%
    ...
    Successfully installed httpie-3.2.2
     The install of httpie was successful.
      Software install location not explicitly set, it could be in package or
      default install location of installer.
    
    Chocolatey installed 1/1 packages.
     See the log for details (C:\ProgramData\chocolatey\logs\chocolatey.log).

  1. Вернемся на локальную машину, выполним проброс локального порта, как описано здесь, и рассмотрим несколько примеров работы API.
    Чтение записей. Отзывы за первую неделю августа 2025 в xlsx. Ссылка валидна ограниченное время. Ответ 200 OK.
    /d/git/bankiru-dbapi (main)
    $ http GET localhost:1706/reviews \
    > startDate==20250801 endDate==20250807 outputFormat==xlsx \
    > --format-options json.sort_keys:false
    HTTP/1.1 200 OK
    content-length: 136
    content-type: application/json
    date: Thu, 14 Aug 2025 07:58:48 GMT
    server: uvicorn
    
    {
        "startDate": "2025-08-01",
        "endDate": "2025-08-07",
        "outputFormat": "xlsx",
        "generateUrl": true,
        "url": "https://spoo.me/dv7ta3",
        "comment": null
    }

    То же в HTTPie desktop:

    HTTPie desktop app

    С параметрами по умолчанию получим всю БД в parquet:

    /d/git/bankiru-dbapi (main)
    $ http GET localhost:1706/reviews --unsorted
    HTTP/1.1 200 OK
    date: Thu, 14 Aug 2025 08:00:09 GMT
    server: uvicorn
    content-length: 123
    content-type: application/json
    
    {
        "startDate": null,
        "endDate": null,
        "outputFormat": "parquet",
        "generateUrl": true,
        "url": "https://spoo.me/OuJjmw",
        "comment": null
    }

  1. Добавление записей. Сымитируем работу парсера и добавим в БД одну запись (представим, что парсер отправил список, в котором единственная запись):
    /d/git/bankiru-dbapi (main)
    $ cat reviews.json
    [
        {
            "datePublished": "2026-01-01 01:23:45",
            "reviewBody": "Я закопал монетки, и они пропали...",
            "bankName": "Банк Поле Чудес",
            "url": "https://banki.ru/buratino-review",
            "location": "Сказочный город",
            "product": "Вклад"
        }
    ]
    /d/git/bankiru-dbapi (main)
    $ http POST localhost:1706/reviews API-Token:$API_TOKEN < reviews.json
    HTTP/1.1 201 Created
    content-length: 4
    content-type: application/json
    date: Thu, 14 Aug 2025 08:01:50 GMT
    server: uvicorn
    
    null

    Ответ null со статусом 201 Created, потому что парсеру ответ не требуется. Как сказано в описании API выше, после каждого коммита (добавления или удаления записей) API создает бэкап базы данных в parquet и перезаписывает предыдущую версию в бакете объектного хранилища:

    Логика проста. Если это выборка по запросу пользователя, то API дает информативный ответ с полями, описывающими выборку, а файлу дается уникальное uuid-имя. Если эта выборка — автоматический бэкап, спровоцированный парсером, то осмысленный ответ не требуется, а файл в parquet получает [статичное] предопределенное имя bankiru_reviews_db_backup, и это приводит к перезаписи предыдущего бэкапа.

    Так наша запись выглядит в БД. На скриншоте 5 последних записей, начиная с последней.

  1. Удаление записей. Удалим нашу запись, отправив соответствующему эндпоинту массив из одного id, — id записи к удалению:
    /d/git/bankiru-dbapi (main)
    $ echo '[220345]' | http DELETE localhost:1706/reviews API-Token:$API_TOKEN
    HTTP/1.1 204 No Content
    content-type: application/json
    date: Thu, 14 Aug 2025 08:06:48 GMT
    server: uvicorn

    Ожидаемый ответ 204 No Content, запись удалена, а бэкап обновлен, что видно по свежей дате последнего изменения в бакете:

  1. Примеры валидации данных. В обоих случаях ответ 422 Unprocessable Content.
    29 февраля у невисокосного 2025 года:
    /d/git/bankiru-dbapi (main)
    $ http GET localhost:1706/reviews \
    > startDate==20250229
    HTTP/1.1 422 Unprocessable Content
    content-length: 152
    content-type: application/json
    date: Thu, 14 Aug 2025 08:09:14 GMT
    server: uvicorn
    
    {
        "detail": [
            {
                "ctx": {
                    "error": {}
                },
                "input": "20250229",
                "loc": [
                    "query",
                    "startDate"
                ],
                "msg": "Value error, day is out of range for month",
                "type": "value_error"
            }
        ]
    }

    Неизвестный формат файла, для которого нет обработчика:

    /d/git/bankiru-dbapi (main)
    $ http GET localhost:1706/reviews \
    > outputFormat==abc
    HTTP/1.1 422 Unprocessable Content
    content-length: 197
    content-type: application/json
    date: Thu, 14 Aug 2025 08:09:54 GMT
    server: uvicorn
    
    {
        "detail": [
            {
                "ctx": {
                    "expected": "'csv', 'json', 'parquet' or 'xlsx'"
                },
                "input": "abc",
                "loc": [
                    "query",
                    "outputFormat"
                ],
                "msg": "Input should be 'csv', 'json', 'parquet' or 'xlsx'",
                "type": "literal_error"
            }
        ]
    }

    Здесь используется симпатичный мне подход “бесшовной валидации”. Новые обработчики, конвертирующие выборку из БД в тот или иной формат, пишутся как потомки базового абстрактного класса. В момент выполнения кода inspect формирует маппинг между расширением файла и соответствующим конвертером. Так, с одной стороны, определяется конвертер по указанному в запросе желаемому выходному формату. С другой — ключи этого маппинга, т.е. расширения, определяют значения typing.Literal, который валидирует выходной формат в запросе. Так в запросе валидны только те форматы, для которых в коде существуют конвертеры. Если разработчик написал новый конвертер, то ничего дополнительно делать не нужно: соблюдение протокола базового класса сразу делает конвертер активным, а соответствующее расширение выходного файла — валидным значением в запросе.

  1. Как это может выглядеть в Pydantic Logfire. Это платформа с огромным функционалом для мониторинга, сбора и анализа телеметрии и логов приложений. Logfire выходит за рамки этого примера и упоминается здесь лишь для того чтобы привлечь ваше внимание к этому замечательному инструменту.
    Pydantic Logfire project

  1. В конце несколько слов о Termius.
    • защищенное подключение к хосту в один клик;
    • хранение профилей и данных в зашифрованном хранилище, доступном со всех устройств, включая мобильные;
    • личное и командное пространства;
    • SFTP-клиент;
    • сниппеты для быстрого запуска скриптов на всем прикрепленном парке хостов;
    • режим синхронизации терминалов;
    • автодополнения как в IDE;
    • удобное управление ключами.
    Termius SSH client

Fail2Ban

ℹ️

Fail2Ban на всех машинах уже настроен

На вашу виртуальную машину уже установлен Fail2Ban — пакет, который сканирует логи, например, /var/log/auth.log, и блокирует IP-адреса, которые предпринимают аномально большое число ошибочных попыток подключения.

sudo fail2ban-client version
1.0.2
sudo fail2ban-client -h
Usage: fail2ban-client [OPTIONS] <COMMAND>

Fail2Ban v1.0.2 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules.

...

  1. Изменить конфигурацию. Сначала нужно сделать копию jail.confjail.local — и вносить правки в jail.local. Не редактируйте jail.conf — правки будут перезаписаны при обновлении пакета.
    sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
    sudo nano /etc/fail2ban/jail.local

    У Fail2Ban богатые гибкие настройки, снабженные подробными комментариями. Пожалуйста, изучите их перед активацией/изменением настроек.

    Обязательная перезагрузка службы после внесения правок:

    sudo systemctl restart fail2ban

  1. Лог. Последние 50 строк:
    sudo tail -50 /var/log/fail2ban.log

    Только баны и снятия банов:

    sudo zgrep -E 'Ban|Unban' /var/log/fail2ban.log*

  1. Кто забанен? IP-адреса с разбивкой по джейлам:
    sudo fail2ban-client banned

    Адреса в конкретном jail с указанием времени начала и окончания бана:

    read -p "Jail (default: sshd): " jail; sudo fail2ban-client get ${jail:-sshd} banip --with-time

  1. Отслеживать обновление auth.log, чтобы увидеть, что пытаются сделать боты:
    sudo tail -f /var/log/auth.log

  1. Забанить IP вручную:
    read -p "IP to ban: " ip; read -p "Jail (default: sshd): " jail; sudo fail2ban-client set ${jail:-sshd} banip $ip

  1. Разбанить IP:
    read -p "IP to unban: " ip; read -p "Jail (default: sshd): " jail; sudo fail2ban-client set ${jail:-sshd} unbanip $ip

Сменить метод двухфакторной аутентификации

ℹ️

Раздел предназначен для пользователей, созданных до 7 декабря 2025 г. включительно

В сигме есть проблема с долгим получением кода двухфакторной аутентификации (2FA) на почту. Желающие могут подключить приложение для генерирования одноразовых паролей — например, Authy или GoogleAuthenticator:

  1. Перейдите в настройки безопасности. Вход в консоль Cloud.ru Advanced осуществляется с помощью IAM username и IAM password.
  1. Вкладка Critical operations, первая строка — Virtual MFA Device. Справа — bind.
  1. Отсканируйте появившийся в консоли QR-код установленным приложением (например, Authy). Так вы создадите в приложении запись для генерирования одноразовых паролей для входа в консоль Cloud.ru Advanced.
  1. Введите в консоли 2 подряд идущих кода из приложения, чтобы завершить регистрацию.
  1. Напишите об изменении метода 2FA с почты на one-time-password-приложение администратору Облака УВА — мне или Радику Махмутову.

Подключить приложение одноразовых паролей

ℹ️

Раздел предназначен для пользователей, создаваемых начиная с 8 декабря 2025 г.

Ваш метод двухфакторной аутентификации (2FA) — Virtual MFA Device. Завершите настройку 2FA, подключив приложение для генерирования одноразовых паролей:

  1. Введите ваши IAM username и IAM password в консоли Cloud.ru Advanced.
  1. Установите на телефон приложение для генерирования одноразовых паролей — например, Authy или GoogleAuthenticator, — и прикрепите его к вашей учетной записи в Cloud.ru:
  1. Отсканируйте приложением появившийся в консоли QR-код.
  1. Введите в консоли 2 подряд идущих кода из приложения, чтобы завершить регистрацию.

Требования к паролям

  1. Перейдите в настройки безопасности. Вход в консоль Cloud.ru Advanced осуществляется с помощью IAM username и IAM password.
  1. Изучите поля во вкладках Password Policy и Login Authentication Policy. Эти требования распространяются и на IAM password, и на пароль к вашей учетной записи на виртуальной машине.

Last update: 2026-01-24, evm