Yandex Cloud
Поиск
Связаться с намиПопробовать бесплатно
  • Кейсы
  • Документация
  • Блог
  • Все сервисы
  • Статус работы сервисов
  • Marketplace
    • Доступны в регионе
    • Инфраструктура и сеть
    • Платформа данных
    • Искусственный интеллект
    • Безопасность
    • Инструменты DevOps
    • Бессерверные вычисления
    • Управление ресурсами
  • Все решения
    • По отраслям
    • По типу задач
    • Экономика платформы
    • Безопасность
    • Техническая поддержка
    • Каталог партнёров
    • Обучение и сертификация
    • Облако для стартапов
    • Облако для крупного бизнеса
    • Центр технологий для общества
    • Партнёрская программа
    • Поддержка IT-бизнеса
    • Облако для фрилансеров
    • Обучение и сертификация
    • Блог
    • Документация
    • Мероприятия и вебинары
    • Контакты, чаты и сообщества
    • Идеи
    • Калькулятор цен
    • Тарифы
    • Акции и free tier
  • Кейсы
  • Документация
  • Блог
Создавайте контент и получайте гранты!Готовы написать своё руководство? Участвуйте в контент-программе и получайте гранты на работу с облачными сервисами!
Подробнее о программе
Проект Яндекса
© 2026 ТОО «Облачные Сервисы Казахстан»
Практические руководства
    • Все руководства
      • Развертывание веб-приложения с использованием Java Servlet API
      • Разработка пользовательской интеграции в API Gateway
        • Обзор
        • Консоль управления
        • Terraform
      • Разработка CRUD API для сервиса фильмов
      • Создание интерактивного serverless-приложения с использованием WebSocket
      • Работа с API-шлюзом по протоколу WebSocket
      • Создание функции Node.js с помощью TypeScript
      • Запуск контейнерного приложения в Serverless Containers
      • Подключение к YDB из функции Cloud Functions на Python
      • Подключение к базе данных YDB из функции Cloud Functions на Node.js
      • Настройка подключения к Yandex Managed Service for PostgreSQL из контейнера Serverless Containers
      • Развертывание веб-приложения с JWT-авторизацией в API Gateway и аутентификацией в Firebase
      • Разработка функций в Functions Framework и их развертывание в Yandex Serverless Containers
      • Канареечный релиз функции Cloud Functions
      • Интерактивная отладка функций Cloud Functions
      • Создание адреса Yandex Cloud Postbox и проверка владения доменом с помощью Terraform
      • Настройка Postfix для отправки через Yandex Cloud Postbox

В этой статье:

  • Подготовьте облако к работе
  • Необходимые платные ресурсы
  • Создайте инфраструктуру
  • Проверьте работу сокращателя ссылок
  • Как удалить созданные ресурсы
  1. Бессерверные технологии
  2. Бэкенд на Serverless
  3. Сокращатель ссылок
  4. Terraform

Сокращатель ссылок с помощью Terraform

Статья создана
Yandex Cloud
Улучшена
kvendingoldo
Обновлена 8 мая 2026 г.
  • Подготовьте облако к работе
    • Необходимые платные ресурсы
  • Создайте инфраструктуру
  • Проверьте работу сокращателя ссылок
  • Как удалить созданные ресурсы

Важно

Часть ресурсов, необходимых для прохождения практического руководства, доступны только в регионе Россия.

Чтобы создать сокращатель ссылок с помощью Terraform:

  1. Подготовьте облако к работе.
  2. Создайте инфраструктуру.
  3. Проверьте работу сокращателя.

Если созданные ресурсы вам больше не нужны, удалите их.

Подготовьте облако к работеПодготовьте облако к работе

Зарегистрируйтесь в Yandex Cloud и создайте платежный аккаунт:

  1. Перейдите в консоль управления, затем войдите в Yandex Cloud или зарегистрируйтесь.
  2. На странице Yandex Cloud Billing убедитесь, что у вас подключен платежный аккаунт, и он находится в статусе ACTIVE или TRIAL_ACTIVE. Если платежного аккаунта нет, создайте его и привяжите к нему облако.

Если у вас есть активный платежный аккаунт, вы можете создать или выбрать каталог, в котором будет работать ваша инфраструктура, на странице облака.

Подробнее об облаках и каталогах.

Необходимые платные ресурсыНеобходимые платные ресурсы

В стоимость поддержки инфраструктуры для сокращателя ссылок входят:

  • плата за хранение данных (см. тарифы Yandex Object Storage);
  • плата за операции с базой данных YDB и хранение данных (см. тарифы Managed Service for YDB);
  • плата за количество вызовов функции, вычислительные ресурсы, выделенные для выполнения функции, и исходящий трафик (см. тарифы Cloud Functions);
  • плата за количество запросов к API-шлюзу и исходящий трафик (см. тарифы API Gateway).

Создайте инфраструктуруСоздайте инфраструктуру

Terraform позволяет быстро создать облачную инфраструктуру в Yandex Cloud и управлять ею с помощью файлов конфигураций. В файлах конфигураций хранится описание инфраструктуры на языке HCL (HashiCorp Configuration Language). При изменении файлов конфигураций Terraform автоматически определяет, какая часть вашей конфигурации уже развернута, что следует добавить или удалить.

Terraform распространяется под лицензией Business Source License, а провайдер Yandex Cloud для Terraform — под лицензией MPL-2.0.

Подробную информацию о ресурсах провайдера смотрите в документации на сайте Terraform или в зеркале.

Для создания инфраструктуры с помощью Terraform:

  1. Установите Terraform, получите данные для аутентификации и укажите источник для установки провайдера Yandex Cloud (раздел Настройте провайдер, шаг 1).

  2. Подготовьте файлы с описанием инфраструктуры:

    Готовая конфигурация
    Вручную
    1. Клонируйте репозиторий с конфигурационными файлами.

      git clone https://github.com/yandex-cloud-examples/yc-serverless-url-shortener.git
      
    2. Перейдите в директорию с репозиторием. В ней должны появиться файлы:

      • serverless-url-shortener.tf — конфигурация создаваемой инфраструктуры;
      • serverless-url-shortener.auto.tfvars — файл с пользовательскими данными;
      • index.html — HTML-страница вашего сервиса;
      • function.zip — архив с кодом функции Cloud Functions.
    1. Создайте папку для конфигурационных файлов.

    2. Создайте в папке:

      1. Конфигурационный файл serverless-url-shortener.tf:

        serverless-url-shortener.tf
        # Объявление переменных для конфиденциальных параметров
        
        variable "cloud_id" {
          type = string
        }
        
        variable "folder_id" {
          type = string
        }
        
        variable "bucket_name" {
          type = string
        }
        
        # Настройка провайдера
        
        terraform {
          required_providers {
            yandex = {
              source = "yandex-cloud/yandex"
            }
          }
        }
        
        provider "yandex" {
          cloud_id  = var.cloud_id
          folder_id = var.folder_id
        }
        
        # Создание сервисного аккаунта
        
        resource "yandex_iam_service_account" "shortener_sa" {
          name        = "serverless-shortener"
          description = "Service account for the URL shortener"
        }
        
        # Назначение роли сервисному аккаунту
        
        resource "yandex_resourcemanager_folder_iam_member" "shortener_role" {
          folder_id = var.folder_id
          role      = "editor"
          member    = "serviceAccount:${yandex_iam_service_account.shortener_sa.id}"
        }
        
        # Создание статического ключа
        
        resource "yandex_iam_service_account_static_access_key" "shortener_sa_key" {
          service_account_id = yandex_iam_service_account.shortener_sa.id
          description        = "Static access key for the service account"
        }
        
        # Создание бакета
        
        resource "yandex_storage_bucket" "shortener_bucket" {
          bucket     = var.bucket_name
          access_key = yandex_iam_service_account_static_access_key.shortener_sa_key.access_key
          secret_key = yandex_iam_service_account_static_access_key.shortener_sa_key.secret_key
          max_size = 1073741824
          anonymous_access_flags {
            read        = true
            list        = false
            config_read = false
          }
        
          website {
            index_document = "index.html"
          }
        }
        
        # Загрузка объекта в бакет
        
        resource "yandex_storage_object" "shortener_index" {
          bucket        = yandex_storage_bucket.shortener_bucket.bucket
          key           = "index.html"
          source        = "index.html"
          acl           = "public-read"
          access_key    = yandex_iam_service_account_static_access_key.shortener_sa_key.access_key
          secret_key    = yandex_iam_service_account_static_access_key.shortener_sa_key.secret_key
          content_type  = "text/html"
        }
        
        # Создание базы данных YDB
        
        resource "yandex_ydb_database_serverless" "shortener_db" {
          name        = "shortener-ydb-main"
          location_id = "kz1"
        }
        
        
        # Создание таблицы YDB
        
        resource "yandex_ydb_table" "test_table" {
          path              = "links"
          connection_string = yandex_ydb_database_serverless.shortener_db.ydb_full_endpoint
          column {
            name     = "id"
            type     = "Utf8"
            not_null = true
          }
          column {
            name     = "link"
            type     = "Utf8"
            not_null = true
          }
          primary_key = ["id"]
          
          depends_on = [ yandex_ydb_database_serverless.shortener_db ]
          }
        
        # Создание функции
        
        resource "yandex_function" "shortener_function" {
          name               = "shortener-function-main"
          description        = "Function for the URL shortener"
          runtime            = "python312"
          entrypoint         = "index.handler"
          memory             = 256
          execution_timeout  = 5
          service_account_id = yandex_iam_service_account.shortener_sa.id
        
          content {
            zip_filename = "function.zip"
          }
        
          user_hash = filesha256("function.zip")
        
          environment = {
            endpoint = "grpcs://${yandex_ydb_database_serverless.shortener_db.ydb_api_endpoint}"
            database = yandex_ydb_database_serverless.shortener_db.database_path
          }
        }
        
        resource "yandex_function_iam_binding" "public_invoker" {
          function_id = yandex_function.shortener_function.id
          role        = "functions.functionInvoker"
          members     = ["system:allUsers"]
        }
        
        # Создание API-шлюза
        
        resource "yandex_api_gateway" "shortener_gateway" {
          name = "shortener-gateway-main"
        
          spec = jsonencode({
            openapi = "3.0.0"
            info = {
              title   = "Shortener API Gateway"
              version = "1.0.0"
            }
            paths = {
              "/" = {
                get = {
                  "x-yc-apigateway-integration" = {
                    type               = "object_storage"
                    bucket             = yandex_storage_bucket.shortener_bucket.bucket
                    object             = "index.html"
                    presigned_redirect = false
                    service_account_id = yandex_iam_service_account.shortener_sa.id
                  }
                  operationId = "static"
                }
              }
              "/shorten" = {
                post = {
                  "x-yc-apigateway-integration" = {
                    type        = "cloud_functions"
                    function_id = yandex_function.shortener_function.id
                  }
                  operationId = "shorten"
                }
              }
              "/r/{id}" = {
                get = {
                  "x-yc-apigateway-integration" = {
                    type        = "cloud_functions"
                    function_id = yandex_function.shortener_function.id
                  }
                  operationId = "redirect"
                  parameters = [
                    {
                      description = "ID of the shortened link"
                      explode     = false
                      in          = "path"
                      name        = "id"
                      required    = true
                      schema = {
                        type = "string"
                      }
                      style = "simple"
                    }
                  ]
                }
              }
            }
          })
        }
        
        # URL для работы с сокращателем ссылок
        
        output "url" {
          value = "https://${yandex_api_gateway.shortener_gateway.domain}"
        }
        
      2. Файл с пользовательскими данными serverless-url-shortener.auto.tfvars:

        serverless-url-shortener.auto.tfvars
        cloud_id    = "<идентификатор_облака>" 
        folder_id   = "<идентификатор_каталога>"
        bucket_name = "<имя_бакета>"
        
      3. HTML-страница вашего сервиса index.html:

        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>
        
      4. Файл с кодом функции Cloud Functions index.py:

        index.py
        import ydb
        import urllib.parse
        import hashlib
        import base64
        import json
        import os
        import traceback
        
        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):
            # Всегда отдаём строку в body
            if not isinstance(body, str):
                body = json.dumps(body, ensure_ascii=False)
            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("Нужно указать обе переменные окружения: endpoint и database")
            credentials = ydb.iam.MetadataUrlCredentials()
            return ydb.DriverConfig(endpoint, database, credentials=credentials)
        
        def execute(config, query, params):
            with ydb.Driver(config) as driver:
                try:
                    driver.wait(timeout=5, fail_fast=True)
                except Exception as e:
                    print("Connect failed to YDB:", e)
                    print(driver.discovery_debug_details())
                    raise
        
                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):
            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
        
            # Учитываем структуру результата от ydb
            return result_set[0].rows[0].link
        
        def shorten(event):
            try:
                body = event.get('body')
                if body is None:
                    return response(400, {'Content-Type': 'application/json'}, False,
                                    {'error': 'В теле запроса отсутствует тело'})
        
                body = decode(event, body)
        
                # Попробуем распарсить JSON с ключом url, иначе считаем body как plain string
                url_value = None
                try:
                    parsed = json.loads(body)
                    if isinstance(parsed, dict):
                        url_value = parsed.get('url')
                    else:
                        # если отправили не-объект JSON, игнорируем
                        url_value = None
                except Exception:
                    # body не JSON — считаем, что это plain URL
                    url_value = body
        
                if not url_value:
                    return response(400, {'Content-Type': 'application/json'}, False,
                                    {'error': 'Ожидался параметр url в теле запроса'})
        
                # Очищаем URL от эвентуальных кодированных символов
                clean_url = urllib.parse.unquote(url_value).strip()
                if not clean_url:
                    return response(400, {'Content-Type': 'application/json'}, False,
                                    {'error': 'Пустой url'})
        
                link_id = hashlib.sha256(clean_url.encode('utf8')).hexdigest()[:6]
                insert_link(link_id, clean_url)
        
                # Возвращаем относительный путь — фронт сам допишет origin
                return response(200, {'Content-Type': 'application/json'}, False,
                                {'url': f'/r/{link_id}'})
            except Exception as e:
                print("Exception in shorten():", e)
                traceback.print_exc()
                return response(500, {'Content-Type': 'application/json'}, False,
                                {'error': 'internal server error'})
        
        def redirect(event):
            try:
                # защитно доставать path params
                path_params = event.get('pathParams') or event.get('pathParameters') or {}
                link_id = path_params.get('id')
                if not link_id:
                    # возможно пришёл полный путь в event['url'] или ['path']
                    url = event.get('url') or event.get('path') or ''
                    if url and url.startswith('/r/'):
                        link_id = url.split('/r/')[-1].split('?')[0]
        
                if not link_id:
                    return response(400, {'Content-Type': 'application/json'}, False, {'error': 'id отсутствует'})
        
                redirect_to = find_link(link_id)
        
                if redirect_to:
                    return response(302, {'Location': redirect_to}, False, '')
                return response(404, {'Content-Type': 'application/json'}, False, {'error': 'Данной ссылки не существует'})
            except Exception as e:
                print("Exception in redirect():", e)
                traceback.print_exc()
                return response(500, {'Content-Type': 'application/json'}, False, {'error': 'internal server error'})
        
        def get_result(url, event):
            if url == "/shorten" or url.startswith("/shorten"):
                return shorten(event)
            if url.startswith("/r/"):
                return redirect(event)
            return response(404, {'Content-Type': 'application/json'}, False, {'error': 'Данного пути не существует'})
        
        def handler(event, context):
            url = event.get('url') or event.get('path') or ''
            if url:
                # Иногда URL из шлюза приходит с вопросительным знаком на конце
                if url.endswith('?'):
                    url = url[:-1]
                return get_result(url, event)
            return response(404, {'Content-Type': 'application/json'}, False, {'error': 'Функция должна вызываться через API Gateway.'})
        
      5. Файл с параметрами окружения функции Cloud Functions requirements.txt:

        ydb
        
    3. Создайте в папке архив function.zip, содержащий файлы index.py и requirements.txt.

    4. В файле serverless-url-shortener.auto.tfvars задайте пользовательские параметры:

      • cloud_id — идентификатор облака.
      • folder_id — идентификатор каталога.
      • bucket_name — имя бакета, в котором будут создаваться ресурсы.

    Более подробную информацию о параметрах используемых ресурсов в Terraform см. в документации провайдера:

    • Сервисный аккаунт — yandex_iam_service_account.
    • Статический ключ — yandex_iam_service_account_static_access_key.
    • Бакет — yandex_storage_bucket.
    • Объект — yandex_storage_object.
    • База данных Managed Service for YDB — yandex_ydb_database_serverless.
    • Таблица Managed Service for YDB — yandex_ydb_table.
    • Функция — yandex_function.
    • API-шлюз — yandex_api_gateway.
  3. Создайте ресурсы:

    1. В терминале перейдите в директорию с конфигурационным файлом.

    2. Проверьте корректность конфигурации с помощью команды:

      terraform validate
      

      Если конфигурация является корректной, появится сообщение:

      Success! The configuration is valid.
      
    3. Выполните команду:

      terraform plan
      

      В терминале будет выведен список ресурсов с параметрами. На этом этапе изменения не будут внесены. Если в конфигурации есть ошибки, Terraform на них укажет.

    4. Примените изменения конфигурации:

      terraform apply
      
    5. Подтвердите изменения: введите в терминале слово yes и нажмите Enter.

  4. Скопируйте URL, полученный в результате создания инфраструктуры, чтобы проверить работу сокращателя ссылок.

Проверьте работу сокращателя ссылокПроверьте работу сокращателя ссылок

Чтобы проверить правильность взаимодействия компонентов сервиса:

  1. Откройте в браузере скопированный ранее URL сокращателя.

  2. В поле для ввода введите ссылку, которую вы хотите сократить.

  3. Нажмите кнопку Сократить.

    Ниже отобразится сокращенная ссылка.

  4. Перейдите по сокращенной ссылке — должна открыться та же страница, что и по ссылке до сокращения.

Как удалить созданные ресурсыКак удалить созданные ресурсы

Чтобы перестать платить за созданные ресурсы:

  1. Откройте конфигурационный файл serverless-url-shortener.tf и удалите описание создаваемой инфраструктуры из файла.

  2. Примените изменения:

    1. В терминале перейдите в директорию с конфигурационным файлом.

    2. Проверьте корректность конфигурации с помощью команды:

      terraform validate
      

      Если конфигурация является корректной, появится сообщение:

      Success! The configuration is valid.
      
    3. Выполните команду:

      terraform plan
      

      В терминале будет выведен список ресурсов с параметрами. На этом этапе изменения не будут внесены. Если в конфигурации есть ошибки, Terraform на них укажет.

    4. Примените изменения конфигурации:

      terraform apply
      
    5. Подтвердите изменения: введите в терминале слово yes и нажмите Enter.

См. такжеСм. также

  • Сокращатель ссылок с помощью консоли управления

Была ли статья полезна?

Предыдущая
Консоль управления
Следующая
Разработка CRUD API для сервиса фильмов
Создавайте контент и получайте гранты!Готовы написать своё руководство? Участвуйте в контент-программе и получайте гранты на работу с облачными сервисами!
Подробнее о программе
Проект Яндекса
© 2026 ТОО «Облачные Сервисы Казахстан»