Мне кажется, что DevOps — это про творчество. У нас есть огромное количество способов достичь одной цели, и зачастую не будет единственного правильного решения. Что-то более оптимальное, что-то сложнее, что-то удобнее поддерживать.
Мы можем собрать бинарник и положить в контейнер, а можем собрать и закинуть файл по ssh. О, а как запустить — можем просто docker run
сделать, а можем завернуть в compose. Или обернуть всё в systemd-сервис, или может в init.d… Ой, а если еще затронуть Kubernetes и Helm…
Мне захотелось на досуге попробовать поднять блог на Hugo. Я раньше это делал, но так нормально и не разобрался с порядком работы. Поднимать буду прямо на виртуалке, сделаю CI/CD пайплайн с разными окружениями и покажу как тестировать все локально.
Что такое Hugo и почему именно он? #
Hugo — это так называемый генератор статических вебсайтов. Это приложение на языке Go, которое из определенного набора файлов сгенерирует нам статику, а ее мы будем раздавать пользователям с помощью вебсервера.
Классная особенность статических страниц в том, что мы можем раздавать их хоть с s3-бакета, хоть с GitHub Pages.
Почему же именно Hugo? На самом деле генераторов статики несколько: Jekyll, Hugo, Gatsby… Hugo — бесплатный, посты мы готовим в знакомым многим Markdown формате, есть встроенный веб-сервер в не-продакшн режиме… ну и другие я не пробовал.
Выбираем стек #
Чтобы было повеселее сегодня я сделаю следующее:
- Покажу как установить Hugo на виртуальную машину, локально и как запускать его
- Настрою Nginx для раздачи статики, добавлю бесплатный SSL сертификат
- Установлю self-hosted GitHub Actions Runner на виртуальную машину
- Настрою пайплайн со сборкой и доставкой статики на наш сервер
Устанавливаем Hugo на виртуальную машину #
Тут официальная документация
предлагает нам несколько вариантов, вплоть до установки из стандартных репозиториев Ubuntu (apt install hugo
), но там устанавливается очень старая версия.
Мы же скачаем .deb
пакет и установим его. Идем в репозиторий на GitHub и открываем
страницу с последним релизом. Качаем .deb
файл с amd64 архитектурой.
# Скачиваем пакет
wget -P /tmp https://github.com/gohugoio/hugo/releases/download/v0.117.0/hugo_extended_0.117.0_linux-amd64.deb
# Устанавливаем пакет
dpkg -i /tmp/hugo_extended_0.117.0_linux-amd64.deb
# Проверяем установку
hugo version
Устанавливаем Hugo локально #
Параллельно с этим я устанавливаю Hugo себе локально, чтобы было удобнее отлаживать код, а так же чтобы не мучиться с иницализацией репозитория.
Так как у меня MacOS я могу установить Hugo через менеджер пакетов brew:
brew install hugo
Разбираемся как оно работает #
Эти действия я предлагаю выполнять на своей локальной машине
Для начала создадим структуру для нового сайта. Предположим, что мы у себя в домашней директории:
❯ hugo new site superblog
Congratulations! Your new Hugo site is created in /Users/admin/superblog.
Just a few more steps and you're ready to go:
1. Download a theme into the same-named folder.
Choose a theme from https://themes.gohugo.io/ or
create your own with the "hugo new theme <THEMENAME>" command.
2. Perhaps you want to add some content. You can add single files
with "hugo new <SECTIONNAME>/<FILENAME>.<FORMAT>".
3. Start the built-in live server via "hugo server".
Переходим в созданную директорию и смотрим, что лежит внутри:
❯ cd superblog
❯ tree .
.
├── archetypes
│ └── default.md
├── assets
├── content <~~ здесь все посты
├── data
├── hugo.toml <~~ конфигурационный файл сайта
├── layouts
├── static
└── themes <~~ темы
Теперь нужно установить тему. Повыбирать можно тут, а в качестве примера воспользуемся темой Stack.
Документация предлагает два варианта — установить через git submodules, либо зашить тему вглубь директории layouts (установка через модули Hugo). Первый вариант мне кажется более подходящим — он делает простым смену тем.
Сначала инициализируем git-репозиторий, а затем добавим туда тему как submodule.
git init
git submodule add https://github.com/CaiJimmy/hugo-theme-stack.git themes/stack
И добавим нашу тему в конфигурационный файл:
echo "theme = 'stack'" >> hugo.toml
На этом моменте мы можем уже попробовать запустить Hugo в не-продакшен режиме: в этом случае нам не нужно настраивать Nginx — это очень удобно для локальной отладки нашего сайта и этим мы будем пользоваться постоянно. К тому же оно поддерживает горячее изменение файлов без перезапуска.
❯ hugo server
Watching for changes in /Users/admin/superblog/{archetypes,assets,content,data,layouts,static,themes}
Watching for config changes in /Users/admin/superblog/hugo.toml, /Users/admin/superblog/themes/stack/config.yaml
Start building sites …
hugo v0.117.0-b2f0696cad918fb61420a6aff173eb36662b406e+extended darwin/amd64 BuildDate=2023-08-07T12:49:48Z VendorInfo=brew
| EN
-------------------+-----
Pages | 7
Paginator pages | 0
Non-page files | 0
Static files | 0
Processed images | 3
Aliases | 3
Sitemaps | 1
Cleaned | 0
Built in 56 ms
Environment: "development"
Serving pages from memory
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop
Ого, оно что-то сделало и предлагает нам зайти на http://127.0.0.1:1313
:
Ну, можно выйти и продолжить: Ctrl-C
.
Будем предполагать, что именно таким образом мы будем локально тестировать наш блог: отредактировали, запустили hugo server, посмотрели локально, а затем пушим в репозиторий
Создаем первый пост #
Общая структура файлов с постами будет следующей:
❯ tree content
content
└── posts <~ общая категория posts, может быть любой другой
├── first-post <~ директория под файлы одной статьи
│ ├── image1.png <~ картинка-аттачмент для первого поста
│ └── index.md <~ markdown файл первого поста
└── second-post <~ директория под второй пост
└── index.md
Перезапустим наш веб-сервер hugo server
:
Сейчас нашу статику раздает встроенный веб-сервер hugo. Это так называемый “не-продакшн” режим.
С одной стороны мы можем написать systemd-сервис, который будет запускать команду hugo server
. Затем мы можем либо опубликовать этот веб-сервер на 0.0.0.0, либо настроить наш Nginx на обратной проксирование на 127.0.0.1:1313. Но так не было задумано разработчиками.
👉 С помощью команды hugo
, приложение сгенерирует статическую версию нашего сайта из всех файлов и положит ее в поддиректорию public
.
Установим Github Actions Runner #
У нас есть возможность хостить наши статические файлы даже в GitHub Pages, и это совершенно несложно. Но не так интересно.
Я буду хостить сайт прямо на виртуальной машине, и Github Actions Runner тоже установлю как self-hosted runner туда же.
Предположим, что мы уже создали отдельный пустой репозиторий в GitHub, к которому мы потом привяжем тот локальный, что мы создавали командой git init
.
Переходим в Settings, затем слева Actions - Runners, New self-hosted runner:
Дальше после выбора нужной платформы нам предоставят список команд для установки и инициализации раннера. В конце я рекомендую установить раннер как сервис, для этого можно обратиться к документации, где скажут запустить его командой:
sudo ./svc.sh install
Вот такая картина говорит нам о том, что наш раннер успешно подключился к Github Actions и ждет работы:
Создаем CI/CD пайплайн #
Документация для референса: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
Возвращаемся в наш репозиторий, создаем директорию .github
, а в ней поддиректорию workflows
. Workflows это отдельные пайплайны в Github Actions. Поэтому создаем файл dev-cicd.yml
и начинаем творчество. Я сделал так:
name: dev-etogeek cicd
on:
workflow_dispatch:
push:
branches:
- dev
jobs:
deploy-dev:
runs-on: self-hosted
environment:
name: dev
url: https://dev.mysite.tech
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
submodules: recursive
- name: Deploy Dev
run: |
hugo
sudo rm -rf /opt/dev-etogeek
sudo mkdir -p /opt/dev-etogeek/static
sudo cp -r public/* /opt/dev-etogeek/static/
Теперь по порядку о том, что здесь происходит:
on: workflow_dispatch:
— позволяет нам запустить пайплайн вручную, через Run Workflowon: push: branches: -dev
— пайплайн будет запущен автоматически при пуше кода в dev-веткуruns-on: self-hosted
— указывает, где будет запущен наш пайплайн; здесь указываются теги self-hosted раннеров, либо название github-actions образаenvironment:
— позволит более-менее удобно следить деплоями, не самая обязательная красивость- Этап
checkout
— склонирует наш репозиторий в рабочую директорию runner-а, а параметрwith: submodules: recursive
скачает дополнительно нашу тему, которую мы установили как git-submodule. По умолчанию рабочая директория очищается при каждом деплое.
Этап деплоя я сделал таким образом:
- Команда
hugo
генерирует в директориюpublic
статику из нашего hugo-репозитория. Команда, конечно же, требует установленного hugo на виртуальной машине, что мы сделали выше. - Далее мы удаляем директорию с нашей статикой, чтобы каждый раз копировать туда актуальные файлы. Сразу после удаления мы создаем ее заново.
- Копируем с ключем
-r
(recursive) все файлы из директорииpublic
в нашу директорию, на которую в будущем будет ссылаться Nginx
Делаем полностью аналогичный файл, только для production-версии нашего блога. Меняем названия, путь к директории и ветку для триггера на main
.
Обязательно добавляем в файл .gitignore
нашу директорию public
:
❯ cat .gitignore
/public/
.hugo_build.lock
Коммитим наш код. Первый раз, думаю, проще будет задеплоиться из основной ветки, чтобы все проверить. А про общий воркфлоу я напишу ниже. Смотрим, что наш пайплайн будет зеленым. Идем на виртуальную машину, проверяем, что все файлы на месте в нужной директории и мы можем приступить к настройке Nginx.
Настройка Nginx #
На виртуальной машине создаем файл с конфигурацией Nginx /etc/nginx/sites-available/prod.conf
:
server {
server_name mysite.tech;
root /opt/prod-etogeek/static/;
index index.html;
location / {
try_files $uri $uri/ =404;
}
listen 80;
}
Затем создаем symlink в директорию sites-enabled
с конфигурациями, которая считывается Nginx-ом:
ln -s /etc/nginx/sites-available/prod.conf /etc/nginx/sites-enabled/prod.conf
Проверяем конфигурацию и перезапускаем Nginx:
nginx -t
systemctl restart nginx
# или systemctl reload nginx
Уже сейчас можем зайти по доменному имени dev.mysite.tech
, которое мы конечно же поменяли на свое реальное, и увидим наш сайт. Но, добавить на сайт SSL-сертификат. Нет ничего проще, чем обратиться за помощью к Let’s Encrypt и скрипту certbot:
apt install certbot
certbot
Выбираем наш конфигурационный файл в интерактивном меню, а в конце выбираем Redirect. Можно будет увидеть, что certbot добавил в нашу nginx-конфигурацию несколько новых строк и переадресацию с http на https.
Если и пока у тебя нет доменного имени, можно схитрить и добавить адрес виртуальной машины в локальный
/etc/hosts
— тогда Nginx будет нормально обрабатывать имя из адресной строки
Процесс работы #
Основная часть работы позади, автоматизированная доставка кода настроена, стоит подытожить планируемый процесс работы:
- Разработку веду в локальном репозитории. Создаю статьи как отдельные
.md
файлы. - Тестирую работоспособность сайта, визуальную составляющую с помощью встроенного веб-сервера командой
hugo server
. - Вливаю свою код в
dev
ветку, спустя минуту-другую наблюдаю изменения на dev-версии блога - Делаю Pull Request из
dev
-ветки вmain
-ветку, вливаю его, наблюдаю изменения на production версии блога
Что можно усовершенствовать? #
Немного размышлений: это сложно назвать прямо идеальным девопсерским ci/cd пайплайном как минимум потому что у нас никак не отделены этапы сборки и выкатки кода.
Мы можем выполнять команду hugo в отдельном этапе, хоть даже на отдельной виртуальной машине или в контейнере. Затем собирать статику в архив, помечать его некой версией, например хэшом коммита и сохранять как артефакт. Затем этот артефакт уносить на dev-машину, и таким же образом — на prod-инстанс.
Но я не уверен, что на текущем этапе мне нужны эти усложнения — я все делаю на одной и той же виртуальной машине, и даже dev и prod окружения у меня лежат просто в соседних директориях. Зачем большее для блога?
Так же мы можем собирать это все не в self-hosted раннере, а в cloud-режиме, который предоставляет GitHub. В конце концов нужно будет просто скопировать собранную статику на нашу виртуалку.
Я писал однажды небольшой пост «Не усложняй» как раз про использование более сложных технологий, чем требует продукт.
🙋♂️С комментариями можно смело приходить в наш телеграм-чат — https://t.me/etogeekchat
🌀Так же рекомендую подписаться на телеграм-канал, чтобы не пропускать классные посты и анонсы новых статей — https://t.me/etogeek