ПР. Сокращатель ссылок
Кратко:
- Создание сокращателя ссылок с использованием сервисов Yandex Cloud.
- Создание сервисного аккаунта, добавление ролей и создание бекета в Object Storage.
- Создание бессерверной базы данных YDB и создание таблицы с помощью SQL-скрипта.
- Создание функции для обработки ссылок и вставка кода в файл index.py.
- Настройка Yandex API Gateway с использованием спецификации for-serverless-shortener.yml.
- Запуск и проверка работоспособности созданного приложения.
- Возможность дальнейшего развития и расширения функциональности приложения.
Практическая работа. Сокращатель ссылок
В рамках этого курса вы изучили несколько ключевых сервисов Yandex Cloud, относящихся к группе Serverless. Давайте объединим их для решения ещё одной практической задачи и создадим сервис, который конвертирует длинные ссылки в короткие.
Шаг 1. Сервисный аккаунт
Создание аккаунта
Создайте сервисный аккаунт с именем
serverless-shortener:
export SERVICE_ACCOUNT_SHORTENER_ID=$(yc iam service-account create --name serverless-shortener \
--description "service account for serverless" \
--format json | jq -r .)
Проверьте текущий список сервисных аккаунтов:
yc iam service-account list
После проверки запишите идентификатор созданного сервисного аккаунта в переменную
SERVICE_ACCOUNT_SHORTENER_ID:
echo "export SERVICE_ACCOUNT_SHORTENER_ID=<идентификатор сервисного аккаунта>" >> ~/.bashrc && . ~/.bashrc
echo $SERVICE_ACCOUNT_SHORTENER_ID
Назначение ролей
Добавьте созданному сервисному аккаунту роли
editor, storage.viewer и ydb.admin:
echo "export FOLDER_ID=$(yc config get folder-id)" >> ~/.bashrc && . ~/.bashrc
echo $FOLDER_ID
echo "export OAUTH_TOKEN=$(yc config get token)" >> ~/.bashrc && . ~/.bashrc
echo $OAUTH_TOKEN
echo "export CLOUD_ID=$(yc config get cloud-id)" >> ~/.bashrc && . ~/.bashrc
echo $CLOUD_ID
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_SHORTENER_ID \
--role editor
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_SHORTENER_ID \
--role ydb.admin
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_SHORTENER_ID \
--role storage.viewer
Шаг 2. Создание бакета в Object Storage
Сделаем для нашего сервиса веб-интерфейс. Поскольку это будет статическая веб-страница, разместим её в объектном хранилище.
В консоли управления в вашем рабочем каталоге выберите сервис Object Storage. Нажмите кнопку Создать бакет.
На странице создания бакета:
- Введите имя бакета. В нашем примере это будет
storage-for-serverless-shortener. - Ограничьте максимальный размер бакета (например 1 ГБ).
- Выберите тип доступа
Публичныйво всех случаях. - Выберите класс хранилища
Стандартное.
Нажмите кнопку Создать бакет для завершения операции.

Создайте файл
index.html и загрузите его в созданный бакет — это будет стартовая страничка для нашего сокращателя:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Сокращатель URL</title>
<!-- предостережет от лишнего GET запроса на адрес /favicon.ico -->
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>
<h1>Добро пожаловать</h1>
<form action="javascript:shorten()">
<label for="url">Введите ссылку:</label><br>
<input id="url" name="url" type="text"><br>
<input type="submit" value="Сократить">
</form>
<p id="shortened"></p>
</body>
<script>
function shorten() {
const link = document.getElementById("url").value
fetch("/shorten", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: link
})
.then(response => response.json())
.then(data => {
const url = data.url
document.getElementById("shortened").innerHTML = `<a href=${url}>${url}</a>`
})
.catch(error => {
document.getElementById("shortened").innerHTML = `<p>Произошла ошибка ${error}, попробуйте еще раз</p>`
})
}
</script>
</html>
Шаг 3. Создание базы данных
Создадим бессерверную базу данных YDB с именем
for-serverless-shortener. Чтобы не переключаться из терминала, снова воспользуемся CLI. Обязательно укажите флаг --serverless для выбора типа создаваемой базы данных.
yc ydb database create for-serverless-shortener \
--serverless \
--folder-id $FOLDER_ID
yc ydb database list
Далее выполните команду:
yc ydb database get --name for-serverless-shortener
В выводе вы увидите значение
endpoint. Оно состоит из двух частей: собственно эндпоинта (обычно это ydb.serverless.yandexcloud.net:2135) и пути базы данных (он указывается после ключевого слова database и начинается с символа /, например /ru-central1/...).Сохраним адрес эндпоинта в переменную
YDB_ENDPOINT, а путь базы данных — в переменную YDB_DATABASE. Они пригодятся нам для подключения функции.
echo "export YDB_ENDPOINT=<YDB_ENDPOINT>" >> ~/.bashrc && . ~/.bashrc
echo $YDB_ENDPOINT
echo "export YDB_DATABASE=<YDB_DATABASE>" >> ~/.bashrc && . ~/.bashrc
echo $YDB_DATABASE
Для дальнейшей работы нам понадобится утилита интерфейса командной строки YDB CLI:
curl https://storage.yandexcloud.net/yandexcloud-ydb/install.sh | bash
С помощью CLI создадим авторизованный ключ сервисного аккаунта
serverless-shortener:
yc iam key create \
--service-account-name serverless-shortener \
--output serverless-shortener.sa
Сохраним путь к файлу с ключом в переменную окружения:
echo "export SA_KEY_FILE=$PWD/serverless-shortener.sa" >> ~/.bashrc && . ~/.bashrc
echo $SA_KEY_FILE
Проверим работоспособность с помощью команды:
ydb \
--endpoint $YDB_ENDPOINT \
--database $YDB_DATABASE \
--sa-key-file $SA_KEY_FILE \
discovery whoami \
--groups
- Сохраним в файл
links.yqlSQL-скрипт для создания таблицы:CREATE TABLE links ( id Utf8, link Utf8, PRIMARY KEY (id) ); COMMIT;
Запустите создание таблицы, а затем проверьте результат:
ydb \
--endpoint $YDB_ENDPOINT \
--database $YDB_DATABASE \
--sa-key-file $SA_KEY_FILE \
scripting yql --file links.yql
ydb \
--endpoint $YDB_ENDPOINT \
--database $YDB_DATABASE \
--sa-key-file $SA_KEY_FILE \
scheme describe links
Шаг 4. Создание функции
В рабочем каталоге создайте файл
index.py:
import ydb
import urllib.parse
import hashlib
import base64
import json
import os
def decode(event, body):
# тело запроса может быть закодировано
is_base64_encoded = event.get('isBase64Encoded')
if is_base64_encoded:
body = str(base64.b64decode(body), 'utf-8')
return body
def response(statusCode, headers, isBase64Encoded, body):
return {
'statusCode': statusCode,
'headers': headers,
'isBase64Encoded': isBase64Encoded,
'body': body,
}
def get_config():
endpoint = os.getenv("endpoint")
database = os.getenv("database")
if endpoint is None or database is None:
raise AssertionError("Нужно указать обе переменные окружения")
credentials = ydb.construct_credentials_from_environ()
return ydb.DriverConfig(endpoint, database, credentials=credentials)
def execute(config, query, params):
with ydb.Driver(config) as driver:
try:
driver.wait(timeout=5)
except TimeoutError:
print("Connect failed to YDB")
print("Last reported errors by discovery:")
print(driver.discovery_debug_details())
return None
session = driver.table_client.session().create()
prepared_query = session.prepare(query)
return session.transaction(ydb.SerializableReadWrite()).execute(
prepared_query,
params,
commit_tx=True
)
def insert_link(id, link):
config = get_config()
query = """
DECLARE $id AS Utf8;
DECLARE $link AS Utf8;
UPSERT INTO links (id, link) VALUES ($id, $link);
"""
params = {'$id': id, '$link': link}
execute(config, query, params)
def find_link(id):
print(id)
config = get_config()
query = """
DECLARE $id AS Utf8;
SELECT link FROM links where id=$id;
"""
params = {'$id': id}
result_set = execute(config, query, params)
if not result_set or not result_set[0].rows:
return None
return result_set[0].rows[0].link
def shorten(event):
body = event.get('body')
if body:
body = decode(event, body)
original_host = event.get('headers').get('Origin')
link_id = hashlib.sha256(body.encode('utf8')).hexdigest()[:6]
# в ссылке могут быть закодированные символы, например, %. это помешает работе api-gateway при редиректе,
# поэтому следует избавиться от них вызовом urllib.parse.unquote
insert_link(link_id, urllib.parse.unquote(body))
return response(200, {'Content-Type': 'application/json'}, False, json.dumps({'url': f'{original_host}/r/{link_id}'}))
return response(400, {}, False, 'В теле запроса отсутствует параметр url')
def redirect(event):
link_id = event.get('pathParams').get('id')
redirect_to = find_link(link_id)
if redirect_to:
return response(302, {'Location': redirect_to}, False, '')
return response(404, {}, False, 'Данной ссылки не существует')
# эти проверки нужны, поскольку функция у нас одна
# в идеале сделать по функции на каждый путь в api-gw
def get_result(url, event):
if url == "/shorten":
return shorten(event)
if url.startswith("/r/"):
return redirect(event)
return response(404, {}, False, 'Данного пути не существует')
def handler(event, context):
url = event.get('url')
if url:
# из API-gateway url может прийти со знаком вопроса на конце
if url[-1] == '?':
url = url[:-1]
return get_result(url, event)
return response(404, {}, False, 'Эту функцию следует вызывать при помощи api-gateway')
Создайте файл
requirements.txt со следующим содержимым:
ydb==2.13.3
Находясь в директории с исходными файлами, упакуйте их в zip-архив:
zip src.zip index.py requirements.txt
Создадим нашу функцию
for-serverless-shortener, задав все необходимые переменные. В переменные окружения функции необходимо добавить:-
endpoint— нужно указать протоколgrpcs://и добавить значение Эндпоинт из секции YDB эндпоинт, обычно получаетсяgrpcs://ydb.serverless.yandexcloud.net:2135; -
database— это значение поля База данных из секции YDB эндпоинт (начинается с/ru-central1/....); -
USE_METADATA_CREDENTIALS— выставите значение переменной в1.
Также сразу сделаем функцию публичной.
yc serverless function create \
--name for-serverless-shortener \
--description "function for serverless-shortener"
yc serverless function version create \
--function-name for-serverless-shortener \
--memory=256m \
--execution-timeout=5s \
--runtime=python39 \
--entrypoint=index.handler \
--service-account-id $SERVICE_ACCOUNT_SHORTENER_ID \
--environment USE_METADATA_CREDENTIALS=1 \
--environment endpoint=grpcs://ydb.serverless.yandexcloud.net:2135 \
--environment database=$YDB_DATABASE \
--source-path src.zip
yc serverless function allow-unauthenticated-invoke for-serverless-shortener
Шаг 5. Конфигурирование Yandex API Gateway
Создадим спецификацию
for-serverless-shortener.yml со следующим содержанием:
openapi: 3.0.0
info:
title: for-serverless-shortener
version: 1.0.0
paths:
/:
get:
x-yc-apigateway-integration:
type: object_storage
bucket: <bucket_name> # <-- имя бакета
object: <html_file> # <-- имя html-файла
presigned_redirect: false
service_account: <service_account_id> # <-- идентификатор сервисного аккаунта
operationId: static
/shorten:
post:
x-yc-apigateway-integration:
type: cloud_functions
function_id: <function_id> # <-- идентификатор функции
operationId: shorten
/r/{id}:
get:
x-yc-apigateway-integration:
type: cloud_functions
function_id: <function_id> # <-- идентификатор функции
operationId: redirect
parameters:
- description: id of the url
explode: false
in: path
name: id
required: true
schema:
type: string
style: simple
Не забудьте подставить в спецификацию актуальные для вас значения переменных.
Используем спецификацию для инициализации:
yc serverless api-gateway create \
--name for-serverless-shortener \
--spec=for-serverless-shortener.yml \
--description "for serverless shortener"
В результате успешного создания API-шлюза получим значение параметра
domain:
yc serverless api-gateway list
yc serverless api-gateway get --name for-serverless-shortener
Чтобы проверить работоспособность API-шлюза и созданного приложения целиком, скопируйте служебный домен (вида
https://<идентификатор API Gateway>.apigw.yandexcloud.net/) и вставьте адрес в браузер.Добавляйте адреса сайтов в форму, они будут сохранятся в базу данных. А вам будет доступна ссылка, за которой будет скрываться оригинальный адрес. Ваше приложение полностью работоспособно. Теперь вы умеете использовать serverless-стек технологий Yandex Cloud.

Итак, вы создали приложение с использованием Cloud Functions, API Gateway, Object Storage и YDB. Конечно, вы можете развивать его и дальше, расширяя функциональность.
Вводный курс по serverless-разработке на этом завершён. Осталось пройти лишь заключительный тест, который проверит ваши знания по всем рассмотренным в курсе сервисам.
- Категория: Cloud Services Engineer
- Просмотров: 1324
ПР. Однократная отправка сообщений
Кратко:
- Реализация проекта по конвертированию видеофайлов в GIF с использованием Yandex Cloud Functions.
- Использование Yandex Message Queue для передачи задач в очередь.
- Создание очереди с помощью утилиты aws или Terraform.
- Создание базы данных в Yandex Database Service (YDB) и создание документной таблицы.
- Создание объекта хранения в Yandex Object Storage (Object Storage) и получение URL для доступа к нему.
- Создание функций для обработки видео и получения URL для результатов.
- Создание триггера для вызова функции обработчика и отправки сообщений в очередь.
- Протестирование системы с использованием идентификатора задачи для получения статуса из базы данных.
- Удаление триггера после завершения работы
Практическая работа. Однократная отправка сообщений
В этой практической работе мы реализуем проект, который позволит пользователям конвертировать видеофайлы в GIF. Такая задача хорошо подходит для Cloud Functions, потому что конвертирование отнимает немало ресурсов процессора, и чем качественнее видео, тем больше ресурсов требуется на его обработку.
Почему для решения этой задачи нужны очереди?
Представим, что мы попытались решить эту задачу «в лоб». Пользователь заходит на страницу и вводит ссылку на видеофайл. Сервис скачивает его, конвертирует и отдает ссылку на GIF. Возникают две серьёзные проблемы:
- Синхронное соединение не всегда стабильно. Чем дольше вы его держите, тем выше вероятность, что оно разорвётся. В этом случае всё придётся сделать заново. А если соединение нестабильно, то пользователь может и не дождаться результата.
- Задача ресурсоёмкая: если сервисом одновременно воспользуются много пользователей с большими видеороликами, мощностей может не хватить.
Чтобы избежать этих проблем, в архитектуру сервиса необходимо встроить очередь.
Шаг 1. Сервисный аккаунт и Lockbox
Создание сервисного аккаунта
Создайте сервисный аккаунт с именем
ffmpeg-account-for-cf:
export SERVICE_ACCOUNT=$(yc iam service-account create --name ffmpeg-account-for-cf \
--description "service account for serverless" \
--format json | jq -r .)
Проверьте текущий список сервисных аккаунтов:
yc iam service-account list
После проверки запишите ID созданного сервисного аккаунта в переменную
SERVICE_ACCOUNT_ID:
echo "export SERVICE_ACCOUNT_FFMPEG_ID=<ID>" >> ~/.bashrc && . ~/.bashrc
echo $SERVICE_ACCOUNT_FFMPEG_ID
Назначение роли сервисному аккаунту
Добавим вновь созданному сервисному аккаунту роли
storage.viewer, storage.uploader, ymq.reader, ymq.writer, ydb.admin, serverless.functions.invoker, и lockbox.payloadViewer:
echo "export FOLDER_ID=$(yc config get folder-id)" >> ~/.bashrc && . ~/.bashrc
echo $FOLDER_ID
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role storage.viewer
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role storage.uploader
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role ymq.reader
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role ymq.writer
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role ydb.admin
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role serverless.functions.invoker
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role lockbox.payloadViewer
yc resource-manager folder add-access-binding $FOLDER_ID \
--subject serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--role editor
Вы можете назначить несколько ролей и с помощью команды
set-access-binding. Но эта команда полностью перезаписывает права доступа к ресурсу и все текущие роли на него будут удалены! Поэтому сначала убедитесь, что ресурсу не назначены роли, которые вы не хотите потерять:
yc resource-manager folder list-access-bindings $FOLDER_ID
yc resource-manager folder set-access-bindings $FOLDER_ID \
--access-binding role=storage.viewer,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--access-binding role=storage.uploader,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--access-binding role=ymq.reader,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--access-binding role=ymq.writer,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--access-binding role=ydb.admin,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--access-binding role=serverless.functions.invoker,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--access-binding role=lockbox.payloadViewer,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID \
--access-binding role=editor,subject=serviceAccount:$SERVICE_ACCOUNT_FFMPEG_ID
Создание ключа доступа для сервисного аккаунта
Этот этап нужен для получения идентификатора ключа доступа и секретного ключа, которые будут использованы для загрузки файлов в Object Storage, работы с Yandex Message Queue и т. д. Для создания ключа доступа необходимо вызвать следующую команду:
yc iam access-key create --service-account-name ffmpeg-account-for-cf
В результате вы получите примерно следующее:
access_key:
id: ajefraollq5puj2tir1o
service_account_id: ajetdv28pl0a1a8r41f0
created_at: "2021-08-23T21:13:05.677319393Z"
key_id: BTPNvWthv0ZX2xVmlPIU
secret: cWLQ0HrTM0k_qAac43cwMNJA8VV_rfTg_kd4xVPi
Здесь
key_id — это идентификатор ключа доступа ACCESS_KEY_ID. А secret — это секретный ключ SECRET_ACCESS_KEY. Переменные ACCESS_KEY_ID и SECRET_ACCESS_KEY могут быть использованы для задания соответствующих значений aws_access_key_id и aws_secret_access_key при использовании библиотеки boto3.Создание элемента в сервисе Lockbox
В сервисе Lockbox (находится на стадии Preview) создайте ваш первый секрет, состоящий из набора версий, в которых хранятся ваши данные. Версия содержит наборы ключей и значений:
- Ключ — несекретное название для значения, по которому вы будете его идентифицировать.
- Значение — это секретные данные.
Версия не изменяется. Для любого изменения количества пар ключей-значений или их содержимого необходимо создать новую версию. Создадим секрет с именем
ffmpeg-sa-key и парой ключей ACCESS_KEY_ID и SECRET_ACCESS_KEY:
yc lockbox secret create --name ffmpeg-sa-key \
--folder-id $FOLDER_ID \
--description "keys for serverless" \
--payload '[{"key": "ACCESS_KEY_ID", "text_value": <ACCESS_KEY_ID>}, {"key": "SECRET_ACCESS_KEY", "text_value": "<SECRET_ACCESS_KEY>"}]'
Получим и запишем значение
SECRET_ID, оно нам потребуется при создании функции:
yc lockbox secret list
yc lockbox secret get --name ffmpeg-sa-key
echo "export SECRET_ID=<SECRET_ID>" >> ~/.bashrc && . ~/.bashrc
echo $SECRET_ID
Шаг 2. Создание очереди Yandex Message Queue
Для создания очереди Yandex Message Queue вы можете использовать три разных способа:
- консоль управления;
- консольная утилита
aws; - Terraform.
Создание очереди с помощью утилиты aws
Воспользуемся AWS CLI. Для начала задайте конфигурацию с помощью команды
aws configure. При этом от вас потребуется ввести:AWS Access Key ID— идентификатор ключа доступаkey_idсервисного аккаунта, полученный на предыдущем шаге.AWS Secret Access Key— секретный ключsecretсервисного аккаунта, полученный на предыдущем шаге.Default region name— используйте значениеru-central1.
По завершению конфигурации вы сможете создать очередь:
aws configure
aws sqs create-queue --queue-name ffmpeg --endpoint https://message-queue.api.cloud.yandex.net/
В результате успешного выполнения предыдущей команды в ответ вы получите URL:
{
"QueueUrl": "https://message-queue.api.cloud.yandex.net/b1ga4gj7agij03ln6aov/dj6000000003kv2t02b3/ffmpeg"
}
Запишем значения URL в переменную
YMQ_QUEUE_URL. Она потребуется нам при создании функции:
echo "export YMQ_QUEUE_URL=<YMQ_QUEUE_URL>" >> ~/.bashrc && . ~/.bashrc
echo $YMQ_QUEUE_URL
Ещё вам потребует значение атрибута
QueueArn, получим его:
aws sqs get-queue-attributes \
--endpoint https://message-queue.api.cloud.yandex.net \
--queue-url $YMQ_QUEUE_URL \
--attribute-names QueueArn
В результате вы получите ответ вида:
{
"Attributes": {
"QueueArn": "yrn:yc:ymq:ru-central1:b1gl21bkgss4msekt08i:ffmpeg"
}
}
Сохраним значение
QueueArn в переменную YMQ_QUEUE_ARN:
echo "export YMQ_QUEUE_ARN=<YMQ_QUEUE_ARN>" >> ~/.bashrc && . ~/.bashrc
echo $YMQ_QUEUE_ARN
Шаг 3. Создание базы данных в сервисе YDB
Создадим базу данных YDB с именем
ffmpeg и типом serverless, используя для этого флаг --serverless:
yc ydb database create ffmpeg \
--serverless \
--folder-id $FOLDER_ID
yc ydb database list
Сразу получим и сохраним
document_api_endpoint в значение переменной DOCAPI_ENDPOINT:
yc ydb database get --name ffmpeg
echo "export DOCAPI_ENDPOINT=<DOCAPI_ENDPOINT>" >> ~/.bashrc && . ~/.bashrc
echo $DOCAPI_ENDPOINT
Как только база данных создана, воспользуемся ранее использованной утилитой AWS CLI для создания документной таблицы в этой базе данных. Всю конфигурацию возьмем из файла
tasks.json:
{
"AttributeDefinitions": [
{
"AttributeName": "task_id",
"AttributeType": "S"
}
],
"KeySchema": [
{
"AttributeName": "task_id",
"KeyType": "HASH"
}
],
"TableName": "tasks"
}
Находясь в одном каталоге с файлом
tasks.json, вызовите следующую команду для создания таблицы:
aws dynamodb create-table \
--cli-input-json file://tasks.json \
--endpoint-url $DOCAPI_ENDPOINT \
--region ru-central1
В консоли управления убедитесь, что БД
ffmpeg создана, и в ней есть пустая таблица tasks.Шаг 4. Создание бакета в сервисе Object Storage
Самый простой способ создания бакета в Object Storage — это использование консоли управления.
В консоли управления в ваше