[Документация Yandex Cloud](../index.md) > [Архитектурные решения](index.md) > Отказоустойчивость > Размещение статического сайта в Yandex Cloud

# Развертывание статического веб-сайта в отказоустойчивой конфигурации в Yandex Cloud

Настоящее руководство содержит несколько вариантов решений по развертыванию в инфраструктуре Yandex Cloud статического веб-сайта с динамическими элементами (например, формой регистрации или сбора заявок и т.п.) в отказоустойчивой конфигурации.

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

## Общая архитектура решений {#architecture}

В основе инфраструктуры, используемой всеми рассматриваемыми решениями, лежат сервисы [Yandex Object Storage](../storage/index.md) и [Yandex Virtual Private Cloud](../vpc/index.md). Для ускорения загрузки контента у конечных пользователей используются ресурсы сервиса [Yandex Cloud CDN](../cdn/index.md). Кроме этого, разные варианты решений используют различные комбинации вспомогательных [сервисов](../overview/concepts/services.md) экосистемы Yandex Cloud: [Yandex Cloud DNS](../dns/index.md), [Yandex Certificate Manager](../certificate-manager/index.md), [Yandex API Gateway](../api-gateway/index.md), [Yandex Application Load Balancer](../application-load-balancer/index.md), [Yandex Cloud Postbox](../postbox/index.md) и [Yandex Cloud Functions](../functions/index.md).

Отказоустойчивость рассматриваемых решений обеспечивается за счет автоматической репликации данных, сохраняемых в [бакетах](../storage/concepts/bucket.md) Object Storage, а также за счет опциональной возможности включения [версионирования](../storage/concepts/versioning.md).

Плюсы предлагаемых решений:

* не нужно проектировать отказоустойчивое размещение сайта одновременно в нескольких [зонах доступности](../overview/concepts/geo-scope.md), так как используемые сервисы изначально обеспечивают надежность и отказоустойчивость;
* относительная простота архитектуры;
* относительно невысокая стоимость решений по сравнению с другими способами размещения, а также возможность [нетарифицируемого использования](../billing/concepts/serverless-free-tier.md) (`free tier`), позволяющая практически бесплатно получить решения для проектов с небольшим объемом данных и малым количеством посетителей.

### Автоматическое развертывание инфраструктуры {#automation}

В дополнительных материалах к руководству содержатся примеры конфигурации [Terraform](../terraform/index.md), которые позволяют развернуть необходимую облачную инфраструктуру максимально быстро и содержат примеры решения типовых задач, таких как:

* загрузка и обновление файлов статического сайта в Object Storage;
* создание [сертификата](../certificate-manager/concepts/managed-certificate.md) от Let's Encrypt для сайта в Certificate Manager;
* настройка сервиса Yandex Cloud Postbox для отправки транзакционных писем;
* внедрение на сайт [функции](../functions/concepts/function.md) Cloud Functions для отправки писем и пример такой функции для среды [Node.js](https://nodejs.org/en/docs/).

Дополнительные материалы к руководству доступны в репозитории [![image](../_assets/overview/solution-library-icon.svg) **yc-object-storage-cdn-static-site**](https://github.com/yandex-cloud-examples/yc-object-storage-cdn-static-site) на GitHub.

## Обзор вариантов решений {#solutions}

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

В настоящем обзоре рассматриваются следующие варианты решений по размещению статического сайта в инфраструктуре Yandex Cloud:

* [Бакет Yandex Object Storage с публичным доступом](#simple-bucket)
* [Бакет Yandex Object Storage с публичным доступом и раздачей контента через Yandex Cloud CDN](#bucket-cdn)
* [Бакет Yandex Object Storage без публичного доступа с Yandex API Gateway и раздачей контента через Yandex Cloud CDN](#bucket-cdn-api-gateway)
* [Бакет Yandex Object Storage без публичного доступа с Yandex Application Load Balancer](#bucket-alb)

## Бакет Yandex Object Storage с публичным доступом {#simple-bucket}

В бакетах Yandex Object Storage предусмотрен специальный [режим Хостинг](../storage/concepts/hosting.md), являющийся одним из самых простых и доступных способов размещения статического сайта в Yandex Cloud. Для небольших проектов дополнительным плюсом такого размещения является возможность нетарифицируемого использования (`free tier`). Подробнее о нетарифицируемом использовании сервиса Object Storage читайте в [документации Yandex Cloud Billing](../billing/concepts/serverless-free-tier.md#objstorage).

Схема работы решения:

![simple-bucket](../_assets/tutorials/static-website-in-yandex-cloud/simple-bucket.svg)

### Необходимые ресурсы {#simple-bucket-resources}

Чтобы разместить статический сайт в этом варианте решения, потребуются:

* бакет Object Storage с включенным режимом `Хостинг`;
* доменное имя и SSL-сертификат, выпущенный для этого доменного имени;
* настроенная [публичная зона](../dns/concepts/dns-zone.md#public-zones) DNS для домена и [ресурсная запись](../dns/concepts/resource-record.md), связывающая бакет и доменное имя.

{% note info %}

Для того чтобы к сайту можно было обращаться по доменному имени, [имя бакета](../storage/concepts/bucket.md#naming) должно совпадать с этим доменным именем. Например: `my.example.com`.

{% endnote %}

Пошагово процесс настройки статического сайта с использованием бакета Object Storage с публичным доступом и включенным режимом `Хостинг` описан в разделе [Статический сайт в Yandex Object Storage](../tutorials/web/static/index.md).

### Преимущества и недостатки этого варианта решения {#simple-bucket-advantages-drawbacks}

#|
|| **Преимущества** ![yes](../_assets/common/yes.svg) | **Недостатки** ![no](../_assets/common/no.svg) ||
|| 
* Отказоустойчивость за счет автоматической репликации данных в бакетах Object Storage.
* Относительная простота архитектуры.
* Для больших проектов — относительно невысокая стоимость.
* Для небольших проектов — возможность нетарифицируемого использования.
|
* Невозможность разделить сайт на публичную и закрытую части, так как для всего бакета должен быть открыт [публичный доступ](../storage/concepts/bucket.md#bucket-access) на чтение.
* Невозможность настроить специальное перенаправление запросов (например, для сайтов, построенных по технологии [SPA](https://ru.wikipedia.org/wiki/Одностраничное_приложение)), так как в Object Storage в частности нет аналога инструкции [nginx](https://nginx.org/) `try_files $uri $uri/ /index.html =404;`.
* Конкуренция в поисковых системах между основным доменом сайта и служебными доменами бакета. [Подробнее](#simple-bucket-troubleshooting) о проблеме и способах ее решения.
* Невозможность настроить правила переадресации для существующих [объектов](../storage/concepts/object.md) в бакете (можно настроить только для несуществующих объектов).
* Необходимость настраивать параметры кеширования для клиента отдельно в отношении каждого объекта.
* Тарификация исходящего трафика по [тарифам](../vpc/pricing.md) Yandex Virtual Private Cloud.
* Потенциально высокие расходы на запросы к объектам, если сайт состоит из большого количества объектов: в Object Storage [тарифицируется](../storage/pricing.md) не только хранение объектов, но и запросы к ним.
||
|#

Избавиться от указанных выше недостатков позволяют [другие варианты](#solutions) развертывания.

### Нюансы при реализации решения {#simple-bucket-details}

При развертывании сайта учитывайте следующие нюансы:

* Тип ресурсной DNS-записи, связывающей доменное имя и бакет, зависит от уровня домена:

    * Для доменов второго уровня (например, `example.com`) создайте [ресурсную запись ANAME](../dns/concepts/resource-record.md#aname).
    * Для доменов третьего уровня и выше (например, `my.example.com`, `my.other.example.com` и т.д.) создайте [ресурсную запись CNAME](../dns/concepts/resource-record.md#cname).

    {% note info %}

    Ресурсная запись в любом случае должна указывать на [служебный домен](../storage/concepts/hosting.md#bucket-url) бакета.

    Например, для домена `my.example.com` в публичной DNS-зоне `example.com` создайте CNAME-запись `my`, указывающую на `my.example.com.website.yandexcloud.kz.`
    
    Точка в конце целевого доменного имени обязательна, ее отсутствие приведет к неработоспособности сайта.

    {% endnote %}

    Подробнее о том, как создать ресурсную запись, читайте в разделе [Создать ресурсную запись](../dns/operations/resource-record-create.md).

* Сервис Object Storage интегрирован с сервисом Certificate Manager, который поддерживает работу как с [пользовательскими сертификатами](../certificate-manager/concepts/imported-certificate.md), так и с сертификатами от Let's Encrypt.

    При создании сертификата от Let's Encrypt пройдите [проверку прав на домен](../certificate-manager/concepts/challenges.md). Наиболее надежным и удобным способом проверки является DNS-проверка с использованием CNAME-записи. [Такая проверка](../certificate-manager/concepts/challenges.md#cname) выполняется один раз и в дальнейшем не требует участия пользователя для перевыпуска сертификата.

    Подробнее о том, как пройти проверку прав владения доменом, читайте в разделе [Проверить права на домены](../certificate-manager/operations/managed/cert-validate.md).
* Работа в режиме `Хостинг` требует включения публичного доступа к бакету и предоставляет ряд дополнительных возможностей.

    {% cut "Дополнительные возможности бакета в режиме `Хостинг`" %}

    * Запросы к пути `/` возвращают содержимое файла, заданного в параметре **Главная страница**.
    * Запросы к несуществующим объектам возвращают содержимое файла, заданного в параметре **Страница ошибки**.
    * Доступна дополнительная функциональность переадресации запросов.
    * Служебный домен настраивается для обработки запросов к основному домену по протоколу `https`.
    * Служебный домен бакета с включенным режимом `Хостинг` — `website.yandexcloud.kz`.

        {% note info %}

        Если режим `Хостинг` выключен, то служебный домен бакета — `storage.yandexcloud.kz`.

        {% endnote %}

    {% endcut %}

* Если в качестве имени бакета используется доменное имя с точками, обращение к служебному домену бакета в формате `<доменное_имя>.website.yandexcloud.kz` по протоколу `https` невозможно. Это связано с тем, что служебный домен в этом формате использует [wildcard-сертификат](../glossary/ssl-certificate.md#wildcard-certificate) SSL, который подходит только для доменов четвертого уровня `*.website.yandexcloud.kz`.

    Поэтому в таких случаях для обращения к служебному домену бакета по протоколу `https` используйте URL в формате `https://website.yandexcloud.kz/<доменное_имя>`.

    Например: `https://website.yandexcloud.kz/my.example.com`.
* Доступ к содержимому бакета может быть ограничен с помощью [политик доступа](../storage/concepts/policy.md). Но такие политики не всегда позволяют легко решать практические задачи. Например, можно ограничить доступ к содержимому бакета для определенного диапазона IP-адресов, но нельзя задать такие ограничения для доменных имен.

    Подробнее о том, как настроить политику доступа для бакета, читайте в разделе [Управление политикой доступа (bucket policy)](../storage/operations/buckets/policy.md).
* При загрузке объектов в бакет можно задать ряд заголовков, позволяющих управлять обработкой контента. Например, заголовок `Content-Type` позволяет [задать](#simple-bucket-troubleshooting) MIME-тип содержимого.

    Также существует еще ряд заголовков, которые можно установить для объектов в бакете Object Storage и которые позволяют управлять обработкой контента в браузере.

    {% cut "Наиболее востребованные заголовки" %}

    * `Content-Type` — позволяет задать MIME-тип содержимого объекта в бакете.
    * `Cache-Control` — управляет кешированием на клиенте и промежуточных кеширующих серверах. Иногда установка параметров кеширования на клиенте позволяет значительно ускорить загрузку страниц и снизить потребление трафика.

        {% note warning %}

        С осторожностью используйте заголовок `Cache-Control` для установки параметров кеширования, особенно в отношении HTML-страниц. Клиент может на длительный срок закешировать ошибочную страницу, в результате чего браузер будет отображать пользователю неверную информацию до тех пор, пока не истечет срок кеширования или пользователь вручную не очистит кеш браузера.

        {% endnote %}

    * `Content-Disposition` — сообщает браузеру, что объект должен сохраняться как файл, и задает конкретное имя файла.
    * `Content-Encoding` — указывает метод, которым закодирован файл (например, сжатие `gzip`).

    {% endcut %}

    Подробнее о том, как задать заголовки для объектов в бакете, читайте в разделе [Решение проблем](#simple-bucket-troubleshooting).

### Решение проблем {#simple-bucket-troubleshooting}

{% list tabs accordion %}

- Возникновение поисковой конкуренции между основным доменом сайта и служебными доменами бакета

  Из-за особенностей работы Object Storage содержимое сайта будет доступно не только по основному адресу `my.example.com`, но и через служебные домены Object Storage:

  * `my.example.com.website.yandexcloud.kz`;
  * `website.yandexcloud.kz/my.example.com`;
  * `storage.yandexcloud.kz/my.example.com`.

  С точки зрения алгоритмов поисковых систем между этими адресами может возникнуть конкуренция, что негативно скажется на ранжировании сайта в поисковой выдаче.

  Подробнее об этой проблеме и о том, как ее решить, читайте в [документации к сервису Яндекс Вебмастер](https://yandex.ru/support/webmaster/ru/yandex-indexing/site-addresses).

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

  Такое поведение может быть вызвано некорректным заголовком `Content-Type` в метаданных проблемных объектов в бакете. Если при загрузке объекта в бакет не задать значение этого заголовка явным образом, Object Storage попытается самостоятельно определить тип загружаемого контента. Значение заголовка `Content-Type`, определенное сервисом автоматически, иногда может не соответствовать ожиданиям пользователя.

  Рекомендуем явно задавать значение для заголовка `Content-Type` при загрузке файлов статического сайта в бакет:

  * Чтобы страница сайта открывалась, а не скачивалась, заголовок должен иметь значение `text/html`.
  * Чтобы скрипт `JavaScript` запускался, заголовок должен иметь значение `application/javascript`.

  {% cut "Пример использования [Yandex Cloud CLI](../cli/index.yaml) для загрузки объекта с установкой нужного типа контента." %}

  ```bash
  yc storage s3api put-object \
    --bucket my.example.com \
    --key index.html \
    --body /path/to/my/index.html \
    --content-type 'text/html'
  ```

  {% endcut %}

  {% cut "Пример конфигурации Terraform для автоматизации загрузки файлов сайта с указанием `Content-Type` для каждого объекта на основании его расширения." %}

  ```hcl
  variable "site_source_dir" {
    type        = string
    description = "Site source directory" # Локальная директория с файлами сайта
    default     = "~/path/to/my/site"     # Используйте функцию pathexpand() для обработки ~ и других специальных символов 
  }

  locals {
    site_mime_types = jsondecode(file("mime.json")) # Файл с таблицей сопоставления расширений файлов с Mime-типом
  }

  resource "yandex_storage_object" "files" {
    for_each     = var.site_source_dir == null ? toset([]) : fileset(pathexpand(var.site_source_dir), "**") # рекурсивный перебор всех файлов в каталоге
    bucket       = "my.example.com"
    key          = each.key
    source       = pathexpand("${var.site_source_dir}/${each.key}")
    source_hash  = filemd5(pathexpand("${var.site_source_dir}/${each.key}")) # Хранение дайджеста файла в стейте, чтобы файл перезагружался после изменения
    content_type = lookup(local.site_mime_types, regex("\\.[^.]+$", each.key), null) # Выставление Content-Type для файла в зависимости от его расширения
  }
  ```

  Пример содержимого файла `mime.json`:

  ```json
  {
    ".html": "text/html",
    ".htm": "text/html",
    ".shtml": "text/html",
    ".css": "text/css",
    ".xml": "text/xml",
    ".gif": "image/gif",
    ...
  }
  ```

  {% endcut %}

  О том, как изменить тип контента для группы уже загруженных в бакет объектов, читайте в разделе [Устранение проблем с некорректным MIME-типом объектов при их загрузке в Object Storage](../troubleshooting/storage/known-issues/incorrect-mime-type.md).

{% endlist %}

## Бакет Yandex Object Storage с публичным доступом и раздачей контента через Yandex Cloud CDN {#bucket-cdn}

В этом варианте решения бакет в режиме хостинга используется в качестве [источника](../cdn/concepts/origins.md) для [CDN-ресурса](../cdn/concepts/resource.md) Cloud CDN. Доменное имя сайта связывается с помощью ресурсной записи CNAME со специальным доменным именем CDN-провайдера, которое присваивается [каталогу](../resource-manager/concepts/resources-hierarchy.md#folder) пользователя в [облаке](../resource-manager/concepts/resources-hierarchy.md#cloud) Yandex Cloud.

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

Основное преимущество CDN — геораспределенность. [Точки присутствия](../cdn/concepts/points-of-presence.md) CDN расположены в больших городах, максимально близко к основным потребителям контента. За счет кеширования и сокращения расстояния между клиентом и CDN-сервером сайт быстрее загружается в браузере конечного пользователя.

{% note info %}

Не используйте ресурсную запись [ANAME](../dns/concepts/resource-record.md#aname) с доменными именами для раздачи контента, поскольку в таком случае конечный пользователь получит ответ от CDN-сервера, не связанного с геолокацией пользователя. Ответ всегда будет одинаков для всех пользователей.

{% endnote %}

Схема работы решения для полностью статического сайта:

![cdn-bucket](../_assets/tutorials/static-website-in-yandex-cloud/cdn-bucket.svg)

### Необходимые ресурсы {#bucket-cdn-resources}

Чтобы разместить статический сайт в этом варианте решения, потребуются:

* бакет Object Storage с включенным режимом `Хостинг`;
* CDN-ресурс Cloud CDN;
* доменное имя и SSL-сертификат, выпущенный для этого доменного имени;
* настроенная публичная зона DNS для домена и ресурсная запись, связывающая CDN-ресурс и доменное имя.

Подробнее о том, как создать CDN-ресурс, читайте в разделе [Создание ресурса](../cdn/operations/resources/create-resource.md).

### Преимущества и недостатки этого варианта решения {#bucket-cdn-advantages-drawbacks}

#|
|| **Преимущества** ![yes](../_assets/common/yes.svg) | **Недостатки** ![no](../_assets/common/no.svg) ||
|| 
* Геораспределенность и кеширование контента.
* Возможность [ограничить доступ к бакету-источнику](#bucket-cdn-troubleshooting), сохранив его только для хостов Cloud CDN.
* Возможность настроить перенаправление запросов внутри сайта и базовую переадресацию, например, с `http://` на `https://`.
* Возможность [задать разные настройки](#bucket-cdn-troubleshooting) для объектов сайта в зависимости от пути.
* Возможность создать сайт, содержащий [динамические разделы](#bucket-cdn-troubleshooting).
* Отказоустойчивость за счет автоматической репликации данных в бакетах Object Storage.
* Возможность дополнительно [повысить отказоустойчивость](#bucket-cdn-troubleshooting) за счет использования [групп источников](../cdn/concepts/origins.md#groups).
* Экономия за счет сокращения количества запросов к объектам в бакете (в Object Storage [тарифицируется](../storage/pricing.md) не только хранение объектов, но и запросы к ним).
* Экономия на трафике: сетевой трафик от Cloud CDN к конечному пользователю тарифицируется в соответствии с [тарифами Cloud CDN](../cdn/pricing.md), которые существенно ниже [тарифов Virtual Private Cloud](../vpc/pricing.md). При этом трафик между Object Storage и Cloud CDN не тарифицируется.
|
* Невозможность разделить сайт на публичную и закрытую части, так как для всего бакета должен быть открыт публичный доступ на чтение.
||
|#

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

### Решение проблем {#bucket-cdn-troubleshooting}

{% list tabs accordion %}

- Как ограничить доступ к бакету-источнику?

  Предлагаемое решение позволяет ограничить доступ к бакету, разрешив его только для хостов Cloud CDN. Это запретит индексацию сайта поисковыми системами по служебным доменам бакета.

  Предоставить доступ к бакету только для хостов Cloud CDN можно несколькими способами:

  * С помощью политик доступа открыть доступ к бакету только из подсетей Cloud CDN.

      Актуальный список таких подсетей доступен [публично](https://tech.cdn.yandex.net/prefixes/yc.json).
      
      {% note warning %}

      Список подсетей Cloud CDN может изменяться, поэтому при использовании этого способа ограничения доступа к бакету отслеживайте такие изменения и своевременно вносите изменения в разрешающие правила доступа.

      Подробнее читайте в разделе [Как ограничить доступ к бакету Object Storage диапазоном IP-адресов, принадлежащих Cloud CDN](../troubleshooting/storage/how-to/permit-bucket-access-only-to-cdn-networks.md).

      {% endnote %}

  * Чтобы избежать необходимости постоянно отслеживать изменения в списке подсетей Cloud CDN, можно использовать [экранирование источников](../cdn/concepts/origins-shielding.md), при этом с помощью политик доступа нужно предоставить доступ к бакету только для подсетей, которые используются для экранирования источников:

      * `188.72.110.0/24`;
      * `188.72.111.0/24`.

      Список этих подсетей более стабилен и менее подвержен изменениям.

      Подробнее о том, как настроить политику доступа для бакета, читайте в разделе [Управление политикой доступа (bucket policy)](../storage/operations/buckets/policy.md).

- Как задать разные настройки для объектов сайта в зависимости от пути?

  Предлагаемое решение позволяет устанавливать в рамках одного домена разные источники данных, параметры кеширования и другие настройки в зависимости от пути к запрашиваемому объекту, в том числе по маске. Для этого используются [локационные правила](../cdn/concepts/location-rules.md).

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

  * источник контента — бакет, [L7-балансировщик](../application-load-balancer/concepts/application-load-balancer.md) или произвольный сервер;
  * параметры хранения контента в CDN и параметры кеширования для клиента;
  * правила модификации и перенаправления запросов;
  * значения заголовков [CORS](../cdn/concepts/cors.md).

  Правила обработки запросов позволяют настраивать также и другие параметры. Подробнее читайте в разделах [Локационные правила Yandex Cloud CDN](../cdn/concepts/location-rules.md) и [Настроить локационные правила Yandex Cloud CDN](../cdn/operations/resources/location-rules.md).
 
- Как создать сайт с динамическими элементами?

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

  Используя локационные правила, можно создать достаточно сложный сайт, содержащий как статические, так и динамические разделы (например, регистрационные формы).

  {% note warning %}

  По умолчанию в Cloud CDN HTTP-методы `POST`, `PUT`, `PATCH` и `DELETE` недоступны в запросах клиентов. Чтобы получить возможность их использования, [создайте обращение](https://kz.center.yandex.cloud/support) в службу технической поддержки.

  {% endnote %}

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

  В таких обстоятельствах удобным инструментом обработки запросов являются функции Yandex Cloud Functions. Вызывать функции со страниц сайта можно с помощью [API-шлюза](../api-gateway/concepts/index.md) Yandex API Gateway.

  [Вызов](*function_call) функции осуществляется по специальному URL функции `https://functions.yandexcloud.net/<идентификатор_функции>`. При этом попытка прямого вызова функции со страницы сайта в браузере завершится ошибкой: браузер заблокирует запрос, поскольку у URL функции и у сайта разные источники.
  
  Чтобы вызов функции выполнился успешно, функция должна поддерживать предварительный ([preflight](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing#Preflight_example)) запрос и возвращать ответ с корректными заголовками [CORS](*cors).
  
  Можно реализовать обработку preflight-запросов в функции Cloud Functions. Но существует простой способ решения этой проблемы — использовать для вызова функции API-шлюз с расширением спецификации [x-yc-apigateway-cors](*api_gw_cors).
  
  {% cut "Пример спецификации API-шлюза с расширением `x-yc-apigateway-cors`." %}
  
  ```yaml
  openapi: "3.0.0"
  info:
    version: 1.0.0
    title: Mailer API
  servers:
  - url: https://d5dglu90r7y0********.wm******.apigw.yandexcloud.net
  paths:
    /register:
      x-yc-apigateway-cors:
        origin:
          - https://my.example.com
        methods:
          - POST
      post:
        x-yc-apigateway-integration:
          type: cloud_functions
          function_id: d4edj6bo1uy0********
          service_account_id: ajekjkm28y0********
  ```
  
  API-шлюз, созданный по спецификации из примера, позволяет вызывать функцию Cloud Functions `d4edj6bo1uy0********` по адресу `https://d5dglu90r7y0********.wm******.apigw.yandexcloud.net/register` со страниц сайта, размещенных на домене `https://my.example.com`.
  
  {% note info %}
  
  Поскольку функция `d4edj6bo1uy0********` не является публичной, для ее вызова используется [сервисный аккаунт](*service_account), которому назначена [роль](*functions_invoker) `functions.functionInvoker`.
  
  {% endnote %}
  
  {% endcut %}

  Схема работы решения для статического сайта с динамическими элементами:

  ![cdn-locations](../_assets/tutorials/static-website-in-yandex-cloud/cdn-locations.svg)

  Подробнее о том, как создать функцию Cloud Functions, читайте в разделах [Создать функцию](../functions/operations/function/function-create.md) и [Создать версию функции](../functions/operations/function/version-manage.md).

  Подробнее о том, как создать API-шлюз, читайте в разделе [Создание API-шлюза](../api-gateway/operations/api-gw-create.md).

- Как повысить отказоустойчивость решения?

  Сервис Yandex Cloud CDN можно использовать как глобальный балансировщик нагрузки для реализации отказоустойчивых сценариев. Так, с помощью групп источников можно организовать распределение запросов между несколькими источниками, размещенными у разных провайдеров, или настроить резервный источник на случай выхода из строя основного источника.

  Подробнее о том, как работать с группами источников, читайте в разделе [Группы источников](../cdn/operations/index.md#origin-groups).

{% endlist %}


## Бакет Yandex Object Storage без публичного доступа с Yandex API Gateway и раздачей контента через Yandex Cloud CDN {#bucket-cdn-api-gateway}

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

Одним из способов реализовать такой сценарий является использование сервиса API Gateway и его [расширений](../api-gateway/concepts/extensions/index.md).

Схема работы решения:

![cdn-apigw-bucket](../_assets/tutorials/static-website-in-yandex-cloud/cdn-apigw-bucket.svg)

### Необходимые ресурсы {#bucket-cdn-api-gateway-resources}

Чтобы разместить статический сайт в этом варианте решения, потребуются:

* бакет Object Storage с отключенным режимом `Хостинг`;
* API-шлюз API Gateway;
* CDN-ресурс Cloud CDN;
* доменное имя и SSL-сертификат, выпущенный для этого доменного имени;
* настроенная публичная зона DNS для домена и ресурсная запись, связывающая служебный домен API-шлюза и доменное имя.

В этом варианте решения не нужно присваивать бакету имя, соответствующее имени домена. Конечные пользователи будут обращаться не напрямую к бакету, а к API-шлюзу, с которым будет связано доменное имя.

Подробнее о том, как создать API-шлюз, читайте в разделе [Создание API-шлюза](../api-gateway/operations/api-gw-create.md).

### Преимущества и недостатки этого варианта решения {#bucket-cdn-api-gateway-advantages-drawbacks}

#|
|| **Преимущества** ![yes](../_assets/common/yes.svg) | **Недостатки** ![no](../_assets/common/no.svg) ||
|| 
* Возможность использовать [сервисный аккаунт](../iam/concepts/users/service-accounts.md) для организации доступа к бакету с выключенным публичным доступом.
* Возможность гибко управлять доступом к разделам бакета.
* Возможность авторизовывать пользователя по [JWT-токену](../glossary/jwt.md).
* Возможность модифицировать и перенаправлять запросы пользователей.
* Геораспределенность и кеширование контента.
* Отказоустойчивость за счет автоматической репликации данных в бакетах Object Storage.
* Возможность дополнительно повысить отказоустойчивость за счет использования групп источников.
* Возможность создать сайт, содержащий [динамические разделы](#bucket-cdn-api-gateway-troubleshooting).
* Экономия за счет сокращения количества запросов к объектам в бакете (в Object Storage [тарифицируется](../storage/pricing.md) не только хранение объектов, но и запросы к ним).
* Экономия на трафике: сетевой трафик от Cloud CDN к конечному пользователю тарифицируется в соответствии с [тарифами Cloud CDN](../cdn/pricing.md), которые существенно ниже [тарифов Virtual Private Cloud](../vpc/pricing.md). При этом трафик между Object Storage и Cloud CDN не тарифицируется.
|
* Настроить переадресацию запросов с домена по умолчанию на основной домен сайта в настоящий момент невозможно, так как определение поля `servers` поддерживается только на уровне всего шлюза и не поддерживается на уровне путей и методов.
* Дополнительные расходы в связи с [тарификацией](../api-gateway/pricing.md) запросов, проходящих через API-шлюз.
||
|#

Этот вариант размещения статического сайта не подходит для организации приватного корпоративного портала с доступом через [VPN](../glossary/vpn.md) или [Yandex Cloud Interconnect](../interconnect/index.md), так как API-шлюз всегда имеет [публичный IP-адрес](../vpc/concepts/address.md#public-addresses) и для него отсутствуют встроенные средства ограничения доступа по IP-адресам.

{% note info %}

Ограничить доступ к API Gateway можно, только подключив [профиль безопасности](../smartwebsecurity/concepts/profiles.md) Yandex Smart Web Security и настроив ограничения доступа с помощью базовых правил. Для реализации сценария приватного корпоративного портала лучше подходит вариант размещения [Бакет Yandex Object Storage без публичного доступа с Yandex Application Load Balancer](#bucket-alb).

{% endnote %}

### Нюансы при реализации решения {#bucket-cdn-api-gateway-details}

Так как для бакета с выключенным публичным доступом нельзя включить режим Хостинг, настройте перенаправление запросов. В том числе обязательно настройте обработку запросов к корневой странице `/` так, чтобы в ответ на такой запрос пользователю возвращалось содержимое главной страницы сайта (чаще всего это файл `index.html`). Кроме того, настройте страницу с сообщением об ошибке, которая будет возвращаться вместе с ответом, содержащим код `4xx`.

{% cut "Пример конфигурации API-шлюза API Gateway." %}

```yaml
openapi: "3.0.0"
info:
  version: 1.0.0
  title: Mailer API
servers:
- url: https://d5dglu90r7y0********.wm******.apigw.yandexcloud.net
- url: https://my.example.com
paths:
  /:
    get:
      summary: Serve root page
      parameters:
        - name: file
          in: path
          required: true
          schema:
            type: string
      x-yc-apigateway-integration:
        type: object_storage
        bucket: mysecretbucket0x7adfbu
        object: index.html
        error_object: 
          object: 404.html
          statusCode: 404
        service_account_id: ajebg5lgngy0********
  /{file+}:
    get:
      summary: Serve static site content
      parameters:
        - name: file
          in: path
          required: true
          schema:
            type: string
      x-yc-apigateway-integration:
        type: object_storage
        bucket: mysecretbucket0x7adfbu
        object: '{file}'
        error_object: 
          object: 404.html
          statusCode: 404
        service_account_id: ajebg5lgngy0********
```

Сервисному аккаунту с идентификатором `ajebg5lgngy0********` должна быть выдана [роль](../storage/security/index.md#roles-list), разрешающая чтение из бакета `mysecretbucket0x7adfbu`.

{% endcut %}

Подробнее о том, как изменить настройки API-шлюза, читайте в разделе [Изменить API-шлюз](../api-gateway/operations/api-gw-update.md).

### Решение проблем {#bucket-cdn-api-gateway-troubleshooting}

{% list tabs accordion %}

- Сайт возвращает ошибку `429 Too Many Requests`

  Производительность API-шлюза позволяет обрабатывать значительное, но не бесконечное количество запросов в секунду. После достижения порога шлюз начнет возвращать ошибку `429 Too Many Requests`.

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

  {% note tip %}

  Дополнительно повысить производительность решения за счет использования еще одного кеширующего слоя позволит экранирование источников CDN-ресурса.

  {% endnote %}

  Подробнее о том, как создать CDN-ресурс, читайте в разделе [Создание ресурса](../cdn/operations/resources/create-resource.md).

- Как создать сайт с динамическими разделами?

  Задачей статических сайтов зачастую является привлечение пользователей с тем, чтобы они совершали на этих сайтах определенные целевые действия — переход по ссылке или заполнение регистрационной формы.
  
  Нагрузка на обработчик данных регистрационных форм обычно носит неравномерный характер и может варьироваться от периодов длительного простоя до необходимости обрабатывать сотни запросов в секунду. В таких обстоятельствах удобным инструментом обработки запросов являются [функции](*function) Yandex Cloud Functions.

  [Вызов](*function_call) функции осуществляется по специальному URL функции `https://functions.yandexcloud.net/<идентификатор_функции>`. При этом попытка прямого вызова функции со страницы сайта в браузере завершится ошибкой: браузер заблокирует запрос, поскольку у URL функции и у сайта разные источники.
  
  Чтобы вызов функции выполнился успешно, функция должна поддерживать предварительный ([preflight](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing#Preflight_example)) запрос и возвращать ответ с корректными заголовками [CORS](*cors).
  
  Можно реализовать обработку preflight-запросов в функции Cloud Functions. Но существует простой способ решения этой проблемы — использовать для вызова функции API-шлюз с расширением спецификации [x-yc-apigateway-cors](*api_gw_cors).
  
  {% cut "Пример спецификации API-шлюза с расширением `x-yc-apigateway-cors`." %}
  
  ```yaml
  openapi: "3.0.0"
  info:
    version: 1.0.0
    title: Mailer API
  servers:
  - url: https://d5dglu90r7y0********.wm******.apigw.yandexcloud.net
  paths:
    /register:
      x-yc-apigateway-cors:
        origin:
          - https://my.example.com
        methods:
          - POST
      post:
        x-yc-apigateway-integration:
          type: cloud_functions
          function_id: d4edj6bo1uy0********
          service_account_id: ajekjkm28y0********
  ```
  
  API-шлюз, созданный по спецификации из примера, позволяет вызывать функцию Cloud Functions `d4edj6bo1uy0********` по адресу `https://d5dglu90r7y0********.wm******.apigw.yandexcloud.net/register` со страниц сайта, размещенных на домене `https://my.example.com`.
  
  {% note info %}
  
  Поскольку функция `d4edj6bo1uy0********` не является публичной, для ее вызова используется [сервисный аккаунт](*service_account), которому назначена [роль](*functions_invoker) `functions.functionInvoker`.
  
  {% endnote %}
  
  {% endcut %}

  Схема работы решения с вызовом функции через API-шлюз:

  ![cdn-bucket-cf](../_assets/tutorials/static-website-in-yandex-cloud/cdn-bucket-cf.svg)

  Подробнее о том, как создать функцию Cloud Functions, читайте в разделах [Создать функцию](../functions/operations/function/function-create.md) и [Создать версию функции](../functions/operations/function/version-manage.md).

{% endlist %}

## Бакет Yandex Object Storage без публичного доступа с Yandex Application Load Balancer {#bucket-alb}

Чтобы создать статический сайт на основе бакета Object Storage без публичного доступа, вы можете использовать балансировщик нагрузки уровня приложений (L7-балансировщик).

L7-балансировщики при повышении нагрузки автоматически масштабируются, создавая новые [ресурсные единицы](../application-load-balancer/concepts/application-load-balancer.md#lcu-scaling), что позволяет им обеспечить существенно большую производительность, чем у API-шлюзов API Gateway.

### Необходимые ресурсы {#bucket-alb-resources}

Чтобы разместить статический сайт в этом варианте решения, потребуются:

* бакет Object Storage с отключенным режимом `Хостинг`;
* L7-балансировщик нагрузки Application Load Balancer;
* доменное имя и SSL-сертификат, выпущенный для этого доменного имени;
* настроенная публичная зона DNS для домена и ресурсная запись, связывающая IP-адрес обработчика L7-балансировщика и доменное имя.

В этом варианте решения не нужно присваивать бакету имя, соответствующее имени домена. Конечные пользователи будут обращаться не напрямую к бакету, а к API-шлюзу, с которым будет связано доменное имя.

Подробнее о том, как создать L7-балансировщик нагрузки, читайте в разделе [Создать L7-балансировщик](../application-load-balancer/operations/application-load-balancer-create.md).

### Преимущества и недостатки этого варианта решения {#bucket-alb-advantages-drawbacks}

#|
|| **Преимущества** ![yes](../_assets/common/yes.svg) | **Недостатки** ![no](../_assets/common/no.svg) ||
||
* Возможность организовать доступ к бакету без публичного доступа.
* Высокая производительность.
* Возможность работать с различными протоколами как на уровне приложений (`HTTP`, `TLS`, `WebSocket` и др.), так и на транспортном уровне (`TCP`, `Stream`).
* Возможность модифицировать и перенаправлять запросы пользователей.
* Возможность модифицировать заголовки запросов пользователей.
* Расширенные возможности по балансировке нагрузки: различные [алгоритмы балансировки](../application-load-balancer/concepts/backend-group.md#balancing-mode), [локализация трафика](../application-load-balancer/concepts/backend-group.md#locality) в зоне доступности, Zonal Shift и др.
* Возможность по-разному обрабатывать запросы в зависимости от домена, на который они поступают.
* Возможность иметь только [внутренний IP-адрес](../vpc/concepts/address.md#internal-addresses), доступный исключительно из [облачной сети](../vpc/concepts/network.md#network) Yandex Virtual Private Cloud, через VPN или Yandex Cloud Interconnect.
* Отказоустойчивость за счет автоматической репликации данных в бакетах Object Storage.
* Возможность создать сайт, содержащий [динамические разделы](#bucket-alb-troubleshooting).
|
* Невозможность организовать доступ к бакету от имени сервисного аккаунта или использовать авторизацию по JWT-токену.
* Сравнительно высокая [стоимость](../application-load-balancer/pricing.md) использования L7-балансировщика нагрузки, в том числе тарификация запросов, проходящих через балансировщик.
||
|#

### Нюансы при реализации решения {#bucket-alb-details}

L7-балансировщик Application Load Balancer обладает рядом особенностей, которые следует учитывать при реализации сценария:

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

    {% note tip %}

    Чтобы сократить издержки, при размещении публичного сайта подключите CDN-ресурс, позволяющий кешировать запросы к сайту. Дополнительно повысить производительность решения за счет использования еще одного кеширующего слоя позволит экранирование источников CDN-ресурса.

    Для внутреннего балансировщика использовать кеширование Yandex Cloud CDN нельзя.

    {% endnote %}

    Подробнее о том, как создать CDN-ресурс, читайте в разделе [Создание ресурса](../cdn/operations/resources/create-resource.md).

* Так как для бакета с выключенным публичным доступом или с доступом через сервисное подключение нельзя включить режим Хостинг, на стороне балансировщика настройте обработку запросов к корневой странице `/` так, чтобы в ответ на такой запрос пользователю возвращалось содержимое главной страницы сайта (чаще всего это файл `index.html`).

    Подробнее о том, как настроить обработку запросов в балансировщике, читайте в разделе [Изменить параметры HTTP-роутера](../application-load-balancer/operations/http-router-update.md).
* L7-балансировщик не создает дополнительных (служебных) доменов.

### Решение проблем {#bucket-alb-troubleshooting}

{% list tabs accordion %}

- Как ограничить доступ к данным в бакете?

  Ограничить доступ к данным бакета с использованием L7-балансировщика можно, заменив заголовок `Referer` на секретное слово и настроив для бакета политику доступа, которая разрешает доступ к контенту только при наличии у входящих запросов этого секретного слова в этом заголовке. Разрешить доступ к бакету только для ресурсных единиц Application Load Balancer нельзя, так как Application Load Balancer взаимодействует с бакетом через внутреннюю сервисную сеть.

  Еще один способ ограничить доступ к данным бакета — использовать бакеты с доступом через [сервисное подключение](../vpc/concepts/private-endpoint.md). Сервисные подключения позволяют создавать бакеты, доступные только по внутренним IP-адресам Yandex Virtual Private Cloud и недоступные через интернет. Этот способ подходит в случаях, когда к безопасности хранения данных предъявляются повышенные требования.

  Подробнее о том, как настроить политику доступа для бакета, читайте в разделе [Управление политикой доступа (bucket policy)](../storage/operations/buckets/policy.md).

- Как создать сайт с динамическими разделами?

  Задачей статических сайтов зачастую является привлечение пользователей с тем, чтобы они совершали на этих сайтах определенные целевые действия — переход по ссылке или заполнение регистрационной формы.
  
  Нагрузка на обработчик данных регистрационных форм обычно носит неравномерный характер и может варьироваться от периодов длительного простоя до необходимости обрабатывать сотни запросов в секунду. В таких обстоятельствах удобным инструментом обработки запросов являются [функции](*function) Yandex Cloud Functions.

  [Вызов](../functions/concepts/function-invoke.md#http) функции осуществляется по специальному URL функции `https://functions.yandexcloud.net/<идентификатор_функции>`. При этом попытка прямого вызова функции со страницы сайта в браузере завершится ошибкой: браузер заблокирует запрос, поскольку у URL функции и у сайта разные источники.

  Чтобы вызов функции выполнился успешно, функция должна поддерживать предварительный ([preflight](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing#Preflight_example)) запрос и возвращать ответ с корректными заголовками [CORS](../glossary/cors.md).

  Реализовать обработку preflight-запросов можно в функции Cloud Functions.

  Подробнее о том, как создать функцию Cloud Functions, читайте в разделах [Создать функцию](../functions/operations/function/function-create.md) и [Создать версию функции](../functions/operations/function/version-manage.md).

{% endlist %}