Как запустить статический сайт в Яндекс облаке
В наше время сайты можно запускать везде, не всегда это удобно. Давайте посмотрим как можно запустить статический сайт в Яндекс Облаке и сделать это максимально удобным для публикации статей.
NOTE:
Гид предполагает что вы работаете с ОС Linux или MacOS. Если у вас Windows то советую попробовать WSL2Для того чтобы следовать инструкциям вам понадобиться следующее.
- домен который обслуживается DNS серверами от yandex
- Account в Яндекс Облаке
- Установленный Яндекс cli
- github account
- установленный hugo
- установленный pulumi provider
NOTE:
DNS сервера yandex можно найти под ns1.yandexcloud.net и ns2.yandexcloud.net. Таким образом можно будет управлять записями из облака Яндекса.Настройка yandex cloud
Как уже сказано выше, я рассчитываю что yandex cloud уже установлен в системе,
но все еще нужно настроить его под наше облако.
Для этого есть команда интерактивной настройки.
Просто запустите yc init
.
Pulumi понадобится бэкенд для сохранения состояния нашей инфраструктуры. Существуют разные варианты настройки. Первый и рекомендуемый это их собственный сервис, но так как я не люблю заводить новые логины мы не будем его использовать.
Второй вариант это json
файл на вашей локальной файловой системе. Его можно
использовать в том случае если вы будете работать над своим сайтом только с
одного компьютера или просто хотите попробовать как работает pulumi.
Третий вариант, тот который мы будем использовать для этой статьи, это облачное объектное хранилище совместимое с апи S3.
Для начала понадобится создать такое хранилище с помощью команды yc
которою мы
настроили прежде. Так как бакет должен иметь уникальное имя, желательно добавить
случайный стринг в конце названия. Иначе есть возможность что ваше имя будет уже
использовано.
yc storage bucket create --name your-bucket-name-$(echo $RANDOM | md5sum | head -c 8; echo;)
# Output
name: your-bucket-name-3482bfb2
folder_id: your-folder-id
anonymous_access_flags:
read: false
list: false
default_storage_class: STANDARD
versioning: VERSIONING_DISABLED
acl: {}
created_at: "2023-01-23T17:45:25.688169Z"
Сделав бакет, нам нужен пользователь который сможет писать в него. Так
называемый service-account
которому мы должны присвоить access-key
для
авторизации. Все эти операции, как и прежде, можно сделать с помощью командной
строки.
NOTE:
Из нижеупомянутых команд сохраняем следующие значения.
- id сервисного пользователя
- folder_id
- key_id
- secret
yc iam service-account create --name pulumi-backend-storage
id: aje6lk6ov7u04njl6er6
folder_id: b1glrb61es8if4qbp6j7
created_at: "2023-01-23T18:23:01.681284934Z"
name: pulumi-backend-storage
# Id берем из предыдущий команды
yc iam access-key create --service-account-id aje6lk6ov7u04njl6er6
access_key:
id: ajepic6sdcs39dpoltfp
service_account_id: aje6lk6ov7u04njl6er6
created_at: "2023-01-23T18:26:04.538821120Z"
key_id: YCAJENQtJ0mOaQL_HpwaKGmz2
secret: YCP2YXpYEswXdc0envcb9XY5g2IShgDA-pJizbC4
Осталось добавить пользователю роль администратора хранилища. Так как в Яндекс
облаке роль можно назначить только на все облако или на одну папку в облаке,
нужно запускать следующую команду. Id после add-access-binding
это id нашей
папки в которой мы создали сервисного пользователя.
yc resource-manager folder add-access-binding b1glrb61es8if4qbp6j7 --role storage.admin --subject serviceAccount:aje6lk6ov7u04njl6er6
Теперь облако готово для работы с Pulumi.
Pulumi
Для работы с Pulumi нужно будет инициализировать папку для нашего проекта и произвести логин в выбранный нами бэкенд. Pulumi предлагает несколько языков для работы с ресурсами в Яндекс облаке. Я предпочитаю python, поэтому продолжим именно с ним.
Небольшое пояснение. Для работы с yandex object storage нам надо будет использовать переменные которые являются переменными для AWS S3. Так что не удивляйтесь их именам.
export AWS_ACCESS_KEY_ID=значение из команды создания ключа
export AWS_SECRET_ACCESS_KEY=значение из команды создания ключа
pulumi login s3://имя-вашего-бакета\?endpoint=https://storage.yandexcloud.net\®ion=ru-central1-a
# Создаем новую папку и заводим там новый проект с pulumi.
mkdir site-infrastructure && cd site-infrastructure
pulumi new python
После ответа на вопросы в вашей папке должна быть такая структура.
tree -L 1
.
├── __main__.py
├── Pulumi.dev.yaml
├── Pulumi.yaml
├── requirements.txt
└── venv
Добавьте еще одну зависимость. Это pulumi-yandex-unofficial
. Неофициальный
т.к. из за СВО pulumi отказались дальше поддерживать свой официальный бэкенд.
Добавьте его следующим образом.
source venv/bin/activate
pip install pulumi-yandex-unofficial
Вся последовательная работа будет происходить внутри файла __main__.py
.
Для установки сайта через Pulumi требуются следующие облачные ресурсы.
Название ресурса | Предназначение |
---|---|
static site catalog | Папка в который будут находится все ресурсы |
deployment_user | Пользователь который будет публиковать наш сайт |
service-user-static-access-key | Ключи доступа для пользователя |
role | Роли которые будут присвоены пользователю |
role-binding | Мета сущность которая определяет какие роли присвоены какому пользователю |
folder-editor-role | Роль позволяющая писать в папку |
certificate | Сертификат для безопасности сайта |
site-bucket | Бакет в который будем загружать сайт |
dns-zone | ДНС зона для сайта, в ней будут все именные записи вашего домена |
dns-challenge-record | Запись для подтверждения собственности домена, нужна чтобы получить сертификат |
dns-record | Запись на которую будут ходить ваши посетители |
С Pulumi можно создать все эти ресурсы программатическим образом.
import pulumi
import pulumi_yandex_unofficial as yc
site_domain_name = "chipnibbles.ru"
static_site_catalog = yc.ResourcemanagerFolder(
"static-site"
)
deployment_user = yc.IamServiceAccount(
"github-actions-user",
description="Service user for yc to deploy static site",
folder_id=static_site_catalog.id,
)
service_user_static_access_key = yc.IamServiceAccountStaticAccessKey(
"github-actions-access-key",
service_account_id=deployment_user.id,
description="Static access key for deployment user. Used for object storage",
)
roles = ["storage.admin"]
role_bindings = []
for role in roles:
role_bindings.append(
yc.IamServiceAccountIamBinding(
f"{role}-binding",
members=[pulumi.Output.concat("serviceAccount:", deployment_user.id)],
role=role,
service_account_id=deployment_user.id,
)
)
folder_editor_roles = yc.ResourcemanagerFolderIamMember(
"folder-editor",
folder_id=static_site_catalog.id,
role="storage.editor",
member=deployment_user.id.apply(lambda id: f"serviceAccount:{id}"),
)
certificate = yc.CmCertificate(site_domain_name.replace(".", "-"),
domains=[site_domain_name],
folder_id=static_site_catalog.id,
managed=yc.CmCertificateManagedArgs(
challenge_type="DNS_CNAME",
)
)
site_bucket = yc.StorageBucket(
site_domain_name,
access_key=service_user_static_access_key.access_key,
secret_key=service_user_static_access_key.secret_key,
bucket=site_domain_name,
acl="public-read",
https=yc.StorageBucketHttpsArgs(
certificate_id=certificate.id
),
website=yc.StorageBucketWebsiteArgs(
error_document="error.html",
index_document="index.html",
),
)
dns_zone = yc.DnsZone(
site_domain_name.replace(".", "-"),
description="Zone for the chipnibbles-ru static site",
folder_id=static_site_catalog.id,
zone=f"{site_domain_name}.",
public=True,
)
dns_challenge_record = yc.DnsRecordSet(
"cert_challenge",
zone_id=dns_zone.id,
ttl=200,
type=certificate.challenges[0].dns_type,
name=certificate.challenges[0].dns_name,
datas=[certificate.challenges[0].dns_value]
)
dns_record = yc.DnsRecordSet(
f"{site_domain_name}.",
name=f"{site_domain_name}.",
zone_id=dns_zone.id,
type="ANAME",
ttl=200,
datas=[site_bucket.website_endpoint],
)
pulumi.export("Static-Access-Key-Var", service_user_static_access_key.access_key.apply(
lambda access_key: f"AWS_ACCESS_KEY_ID={access_key}"
))
pulumi.export("Static-Access-Key-Secret-Var", service_user_static_access_key.secret_key.apply(
lambda secret_key: f"AWS_SECRET_ACCESS_KEY={secret_key}"
))
pulumi.export("Catalog Link", static_site_catalog.id.apply(
lambda id: f"https://console.cloud.yandex.ru/folders/{id}"
))
Этот код довольно простой, я всего лишь обращу внимание на ресурсы certificate
и dns_zone
. В них не допускаются точки в значении имени ресурса поэтому я их
заменил дефисом. Учтите что название бакета и сайта должны совпадать. Код это
предусматривает, но если вы будете повторять его сами, то об этом стоит знать.
Осталось запустить инфраструктуру.
pulumi up
В выводе команды вы получите новые ключи доступа которые нам понадобятся в следующем отделе.
Hugo
Для создания нового сайта в hugo предусмотрена команда
hugo new site имя-вашего-сайта
таким образом hugo создаст новую папку с
аналогичным именем. В ней следующее содержание.
.
├── archetypes
│ └── default.md
├── assets
├── config.toml
├── content
├── data
├── layouts
├── public
├── static
└── themes
Создайте базовую настройку сайта. Для этого нужно прописать параметры в файле
config.toml
, добавить дизайн сайта и установить golang.
Установить golang можно по инструкции на официальном сайте.
Теперь настроим hugo
для использования модулей и добавим репозиторий с
дизайном ananke
.
hugo mod init github.com/имя-пользователя/имя-сайта
# Добавляем строку в config.toml
echo "theme = ['github.com/theNewDynamic/gohugo-theme-ananke']" >> config.toml
Для наглядности добавим еще одну статью. И заполним ее шаблонным текстом.
hugo new post/hello-hugo.md
curl 'https://baconipsum.com/api/\?type\=meat-and-filler\¶s\=5\&format\=text' >> content/post/hello-hugo.md
Теперь запустите сайт. Команда hugo server -D
стартует сервер. Зайдя на
http://localhost:1313
вы увидите следующее.
В hugo есть команда hugo deploy
которая загружает генерированный сайт в бакет.
Необходимые ключи авторизации hugo считывает из файла ~/.aws/credentials
.
Добавляем наши ключи в следующем формате в этот файл.
[default]
aws_access_key_id = id из команды pulumi up
aws_secret_access_key = секрет из команды pulumi up
И прописываем настройки бакета в config.toml
.
[[deployment.targets]]
name = "yandex-cloud"
URL = "s3://имя-вашего-бакета-для-pulumi?region=ru-central1-a&endpoint=https://storage.yandexcloud.net"
Также убираем статус наброска из статьи и запускаем команду.
# В файле content/post/hello-hugo.md меняем четвертую строку на
draft: false
# И запускаем команду
hugo deploy
Сайт доступен под https://имя-вашего-бакета
Теперь можно писать статьи и добавлять новую инфраструктуру к вашему проекту. Но
для публикации все еще надо будет запускать несколько команд вручную.
Мы ленивые и не хотим повторять одну и ту же тривиальную работу, мы можем
воспользоваться технологией github actions
и компьютер будет исполнять эту
работу за нас.
Github Actions
Github Actions это механизм постоянной интеграции и доставки (CI/CD) каждый раз, в зависимости от настроек, когда мы будем фиксировать новое состояние кода github будет запускать программу на изолированной виртуальной машине.
Для настройки этого функционала нужно добавить новые папки под названием
.github/workflows
в наш репозиторий и создать в нем новый файл deploy.yml
с
следующим содержанием.
name: build-chipnibbles-site
on: push
jobs:
provision-infrastructure:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
working-directory: infrastructure
container:
image: pulumi/pulumi
options: --user root
env:
YC_TOKEN: ${{ secrets.YC_TOKEN }}
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
steps:
- name: Checkout Repository
uses: actions/checkout@v3
with:
path: .
- name: init venv
run: |
python3 -m venv venv
source venv/bin/activate
pip3 install -r requirements.txt
- name: Provision infrastructure
run: |
pulumi login s3://${{ vars.STATE_BUCKET }}\?endpoint=https://storage.yandexcloud.net\®ion=ru-central1-a
pulumi up --non-interactive -s prod -y
- name: Create secret vars
run: |
echo "::add-mask::$GEN_ACCESS_KEY_ID_VAR"
echo "::add-mask::$GEN_ACCESS_KEY_SECRET_VAR"
GEN_ACCESS_KEY_ID_VAR=$(pulumi stack output -s prod --show-secrets Static-Access-Key-Var)
GEN_ACCESS_KEY_SECRET_VAR=$(pulumi stack output -s prod --show-secrets Static-Access-Key-Secret-Var)
echo "$GEN_ACCESS_KEY_ID_VAR" >> $GITHUB_ENV
echo "$GEN_ACCESS_KEY_SECRET_VAR" >> $GITHUB_ENV
- name: Register Access Key ID
uses: dacbd/gha-secrets@v1
with:
token: ${{ secrets.GH_PAT }}
name: DEPLOY_AWS_ACCESS_KEY_ID
value: ${{ env.DEPLOY_AWS_ACCESS_KEY_ID }}
- name: Register Access Key ID
uses: dacbd/gha-secrets@v1
with:
token: ${{ secrets.GH_PAT }}
name: DEPLOY_AWS_SECRET_ACCESS_KEY
value: ${{ env.DEPLOY_AWS_SECRET_ACCESS_KEY }}
build-chipnibbles-site:
runs-on: ubuntu-latest
needs: provision-infrastructure
container:
image: cr.yandex/crpp8tmsg2hn2jguoorb/hugo:latest
options: --user root
env:
AWS_ACCESS_KEY_ID: ${{ secrets.DEPLOY_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DEPLOY_AWS_SECRET_ACCESS_KEY }}
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Build Site
run: hugo --minify
- name: Deploy site
run: hugo deploy
Теперь с каждым новым коммитом github actions будет запускать pulumi и после собирать наш сайт с помощью hugo. Чтобы эти скрипты работали надо создать секреты.
Зайдите в настройки вашего репозитория. Под пунктами Security -> Secrets and variables -> Actions добавьте следующие переменные.
# Креды для бакета с состоянием pulumi
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
# Персональный токен для Github
GH_PAT
# Пароль которые вы использовали для шифрования состояния в pulumi
PULUMI_CONFIG_PASSPHRASE
# Токен для доступа в Яндекс Облако
YC_TOKEN
Итог
Техническая часть запуска сайта закончилась, осталось только писать и публиковать статьи когда хотите. О стоимости этого сайта не стоит долго задумываться, например сейчас я плачу максимум 4 рубля в месяц за chipnibbles.ru.
Поэтому если вам нужна страница в интернете, но при этом не нужен динамический функционал wordpress, или текстовый редактор в браузере то статический сайт хороший вариант.