309 lines
11 KiB
Markdown
309 lines
11 KiB
Markdown
---
|
||
title: "Блог на Hugo в K3s: часть 1 - архитектура и первый запуск"
|
||
date: 2026-01-03
|
||
draft: false
|
||
description: "Переезд с Jekyll на Hugo: выбор темы Blowfish, настройка K3s кластера и два окружения с нуля. Старт цикла о production блоге на Open Source стеке."
|
||
tags: ["hugo", "blowfish", "gitea", "homelab", "devops"]
|
||
categories: ["Веб-разработка"]
|
||
series: ["Блог на Hugo в K3s"]
|
||
series_order: 1
|
||
showComments: true
|
||
---
|
||
|
||
Предыдущий блог жил на Jekyll. Жил - громко сказано. Скорее существовал, периодически ломаясь при обновлениях и требуя ритуальных танцев с Ruby каждый раз когда я садился за новую статью.
|
||
|
||
Однажды я решил что хватит.
|
||
|
||
---
|
||
|
||
## Почему не Jekyll
|
||
|
||
Jekyll - зрелый инструмент с большим сообществом. Но у него есть фундаментальная проблема: он написан на Ruby.
|
||
|
||
Звучит невинно. На практике это означает:
|
||
|
||
**Версионный ад.** Ruby, Bundler, Gems - у каждого своя версия, и они регулярно конфликтуют друг с другом. Клонируешь репозиторий на новую машину - час уходит на то чтобы собрать рабочее окружение. Обновляешь Jekyll - ломаются плагины. Обновляешь плагины - ломается что-то ещё.
|
||
|
||
**Зависимости ради зависимостей.** Простой блог тянет 1000+ gems, половина из которых устаревшая или находится в состоянии "поддерживается постольку-поскольку". Каждый `bundle install` - это лотерея.
|
||
|
||
**Лишний мусор.** Jekyll генерирует страницы без ссылок, которые непонятно зачем существуют. Приходится явно прописывать что не генерировать.
|
||
|
||
Hugo решает все эти проблемы радикально: это один бинарник на Go. Никаких зависимостей, никаких конфликтов версий, никакого `bundle install`. Скачал - работает. На любой машине, всегда.
|
||
|
||
Бинарник весит ~50MB. Собирает сайт из 40+ страниц за полторы секунды. Jekyll на том же контенте думал заметно дольше.
|
||
|
||
---
|
||
|
||
## Почему Blowfish
|
||
|
||
Все просто: понравилась.
|
||
|
||
Смотрел темы несколько часов. Blowfish выглядела именно так как я хотел - чисто, без лишнего, с хорошей типографикой. Взял её.
|
||
|
||
Ставится как Git submodule - обновляется одной командой, не засоряет репозиторий.
|
||
|
||
---
|
||
|
||
## Архитектура: два окружения
|
||
|
||
У любого нормального инфраструктурного проекта есть тестовый и production контур. Блог - не исключение: обновления Hugo, изменения темы, новые статьи - всё это нужно проверять до того как это увидят читатели.
|
||
|
||
```
|
||
┌────────────────────────────────────┐
|
||
│ Gitea (внутренний) │
|
||
│ репозиторий: blog │
|
||
│ │
|
||
│ ветка: main ветка: dev │
|
||
└───────┬────────────────────┬───────┘
|
||
│ │
|
||
▼ ▼
|
||
┌───────────────┐ ┌───────────────┐
|
||
│ hugo-builder │ │ hugo-builder │
|
||
│ prod │ │ dev │
|
||
└───────┬───────┘ └───────┬───────┘
|
||
│ │
|
||
▼ ▼
|
||
┌───────────────┐ ┌───────────────┐
|
||
│ nginx │ │ nginx-dev │
|
||
│ blog.ru │ │ dev.blog.ru │
|
||
│ (публичный) │ │ (Basic Auth) │
|
||
└───────────────┘ └───────────────┘
|
||
```
|
||
|
||
**Production** (`blog.ru`) - публичный. Ветка `main`.
|
||
|
||
**Development** (`dev.blog.ru`) - тестовый контур. Ветка `dev`. Закрыт Basic Auth через Traefik Middleware - без логина и пароля не войти.
|
||
|
||
Оба окружения в одном K3s namespace. Один репозиторий, две ветки, два независимых пайплайна.
|
||
|
||
---
|
||
|
||
## Шаг 1: Создаём Hugo проект
|
||
|
||
```bash
|
||
# Создаём новый Hugo сайт
|
||
cd ~/projects
|
||
hugo new site blog
|
||
cd blog
|
||
|
||
# Инициализируем Git репозиторий
|
||
git init
|
||
git remote add origin https://git.example.com/user/blog.git
|
||
|
||
# Добавляем тему Blowfish как Git submodule
|
||
git submodule add -b main https://github.com/nunocoracao/blowfish.git themes/blowfish
|
||
|
||
# Подтягиваем файлы submodule
|
||
git submodule update --init --recursive
|
||
|
||
# Удаляем дефолтный конфиг Hugo
|
||
rm -f hugo.toml
|
||
|
||
# Создаём структуру для конфигов
|
||
mkdir -p config/_default
|
||
|
||
# Копируем примеры конфигов из темы
|
||
cp themes/blowfish/config/_default/*.toml config/_default/
|
||
```
|
||
|
||
Структура после инициализации:
|
||
|
||
```
|
||
blog/
|
||
├── content/ ← статьи в Markdown
|
||
├── static/ ← изображения, favicon
|
||
├── themes/
|
||
│ └── blowfish/ ← тема (Git submodule)
|
||
└── config/
|
||
└── _default/
|
||
├── hugo.toml
|
||
├── languages.ru.toml
|
||
├── menus.ru.toml
|
||
├── markup.toml
|
||
└── params.toml
|
||
```
|
||
|
||
---
|
||
|
||
## Шаг 2: Минимальная конфигурация
|
||
|
||
### hugo.toml
|
||
|
||
```toml
|
||
baseURL = "https://blog.ru/"
|
||
theme = "blowfish"
|
||
defaultContentLanguage = "ru"
|
||
```
|
||
|
||
### languages.ru.toml
|
||
|
||
```bash
|
||
# Переименовываем файл языка для русского
|
||
mv config/_default/languages.en.toml config/_default/languages.ru.toml
|
||
```
|
||
|
||
```toml
|
||
languageCode = "ru"
|
||
languageName = "Русский"
|
||
weight = 1
|
||
title = "Мой Блог"
|
||
|
||
[params]
|
||
displayName = "RU"
|
||
isoCode = "ru"
|
||
rtl = false
|
||
dateFormat = "2 January 2006"
|
||
|
||
[params.author]
|
||
name = "Ваше Имя"
|
||
headline = "DevOps Engineer | Kubernetes | Homelab"
|
||
```
|
||
|
||
### menus.ru.toml
|
||
|
||
```bash
|
||
# Переименовываем файл меню
|
||
mv config/_default/menus.en.toml config/_default/menus.ru.toml
|
||
```
|
||
|
||
```toml
|
||
[[main]]
|
||
name = "Блог"
|
||
pageRef = "posts"
|
||
weight = 10
|
||
|
||
[[main]]
|
||
name = "О сайте"
|
||
pageRef = "about"
|
||
weight = 20
|
||
```
|
||
|
||
---
|
||
|
||
## Шаг 3: Первая статья
|
||
|
||
```bash
|
||
# Создаём новую статью
|
||
hugo new content posts/hello-world/index.md
|
||
```
|
||
|
||
```markdown
|
||
---
|
||
title: "Hello World"
|
||
date: 2026-02-13
|
||
draft: false
|
||
description: "Первый пост на новом сайте"
|
||
tags: ["test"]
|
||
categories: ["Веб-разработка"]
|
||
---
|
||
|
||
Это первый пост на blog.ru.
|
||
```
|
||
|
||
---
|
||
|
||
## Шаг 4: Проверяем локально
|
||
|
||
```bash
|
||
# Запускаем локальный сервер Hugo
|
||
# -D: показывать черновики (draft: true)
|
||
# --bind 0.0.0.0: доступен с любого IP в локальной сети
|
||
hugo server -D --bind 0.0.0.0
|
||
```
|
||
|
||
Открываем `http://192.168.11.10:1313/` (ваш IP) - сайт с Blowfish темой и первой статьёй.
|
||
|
||
Hugo автоматически пересобирает сайт при сохранении файлов - изменения видны сразу после обновления страницы в браузере.
|
||
|
||
---
|
||
|
||
## Шаг 5: Пушим в Gitea
|
||
|
||
```bash
|
||
# Добавляем все файлы в Git
|
||
git add .
|
||
|
||
# Создаём первый коммит
|
||
git commit -m "Initial commit: Hugo + Blowfish v2.98.0"
|
||
|
||
# Переименовываем ветку в main (если по умолчанию master)
|
||
git branch -M main
|
||
|
||
# Пушим в Gitea
|
||
git push -u origin main
|
||
|
||
# Создаём ветку dev для development окружения
|
||
git checkout -b dev
|
||
git push -u origin dev
|
||
|
||
# Возвращаемся на main
|
||
git checkout main
|
||
```
|
||
|
||
---
|
||
|
||
## Workflow: как я пишу статьи
|
||
|
||
**Пишу (ветка dev):**
|
||
|
||
```bash
|
||
# Переходим в папку проекта
|
||
cd ~/projects/blog
|
||
|
||
# Переключаемся на ветку dev
|
||
git checkout dev
|
||
|
||
# Запускаем локальный предпросмотр
|
||
hugo server -D --bind 0.0.0.0
|
||
|
||
# Создаём новую статью
|
||
hugo new content posts/название-статьи/index.md
|
||
|
||
# Редактируем в любом редакторе, сохраняем, смотрим в браузере
|
||
# Когда готово - коммитим
|
||
git add .
|
||
git commit -m "feat: новая статья про X"
|
||
|
||
# Пушим в dev ветку
|
||
git push origin dev
|
||
# → webhook срабатывает → автосборка → dev.blog.ru
|
||
```
|
||
|
||
**Проверяю:**
|
||
|
||
Открываю `https://dev.blog.ru` (вводя логин/пароль Basic Auth) - вижу статью как её увидят читатели.
|
||
|
||
**Публикую:**
|
||
|
||
```bash
|
||
# Переключаемся на main
|
||
git checkout main
|
||
|
||
# Мержим изменения из dev
|
||
git merge dev
|
||
|
||
# Пушим в production
|
||
git push origin main
|
||
# → webhook срабатывает → автосборка → blog.ru
|
||
```
|
||
|
||
Золотое правило: **все изменения только через ветку `dev`**. В `main` - только через merge. Никогда не редактировать файлы находясь на `main`.
|
||
|
||
Почему это важно - расскажу отдельно, когда дойдём до того как я нарушил это правило и что из этого вышло.
|
||
|
||
---
|
||
|
||
## Что дальше
|
||
|
||
Локально всё работает. Но `hugo server` умрёт как только закрою терминал.
|
||
|
||
Нужно развернуть это в K3s: Hugo Builder который пересобирает сайт при каждом пуше, Nginx который раздаёт статику, SSL сертификаты, NFS хранилище. Об этом - в следующей части.
|
||
|
||
---
|
||
|
||
**Стек этой части:**
|
||
- Hugo v0.155.3 extended
|
||
- Blowfish v2.98.0
|
||
- Gitea (внутренний)
|
||
- Рабочая станция: Debian/Ubuntu
|