Развертывание простого веб-сайта в Yandex Cloud
Описаны несколько способов развертывания простых веб-сайтов на российских облачных платформах. Акцент сделан на Yandex Cloud.
Содержание
Я люблю делать пет-проекты. Делаю это ради тренировки профессиональных навыков, либо просто ради развлечения. Количество пользователей таких проектов стремится к нулю и денег они не зарабатывают. Очень часто такие проекты представляют собой веб-сайт, который надо развернуть на каком-то хостинге, а хостинг стоит денег. Далее я приведу решения, которые позволяют мне максимально экономить на хостинге при развертывании пет-проектов.
Есть два наиболее часто встречающихся варианта веб-сайта, с которыми я сталкивался: сайт без серверной логики, сайт с серверной логикой. В этой статье я опишу архитектуры для реализации этих видов сайтов. Эти архитектуры хорошо подходят для моих задач (пет-проекты с нулевым бюджетом), но могут не подойти для бизнес-ориентированных сайтов. Справедливости ради, скажу, что они могут, но не обязаны подойти для бизнес-ориентированных приложений.
В качестве демонстрации, я предлагаю рассмотреть два проекта. Назовем их следующим образом: Проект-1 и Проект-2.
Требования к Проекту-1
- Пользователь может зайти на веб-сайт и увидеть статическую информацию.
Требования к Проекту-2
- Пользователь может зайти на веб-сайт и увидеть статическую информацию.
- На веб-сайте пользователь может заполнить форму обратной связи, которая отправляет сообщение в приватный чат в Telegram.
Архитектурные характеристики
Так как это пет-проекты, не предназначенные для зарабатывания денег, в обоих случаях для нас важны следующие архитектурные характеристики:
- Низкая стоимость хостинга (в идеале бесплатно).
- Простота реализации и развертывания.
- Простота поддержки (в идеале, вообще не требует поддержки после запуска).
- Базовая защищенность веб-сайта.
- Сайт легко найти в интернете.
Разберем по порядку решения для этих проектов.
Проект-1
Учитывая требования, самым простым решением будет статический веб-сайт. Я обычно использую Vue для SPA и Astro для SSG. В обоих случаях результатом сборки будет папка с пачкой файлов, которые надо положить на веб-сервер.
Далее я рассмотрю два варианта в качестве платформы для развертывания нашего сайта.
Первый вариант: Timeweb Cloud
Размещаем сайт на хостинге Timeweb Cloud (смотри раздел Apps). На момент написания этой статьи статический сайт на этой платформе обходится мне примерно в 1₽ в месяц. Бонусом идет автоматически настроенный CI с перенакаткой сайта по коммиту в мастер ветку в GitHub. Здесь также можно подключить собственный домен. Бесплатный сертификат Let’s Encrypt выпускается и обновляется автоматически. Данное решение удовлетворяет вообще всем нашим архитектурным требованиям. Единственная проблема - сертификаты иногда не успевают обновляться и сайт становится недоступным. Не знаю, кто в этом виноват, но решается это быстро, путем переподключения домена к сайту через админку Timeweb.
Второй вариант: Yandex Object Storage
Размещаем сайт на хостинге Yandex Object Storage. Для этого выполняем следующие действия:
- Создаем бакет с именем, повторяющим имя домена. Имя бакета может состоять только из латинских букв. Поэтому, если у нас кириллический домен, то надо назвать бакет в punycode кодировке.
- Создаем сертификат Let’s Encrypt для нашего домена в Yandex Certificate Manager. После того, как домен верифицирован и сертификат выпущен, нам больше не надо думать об обновлении сертификатов. Облако берет это на себя.
- Идем в настройки бакета в раздел “Безопасность - HTTPS” и прикрепляем наш новый домен к бакету.
- Добавляем DNS записи для нашего домена, чтобы домен резолвился в бакет. Нюанс в том, что у бакета нет фиксированного IP адреса, у него есть только служебный домен. В случае, если мы хотим расположить сайт на поддомене (например, example.mysite.com), это не проблема, т.к. мы можем создать CNAME запись у нашего администратора домена, которая будет вести на служебный домен бакета. Если же мы хотим расположить сайт на корневом домене (например, mysite.com), и наш DNS администратор не поддерживает ANAME записи, то придется делегировать наш домен сервису Yandex Cloud DNS. Для этого идем в админку нашего DNS администратора и прописываем сервера яндекса в качестве DNS серверов. Переезд может занять до суток. Далее идем в Yandex Cloud DNS и создаем записи для нашего домена. На момент написания статьи, оплата за домен почасовая. Это единственная статья расходов в Yandex Cloud для моего проекта. В месяц выходит около 40₽. Но, напомню, если нам нужно разместить сайт на поддомене (ограничившись CNAME записью), то все эти манипуляции с Yandex Cloud DNS не требуются, и эта статья расходов не появится.
- Если у вас SPA, то в настройках сайта надо прописать error page: index.html.
Загрузка файлов в Yandex Object Storage
Бакет создан, теперь нужно загрузить в него файлы сайта. Можно сделать это руками через веб интерфейс Object Storage. Но лучше воспользоваться cli, т.к. это позволит нам быстро и удобно вносить изменения в сайт в будущем.
Сначала устанавливаем aws cli.
Да, это не опечатка. Мы загружаем файлы в яндекс бакет с помощью aws cli.
В ~/.aws/config
прописываем регион и эндпоинт:
[default]
region = ru-central1
endpoint_url = https://storage.yandexcloud.net
В Yandex IAM создаем сервисный аккаунт и назначаем ему роль storage.editor
. Далее для этого
аккаунта создаем пару ключей (“Создать статический ключ доступа”).
В ~/.aws/credentials
прописываем только что созданные ключи:
[default]
aws_access_key_id = <id value>
aws_secret_access_key = <secret value>
Далее можно загружать файлы в Object Storage с помощью следующего скрипта:
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd ROOT_DIR
DIST_DIR="./dist/"
npm ci
npm run build
source ./.yandex-cloud.env
aws s3 sync --delete $DIST_DIR s3://$BUCKET_NAME/
echo "Файлы загружены в Object Storage"
Сравнение двух вариантов развертывания Проекта-1
Выше я приводил архитектурные характеристики, которым должно соответствовать наше решение. Сравним два варианта развертывания на основе этих характеристик.
Низкая стоимость хостинга | ★★★★★ | 1 ₽ в месяц - почти бесплатно |
---|---|---|
Простота реализации и развертывания | ★★★★★ | Интуитивно в пару кликов создается и развертывается приложение |
Простота поддержки | ★★★★ | Иногда слетают сертификаты, зато настроен CI |
Базовая защищенность веб-сайта | ★★★★★ | Сертификат Let’s Encrypt прилагается |
Сайт легко найти в интернете | ★★★★★ | Можно подключить свой домен |
Низкая стоимость хостинга | ★★★★ | Если надо делегировать домен в Yandex Cloud DNS, то появится статья расходов примерно в 40 ₽ в месяц |
---|---|---|
Простота реализации и развертывания | ★★★ | Быстро получится только, если знать, что делать. Не интуитивно. Много действий |
Простота поддержки | ★★★★ | После первоначальной настройки работать будет вечно, без вашего участия. Но CI из коробки не идет. Поэтому деплой настраиваем сами |
Базовая защищенность веб-сайта | ★★★★★ | Сертификат Let’s Encrypt прилагается |
Сайт легко найти в интернете | ★★★★★ | Можно подключить свой домен |
Проект-2
Учитывая требования, нам нужен веб-сайт с серверной логикой. В Timeweb Cloud можно развертывать бэкенд так же просто, как и фронтенд. Но стоить это будет уже существенно дороже, чем статический веб сайт (сотни рублей в месяц вместо единиц рублей). Поэтому, учитывая требования жесткой экономии, я выбрал Yandex Cloud.
Для реализации этого проекта нам нужен небольшой бэкенд, который будет принимать форму и вызывать API Teleram для отправки данных в телеграм.
Самый простой и дешевый вариант (при количестве пользователей, стремящемся к нулю) - это Yandex Cloud Function.
Веб-интерфейс сайта располагается в Object Storage (так же, как и в Проекте-1). Чтобы склеить веб-интерфейс и Cloud Function в единый сайт, мы используем сервис Yandex API Gateway.

Архитектура нашего веб-сайта
В админке Yandex API Gateway создаем шлюз. Этот шлюз настраивается yaml файлом. Для перенаправления запросов в Object Storage добавляем в конфиг интеграцию с Object Storage. А для перенаправления запросов в Cloud Function - интеграцию с Cloud Function. Должен получиться примерно такой конфиг:
openapi: 3.0.0
info:
title: Sample API
version: 1.0.0
servers:
- url: <технический урл>
- url: <публичный урл>
paths:
/:
get:
x-yc-apigateway-integration:
bucket: <имя бакета со статическим сайтом>
error_object: index.html
type: object_storage
service_account_id: <айди сервисного аккаунта с правами на чтение бакета>
object: 'index.html'
/{file+}:
get:
parameters:
- name: file
in: path
required: false
schema:
type: string
x-yc-apigateway-integration:
bucket: <имя бакета со статическим сайтом>
error_object: index.html
type: object_storage
service_account_id: <айди сервисного аккаунта с правами на чтение бакета>
object: '{file}'
/api/{path+}:
x-yc-apigateway-any-method:
parameters:
- name: path
in: path
description: backend path
required: true
schema:
type: string
x-yc-apigateway-integration:
type: cloud_functions
function_id: <айди облачной функции с нашим бэкендом>
tag: "$latest"
service_account_id: <айди сервисного аккаунта с правами на вызов облачной функции>
В результате, запросы на /api/*
будут направлены в Cloud Function, а все остальные запросы пойдут на Object Storage.
Код отправки веб формы на фронте выглядит примерно так:
const sendMessage = async (name, message) => {
const res = await fetch("/api/send-message", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name, message }),
});
return res.ok;
};
Деплоится фронт так же, как и в Проекте-1.
На бэкенде, Cloud Function принимает объект event в качестве аргумента. В этом объекте находятся все параметры запроса (заголовки, метод, тело и т.д.). Проект у нас небольшой, поэтому нам не понадобится мощный бэкенд-фреймворк. Все запросы мы будем разбирать вручную, без использования стороннего роутера.
Код обработчика в Cloud Function выглядит примерно так:
import { sendMessage } from "../service/sendMessage.js";
const handleSendMessage = async (event) => {
const body = JSON.parse(event.body);
await sendMessage(body.name, body.message);
return {
statusCode: 200,
};
};
export const handler = async (event) => {
const method = event.httpMethod;
const [path] = event.url.split("?");
if (method === "POST" && path === "/api/send-message") {
return handleSendMessage(event);
}
return {
statusCode: 404,
};
};
Для деплоя бэкенда нам понадобится yc cli, установим её по инструкции.
Далее используем примерно такой скрипт:
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $ROOT_DIR
DIST_DIR="./yandexCloudFnDist"
FUNCTION_ARCHIVE_PATH="./yandexCloudFn.zip"
npm --omit=dev ci
if [[ -d $DIST_DIR ]]; then
echo "Удаление существующей директории ${DIST_DIR}..."
rm -rf $DIST_DIR
fi
rm -f $FUNCTION_ARCHIVE_PATH
echo "Создание новой директории ${DIST_DIR}..."
mkdir $DIST_DIR
echo "Перемещение исходных файлов в ${DIST_DIR}..."
cp -R \
package.json \
node_modules/ \
src/ \
$DIST_DIR/
echo "Архивация содержимого директории ${DIST_DIR}..."
zip -r $FUNCTION_ARCHIVE_PATH $DIST_DIR
echo "Yandex Cloud Function собрана"
source ./.yandex.cloud.env
echo "Загрузка Yandex Cloud Function в облако..."
yc serverless function version create \
--function-name="${FUNCTION_NAME}" \
--runtime nodejs22 \
--entrypoint "yandexCloudFnDist/src/controller/yandexCloudFunction.handler" \
--memory 128m \
--execution-timeout 5s \
--environment TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN}" \
--environment TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID}" \
--source-path ./yandexCloudFn.zip
Обратите внимание, что мы скипаем установку dev зависимостей. Это очень важно, т.к. Cloud Function имеет очень небольшой лимит на размер функции, и, если засунуть в нее все dev зависимости, то скорее всего этот лимит будет превышен.
Сравнение двух вариантов развертывания Проекта-2
Выше я приводил архитектурные характеристики, которым должно соответствовать наше решение. Сравним два варианта развертывания на основе этих характеристик.
Низкая стоимость хостинга | ★★ | Из-за бэкенда стоимость увеличивается до сотен рублей в месяц |
---|---|---|
Простота реализации и развертывания | ★★★★★ | Интуитивно в пару кликов создается и развертывается приложение |
Простота поддержки | ★★★★ | Иногда слетают сертификаты, зато настроен CI |
Базовая защищенность веб-сайта | ★★★★★ | Сертификат Let’s Encrypt прилагается |
Сайт легко найти в интернете | ★★★★★ | Можно подключить свой домен |
Низкая стоимость хостинга | ★★★★ | Если надо делегировать домен в Yandex Cloud DNS, то появится статья расходов примерно в 40 ₽ в месяц |
---|---|---|
Простота реализации и развертывания | ★★ | Быстро получится только, если знать, что делать. Не интуитивно. Много действий. Еще сложнее, чем статический сайт в Object Storage |
Простота поддержки | ★★★★ | После первоначальной настройки работать будет вечно, без вашего участия. Но CI из коробки не идет. Поэтому деплой настраиваем сами |
Базовая защищенность веб-сайта | ★★★★★ | Сертификат Let’s Encrypt прилагается |
Сайт легко найти в интернете | ★★★★★ | Можно подключить свой домен |
Полный исходный код Проекта-2 можно посмотреть здесь. Живой пример развернутого Проекта-2 можно посмотреть здесь.