Compare commits

...

2 Commits

Author SHA1 Message Date
Astronit b7c1d5d478 Merge branch 'dev'
merge: Обновление из dev
2026-02-19 20:10:53 +00:00
Astronit b05195a735 Добавил серию Blog 1-5 части. Подправил настройки и внешний вид сайта 2026-02-19 19:49:44 +00:00
238 changed files with 53664 additions and 1026 deletions

View File

@ -11,7 +11,7 @@ autoSwitchAppearance = false
enableA11y = false enableA11y = false
enableSearch = true enableSearch = true
enableCodeCopy = true enableCodeCopy = false
enableStructuredBreadcrumbs = false enableStructuredBreadcrumbs = false
# enableStyledScrollbar = true # disable to use native scrollbar style (defaults to true) # enableStyledScrollbar = true # disable to use native scrollbar style (defaults to true)
@ -70,8 +70,8 @@ forgejoDefaultServer = "https://v11.next.forgejo.org"
showDateUpdated = false showDateUpdated = false
showAuthor = true showAuthor = true
# showAuthorBottom = false # showAuthorBottom = false
showHero = false showHero = true
# heroStyle = "basic" # valid options: basic, big, background, thumbAndBackground heroStyle = "background" # valid options: basic, big, background, thumbAndBackground
layoutBackgroundBlur = true # only used when heroStyle equals background or thumbAndBackground layoutBackgroundBlur = true # only used when heroStyle equals background or thumbAndBackground
layoutBackgroundHeaderSpace = true # only used when heroStyle equals background layoutBackgroundHeaderSpace = true # only used when heroStyle equals background
showBreadcrumbs = false showBreadcrumbs = false
@ -85,8 +85,8 @@ forgejoDefaultServer = "https://v11.next.forgejo.org"
invertPagination = false invertPagination = false
showReadingTime = true showReadingTime = true
showTableOfContents = true showTableOfContents = true
# showRelatedContent = false showRelatedContent = true
# relatedContentLimit = 3 relatedContentLimit = 6
showTaxonomies = true # use showTaxonomies OR showCategoryOnly, not both showTaxonomies = true # use showTaxonomies OR showCategoryOnly, not both
showCategoryOnly = false # use showTaxonomies OR showCategoryOnly, not both showCategoryOnly = false # use showTaxonomies OR showCategoryOnly, not both
showAuthorsBadges = false showAuthorsBadges = false
@ -96,8 +96,8 @@ forgejoDefaultServer = "https://v11.next.forgejo.org"
# externalLinkForceNewTab = false # disable to allow external links in the same tab (defaults to true) # externalLinkForceNewTab = false # disable to allow external links in the same tab (defaults to true)
[list] [list]
showHero = false showHero = true
# heroStyle = "background" # valid options: basic, big, background, thumbAndBackground heroStyle = "background" # valid options: basic, big, background, thumbAndBackground
layoutBackgroundBlur = true # only used when heroStyle equals background or thumbAndBackground layoutBackgroundBlur = true # only used when heroStyle equals background or thumbAndBackground
layoutBackgroundHeaderSpace = true # only used when heroStyle equals background layoutBackgroundHeaderSpace = true # only used when heroStyle equals background
showBreadcrumbs = false showBreadcrumbs = false
@ -107,7 +107,7 @@ forgejoDefaultServer = "https://v11.next.forgejo.org"
showTableOfContents = false showTableOfContents = false
showCards = false showCards = false
orderByWeight = false orderByWeight = false
groupByYear = true groupByYear = false
cardView = false cardView = false
cardViewScreenWidth = false cardViewScreenWidth = false
constrainItemsWidth = false constrainItemsWidth = false
@ -117,8 +117,8 @@ forgejoDefaultServer = "https://v11.next.forgejo.org"
[taxonomy] [taxonomy]
showTermCount = true showTermCount = true
showHero = false showHero = true
# heroStyle = "background" # valid options: basic, big, background, thumbAndBackground heroStyle = "background" # valid options: basic, big, background, thumbAndBackground
showBreadcrumbs = false showBreadcrumbs = false
showViews = false showViews = false
showLikes = false showLikes = false
@ -126,8 +126,8 @@ forgejoDefaultServer = "https://v11.next.forgejo.org"
cardView = false cardView = false
[term] [term]
showHero = false showHero = true
# heroStyle = "background" # valid options: basic, big, background, thumbAndBackground heroStyle = "background" # valid options: basic, big, background, thumbAndBackground
showBreadcrumbs = false showBreadcrumbs = false
showViews = false showViews = false
showLikes = false showLikes = false

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 KiB

View File

@ -0,0 +1,306 @@
---
title: "Блог на Hugo в K3s: часть 1 - архитектура и первый запуск"
date: 2026-01-03
draft: false
description: "Как я переехал с Jekyll на Hugo, почему выбрал тему Blowfish и как настроил два окружения с нуля. Начало цикла о том как я строил oakazanin.ru."
tags: ["hugo", "blowfish", "gitea", "homelab", "devops"]
categories: ["infrastructure"]
series: ["Блог на Hugo в K3s"]
series_order: 1
---
Предыдущий блог жил на 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"]
---
Это первый пост на 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -0,0 +1,593 @@
---
title: "Блог на Hugo в K3s: часть 2 - деплой в кластер"
date: 2026-01-08
draft: false
description: "NFS хранилище, Hugo Builder с webhook listener, Nginx с Prometheus exporter и автоматический SSL от Let's Encrypt. Полный деплой production окружения."
tags: ["hugo", "k3s", "kubernetes", "nfs", "traefik", "cert-manager"]
categories: ["infrastructure"]
series: ["Блог на Hugo в K3s"]
series_order: 2
---
В первой части мы запустили Hugo локально. Сайт работает пока открыт терминал. Закрыл терминал - сайт умер.
Пора переносить это в K3s.
---
## Архитектура деплоя
```
Git Push
Gitea (внутренний)
↓ webhook POST
Hugo Builder
├→ git clone + submodule
├→ hugo --minify
└→ output → NFS
/export/blog-public/
Nginx (x2 реплики)
Traefik Ingress
your-blog.ru (SSL)
```
Пять компонентов:
1. **NFS** - хранилище для статики (OpenMediaVault)
2. **Hugo Builder** - пересобирает сайт при каждом пуше
3. **Nginx** - раздаёт статику с NFS
4. **cert-manager** - автоматический SSL от Let's Encrypt
5. **Traefik IngressRoute** - маршрутизация с SSL терминацией
---
## Шаг 1: NFS хранилище
Hugo собирает статику в HTML/CSS/JS файлы. Nginx раздаёт эти файлы. Значит нужно общее хранилище куда Hugo пишет, а Nginx читает.
NFS - самый простой вариант для homelab. У меня OpenMediaVault на отдельной машине.
### Создаём директории на NAS
```bash
# Подключаемся к NAS (SSH на нестандартном порту для безопасности)
ssh -p 33322 nasadmin@192.168.11.30
# Создаём папки для production и development окружений
sudo mkdir -p /srv/storage/blog/blog-public
sudo mkdir -p /srv/storage/blog/blog-public-dev
# Выдаём права на запись (контейнеры пишут от root)
sudo chmod -R 775 /srv/storage/blog/
```
**Почему SSH на порту 33322?** Стандартный порт 22 - первая цель сканеров и ботов. Нестандартный порт снижает шум в логах и количество brute-force попыток до нуля. Безопасность через скрытность работает для домашних серверов.
### Настраиваем NFS через OMV Web UI
Storage → Shared Folders → Create:
- Name: `blog-public`
- Device: основной диск
- Path: `/blog/blog-public`
Services → NFS → Shares → Create:
- Shared folder: `blog-public`
- Client: `192.168.11.0/24`
- Privilege: Read/Write
- Extra options: `rw,sync,no_subtree_check,no_root_squash`
То же для `blog-public-dev`.
**Критично:** `no_root_squash` - без этого контейнеры не смогут записывать файлы (они пишут от root внутри контейнера).
### Проверяем экспорт
```bash
# Заходим на NAS
ssh -p 33322 nasadmin@192.168.11.30
# Проверяем что NFS экспортирует наши шары
sudo exportfs -v | grep blog
# Ожидаемый вывод - две строки с настройками экспорта:
# /export/blog-public 192.168.11.0/24(rw,sync,no_root_squash,...)
# /export/blog-public-dev 192.168.11.0/24(rw,sync,no_root_squash,...)
```
---
## Шаг 2: PersistentVolumes в K3s
K3s нужно сказать где лежат NFS шары. Создаём манифест с PersistentVolume ресурсами.
**Файл:** `02-pv.yaml`
```yaml
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: blog-public-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
nfs:
server: 192.168.11.30 # IP вашего NAS
path: /export/blog-public
mountOptions:
- nfsvers=3
- hard
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: blog-public-dev-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
nfs:
server: 192.168.11.30
path: /export/blog-public-dev
mountOptions:
- nfsvers=3
- hard
```
**Почему NFSv3, а не NFSv4?** Потому что NFSv4.2 в K3s не работал - поды виснут в `ContainerCreating` с ошибкой `mount.nfs: No such file or directory`. NFSv3 работает стабильно. Не надо усложнять то что работает.
```bash
# Применяем манифест
kubectl apply -f 02-pv.yaml
# Проверяем что PV создались и привязались
kubectl get pv | grep blog
# blog-public-pv 5Gi RWX Bound blog/blog-public-pvc
```
---
## Шаг 3: Hugo Builder
Нужен контейнер который слушает webhook от Gitea, клонирует репозиторий и собирает Hugo.
### Зачем нужен Hugo Builder?
**Проблема:** Hugo генерирует статику командой `hugo`. Где её запускать? На локальной машине? Тогда нужно вручную заливать файлы на сервер после каждого изменения. Неудобно и ломает автоматизацию.
**Решение:** Контейнер который живёт в K3s, слушает webhook от Gitea и автоматически пересобирает сайт при каждом `git push`.
### Dockerfile
```dockerfile
FROM alpine:3.19
# Устанавливаем всё что нужно Hugo и Git
RUN apk add --no-cache \
git nodejs npm bash curl wget \
libc6-compat libstdc++ ca-certificates
# Скачиваем Hugo Extended v0.155.3
WORKDIR /tmp
RUN wget https://github.com/gohugoio/hugo/releases/download/v0.155.3/hugo_extended_0.155.3_linux-amd64.tar.gz && \
tar -xzf hugo_extended_0.155.3_linux-amd64.tar.gz && \
cp hugo /usr/bin/hugo && \
chmod +x /usr/bin/hugo && \
rm -rf /tmp/*
WORKDIR /workspace
# Копируем скрипты
COPY webhook-listener.sh /usr/local/bin/
COPY build.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/*.sh
EXPOSE 8080
CMD ["/usr/local/bin/webhook-listener.sh"]
```
### build.sh - скрипт сборки Hugo
**Зачем:** Отдельный скрипт сборки нужен чтобы его можно было запускать не только из webhook listener, но и вручную для тестирования. Один скрипт - одна ответственность.
```bash
#!/bin/bash
set -e # Остановиться при первой ошибке
export GIT_TERMINAL_PROMPT=0 # Не запрашивать пароли интерактивно
REPO_URL="https://git.example.com/user/blog.git" # URL вашего Gitea репозитория
BRANCH="${BRANCH:-main}" # Ветка (передаётся через env)
OUTPUT_DIR="/mnt/blog-public" # Куда складывать собранную статику (NFS)
WORK_DIR="/tmp/build" # Временная папка для клонирования
# Чистим рабочую директорию от прошлой сборки
rm -rf ${WORK_DIR}
mkdir -p ${WORK_DIR}
# Клонируем репозиторий (только нужную ветку, без истории)
cd ${WORK_DIR}
git clone --branch ${BRANCH} --depth 1 ${REPO_URL} site 2>&1
cd site
# Подтягиваем тему Blowfish как Git submodule
git submodule update --init --recursive --depth 1 2>&1
# Собираем сайт (минифицируем CSS/JS/HTML)
hugo --minify --destination ${OUTPUT_DIR} 2>&1
# Проверяем что сборка прошла успешно
if [ -f "${OUTPUT_DIR}/index.html" ]; then
echo "Build successful!"
else
echo "Build failed - index.html not found"
exit 1
fi
# Убираем за собой
rm -rf ${WORK_DIR}
```
### webhook-listener.sh - слушатель webhook
**Зачем:** Gitea отправляет HTTP POST запрос при каждом `git push`. Нужен простой HTTP сервер который принимает этот запрос и запускает сборку. netcat - самый простой способ поднять HTTP listener без зависимостей.
```bash
#!/bin/bash
set -e
echo "Starting webhook listener on port 8080..."
while true; do
# Принимаем HTTP запрос через netcat и сразу отвечаем 200 OK
echo -e "HTTP/1.1 200 OK\r\n\r\nWebhook received" | nc -l -p 8080
# Запускаем сборку синхронно (чтобы видеть логи в kubectl logs)
echo "$(date): Webhook triggered, starting build..."
/usr/local/bin/build.sh
echo "$(date): Build completed, waiting for next webhook..."
done
```
### Сборка и деплой образа
```bash
# Собираем Docker образ
docker build -t hugo-builder:latest .
# Сохраняем в tar файл
docker save hugo-builder:latest -o /tmp/hugo-builder.tar
# Копируем на все K3s worker ноды
for ip in 210 211; do
scp /tmp/hugo-builder.tar k3s@192.168.11.$ip:/tmp/
# Импортируем образ в containerd K3s
ssh k3s@192.168.11.$ip "sudo k3s ctr images import /tmp/hugo-builder.tar && rm /tmp/hugo-builder.tar"
done
```
### Deployment и Service
```yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hugo-builder-prod
namespace: blog
spec:
replicas: 1
selector:
matchLabels:
app: hugo-builder-prod
template:
metadata:
labels:
app: hugo-builder-prod
spec:
containers:
- name: hugo-builder
image: hugo-builder:latest
imagePullPolicy: Never # Образ локальный, не тянуть из registry
env:
- name: BRANCH
value: "main" # Для prod используем main ветку
volumeMounts:
- name: public
mountPath: /mnt/blog-public # NFS хранилище
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: public
persistentVolumeClaim:
claimName: blog-public-pvc
---
apiVersion: v1
kind: Service
metadata:
name: hugo-builder-prod
namespace: blog
spec:
selector:
app: hugo-builder-prod
ports:
- port: 8080
targetPort: 8080
name: webhook
```
```bash
# Применяем манифест
kubectl apply -f 01-hugo-builder-prod.yaml
# Проверяем что под запустился
kubectl get pods -n blog | grep hugo-builder
# Смотрим логи - должна быть строка "Starting webhook listener"
kubectl logs -n blog deployment/hugo-builder-prod
```
---
## Шаг 4: Nginx с Prometheus exporter
Nginx раздаёт статику с того же NFS где Hugo её собрал. Две реплики для минимальной доступности при обновлениях.
Бонус: sidecar контейнер с nginx-prometheus-exporter для мониторинга через Grafana.
```yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: blog
spec:
replicas: 2 # Две реплики для доступности
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
# Основной контейнер - Nginx
- name: nginx
image: nginx:1.25-alpine
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true # Nginx только читает, не пишет
- name: config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
resources:
requests:
cpu: 50m
memory: 64Mi
# Sidecar - экспортер метрик для Prometheus
- name: nginx-exporter
image: nginx/nginx-prometheus-exporter:1.1.0
args:
- -nginx.scrape-uri=http://localhost/nginx_status
ports:
- containerPort: 9113
name: metrics
resources:
requests:
cpu: 10m
memory: 16Mi
volumes:
- name: html
persistentVolumeClaim:
claimName: blog-public-pvc # NFS хранилище
- name: config
configMap:
name: nginx-config
---
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: blog
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
name: http
- port: 9113
targetPort: 9113
name: metrics # Для Prometheus
```
---
## Шаг 5: SSL сертификаты
cert-manager автоматически получает сертификаты от Let's Encrypt через HTTP-01 challenge.
**Важно:** Сначала настрой A-запись у DNS провайдера:
```
your-blog.ru A 77.37.XXX.XXX (ваш внешний IP)
www.your-blog.ru A 77.37.XXX.XXX
```
Без этого Let's Encrypt не сможет проверить что домен принадлежит вам.
```yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: blog-tls
namespace: blog
spec:
secretName: blog-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- your-blog.ru
- www.your-blog.ru
```
```bash
# Применяем манифест
kubectl apply -f 04-certificate.yaml
# Ждём 30-60 секунд пока cert-manager получит сертификат
kubectl get certificate -n blog
# Должно быть READY=True
# NAME READY SECRET AGE
# blog-tls True blog-tls 45s
```
---
## Шаг 6: IngressRoute через Traefik
Traefik маршрутизирует трафик на Nginx и делает SSL терминацию.
```yaml
---
# HTTP → HTTPS редирект (опционально)
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: blog-http
namespace: blog
spec:
entryPoints:
- web # Порт 80
routes:
- match: Host(`your-blog.ru`) || Host(`www.your-blog.ru`)
kind: Rule
services:
- name: nginx
port: 80
---
# HTTPS с SSL
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: blog-https
namespace: blog
spec:
entryPoints:
- websecure # Порт 443
routes:
- match: Host(`your-blog.ru`) || Host(`www.your-blog.ru`)
kind: Rule
services:
- name: nginx
port: 80
tls:
secretName: blog-tls # Сертификат от cert-manager
```
```bash
# Применяем манифест
kubectl apply -f 05-ingressroute.yaml
# Проверяем что сайт доступен
curl -I https://your-blog.ru
# HTTP/2 200
```
---
## Шаг 7: Webhook в Gitea
Последний шаг - связать Gitea с Hugo Builder.
Gitea → ваш репозиторий → Settings → Webhooks → Add Webhook → Gitea
- **URL:** `http://hugo-builder-prod.blog.svc.cluster.local:8080`
- **HTTP Method:** POST
- **Content Type:** application/json
- **Trigger On:** Push events
- **Branch filter:** `main`
Нажимаем "Test Delivery" - должен вернуть `200 OK`.
Проверяем логи Hugo Builder:
```bash
# Следим за логами в реальном времени
kubectl logs -n blog deployment/hugo-builder-prod -f
# Должно появиться:
# Webhook triggered, starting build...
# Cloning repository...
# Initializing submodules...
# Building Hugo site...
# Build successful!
```
---
## Проверка работы
```bash
# Меняем статью
cd ~/hugo-projects/blog
git checkout main
echo "## Тестовая правка" >> content/posts/hello-world/index.md
# Коммитим и пушим
git add .
git commit -m "test: проверка автосборки"
git push origin main
# Следим за логами Hugo Builder
kubectl logs -n blog deployment/hugo-builder-prod -f
# Через 5-7 секунд сборка завершится
# Проверяем что изменение попало на сайт
curl -s https://your-blog.ru/posts/hello-world/ | grep "Тестовая правка"
```
Если видите "Тестовая правка" - всё работает. Каждый `git push` автоматически обновляет сайт.
---
## Что дальше
Production окружение развёрнуто. Но пока только для ветки `main`.
В следующей части добавим development окружение с отдельным Hugo Builder, Nginx и защитой через Basic Auth. Два независимых пайплайна в одном namespace.
---
**Стек этой части:**
- K3s 1.30
- NFS на OpenMediaVault
- Hugo Builder (Alpine + Hugo v0.155.3)
- Nginx 1.25 + Prometheus exporter
- cert-manager + Let's Encrypt
- Traefik IngressRoute

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -0,0 +1,504 @@
---
title: "Блог на Hugo в K3s: часть 3 - development окружение"
date: 2026-01-15
draft: false
description: "Второй пайплайн для ветки dev с отдельным Hugo Builder, Nginx и защитой через Basic Auth. Тестируем изменения перед публикацией в production."
tags: ["hugo", "k3s", "kubernetes", "traefik", "basic-auth"]
categories: ["infrastructure"]
series: ["Блог на Hugo в K3s"]
series_order: 3
---
В части 2 мы развернули production окружение для ветки `main`. Каждый пуш в `main` автоматически обновляет публичный сайт.
Проблема: нельзя проверить как выглядит статья до публикации. Локальный `hugo server` показывает одно, а production может выглядеть по-другому из-за версий Hugo, конфигов, CSS.
Нужен второй пайплайн - тестовый контур где можно проверить изменения перед мержем в `main`.
---
## Архитектура dev окружения
```
Git Push (dev branch)
Gitea
↓ webhook
Hugo Builder Dev
├→ Clone dev branch
├→ hugo --minify
└→ Output → NFS (dev)
/export/blog-public-dev/
Nginx Dev (1 реплика)
Traefik Ingress
├→ Basic Auth Middleware
└→ dev.blog.ru (SSL)
```
Отличия от production:
- Отдельный Hugo Builder (переменная `BRANCH=dev`)
- Отдельный NFS volume (`blog-public-dev`)
- Отдельный Nginx (одна реплика вместо двух)
- **Basic Auth** - доступ только по логину и паролю
- Отдельный домен (`dev.blog.ru`)
Всё это живёт в том же namespace что и production. Два независимых пайплайна, нулевое пересечение.
---
## Шаг 1: Hugo Builder для dev
Используем тот же Docker образ что и для production. Разница - в переменной окружения `BRANCH`.
**Файл:** `01-hugo-builder-dev.yaml`
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hugo-builder-dev
namespace: blog
spec:
replicas: 1
selector:
matchLabels:
app: hugo-builder-dev
template:
metadata:
labels:
app: hugo-builder-dev
spec:
containers:
- name: hugo-builder
image: hugo-builder:latest
imagePullPolicy: Never
env:
- name: BRANCH
value: "dev" # Главное отличие - используем dev ветку
volumeMounts:
- name: public
mountPath: /mnt/blog-public
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: public
persistentVolumeClaim:
claimName: blog-public-dev-pvc # Отдельный PVC
---
apiVersion: v1
kind: Service
metadata:
name: hugo-builder-dev
namespace: blog
spec:
selector:
app: hugo-builder-dev
ports:
- port: 8080
targetPort: 8080
name: webhook
```
```bash
# Применяем манифест
kubectl apply -f 01-hugo-builder-dev.yaml
# Проверяем что под запустился
kubectl get pods -n blog | grep hugo-builder-dev
# Смотрим логи
kubectl logs -n blog deployment/hugo-builder-dev
# Starting webhook listener on port 8080...
```
---
## Шаг 2: Nginx для dev
Одна реплика вместо двух - для тестового окружения высокая доступность не критична.
**Файл:** `03-nginx-dev-deployment.yaml`
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-dev
namespace: blog
spec:
replicas: 1 # Тестовому окружению достаточно одной реплики
selector:
matchLabels:
app: nginx-dev
template:
metadata:
labels:
app: nginx-dev
spec:
containers:
- name: nginx
image: nginx:1.25-alpine
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
- name: config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
resources:
requests:
cpu: 50m
memory: 64Mi
volumes:
- name: html
persistentVolumeClaim:
claimName: blog-public-dev-pvc # Отдельный PVC
- name: config
configMap:
name: nginx-dev-config
---
apiVersion: v1
kind: Service
metadata:
name: nginx-dev
namespace: blog
spec:
selector:
app: nginx-dev
ports:
- port: 80
targetPort: 80
name: http
```
```bash
# Применяем манифест
kubectl apply -f 03-nginx-dev-deployment.yaml
# Проверяем
kubectl get pods -n blog | grep nginx-dev
```
---
## Шаг 3: Basic Auth через Traefik
Dev окружение должно быть закрыто от посторонних. Traefik поддерживает Basic Auth через Middleware.
### Создаём пароль
```bash
# Генерируем htpasswd (логин: dev, пароль: ваш пароль)
htpasswd -nb dev your-password
# dev:$apr1$...хеш...
# Кодируем в base64 для Kubernetes Secret
echo -n "dev:$apr1$...хеш..." | base64
# ZGV2OiRhcHIxJC4uLg==
```
### Secret с паролем
**Файл:** `07-basic-auth-secret.yaml`
```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: dev-basic-auth
namespace: blog
type: Opaque
data:
users: ZGV2OiRhcHIxJC4uLg== # ваш base64 хеш
```
```bash
# Применяем секрет
kubectl apply -f 07-basic-auth-secret.yaml
# Проверяем что секрет создался
kubectl get secret -n blog | grep dev-basic-auth
```
### Middleware для Basic Auth
**Файл:** `08-basic-auth-middleware.yaml`
```yaml
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: dev-basic-auth
namespace: blog
spec:
basicAuth:
secret: dev-basic-auth
removeHeader: true # Убираем заголовок Authorization после проверки
```
```bash
# Применяем middleware
kubectl apply -f 08-basic-auth-middleware.yaml
# Проверяем
kubectl get middleware -n blog
# NAME AGE
# dev-basic-auth 5s
```
---
## Шаг 4: IngressRoute с Basic Auth
Связываем всё вместе: домен → middleware → nginx-dev.
**Файл:** `06-ingressroute-dev.yaml`
```yaml
---
# HTTP (без SSL)
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: blog-dev-http
namespace: blog
spec:
entryPoints:
- web
routes:
- match: Host(`dev.blog.ru`)
kind: Rule
services:
- name: nginx-dev
port: 80
---
# HTTPS с Basic Auth
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: blog-dev-https
namespace: blog
spec:
entryPoints:
- websecure
routes:
- match: Host(`dev.blog.ru`)
kind: Rule
middlewares:
- name: dev-basic-auth # Добавляем Basic Auth
services:
- name: nginx-dev
port: 80
tls:
secretName: blog-dev-tls
```
```bash
# Применяем IngressRoute
kubectl apply -f 06-ingressroute-dev.yaml
# Проверяем
kubectl get ingressroute -n blog | grep dev
```
---
## Шаг 5: SSL сертификат для dev
cert-manager выпустит отдельный сертификат для `dev.blog.ru`.
**Не забудьте:** Добавить A-запись в DNS:
```
dev.blog.ru A 77.37.XXX.XXX
```
**Файл:** `04-certificate-dev.yaml`
```yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: blog-dev-tls
namespace: blog
spec:
secretName: blog-dev-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- dev.blog.ru
```
```bash
# Применяем манифест
kubectl apply -f 04-certificate-dev.yaml
# Ждём получения сертификата (30-60 секунд)
kubectl get certificate -n blog
# Должно быть READY=True
# NAME READY SECRET AGE
# blog-dev-tls True blog-dev-tls 45s
```
---
## Шаг 6: Webhook в Gitea для dev
Создаём второй webhook который триггерится на пуши в ветку `dev`.
Gitea → ваш репозиторий → Settings → Webhooks → Add Webhook → Gitea
- **URL:** `http://hugo-builder-dev.blog.svc.cluster.local:8080`
- **HTTP Method:** POST
- **Content Type:** application/json
- **Trigger On:** Push events
- **Branch filter:** `dev` ← Главное отличие от prod
Нажимаем "Test Delivery" → должен вернуть `200 OK`.
Проверяем логи:
```bash
# Смотрим логи Hugo Builder Dev
kubectl logs -n blog deployment/hugo-builder-dev -f
# Должно появиться:
# Webhook triggered, starting build...
# Cloning repository (branch: dev)...
# Build successful!
```
---
## Проверка работы
```bash
# Создаём тестовую статью в dev
cd ~/projects/blog
git checkout dev
# Создаём статью
hugo new content posts/test-dev/index.md
echo "Тестовая статья в dev окружении" >> content/posts/test-dev/index.md
# Коммитим и пушим
git add .
git commit -m "test: проверка dev окружения"
git push origin dev
# Следим за логами Hugo Builder Dev
kubectl logs -n blog deployment/hugo-builder-dev -f
# Через 5-7 секунд сборка завершится
```
Открываем `https://dev.blog.ru` в браузере:
1. Браузер запросит логин и пароль (Basic Auth)
2. Вводим: логин `dev`, пароль который задали
3. Видим тестовую статью
**Критично:** Статья появилась на `dev.blog.ru`, но её **нет** на `blog.ru` - окружения изолированы.
---
## Workflow: dev → prod
Типичный процесс работы:
**1. Пишу статью в dev:**
```bash
cd ~/projects/blog
git checkout dev
# Создаю статью
hugo new content posts/kubernetes-intro/index.md
# Пишу контент, коммичу
git add .
git commit -m "feat: статья про Kubernetes"
git push origin dev
# → автосборка → dev.blog.ru
```
**2. Проверяю на dev.blog.ru:**
Открываю `https://dev.blog.ru` (вводя логин/пароль), читаю статью, проверяю форматирование, ссылки, изображения.
Нахожу опечатку - исправляю локально, пушу в `dev` снова. Повторяю пока не доволен результатом.
**3. Публикую в production:**
```bash
# Всё отлично на dev - мержу в main
git checkout main
git merge dev
git push origin main
# → автосборка → blog.ru
```
Статья появляется на публичном сайте.
---
## Итоговая архитектура
Два полностью независимых пайплайна в одном namespace:
```
Production:
main branch → hugo-builder-prod → blog-public-pvc → nginx (x2) → blog.ru
Development:
dev branch → hugo-builder-dev → blog-public-dev-pvc → nginx-dev (x1) → dev.blog.ru + Basic Auth
```
Общее:
- Namespace: `blog`
- Gitea репозиторий
- Docker образ Hugo Builder
- Traefik IngressRoute
- cert-manager
Отдельное:
- Deployments
- Services
- PersistentVolumes
- SSL сертификаты
- Домены
---
## Что дальше
Два окружения работают. Можно писать статьи, проверять на dev, публиковать в production.
Но есть проблема: я долго работал в двух папках - `~/projects/blog` (main) и `~/projects/blog-dev` (dev). Это создавало конфликты при merge, рассинхронизацию веток и головную боль.
В следующей части расскажу как я от этого избавился и почему одна папка с переключением веток лучше чем две отдельные папки.
---
**Стек этой части:**
- Hugo Builder Dev (та же версия Hugo)
- Nginx Dev (одна реплика)
- Traefik Basic Auth Middleware
- cert-manager (отдельный сертификат)
- NFS (отдельный volume)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -0,0 +1,417 @@
---
title: "Блог на Hugo в K3s: часть 4 - выбор Git workflow"
date: 2026-02-16
draft: false
description: "Две папки или одна с переключением веток? Разбираем варианты организации работы с dev и production окружениями на практических примерах."
tags: ["git", "workflow", "devops", "hugo"]
categories: ["infrastructure"]
series: ["Блог на Hugo в K3s"]
series_order: 4
---
В части 3 мы развернули два окружения - production и development. Один репозиторий, две ветки (`main` и `dev`), два пайплайна.
Теперь встаёт вопрос: **как организовать работу локально?**
---
## Проблема
У нас есть:
- Репозиторий в Gitea
- Две ветки: `main` (production) и `dev` (development)
- Необходимость постоянно переключаться между ними
Как это делать на локальной машине? Два варианта.
---
## Вариант А: Две отдельные папки
Клонируем репозиторий дважды - в разные папки:
```
~/projects/
├── blog/ ← ветка main (production)
└── blog-dev/ ← ветка dev (development)
```
**Логика:** Хочу работать с dev - иду в `blog-dev`. Хочу что-то проверить в production - иду в `blog`. Без переключения веток.
### Кажущиеся преимущества
**Параллельная работа.** Можно держать открытыми два терминала - в одном `hugo server` для dev, в другом смотреть production код.
**Изоляция.** Каждая папка - своя песочница. Изменения в одной не влияют на другую.
**Простота навигации.** `cd blog-dev` вместо `git checkout dev`. Меньше команд.
**Привычный паттерн.** Многие админы и разработчики держат несколько клонов для разных задач.
### Реальные проблемы
#### Проблема 1: Рассинхронизация локальных ветокin
Работаю в `blog-dev` - пишу статьи, коммичу, пушу в `origin/dev`. Всё хорошо.
Но локальная ветка `dev` в папке `blog` при этом **не обновляется**. Она отстаёт от `origin/dev`.
Приходишь делать merge:
```bash
cd blog
git checkout main
git merge dev
# Already up to date. ← НО есть НЮАНС!
```
Git говорит "всё актуально", имея в виду **локальную** ветку `dev`, которая отстала на три коммита. Статьи не попадают в production.
Приходится помнить делать `git pull origin dev` перед каждым merge. Забыл - публикуешь устаревшую версию.
#### Проблема 2: Конфликты при merge
Редактируешь `config/params.toml` в обеих папках независимо:
- В `blog-dev` добавил Firebase конфиг
- В `blog` изменил название сайта
При merge Git честно сообщает о конфликте:
```
CONFLICT (content): Merge conflict in config/params.toml
```
И это повторяется **каждый раз** когда трогаешь конфигурацию. Потому что две папки - это две независимые истории изменений одного файла.
#### Проблема 3: Работа не в той папке
Несколько раз ловил себя на том что редактирую статьи прямо в `blog` - папке production. Это нарушает весь смысл раздельных окружений.
#### Проблема 4: Умственная нагрузка
Постоянный вопрос "в какой папке я сейчас?" Для простого блога это лишняя когнитивная нагрузка.
---
## Вариант Б: Одна папка с переключением веток
Один клон репозитория, работа через `git checkout`:
```
~/projects/
└── blog/ ← одна папка, две ветки: main и dev
```
### Как это работает
**Пишу статью:**
```bash
cd ~/projects/blog
# Переключаюсь на dev
git checkout dev
# Проверяю что dev актуален
git pull origin dev
# Запускаю локальный сервер
hugo server -D --bind 0.0.0.0
# Создаю статью
hugo new content posts/название/index.md
# Коммичу и пушу
git add .
git commit -m "feat: новая статья"
git push origin dev
```
**Публикую:**
```bash
# Убеждаюсь что dev актуален
git checkout dev
git pull origin dev
# Переключаюсь на main и мержу
git checkout main
git pull origin main
git merge dev
git push origin main
```
### Реальные преимущества
**Никакой рассинхронизации.** Все ветки в одном репозитории. `git pull` обновляет всё что нужно.
**Нет конфликтов из-за независимых изменений.** Когда работаешь в одной папке, `params.toml` существует в одном экземпляре. Все изменения делаются в `dev`, в `main` попадают только через merge.
Конфликт возможен только если кто-то редактирует `main` напрямую - а это нарушение workflow.
**Невозможно ошибиться с веткой.** `git branch` показывает где ты сейчас. Случайно отредактировать файлы в `main` - сложнее.
**Меньше места на диске.** Один клон вместо двух. Один `.git` вместо двух.
---
## Сравнение на практических примерах
### Пример 1: Обновление темы Blowfish
**Две папки:**
```bash
cd blog-dev
git submodule update --remote themes/blowfish
git add themes/blowfish
git commit -m "update: Blowfish theme"
git push origin dev
# Проверяешь на dev.blog.ru
# Если всё ок - мержишь
cd ../blog
git checkout dev
git pull origin dev # ← ЛЕГКО ЗАБЫТЬ
git checkout main
git merge dev
git push origin main
```
**Одна папка:**
```bash
cd blog
git checkout dev
git pull origin dev
git submodule update --remote themes/blowfish
git add themes/blowfish
git commit -m "update: Blowfish theme"
git push origin dev
# Проверяешь на dev.blog.ru
# Если всё ок - мержишь
git checkout main
git pull origin main
git merge dev
git push origin main
```
Меньше команд, меньше переходов между папками, меньше шансов забыть `git pull`.
### Пример 2: Правка опечатки в production
Нашёл опечатку на `blog.ru`. Нужно исправить быстро.
**Две папки:**
Опасность: хочется исправить прямо в `blog` (ветка `main`). Это нарушает workflow - все изменения должны идти через `dev`.
Правильно:
```bash
cd blog-dev
git checkout dev
# Исправляешь
git commit -m "fix: опечатка"
git push origin dev
cd ../blog
git checkout main
git pull origin dev # ← опять легко забыть
git merge dev
git push origin main
```
**Одна папка:**
```bash
cd blog
git checkout dev
git pull origin dev
# Исправляешь
git commit -m "fix: опечатка"
git push origin dev
git checkout main
git merge dev
git push origin main
```
Проще, меньше команд, понятнее.
### Пример 3: Долгая работа над статьёй
Пишешь большую статью несколько дней. Между сеансами работы кто-то (или ты сам) запушил другие изменения в `dev`.
**Две папки:**
```bash
cd blog-dev
# День 1: пишешь
git add .
git commit -m "wip: статья"
# День 2: продолжаешь
git pull origin dev # Подтягиваешь чужие изменения
# Пишешь дальше
git add .
git commit -m "feat: закончил статью"
git push origin dev
```
Всё так же как и с одной папкой. Разницы нет.
**Одна папка:**
```bash
cd blog
git checkout dev
# День 1: пишешь
git add .
git commit -m "wip: статья"
# День 2: продолжаешь
git pull origin dev # Подтягиваешь чужие изменения
# Пишешь дальше
git add .
git commit -m "feat: закончил статью"
git push origin dev
```
Идентично. Этот пример работает одинаково в обоих вариантах.
---
## Что выбрать?
**Если ты только начинаешь - сразу делай одну папку.**
Две папки кажутся удобными, но создают проблемы которые регулярно прерывают работу:
- Рассинхронизация веток
- Конфликты при merge
- Когнитивная нагрузка
Одна папка с переключением веток - стандартный Git workflow, проверенный миллионами разработчиков. Требует чуть больше дисциплины (`git checkout dev` вместо `cd blog-dev`), но избавляет от всех проблем выше.
**Золотое правило:** Никогда не редактировать файлы находясь на ветке `main`. Все изменения - через `dev`. Всегда.
---
## Миграция: если начал с двух папок
Если уже работаешь в двух папках - переход простой.
### Шаг 1: Убеждаемся что всё запушено
```bash
# Проверяем обе папки
cd ~/projects/blog-dev
git status
git push origin dev
cd ~/projects/blog
git status
git push origin main
```
### Шаг 2: Синхронизируем ветку dev в основной папке
```bash
cd ~/projects/blog
# Обновляем локальную ветку dev из remote
git checkout dev
git pull origin dev
# Проверяем что всё актуально
git log --oneline -5
# Возвращаемся на main
git checkout main
```
### Шаг 3: Удаляем вторую папку
```bash
# Убеждаемся что в blog-dev нет несохранённых изменений
cd ~/projects/blog-dev
git status
# Должно быть: nothing to commit, working tree clean
# Удаляем папку
cd ~/projects
rm -rf blog-dev
```
### Шаг 4: Проверяем что всё работает
```bash
cd ~/projects/blog
# Переключаемся на dev и запускаем сервер
git checkout dev
hugo server -D --bind 0.0.0.0
# Открываем http://localhost:1313/
# Видим dev версию сайта
```
---
## Новый workflow: шпаргалка
### Создаю статью
```bash
cd ~/projects/blog
git checkout dev
hugo new content posts/название/index.md
# Пишу, сохраняю, проверяю в hugo server
git add .
git commit -m "feat: название статьи"
git push origin dev # → dev.blog.ru
```
### Публикую статью
```bash
git checkout dev
git pull origin dev # Убеждаюсь что dev актуален
git checkout main
git pull origin main # Убеждаюсь что main актуален
git merge dev
git push origin main # → blog.ru
```
### Меняю конфигурацию
```bash
git checkout dev # ВСЕ изменения только через dev!
nano config/params.toml
git add .
git commit -m "feat: изменил конфиг"
git push origin dev # Проверяю на dev.blog.ru
# Если всё ок
git checkout main
git merge dev
git push origin main
```
---
## Что дальше
Workflow выбран, окружения работают. Можно писать статьи.
Но есть ещё одна тема которую стоит разобрать - что делать когда что-то сломалось. Как диагностировать проблемы когда сайт вдруг начал отдавать 503, или SSL перестал работать, или webhook не срабатывает.
В следующей части покажу процесс диагностики на реальном примере - как я чинил `blog.ru` когда он внезапно стал недоступен из интернета.
---
**Рекомендация этой части:**
- Одна папка `~/projects/blog`
- Переключение веток через `git checkout`
- Все изменения через `dev` → merge в `main`
- Никогда не редактировать находясь на `main`

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -0,0 +1,634 @@
---
title: "Блог на Hugo в K3s: часть 5 - что делать когда всё внезапно сломалось"
date: 2026-02-17
draft: false
description: "Сайт работал вчера, а сегодня 503. Алгоритм диагностики Kubernetes проблем за 5 минут - от DNS до пода, без паники и танцев с бубном."
tags: ["kubernetes", "k3s", "traefik", "debugging", "nginx", "troubleshooting"]
categories: ["infrastructure"]
series: ["Блог на Hugo в K3s"]
series_order: 5
---
В части 4 мы разобрались с Git workflow. Всё работает: пушишь в `dev` - видишь на тестовом окружении, мержишь в `main` - публикуется на production.
А потом в один прекрасный день открываешь свой сайт и видишь `503 Service Temporarily Unavailable`.
Вчера же все работало! Ты ничего не менял. Что произошло?
Добро пожаловать в мир эксплуатации Kubernetes, где проблемы тоже возникают и требуют системного подхода без паники.
Эта статья - алгоритм диагностики от DNS до пода. Проходишь по шагам сверху вниз, находишь проблему за 5 минут. Не гадаешь, не тыкаешь наугад - работаешь по системе.
---
## Анатомия HTTP запроса в K3s
Прежде чем искать проблему, нужно понять путь запроса от браузера до nginx:
```
Браузер
↓ DNS запрос
DNS сервер (провайдер или Cloudflare)
↓ Возвращает IP адрес
Роутер/Файрвол (OPNsense, MikroTik)
↓ Port Forward 443 → K3s node
MetalLB LoadBalancer
↓ External IP
Traefik Ingress Controller
↓ IngressRoute matching
Kubernetes Service
↓ Endpoint selection
Pod (Nginx контейнер)
↓ Volume mount
NFS хранилище
```
Проблема может быть на любом из этих уровней. Секрет эффективной диагностики - проверять снаружи внутрь, последовательно исключая рабочие компоненты.
Когда тыкаешь наугад, проверяя сначала поды, потом DNS, потом снова поды - тратишь время. Когда идёшь по алгоритму - находишь проблему за минуты.
---
## Шаг 1: DNS - доходит ли домен до твоего IP
Первым делом проверяем что домен резолвится в правильный IP. Без этого дальше проверять бессмысленно - браузер просто не знает куда направлять запрос.
### Проверка
```bash
# Проверяем DNS резолв (используй свой домен)
dig blog.example.com +short
# Альтернатива если dig не установлен
nslookup blog.example.com
```
### Ожидаемый результат
```
77.37.XXX.XXX
```
Должен вернуться твой **публичный IP адрес** (тот который прописан в A-записи у DNS провайдера).
### Что может пойти не так
| Симптом | Причина | Как проверить | Решение |
|---------|---------|---------------|---------|
| Возвращается старый IP | DNS кеш не обновился | `dig blog.example.com @8.8.8.8` | Подожди TTL (обычно 300-3600 сек) |
| `NXDOMAIN` ошибка | Домен не делегирован | Проверь NS записи у регистратора | Настрой DNS правильно |
| Возвращается `127.0.0.1` | Локальный override | `cat /etc/hosts \| grep blog` | Удали строку из /etc/hosts |
| Возвращается несколько IP | Round-robin DNS | Проверь все ли IP твои | Удали лишние A-записи |
Если DNS правильный - идём дальше.
---
## Шаг 2: Внешний доступ - доходит ли запрос до сервера
Теперь проверяем что запрос физически доходит до сервера. DNS может быть правильным, но файрвол может блокировать трафик.
### Проверка
```bash
# Пробуем подключиться извне (важно - НЕ из локальной сети!)
curl -v https://blog.example.com 2>&1 | head -30
```
**Важно:** Запускай эту команду с **внешнего** сервера или используй мобильный интернет. Тест из локальной сети ничего не докажет - можешь обходить файрвол.
### Ситуация А: Connection refused или timeout
```
curl: (7) Failed to connect to blog.example.com port 443: Connection refused
```
Запрос вообще не дошёл до сервера. Проблема на сетевом уровне.
**Возможные причины:**
**1. Порт 443 закрыт на файрволе/роутере**
Проверь Port Forward правила на OPNsense/MikroTik. Должно быть:
```
WAN:443 → 192.168.X.X:443 (IP любой K3s ноды)
```
**2. MetalLB не назначил External IP для Traefik**
```bash
# Проверяем MetalLB
kubectl get svc -n traefik traefik
# Ожидаемый результат
NAME TYPE EXTERNAL-IP PORT(S)
traefik LoadBalancer 192.168.X.X 80:30080/TCP,443:30443/TCP
```
Если `EXTERNAL-IP` показывает `<pending>` - MetalLB не работает или пул IP адресов не настроен.
**3. Traefik под не запущен**
```bash
# Проверяем что Traefik работает
kubectl get pods -n traefik
# Должны быть все Running
NAME READY STATUS
traefik-xxxxxxxxxx-xxxxx 1/1 Running
```
### Ситуация Б: TLS handshake прошёл, но 503
```
< HTTP/2 503
< content-type: text/plain; charset=utf-8
< content-length: 20
no available server
```
Отлично - наша ситуация! Traefik работает, SSL сертификат отдаёт, но дальше запрос упирается в стену.
Сообщение `no available server` означает что Traefik **нашёл роутер**, но **не нашёл живой бэкенд** за ним.
Проблема внутри кластера. Идём глубже.
### Ситуация В: SSL certificate problem
```
curl: (60) SSL certificate problem: unable to get local issuer certificate
```
Сертификат невалидный или не выпущен.
```bash
# Проверяем Certificate объект (используй свой namespace)
kubectl get certificate -n blog
# Должно быть READY=True
NAME READY SECRET AGE
blog-tls True blog-tls 2d
```
Если `READY=False` - cert-manager не смог выпустить сертификат.
```bash
# Смотрим что пошло не так
kubectl describe certificate blog-tls -n blog
# Ищем секцию Events внизу вывода - там описание проблемы
```
### Главное: Запрос доходит до Traefik
```bash
# Проверка (с ВНЕШНЕГО сервера!)
curl -I https://blog.example.com
# Ожидаемый результат (любой из двух)
HTTP/2 200 # Всё работает
HTTP/2 503 # Traefik работает, но бэкенд недоступен
# Если connection refused/timeout - проблема в сети (см. выше)
```
---
## Шаг 3: Traefik - правильно ли маршрутизируется трафик
Traefik получил запрос на твой домен. Что он с ним делает? Смотрим логи.
### Проверка логов Traefik
```bash
# Смотрим последние 50 строк логов Traefik
kubectl logs -n traefik deployment/traefik --tail=50
# Фильтруем только свой домен (убираем шум от других сервисов)
kubectl logs -n traefik deployment/traefik --tail=100 | grep blog.example
```
### Что искать в логах
**Нормальный запрос:**
```json
{
"request": "GET / HTTP/2.0",
"status": 200,
"size": 8994,
"router": "blog-blog-https-xxxxx@kubernetescrd",
"service": "blog-nginx-blog@kubernetescrd",
"backend": "http://10.42.2.40:80",
"duration": 12
}
```
Ключевые поля:
- **router:** Traefik нашёл нужный IngressRoute (`blog-blog-https`)
- **backend:** IP пода nginx куда проксируется запрос (`10.42.2.40:80`)
- **status:** HTTP код ответа от nginx (`200` = всё хорошо)
**Проблемный запрос:**
```json
{
"request": "GET / HTTP/2.0",
"status": 503,
"router": "blog-blog-https-xxxxx@kubernetescrd",
"error": "no available server"
}
```
Traefik нашёл роутер, но поле `backend` отсутствует - под недоступен или не существует.
### Проверяем список IngressRoute
```bash
# Смотрим все IngressRoute в кластере
kubectl get ingressroute -A
# Фильтруем только свой домен
kubectl get ingressroute -A | grep blog.example
```
**Важный момент:** Если один и тот же домен прописан в **двух разных IngressRoute** из разных namespace - Traefik будет балансировать между ними.
Например:
```bash
NAMESPACE NAME AGE
blog blog-https 10d # СТАРЫЙ namespace
blog-new blog-https 2d # НОВЫЙ namespace
```
Оба IngressRoute имеют `match: Host('blog.example.com')`. Traefik видит оба, честно балансирует трафик 50/50.
Если один из бэкендов мёртв - половина запросов уходит в пустоту. 503 через раз.
**Решение:** Удалить старый IngressRoute:
```bash
# Удаляем дубль из старого namespace
kubectl delete ingressroute blog-https blog-http -n blog
```
### Проверяем синтаксис match
Traefik очень требователен к синтаксису. Частая ошибка - забыть backticks или скобки.
**Неправильно:**
```yaml
match: Host(blog.example.com) # Нет backticks
match: Host `blog.example.com` # Нет скобок вокруг Host
match: Host("blog.example.com") # Двойные кавычки вместо backticks
```
**Правильно:**
```yaml
match: Host(`blog.example.com`)
```
Проверяем:
```bash
# Смотрим манифест IngressRoute
kubectl get ingressroute blog-https -n blog -o yaml | grep match:
# Должно быть со скобками и backticks
match: Host(`blog.example.com`)
```
### Главное: Traefik нашёл роутер
```bash
# Проверка
kubectl logs -n traefik deployment/traefik --tail=50 | grep blog.example
# Ожидаемый результат - есть строки с "router": "blog-blog-https"
# Если router не найден - проблема в IngressRoute match синтаксисе
```
---
## Шаг 4: Service - видит ли он поды
Traefik нашёл роутер, проксирует трафик на Service. Но Service может не видеть поды если selector неправильный.
### Проверка endpoints
```bash
# Смотрим endpoints для Service (используй своё имя Service)
kubectl get endpoints nginx -n blog
# Ожидаемый результат - НЕ пустой список IP
NAME ENDPOINTS
nginx 10.42.0.44:80,10.42.2.40:80
```
Если видишь `<none>` - Service не нашёл ни одного пода. Две возможные причины.
### Причина 1: Selector не совпадает с labels
```bash
# Смотрим selector у Service
kubectl get svc nginx -n blog -o yaml | grep -A3 "selector:"
# Вывод
selector:
app: nginx
# Смотрим labels у подов
kubectl get pods -n blog --show-labels | grep nginx
# Вывод
nginx-xxxxxxxxxx-xxxxx 1/1 Running app=nginx-old
```
Видишь проблему? Service ищет `app: nginx`, а под помечен `app: nginx-old`. Не совпадает.
**Решение:** Исправить Deployment или Service чтобы labels совпадали.
### Причина 2: Поды не Running
```bash
# Смотрим статус подов
kubectl get pods -n blog
# Видим
NAME READY STATUS
nginx-xxxxxxxxxx-xxxxx 0/1 CreateContainerError
```
Под существует, но не работает. Service правильно не включает его в endpoints. Идём в следующий шаг - разбираемся почему под не запускается.
### Главное: Service видит поды
```bash
# Проверка
kubectl get endpoints nginx -n blog
# Ожидаемый результат - НЕ пустой
nginx 10.42.0.44:80,10.42.2.40:80
# Если <none> - проблема в селекторах или поды не Running
```
---
## Шаг 5: Pod - что происходит внутри контейнера
Самый глубокий уровень. Под не запускается или падает в цикле перезапусков.
### Проверка статуса подов
```bash
# Смотрим все поды в namespace
kubectl get pods -n blog
# Фильтруем только nginx
kubectl get pods -n blog | grep nginx
```
**Возможные статусы проблем:**
### CreateContainerError
Контейнер вообще не может стартануть. Обычно проблема с volumes или образом.
```bash
# Смотрим детали пода (используй своё имя пода)
kubectl describe pod nginx-xxxxxxxxxx-xxxxx -n blog | tail -30
```
Ищем секцию `Events` внизу вывода. Там будет описание проблемы:
**Пример 1: PVC не примонтировался**
```
Events:
Warning FailedMount MountVolume.SetUp failed for volume "blog-public-pvc":
mount failed: mount.nfs: Connection timed out
```
NFS хранилище недоступно. Возможные причины:
- NFS сервер выключен или перезагружается
- Неправильный IP или путь в PersistentVolume
- Файрвол блокирует NFS трафик (порт 2049)
**Пример 2: Образ не скачался**
```
Events:
Warning Failed Failed to pull image "nginx:latest": rpc error: code = Unknown
```
Контейнер не может скачать образ. Обычно это означает что `imagePullPolicy: Never`, а образ не импортирован на ноду.
```bash
# Проверяем что образ есть на ноде (используй IP своей worker ноды)
ssh user@192.168.X.X "sudo k3s crictl images | grep nginx"
```
Если образа нет - импортируй его через `k3s ctr images import`.
**Пример 3: ConfigMap не найден**
```
Events:
Warning FailedMount ConfigMap "nginx-config" not found
```
Deployment ссылается на несуществующий ConfigMap.
```bash
# Проверяем что ConfigMap существует
kubectl get configmap -n blog | grep nginx-config
```
Если нет - создай или исправь имя в Deployment.
### CrashLoopBackOff
Контейнер запускается, но сразу падает. Смотрим логи **предыдущего** запуска:
```bash
# Логи последнего упавшего контейнера
kubectl logs nginx-xxxxxxxxxx-xxxxx -n blog --previous
```
**Пример: Nginx падает из-за неправильного конфига**
```
nginx: [emerg] unexpected "}" in /etc/nginx/nginx.conf:15
nginx: configuration file /etc/nginx/nginx.conf test failed
```
Синтаксическая ошибка в `nginx.conf`. Проверяем ConfigMap:
```bash
# Смотрим содержимое конфига
kubectl get configmap nginx-config -n blog -o yaml
```
Находим ошибку, исправляем, применяем. Под перезапустится автоматически.
### Главное: Под работает
```bash
# Проверка
kubectl get pods -n blog | grep nginx
# Ожидаемый результат - все Running
nginx-xxxxxxxxxx-xxxxx 1/1 Running 0 2d
# Если не Running - смотри troubleshooting выше
```
---
## Шаг 6: Контент - есть ли файлы для отдачи
Под работает, Service видит его, Traefik проксирует трафик. Но сайт отдаёт `404 Not Found` или пустую страницу.
Проблема: Hugo Builder не записал файлы на NFS или записал не туда.
### Проверка
```bash
# Заходим в под nginx (используй своё имя пода)
kubectl exec -it nginx-xxxxxxxxxx-xxxxx -n blog -- sh
# Внутри пода смотрим что примонтировалось
ls -la /usr/share/nginx/html/
# Должен быть index.html и папки posts, tags, etc
```
**Если директория пустая** - Hugo Builder не сработал. Проверяем его логи:
```bash
# Логи Hugo Builder
kubectl logs -n blog deployment/hugo-builder-prod --tail=50
```
Ищем строку `Build successful!` и список созданных файлов. Если её нет:
1. **Webhook не сработал** - проверь настройки webhook в Gitea
2. **Hugo упал с ошибкой** - читай логи выше, смотри на что ругается
3. **Собрал в другую директорию** - проверь переменную `OUTPUT_DIR` в build.sh
### Главное: Контент на месте
```bash
# Проверка (используй своё имя пода)
kubectl exec -it nginx-xxxxxxxxxx-xxxxx -n blog -- ls /usr/share/nginx/html/ | head -5
# Ожидаемый результат
index.html
posts/
tags/
categories/
# Если пусто - Hugo Builder не отработал (см. выше)
```
---
## Быстрый чеклист для любой проблемы
Сохрани эту последовательность - она работает для 95% проблем:
```
[ ] DNS: dig домен → правильный IP?
[ ] Сеть: curl -v https://домен → доходит до Traefik?
[ ] MetalLB: kubectl get svc -n traefik → External IP назначен?
[ ] Traefik: kubectl get pods -n traefik → Running?
[ ] IngressRoute: kubectl get ingressroute -A | grep домен → нет дублей?
[ ] Match синтаксис: Host(`домен`) со скобками и backticks?
[ ] Endpoints: kubectl get endpoints -n namespace → не пустые?
[ ] Selector: labels подов совпадают с selector Service?
[ ] Pods: kubectl get pods -n namespace → все Running?
[ ] PVC: kubectl get pvc -n namespace → все Bound?
[ ] Контент: kubectl exec ls /usr/share/nginx/html → файлы есть?
```
Проходишь по списку сверху вниз. Останавливаешься на первом `[ ]` где что-то не так. Чинишь. Проверяешь снова.
Не прыгай хаотично между уровнями. Алгоритм экономит время.
---
## Реальный пример: 503 через раз
Мой сайт отдавал `503` примерно в 50% запросов. Половина запросов работала, половина нет.
Прошёл по алгоритму:
1. ✅ DNS - правильный IP
2. ✅ Сеть - Traefik отвечает
3. ✅ MetalLB - External IP назначен
4. ✅ Traefik - поды Running
5. ❌ IngressRoute - **два роутера на один домен**
```bash
kubectl get ingressroute -A | grep oakazanin
NAMESPACE NAME
blog blog-https # СТАРЫЙ, бэкенд в CreateContainerError
oakazanin blog-https # НОВЫЙ, работает
```
Traefik видел два роутера, честно балансировал трафик 50/50. Каждый второй запрос улетал в мёртвый `blog/nginx`.
Диагноз поставлен за 3 минуты. Лечение - одна команда:
```bash
# Удаляем дубль из старого namespace
kubectl delete ingressroute blog-https blog-http -n blog
```
Мораль: **всегда чисти за собой**. Старые namespace с нерабочими сервисами - источник неочевидных проблем.
---
## Откат и cleanup
Если в процессе диагностики что-то сломал ещё больше - откатываемся:
```bash
# Восстанавливаем предыдущую версию манифеста
kubectl apply -f nginx-deployment.yaml
# Перезапускаем поды принудительно
kubectl rollout restart deployment/nginx -n blog
# Смотрим что изменения применились
kubectl rollout status deployment/nginx -n blog
```
**Золотое правило:** Перед экспериментами делай бэкапы манифестов:
```bash
# Экспортируем текущее состояние с датой
kubectl get deployment,service,ingressroute -n blog -o yaml > backup-$(date +%Y%m%d).yaml
```
---
## Что дальше
Ты умеешь диагностировать проблемы. Но лучше их вообще не создавать.
В следующей части покажу как правильно мигрировать сервисы между namespace - без даунтайма, дублей IngressRoute и других сюрпризов которые приводят к 503.
Разберём реальный пример: переносим Gitea между namespace с NFS данными, получаем новый SSL за 32 секунды, и удаляем старый namespace навсегда.
---
**Стек этой части:**
- Traefik 2.11 IngressRoute
- Kubernetes 1.30 (K3s)
- kubectl CLI
- curl для внешних проверок
- dig для DNS диагностики

Binary file not shown.

Before

Width:  |  Height:  |  Size: 663 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 741 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -3,13 +3,9 @@ title: "K3s HA для homelab: архитектура без боли"
date: 2025-10-14 date: 2025-10-14
draft: false draft: false
description: "Полноценный Kubernetes в бинарнике на 50MB вместо 1.5GB зависимостей. Разбираем архитектуру K3s HA кластера: почему именно 3 master ноды, зачем embedded etcd и сколько ресурсов закладывать." description: "Полноценный Kubernetes в бинарнике на 50MB вместо 1.5GB зависимостей. Разбираем архитектуру K3s HA кластера: почему именно 3 master ноды, зачем embedded etcd и сколько ресурсов закладывать."
summary: "Kubernetes слишком тяжёлый, Docker Swarm мёртв, а хочется нормальный кластер для экспериментов. K3s решает эту проблему - полноценный Kubernetes в 50MB. Разберём архитектуру HA кластера без боли."
tags: ["kubernetes", "k3s", "homelab", "proxmox", "architecture", "ha", "devops"] tags: ["kubernetes", "k3s", "homelab", "proxmox", "architecture", "ha", "devops"]
series: ["K3s HA кластер для homelab"] series: ["K3s HA кластер для homelab"]
series_order: 1 series_order: 1
# seriesOpened: false
# showTableOfContents: true
showAuthor: true
--- ---
Kubernetes слишком тяжёлый, Docker Swarm мёртв, а хочется нормальный кластер для экспериментов. Знакомо? K3s решает эту проблему - полноценный Kubernetes в бинарнике на 50MB вместо 1.5GB зависимостей. Но без правильного планирования вы получите нестабильную конструкцию, которая падает в самый неподходящий момент. Kubernetes слишком тяжёлый, Docker Swarm мёртв, а хочется нормальный кластер для экспериментов. Знакомо? K3s решает эту проблему - полноценный Kubernetes в бинарнике на 50MB вместо 1.5GB зависимостей. Но без правильного планирования вы получите нестабильную конструкцию, которая падает в самый неподходящий момент.

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -1,14 +1,11 @@
--- ---
title: "Подготовить инфраструктуру для K3s в Proxmox" title: "K3s HA для homelab: Готовим инфраструктуру в Proxmox"
date: 2025-10-21 date: 2025-10-21
draft: false draft: false
description: "Создаём 5 VM в Proxmox с Debian 12, настраиваем сеть, отключаем swap и готовим систему для K3s. Пошаговая инструкция без сюрпризов." description: "Создаём 5 VM в Proxmox с Debian 12, настраиваем сеть, отключаем swap и готовим систему для K3s. Пошаговая инструкция без сюрпризов."
summary: "Архитектура спланирована, ресурсы посчитаны - пора создавать виртуальные машины. Подготовим 5 VM в Proxmox и настроим ОС так, чтобы K3s установился без сюрпризов."
tags: ["kubernetes", "k3s", "homelab", "proxmox", "infrastructure", "debian", "devops"] tags: ["kubernetes", "k3s", "homelab", "proxmox", "infrastructure", "debian", "devops"]
series: ["K3s HA кластер для homelab"] series: ["K3s HA кластер для homelab"]
series_order: 2 series_order: 2
# seriesOpened: true
showTableOfContents: true
--- ---
Архитектура спланирована, ресурсы посчитаны - пора создавать виртуальные машины. В этой статье подготовим 5 VM в Proxmox и настроим ОС так, чтобы K3s установился без сюрпризов. Архитектура спланирована, ресурсы посчитаны - пора создавать виртуальные машины. В этой статье подготовим 5 VM в Proxmox и настроим ОС так, чтобы K3s установился без сюрпризов.

View File

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

View File

Before

Width:  |  Height:  |  Size: 3.6 MiB

After

Width:  |  Height:  |  Size: 3.6 MiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -1,14 +1,11 @@
--- ---
title: "Установить K3s HA кластер" title: "K3s HA для homelab: Ставим K3s HA кластер"
date: 2025-11-02 date: 2025-11-02
draft: false draft: false
description: "Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер. Устанавливаем K3s на 3 master и 2 worker ноды, настраиваем kubectl." description: "Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер. Устанавливаем K3s на 3 master и 2 worker ноды, настраиваем kubectl."
summary: "Инфраструктура готова: 5 VM работают, ОС настроена, порты открыты. Пора устанавливать K3s. Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер."
tags: ["kubernetes", "k3s", "homelab", "installation", "ha", "etcd", "devops"] tags: ["kubernetes", "k3s", "homelab", "installation", "ha", "etcd", "devops"]
series: ["K3s HA кластер для homelab"] series: ["K3s HA кластер для homelab"]
series_order: 3 series_order: 3
# seriesOpened: true
showTableOfContents: true
--- ---
Инфраструктура готова: 5 VM работают, ОС настроена, порты открыты. Пора устанавливать K3s. Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер. Инфраструктура готова: 5 VM работают, ОС настроена, порты открыты. Пора устанавливать K3s. Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер.

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -23,7 +23,7 @@ article:
other: "{{ .Count }} нравится" other: "{{ .Count }} нравится"
part_of_series: "Эта статья — часть серии." part_of_series: "Эта статья — часть серии."
part: "Часть" part: "Часть"
this_article: "Читаешь сейчас" this_article: "Ты уже здесь"
related_articles: "Статьи по теме" related_articles: "Статьи по теме"
reply_by_email: "Ответить по электронной почте" reply_by_email: "Ответить по электронной почте"

View File

@ -24,7 +24,7 @@
<link rel="canonical" href="http://192.168.11.190:1313/404.html"> <link rel="canonical" href="http://localhost:1313/404.html">
@ -64,7 +64,7 @@
<meta property="og:url" content="http://192.168.11.190:1313/404.html"> <meta property="og:url" content="http://localhost:1313/404.html">
<meta property="og:site_name" content="Олег Казанин"> <meta property="og:site_name" content="Олег Казанин">
<meta property="og:title" content="404 Page not found"> <meta property="og:title" content="404 Page not found">
<meta property="og:locale" content="ru"> <meta property="og:locale" content="ru">
@ -145,8 +145,6 @@
@ -156,8 +154,8 @@
defer defer
type="text/javascript" type="text/javascript"
id="script-bundle" id="script-bundle"
src="/js/main.bundle.min.bdda7dece6cbaf08deef7d254f7f842f3261c2524d247905127c9a20decc03f1011a2950048464c79272c1ce0705a49a41147f39f2b95163bb71d404b33263ef.js" src="/js/main.bundle.min.858f7f82734cfae08d59fcf8d0eb186f01706a84e2f7d20d39edfd7bc8bed6166e02d5c65ecce1de82b1ac52d1e01d77bd1a82d19186fdae5fe6e12d867fcf68.js"
integrity="sha512-vdp97ObLrwje730lT3&#43;ELzJhwlJNJHkFEnyaIN7MA/EBGilQBIRkx5Jywc4HBaSaQRR/OfK5UWO7cdQEszJj7w==" integrity="sha512-hY9/gnNM&#43;uCNWfz40OsYbwFwaoTi99INOe39e8i&#43;1hZuAtXGXszh3oKxrFLR4B13vRqC0ZGG/a5f5uEthn/PaA=="
data-copy="Копировать" data-copy="Копировать"
data-copied="Скопировано"></script> data-copied="Скопировано"></script>
@ -204,6 +202,13 @@
<script type="module" src="/js/firebase.min.cad74e0625f72f359ec6d6fed579b87733749de70400e7614048050ed08832ee3f58983d5d139fb1ddc5f7f2f5047d45ed80ec923534a3660fc3a7965f936866.js" integrity="sha512-ytdOBiX3LzWextb&#43;1Xm4dzN0necEAOdhQEgFDtCIMu4/WJg9XROfsd3F9/L1BH1F7YDskjU0o2YPw6eWX5NoZg=="></script>
@ -228,7 +233,7 @@
"headline": "404 Page not found", "headline": "404 Page not found",
"inLanguage": "ru", "inLanguage": "ru",
"url" : "http://192.168.11.190:1313/404.html", "url" : "http://localhost:1313/404.html",
"author" : { "author" : {
"@type": "Person", "@type": "Person",
"name": "Олег Казанин" "name": "Олег Казанин"
@ -543,7 +548,7 @@
<div <div
id="search-wrapper" id="search-wrapper"
class="invisible fixed inset-0 flex h-screen w-screen cursor-default flex-col bg-neutral-500/50 p-4 backdrop-blur-sm dark:bg-neutral-900/50 sm:p-6 md:p-[10vh] lg:p-[12vh] z-500" class="invisible fixed inset-0 flex h-screen w-screen cursor-default flex-col bg-neutral-500/50 p-4 backdrop-blur-sm dark:bg-neutral-900/50 sm:p-6 md:p-[10vh] lg:p-[12vh] z-500"
data-url="http://192.168.11.190:1313/"> data-url="http://localhost:1313/">
<div <div
id="search-modal" id="search-modal"
class="flex flex-col w-full max-w-3xl min-h-0 mx-auto border rounded-md shadow-lg top-20 border-neutral-200 bg-neutral dark:border-neutral-700 dark:bg-neutral-800"> class="flex flex-col w-full max-w-3xl min-h-0 mx-auto border rounded-md shadow-lg top-20 border-neutral-200 bg-neutral dark:border-neutral-700 dark:bg-neutral-800">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -24,7 +24,7 @@
<link rel="canonical" href="http://192.168.11.190:1313/authors/"> <link rel="canonical" href="http://localhost:1313/authors/">
<link rel="alternate" type="application/rss+xml" href="/authors/index.xml" title="Олег Казанин" /> <link rel="alternate" type="application/rss+xml" href="/authors/index.xml" title="Олег Казанин" />
@ -67,7 +67,7 @@
<meta property="og:url" content="http://192.168.11.190:1313/authors/"> <meta property="og:url" content="http://localhost:1313/authors/">
<meta property="og:site_name" content="Олег Казанин"> <meta property="og:site_name" content="Олег Казанин">
<meta property="og:title" content="Authors"> <meta property="og:title" content="Authors">
<meta property="og:locale" content="ru"> <meta property="og:locale" content="ru">
@ -148,8 +148,6 @@
@ -159,8 +157,8 @@
defer defer
type="text/javascript" type="text/javascript"
id="script-bundle" id="script-bundle"
src="/js/main.bundle.min.bdda7dece6cbaf08deef7d254f7f842f3261c2524d247905127c9a20decc03f1011a2950048464c79272c1ce0705a49a41147f39f2b95163bb71d404b33263ef.js" src="/js/main.bundle.min.858f7f82734cfae08d59fcf8d0eb186f01706a84e2f7d20d39edfd7bc8bed6166e02d5c65ecce1de82b1ac52d1e01d77bd1a82d19186fdae5fe6e12d867fcf68.js"
integrity="sha512-vdp97ObLrwje730lT3&#43;ELzJhwlJNJHkFEnyaIN7MA/EBGilQBIRkx5Jywc4HBaSaQRR/OfK5UWO7cdQEszJj7w==" integrity="sha512-hY9/gnNM&#43;uCNWfz40OsYbwFwaoTi99INOe39e8i&#43;1hZuAtXGXszh3oKxrFLR4B13vRqC0ZGG/a5f5uEthn/PaA=="
data-copy="Копировать" data-copy="Копировать"
data-copied="Скопировано"></script> data-copied="Скопировано"></script>
@ -207,6 +205,32 @@
<script type="module" src="/js/firebase.min.cad74e0625f72f359ec6d6fed579b87733749de70400e7614048050ed08832ee3f58983d5d139fb1ddc5f7f2f5047d45ed80ec923534a3660fc3a7965f936866.js" integrity="sha512-ytdOBiX3LzWextb&#43;1Xm4dzN0necEAOdhQEgFDtCIMu4/WJg9XROfsd3F9/L1BH1F7YDskjU0o2YPw6eWX5NoZg=="></script>
<script id="firebase-config"
type="application/json"
data-views="views_taxonomy_authors"
data-likes="likes_taxonomy_authors">
{
"config": {
"apiKey": "AIzaSyBBfzADrGgnwTIyW67gfZSrAtkoybxvmdI",
"authDomain": "oakazanin-hugo-blowfish.firebaseapp.com",
"projectId": "oakazanin-hugo-blowfish",
"storageBucket": "oakazanin-hugo-blowfish.firebasestorage.app",
"messagingSenderId": "945151844512",
"appId": "1:945151844512:web:22602cc010f5b7e0cca9c5",
"measurementId": ""
}
}
</script>
@ -231,7 +255,7 @@
"headline": "Authors", "headline": "Authors",
"inLanguage": "ru", "inLanguage": "ru",
"url" : "http://192.168.11.190:1313/authors/", "url" : "http://localhost:1313/authors/",
"author" : { "author" : {
"@type": "Person", "@type": "Person",
"name": "Олег Казанин" "name": "Олег Казанин"
@ -433,7 +457,87 @@
<header class="mt-5">
<div id="hero" class="h-[150px] md:h-[200px]"></div>
<div class="fixed inset-x-0 top-0 h-[800px] single_hero_background nozoom">
<img
id="background-image"
src="/img/background_hu_42f1c83933308119.png"
role="presentation"
loading="eager"
decoding="async"
fetchpriority="high"
class="absolute inset-0 w-full h-full object-cover"
>
<div
class="absolute inset-0 bg-gradient-to-t from-neutral dark:from-neutral-800 to-transparent mix-blend-normal"></div>
<div
class="absolute inset-0 opacity-60 bg-gradient-to-t from-neutral dark:from-neutral-800 to-neutral-100 dark:to-neutral-800 mix-blend-normal"></div>
</div>
<div
id="background-blur"
class="fixed opacity-0 inset-x-0 top-0 h-full single_hero_background nozoom backdrop-blur-xl bg-neutral-100/75 dark:bg-neutral-800/60"></div>
<script
type="text/javascript"
src="/js/background-blur.min.605b3b942818f0ab5a717ae446135ec46b8ee5a2ad12ae56fb90dc2a76ce30c388f9fec8bcc18db15bd47e3fa8a09d779fa12aa9c184cf614a315bc72c6c163d.js"
integrity="sha512-YFs7lCgY8KtacXrkRhNexGuO5aKtEq5W&#43;5DcKnbOMMOI&#43;f7IvMGNsVvUfj&#43;ooJ13n6EqqcGEz2FKMVvHLGwWPQ=="
data-blur-id="background-blur"
data-image-id="background-image"
data-image-url="/img/background_hu_42f1c83933308119.png"></script>
<header class="mt-5">
<h1 class="mt-5 text-4xl font-extrabold text-neutral-900 dark:text-neutral">Authors</h1> <h1 class="mt-5 text-4xl font-extrabold text-neutral-900 dark:text-neutral">Authors</h1>
<div class="mt-1 mb-2 text-base text-neutral-500 dark:text-neutral-400 print:hidden"> <div class="mt-1 mb-2 text-base text-neutral-500 dark:text-neutral-400 print:hidden">
@ -583,7 +687,7 @@
<div <div
id="search-wrapper" id="search-wrapper"
class="invisible fixed inset-0 flex h-screen w-screen cursor-default flex-col bg-neutral-500/50 p-4 backdrop-blur-sm dark:bg-neutral-900/50 sm:p-6 md:p-[10vh] lg:p-[12vh] z-500" class="invisible fixed inset-0 flex h-screen w-screen cursor-default flex-col bg-neutral-500/50 p-4 backdrop-blur-sm dark:bg-neutral-900/50 sm:p-6 md:p-[10vh] lg:p-[12vh] z-500"
data-url="http://192.168.11.190:1313/"> data-url="http://localhost:1313/">
<div <div
id="search-modal" id="search-modal"
class="flex flex-col w-full max-w-3xl min-h-0 mx-auto border rounded-md shadow-lg top-20 border-neutral-200 bg-neutral dark:border-neutral-700 dark:bg-neutral-800"> class="flex flex-col w-full max-w-3xl min-h-0 mx-auto border rounded-md shadow-lg top-20 border-neutral-200 bg-neutral dark:border-neutral-700 dark:bg-neutral-800">

View File

@ -2,14 +2,14 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel> <channel>
<title>Authors on Олег Казанин</title> <title>Authors on Олег Казанин</title>
<link>http://192.168.11.190:1313/authors/</link> <link>http://localhost:1313/authors/</link>
<description>Recent content in Authors on Олег Казанин</description> <description>Recent content in Authors on Олег Казанин</description>
<generator>Hugo -- gohugo.io</generator> <generator>Hugo -- gohugo.io</generator>
<language>ru</language> <language>ru</language>
<managingEditor>oakazanin@ya.ru (Олег Казанин)</managingEditor> <managingEditor>oakazanin@ya.ru (Олег Казанин)</managingEditor>
<webMaster>oakazanin@ya.ru (Олег Казанин)</webMaster> <webMaster>oakazanin@ya.ru (Олег Казанин)</webMaster>
<copyright>© 2026 Олег Казанин</copyright> <copyright>© 2026 Олег Казанин</copyright>
<atom:link href="http://192.168.11.190:1313/authors/index.xml" rel="self" type="application/rss+xml" /> <atom:link href="http://localhost:1313/authors/index.xml" rel="self" type="application/rss+xml" />
</channel> </channel>
</rss> </rss>

View File

@ -24,7 +24,7 @@
<link rel="canonical" href="http://192.168.11.190:1313/categories/"> <link rel="canonical" href="http://localhost:1313/categories/">
<link rel="alternate" type="application/rss+xml" href="/categories/index.xml" title="Олег Казанин" /> <link rel="alternate" type="application/rss+xml" href="/categories/index.xml" title="Олег Казанин" />
@ -67,7 +67,7 @@
<meta property="og:url" content="http://192.168.11.190:1313/categories/"> <meta property="og:url" content="http://localhost:1313/categories/">
<meta property="og:site_name" content="Олег Казанин"> <meta property="og:site_name" content="Олег Казанин">
<meta property="og:title" content="Categories"> <meta property="og:title" content="Categories">
<meta property="og:locale" content="ru"> <meta property="og:locale" content="ru">
@ -148,8 +148,6 @@
@ -159,8 +157,8 @@
defer defer
type="text/javascript" type="text/javascript"
id="script-bundle" id="script-bundle"
src="/js/main.bundle.min.bdda7dece6cbaf08deef7d254f7f842f3261c2524d247905127c9a20decc03f1011a2950048464c79272c1ce0705a49a41147f39f2b95163bb71d404b33263ef.js" src="/js/main.bundle.min.858f7f82734cfae08d59fcf8d0eb186f01706a84e2f7d20d39edfd7bc8bed6166e02d5c65ecce1de82b1ac52d1e01d77bd1a82d19186fdae5fe6e12d867fcf68.js"
integrity="sha512-vdp97ObLrwje730lT3&#43;ELzJhwlJNJHkFEnyaIN7MA/EBGilQBIRkx5Jywc4HBaSaQRR/OfK5UWO7cdQEszJj7w==" integrity="sha512-hY9/gnNM&#43;uCNWfz40OsYbwFwaoTi99INOe39e8i&#43;1hZuAtXGXszh3oKxrFLR4B13vRqC0ZGG/a5f5uEthn/PaA=="
data-copy="Копировать" data-copy="Копировать"
data-copied="Скопировано"></script> data-copied="Скопировано"></script>
@ -207,6 +205,32 @@
<script type="module" src="/js/firebase.min.cad74e0625f72f359ec6d6fed579b87733749de70400e7614048050ed08832ee3f58983d5d139fb1ddc5f7f2f5047d45ed80ec923534a3660fc3a7965f936866.js" integrity="sha512-ytdOBiX3LzWextb&#43;1Xm4dzN0necEAOdhQEgFDtCIMu4/WJg9XROfsd3F9/L1BH1F7YDskjU0o2YPw6eWX5NoZg=="></script>
<script id="firebase-config"
type="application/json"
data-views="views_taxonomy_categories"
data-likes="likes_taxonomy_categories">
{
"config": {
"apiKey": "AIzaSyBBfzADrGgnwTIyW67gfZSrAtkoybxvmdI",
"authDomain": "oakazanin-hugo-blowfish.firebaseapp.com",
"projectId": "oakazanin-hugo-blowfish",
"storageBucket": "oakazanin-hugo-blowfish.firebasestorage.app",
"messagingSenderId": "945151844512",
"appId": "1:945151844512:web:22602cc010f5b7e0cca9c5",
"measurementId": ""
}
}
</script>
@ -231,16 +255,16 @@
"headline": "Categories", "headline": "Categories",
"inLanguage": "ru", "inLanguage": "ru",
"url" : "http://192.168.11.190:1313/categories/", "url" : "http://localhost:1313/categories/",
"author" : { "author" : {
"@type": "Person", "@type": "Person",
"name": "Олег Казанин" "name": "Олег Казанин"
}, },
"copyrightYear": "2026",
"dateCreated": "2026-02-17T00:00:00\u002b00:00",
"datePublished": "2026-02-17T00:00:00\u002b00:00",
"dateModified": "2026-02-17T00:00:00\u002b00:00",
@ -433,7 +457,87 @@
<header class="mt-5">
<div id="hero" class="h-[150px] md:h-[200px]"></div>
<div class="fixed inset-x-0 top-0 h-[800px] single_hero_background nozoom">
<img
id="background-image"
src="/img/background_hu_42f1c83933308119.png"
role="presentation"
loading="eager"
decoding="async"
fetchpriority="high"
class="absolute inset-0 w-full h-full object-cover"
>
<div
class="absolute inset-0 bg-gradient-to-t from-neutral dark:from-neutral-800 to-transparent mix-blend-normal"></div>
<div
class="absolute inset-0 opacity-60 bg-gradient-to-t from-neutral dark:from-neutral-800 to-neutral-100 dark:to-neutral-800 mix-blend-normal"></div>
</div>
<div
id="background-blur"
class="fixed opacity-0 inset-x-0 top-0 h-full single_hero_background nozoom backdrop-blur-xl bg-neutral-100/75 dark:bg-neutral-800/60"></div>
<script
type="text/javascript"
src="/js/background-blur.min.605b3b942818f0ab5a717ae446135ec46b8ee5a2ad12ae56fb90dc2a76ce30c388f9fec8bcc18db15bd47e3fa8a09d779fa12aa9c184cf614a315bc72c6c163d.js"
integrity="sha512-YFs7lCgY8KtacXrkRhNexGuO5aKtEq5W&#43;5DcKnbOMMOI&#43;f7IvMGNsVvUfj&#43;ooJ13n6EqqcGEz2FKMVvHLGwWPQ=="
data-blur-id="background-blur"
data-image-id="background-image"
data-image-url="/img/background_hu_42f1c83933308119.png"></script>
<header class="mt-5">
<h1 class="mt-5 text-4xl font-extrabold text-neutral-900 dark:text-neutral">Categories</h1> <h1 class="mt-5 text-4xl font-extrabold text-neutral-900 dark:text-neutral">Categories</h1>
<div class="mt-1 mb-2 text-base text-neutral-500 dark:text-neutral-400 print:hidden"> <div class="mt-1 mb-2 text-base text-neutral-500 dark:text-neutral-400 print:hidden">
@ -473,6 +577,23 @@
<section class="flex flex-wrap max-w-prose -mx-2 overflow-hidden"> <section class="flex flex-wrap max-w-prose -mx-2 overflow-hidden">
<article class="w-full px-2 my-3 overflow-hidden sm:w-1/2 md:w-1/3 lg:w-1/4 xl:w-1/4">
<h2 class="flex items-center">
<a
class="text-xl font-medium decoration-primary-500 hover:underline hover:underline-offset-2"
href="/categories/infrastructure/"
>Infrastructure</a
>
<span class="px-2 text-base text-primary-500">&middot;</span>
<span class="text-base text-neutral-400">
5
</span>
</h2>
</article>
</section> </section>
@ -583,7 +704,7 @@
<div <div
id="search-wrapper" id="search-wrapper"
class="invisible fixed inset-0 flex h-screen w-screen cursor-default flex-col bg-neutral-500/50 p-4 backdrop-blur-sm dark:bg-neutral-900/50 sm:p-6 md:p-[10vh] lg:p-[12vh] z-500" class="invisible fixed inset-0 flex h-screen w-screen cursor-default flex-col bg-neutral-500/50 p-4 backdrop-blur-sm dark:bg-neutral-900/50 sm:p-6 md:p-[10vh] lg:p-[12vh] z-500"
data-url="http://192.168.11.190:1313/"> data-url="http://localhost:1313/">
<div <div
id="search-modal" id="search-modal"
class="flex flex-col w-full max-w-3xl min-h-0 mx-auto border rounded-md shadow-lg top-20 border-neutral-200 bg-neutral dark:border-neutral-700 dark:bg-neutral-800"> class="flex flex-col w-full max-w-3xl min-h-0 mx-auto border rounded-md shadow-lg top-20 border-neutral-200 bg-neutral dark:border-neutral-700 dark:bg-neutral-800">

View File

@ -2,14 +2,24 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel> <channel>
<title>Categories on Олег Казанин</title> <title>Categories on Олег Казанин</title>
<link>http://192.168.11.190:1313/categories/</link> <link>http://localhost:1313/categories/</link>
<description>Recent content in Categories on Олег Казанин</description> <description>Recent content in Categories on Олег Казанин</description>
<generator>Hugo -- gohugo.io</generator> <generator>Hugo -- gohugo.io</generator>
<language>ru</language> <language>ru</language>
<managingEditor>oakazanin@ya.ru (Олег Казанин)</managingEditor> <managingEditor>oakazanin@ya.ru (Олег Казанин)</managingEditor>
<webMaster>oakazanin@ya.ru (Олег Казанин)</webMaster> <webMaster>oakazanin@ya.ru (Олег Казанин)</webMaster>
<copyright>© 2026 Олег Казанин</copyright> <copyright>© 2026 Олег Казанин</copyright>
<atom:link href="http://192.168.11.190:1313/categories/index.xml" rel="self" type="application/rss+xml" /> <lastBuildDate>Tue, 17 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="http://localhost:1313/categories/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>Infrastructure</title>
<link>http://localhost:1313/categories/infrastructure/</link>
<pubDate>Tue, 17 Feb 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/categories/infrastructure/</guid>
<description></description>
</item>
</channel> </channel>
</rss> </rss>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Infrastructure on Олег Казанин</title>
<link>http://localhost:1313/categories/infrastructure/</link>
<description>Recent content in Infrastructure on Олег Казанин</description>
<generator>Hugo -- gohugo.io</generator>
<language>ru</language>
<managingEditor>oakazanin@ya.ru (Олег Казанин)</managingEditor>
<webMaster>oakazanin@ya.ru (Олег Казанин)</webMaster>
<copyright>© 2026 Олег Казанин</copyright>
<lastBuildDate>Tue, 17 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="http://localhost:1313/categories/infrastructure/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>Блог на Hugo в K3s: часть 5 - что делать когда всё внезапно сломалось</title>
<link>http://localhost:1313/posts/blog-part-5-debugging/</link>
<pubDate>Tue, 17 Feb 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/blog-part-5-debugging/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-5-debugging/featured.png" />
</item>
<item>
<title>Блог на Hugo в K3s: часть 4 - выбор Git workflow</title>
<link>http://localhost:1313/posts/blog-part-4-git-workflow/</link>
<pubDate>Mon, 16 Feb 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/blog-part-4-git-workflow/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-4-git-workflow/featured.png" />
</item>
<item>
<title>Блог на Hugo в K3s: часть 3 - development окружение</title>
<link>http://localhost:1313/posts/blog-part-3-dev-environment/</link>
<pubDate>Thu, 15 Jan 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/blog-part-3-dev-environment/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-3-dev-environment/featured.png" />
</item>
<item>
<title>Блог на Hugo в K3s: часть 2 - деплой в кластер</title>
<link>http://localhost:1313/posts/blog-part-2-k8s-deployment/</link>
<pubDate>Thu, 08 Jan 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/blog-part-2-k8s-deployment/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-2-k8s-deployment/featured.png" />
</item>
<item>
<title>Блог на Hugo в K3s: часть 1 - архитектура и первый запуск</title>
<link>http://localhost:1313/posts/blog-part-1-architecture/</link>
<pubDate>Sat, 03 Jan 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/blog-part-1-architecture/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-1-architecture/featured.png" />
</item>
</channel>
</rss>

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<title>http://localhost:1313/categories/infrastructure/</title>
<link rel="canonical" href="http://localhost:1313/categories/infrastructure/">
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=http://localhost:1313/categories/infrastructure/">
</head>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 737 B

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
public/favicon-96x96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

1
public/favicon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -2,43 +2,93 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel> <channel>
<title>Олег Казанин</title> <title>Олег Казанин</title>
<link>http://192.168.11.190:1313/</link> <link>http://localhost:1313/</link>
<description>Recent content on Олег Казанин</description> <description>Recent content on Олег Казанин</description>
<generator>Hugo -- gohugo.io</generator> <generator>Hugo -- gohugo.io</generator>
<language>ru</language> <language>ru</language>
<managingEditor>oakazanin@ya.ru (Олег Казанин)</managingEditor> <managingEditor>oakazanin@ya.ru (Олег Казанин)</managingEditor>
<webMaster>oakazanin@ya.ru (Олег Казанин)</webMaster> <webMaster>oakazanin@ya.ru (Олег Казанин)</webMaster>
<copyright>© 2026 Олег Казанин</copyright> <copyright>© 2026 Олег Казанин</copyright>
<lastBuildDate>Sun, 02 Nov 2025 00:00:00 +0000</lastBuildDate><atom:link href="http://192.168.11.190:1313/index.xml" rel="self" type="application/rss+xml" /> <lastBuildDate>Tue, 17 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="http://localhost:1313/index.xml" rel="self" type="application/rss+xml" />
<item> <item>
<title>Установить K3s HA кластер</title> <title>Блог на Hugo в K3s: часть 5 - что делать когда всё внезапно сломалось</title>
<link>http://192.168.11.190:1313/posts/k3s-installation/</link> <link>http://localhost:1313/posts/blog-part-5-debugging/</link>
<pubDate>Sun, 02 Nov 2025 00:00:00 +0000</pubDate> <pubDate>Tue, 17 Feb 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author> <author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://192.168.11.190:1313/posts/k3s-installation/</guid> <guid>http://localhost:1313/posts/blog-part-5-debugging/</guid>
<description>Инфраструктура готова: 5 VM работают, ОС настроена, порты открыты. Пора устанавливать K3s. Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер.</description> <description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://192.168.11.190:1313/posts/k3s-installation/featured.png" /> <media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-5-debugging/featured.png" />
</item> </item>
<item> <item>
<title>Подготовить инфраструктуру для K3s в Proxmox</title> <title>Блог на Hugo в K3s: часть 4 - выбор Git workflow</title>
<link>http://192.168.11.190:1313/posts/k3s-infrastructure/</link> <link>http://localhost:1313/posts/blog-part-4-git-workflow/</link>
<pubDate>Mon, 16 Feb 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/blog-part-4-git-workflow/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-4-git-workflow/featured.png" />
</item>
<item>
<title>Блог на Hugo в K3s: часть 3 - development окружение</title>
<link>http://localhost:1313/posts/blog-part-3-dev-environment/</link>
<pubDate>Thu, 15 Jan 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/blog-part-3-dev-environment/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-3-dev-environment/featured.png" />
</item>
<item>
<title>Блог на Hugo в K3s: часть 2 - деплой в кластер</title>
<link>http://localhost:1313/posts/blog-part-2-k8s-deployment/</link>
<pubDate>Thu, 08 Jan 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/blog-part-2-k8s-deployment/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-2-k8s-deployment/featured.png" />
</item>
<item>
<title>Блог на Hugo в K3s: часть 1 - архитектура и первый запуск</title>
<link>http://localhost:1313/posts/blog-part-1-architecture/</link>
<pubDate>Sat, 03 Jan 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/blog-part-1-architecture/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-1-architecture/featured.png" />
</item>
<item>
<title>K3s HA для homelab: Ставим K3s HA кластер</title>
<link>http://localhost:1313/posts/k3s-part3-installation/</link>
<pubDate>Sun, 02 Nov 2025 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/k3s-part3-installation/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/k3s-part3-installation/featured.png" />
</item>
<item>
<title>K3s HA для homelab: Готовим инфраструктуру в Proxmox</title>
<link>http://localhost:1313/posts/k3s-part2-infrastructure/</link>
<pubDate>Tue, 21 Oct 2025 00:00:00 +0000</pubDate> <pubDate>Tue, 21 Oct 2025 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author> <author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://192.168.11.190:1313/posts/k3s-infrastructure/</guid> <guid>http://localhost:1313/posts/k3s-part2-infrastructure/</guid>
<description>Архитектура спланирована, ресурсы посчитаны - пора создавать виртуальные машины. Подготовим 5 VM в Proxmox и настроим ОС так, чтобы K3s установился без сюрпризов.</description> <description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://192.168.11.190:1313/posts/k3s-infrastructure/featured.png" /> <media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/k3s-part2-infrastructure/featured.png" />
</item> </item>
<item> <item>
<title>K3s HA для homelab: архитектура без боли</title> <title>K3s HA для homelab: архитектура без боли</title>
<link>http://192.168.11.190:1313/posts/k3s-architecture/</link> <link>http://localhost:1313/posts/k3s-part1-architecture/</link>
<pubDate>Tue, 14 Oct 2025 00:00:00 +0000</pubDate> <pubDate>Tue, 14 Oct 2025 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author> <author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://192.168.11.190:1313/posts/k3s-architecture/</guid> <guid>http://localhost:1313/posts/k3s-part1-architecture/</guid>
<description>Kubernetes слишком тяжёлый, Docker Swarm мёртв, а хочется нормальный кластер для экспериментов. K3s решает эту проблему - полноценный Kubernetes в 50MB. Разберём архитектуру HA кластера без боли.</description> <description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://192.168.11.190:1313/posts/k3s-architecture/featured.png" /> <media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/k3s-part1-architecture/featured.png" />
</item> </item>
</channel> </channel>

View File

@ -0,0 +1 @@
import{initializeApp}from"https://www.gstatic.com/firebasejs/9.23.0/firebase-app.js";import{getFirestore,doc,getDoc,setDoc,updateDoc,increment,onSnapshot,}from"https://www.gstatic.com/firebasejs/9.23.0/firebase-firestore.js";import{getAuth,signInAnonymously}from"https://www.gstatic.com/firebasejs/9.23.0/firebase-auth.js";let app,db,auth,oids;try{const e=document.getElementById("firebase-config");if(!e?.textContent)throw new Error("Firebase config element not found");const t=JSON.parse(e.textContent);app=initializeApp(t.config),oids={views:e.getAttribute("data-views"),likes:e.getAttribute("data-likes")},db=getFirestore(app),auth=getAuth(app)}catch(e){throw console.error("Firebase initialization failed:",e.message),e}const id=oids?.views?.replaceAll("/","-"),id_likes=oids?.likes?.replaceAll("/","-");let liked=!1,authReady=!1;function formatNumber(e){return e.toString().replace(/\B(?=(\d{3})+(?!\d))/g,",")}function toggleLoaders(e){var t,s,n=e.className;if(n=="")return;t=n.split(" ");for(s in t)e.classList.toggle(t[s])}function updateDisplay(e,t){const n=document.getElementById(t);if(!n)return;const s=t.replaceAll("/","-");onSnapshot(doc(db,e,s),t=>{n.innerText=t.exists()?formatNumber(t.data()[e]):0,toggleLoaders(n)},e=>{console.error("Firebase snapshot update failed:",e)})}async function recordView(e){if(!e||localStorage.getItem(e))return;try{const t=doc(db,"views",e),n=await getDoc(t);n.exists()?await updateDoc(t,{views:increment(1)}):await setDoc(t,{views:1}),localStorage.setItem(e,!0)}catch(e){console.error("Record view operation failed:",e.message)}}function updateButton(e){const t=document.querySelectorAll("span[id='button_likes_heart']"),n=document.querySelectorAll("span[id='button_likes_emtpty_heart']"),s=document.querySelectorAll("span[id='button_likes_text']");t.forEach(t=>{t.style.display=e?"":"none"}),n.forEach(t=>{t.style.display=e?"none":""}),s.forEach(t=>{t.innerText=e?"":"\xa0Like"})}async function toggleLike(e){if(!id_likes||!authReady)return;try{const t=doc(db,"likes",id_likes),n=await getDoc(t);liked=e,e?localStorage.setItem(id_likes,!0):localStorage.removeItem(id_likes),updateButton(e),n.exists()?await updateDoc(t,{likes:increment(e?1:-1)}):await setDoc(t,{likes:e?1:0})}catch(t){console.error("Like operation failed:",t.message),liked=!e,e?localStorage.removeItem(id_likes):localStorage.setItem(id_likes,!0),updateButton(!e)}}signInAnonymously(auth).then(()=>{authReady=!0,document.querySelectorAll("span[id^='views_']").forEach(e=>{e.id&&updateDisplay("views",e.id)}),document.querySelectorAll("span[id^='likes_']").forEach(e=>{e.id&&updateDisplay("likes",e.id)}),recordView(id),id_likes&&localStorage.getItem(id_likes)&&(liked=!0,updateButton(!0));const e=document.getElementById("button_likes");e&&e.addEventListener("click",()=>{toggleLike(!liked)})}).catch(e=>{console.error("Firebase anonymous sign-in failed:",e.message),authReady=!1}),window.process_article=()=>toggleLike(!liked)

View File

@ -1,9 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ru"> <html lang="ru">
<head> <head>
<title>http://192.168.11.190:1313/</title> <title>http://localhost:1313/</title>
<link rel="canonical" href="http://192.168.11.190:1313/"> <link rel="canonical" href="http://localhost:1313/">
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=http://192.168.11.190:1313/"> <meta http-equiv="refresh" content="0; url=http://localhost:1313/">
</head> </head>
</html> </html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1003 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,43 +2,93 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel> <channel>
<title>Posts on Олег Казанин</title> <title>Posts on Олег Казанин</title>
<link>http://192.168.11.190:1313/posts/</link> <link>http://localhost:1313/posts/</link>
<description>Recent content in Posts on Олег Казанин</description> <description>Recent content in Posts on Олег Казанин</description>
<generator>Hugo -- gohugo.io</generator> <generator>Hugo -- gohugo.io</generator>
<language>ru</language> <language>ru</language>
<managingEditor>oakazanin@ya.ru (Олег Казанин)</managingEditor> <managingEditor>oakazanin@ya.ru (Олег Казанин)</managingEditor>
<webMaster>oakazanin@ya.ru (Олег Казанин)</webMaster> <webMaster>oakazanin@ya.ru (Олег Казанин)</webMaster>
<copyright>© 2026 Олег Казанин</copyright> <copyright>© 2026 Олег Казанин</copyright>
<lastBuildDate>Sat, 14 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="http://192.168.11.190:1313/posts/index.xml" rel="self" type="application/rss+xml" /> <lastBuildDate>Tue, 17 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="http://localhost:1313/posts/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>Блог на Hugo в K3s: часть 5 - что делать когда всё внезапно сломалось</title>
<link>http://localhost:1313/posts/blog-part-5-debugging/</link>
<pubDate>Tue, 17 Feb 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/blog-part-5-debugging/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-5-debugging/featured.png" />
</item>
<item>
<title>Блог на Hugo в K3s: часть 4 - выбор Git workflow</title>
<link>http://localhost:1313/posts/blog-part-4-git-workflow/</link>
<pubDate>Mon, 16 Feb 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/blog-part-4-git-workflow/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-4-git-workflow/featured.png" />
</item>
<item>
<title>Блог на Hugo в K3s: часть 3 - development окружение</title>
<link>http://localhost:1313/posts/blog-part-3-dev-environment/</link>
<pubDate>Thu, 15 Jan 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/blog-part-3-dev-environment/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-3-dev-environment/featured.png" />
</item>
<item>
<title>Блог на Hugo в K3s: часть 2 - деплой в кластер</title>
<link>http://localhost:1313/posts/blog-part-2-k8s-deployment/</link>
<pubDate>Thu, 08 Jan 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/blog-part-2-k8s-deployment/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-2-k8s-deployment/featured.png" />
</item>
<item>
<title>Блог на Hugo в K3s: часть 1 - архитектура и первый запуск</title>
<link>http://localhost:1313/posts/blog-part-1-architecture/</link>
<pubDate>Sat, 03 Jan 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/blog-part-1-architecture/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/blog-part-1-architecture/featured.png" />
</item>
<item>
<title>K3s HA для homelab: Ставим K3s HA кластер</title>
<link>http://localhost:1313/posts/k3s-part3-installation/</link>
<pubDate>Sun, 02 Nov 2025 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/k3s-part3-installation/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/k3s-part3-installation/featured.png" />
</item>
<item>
<title>K3s HA для homelab: Готовим инфраструктуру в Proxmox</title>
<link>http://localhost:1313/posts/k3s-part2-infrastructure/</link>
<pubDate>Tue, 21 Oct 2025 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://localhost:1313/posts/k3s-part2-infrastructure/</guid>
<description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/k3s-part2-infrastructure/featured.png" />
</item>
<item> <item>
<title>K3s HA для homelab: архитектура без боли</title> <title>K3s HA для homelab: архитектура без боли</title>
<link>http://192.168.11.190:1313/posts/k3s-architecture/</link> <link>http://localhost:1313/posts/k3s-part1-architecture/</link>
<pubDate>Sat, 14 Feb 2026 00:00:00 +0000</pubDate> <pubDate>Tue, 14 Oct 2025 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author> <author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://192.168.11.190:1313/posts/k3s-architecture/</guid> <guid>http://localhost:1313/posts/k3s-part1-architecture/</guid>
<description>Kubernetes слишком тяжёлый, Docker Swarm мёртв, а хочется нормальный кластер для экспериментов. K3s решает эту проблему - полноценный Kubernetes в 50MB. Разберём архитектуру HA кластера без боли.</description> <description></description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://192.168.11.190:1313/posts/k3s-architecture/featured.png" /> <media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://localhost:1313/posts/k3s-part1-architecture/featured.png" />
</item>
<item>
<title>Подготовить инфраструктуру для K3s в Proxmox</title>
<link>http://192.168.11.190:1313/posts/k3s-infrastructure/</link>
<pubDate>Sat, 14 Feb 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://192.168.11.190:1313/posts/k3s-infrastructure/</guid>
<description>Архитектура спланирована, ресурсы посчитаны - пора создавать виртуальные машины. Подготовим 5 VM в Proxmox и настроим ОС так, чтобы K3s установился без сюрпризов.</description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://192.168.11.190:1313/posts/k3s-infrastructure/featured.png" />
</item>
<item>
<title>Установить K3s HA кластер</title>
<link>http://192.168.11.190:1313/posts/k3s-installation/</link>
<pubDate>Sat, 14 Feb 2026 00:00:00 +0000</pubDate>
<author>oakazanin@ya.ru (Олег Казанин)</author>
<guid>http://192.168.11.190:1313/posts/k3s-installation/</guid>
<description>Инфраструктура готова: 5 VM работают, ОС настроена, порты открыты. Пора устанавливать K3s. Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер.</description>
<media:content xmlns:media="http://search.yahoo.com/mrss/" url="http://192.168.11.190:1313/posts/k3s-installation/featured.png" />
</item> </item>
</channel> </channel>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 663 KiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 KiB

View File

@ -28,7 +28,7 @@
<link rel="canonical" href="http://192.168.11.190:1313/posts/k3s-architecture/"> <link rel="canonical" href="http://localhost:1313/posts/k3s-architecture/">
@ -68,7 +68,7 @@
<meta property="og:url" content="http://192.168.11.190:1313/posts/k3s-architecture/"> <meta property="og:url" content="http://localhost:1313/posts/k3s-architecture/">
<meta property="og:site_name" content="Олег Казанин"> <meta property="og:site_name" content="Олег Казанин">
<meta property="og:title" content="K3s HA для homelab: архитектура без боли"> <meta property="og:title" content="K3s HA для homelab: архитектура без боли">
<meta property="og:description" content="Полноценный Kubernetes в бинарнике на 50MB вместо 1.5GB зависимостей. Разбираем архитектуру K3s HA кластера: почему именно 3 master ноды, зачем embedded etcd и сколько ресурсов закладывать."> <meta property="og:description" content="Полноценный Kubernetes в бинарнике на 50MB вместо 1.5GB зависимостей. Разбираем архитектуру K3s HA кластера: почему именно 3 master ноды, зачем embedded etcd и сколько ресурсов закладывать.">
@ -83,13 +83,13 @@
<meta property="article:tag" content="Proxmox"> <meta property="article:tag" content="Proxmox">
<meta property="article:tag" content="Architecture"> <meta property="article:tag" content="Architecture">
<meta property="article:tag" content="Ha"> <meta property="article:tag" content="Ha">
<meta property="og:image" content="http://192.168.11.190:1313/posts/k3s-architecture/featured.png"> <meta property="og:image" content="http://localhost:1313/posts/k3s-architecture/featured.png">
<meta property="og:see_also" content="http://192.168.11.190:1313/posts/k3s-installation/"> <meta property="og:see_also" content="http://localhost:1313/posts/k3s-installation/">
<meta property="og:see_also" content="http://192.168.11.190:1313/posts/k3s-infrastructure/"> <meta property="og:see_also" content="http://localhost:1313/posts/k3s-infrastructure/">
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="http://192.168.11.190:1313/posts/k3s-architecture/featured.png"> <meta name="twitter:image" content="http://localhost:1313/posts/k3s-architecture/featured.png">
<meta name="twitter:title" content="K3s HA для homelab: архитектура без боли"> <meta name="twitter:title" content="K3s HA для homelab: архитектура без боли">
<meta name="twitter:description" content="Полноценный Kubernetes в бинарнике на 50MB вместо 1.5GB зависимостей. Разбираем архитектуру K3s HA кластера: почему именно 3 master ноды, зачем embedded etcd и сколько ресурсов закладывать."> <meta name="twitter:description" content="Полноценный Kubernetes в бинарнике на 50MB вместо 1.5GB зависимостей. Разбираем архитектуру K3s HA кластера: почему именно 3 master ноды, зачем embedded etcd и сколько ресурсов закладывать.">
@ -217,6 +217,39 @@
<script type="module" src="/js/firebase.min.cad74e0625f72f359ec6d6fed579b87733749de70400e7614048050ed08832ee3f58983d5d139fb1ddc5f7f2f5047d45ed80ec923534a3660fc3a7965f936866.js" integrity="sha512-ytdOBiX3LzWextb&#43;1Xm4dzN0necEAOdhQEgFDtCIMu4/WJg9XROfsd3F9/L1BH1F7YDskjU0o2YPw6eWX5NoZg=="></script>
<script id="firebase-config"
type="application/json"
data-views="views_posts/k3s-architecture/index.md"
data-likes="likes_posts/k3s-architecture/index.md">
{
"config": {
"apiKey": "AIzaSyBBfzADrGgnwTIyW67gfZSrAtkoybxvmdI",
"authDomain": "oakazanin-hugo-blowfish.firebaseapp.com",
"projectId": "oakazanin-hugo-blowfish",
"storageBucket": "oakazanin-hugo-blowfish.firebasestorage.app",
"messagingSenderId": "945151844512",
"appId": "1:945151844512:web:22602cc010f5b7e0cca9c5",
"measurementId": ""
}
}
</script>
@ -241,7 +274,7 @@
"headline": "K3s HA для homelab: архитектура без боли", "headline": "K3s HA для homelab: архитектура без боли",
"description": "Полноценный Kubernetes в бинарнике на 50MB вместо 1.5GB зависимостей. Разбираем архитектуру K3s HA кластера: почему именно 3 master ноды, зачем embedded etcd и сколько ресурсов закладывать.", "description": "Полноценный Kubernetes в бинарнике на 50MB вместо 1.5GB зависимостей. Разбираем архитектуру K3s HA кластера: почему именно 3 master ноды, зачем embedded etcd и сколько ресурсов закладывать.",
"inLanguage": "ru", "inLanguage": "ru",
"url" : "http://192.168.11.190:1313/posts/k3s-architecture/", "url" : "http://localhost:1313/posts/k3s-architecture/",
"author" : { "author" : {
"@type": "Person", "@type": "Person",
"name": "Олег Казанин" "name": "Олег Казанин"
@ -445,6 +478,83 @@
<article> <article>
<div id="hero" class="h-[150px] md:h-[200px]"></div>
<div class="fixed inset-x-0 top-0 h-[800px] single_hero_background nozoom">
<img
id="background-image"
src="/posts/k3s-architecture/featured_hu_79735d97c46e93a6.png"
role="presentation"
loading="eager"
decoding="async"
fetchpriority="high"
class="absolute inset-0 w-full h-full object-cover"
>
<div
class="absolute inset-0 bg-gradient-to-t from-neutral dark:from-neutral-800 to-transparent mix-blend-normal"></div>
<div
class="absolute inset-0 opacity-60 bg-gradient-to-t from-neutral dark:from-neutral-800 to-neutral-100 dark:to-neutral-800 mix-blend-normal"></div>
</div>
<div
id="background-blur"
class="fixed opacity-0 inset-x-0 top-0 h-full single_hero_background nozoom backdrop-blur-xl bg-neutral-100/75 dark:bg-neutral-800/60"></div>
<script
type="text/javascript"
src="/js/background-blur.min.605b3b942818f0ab5a717ae446135ec46b8ee5a2ad12ae56fb90dc2a76ce30c388f9fec8bcc18db15bd47e3fa8a09d779fa12aa9c184cf614a315bc72c6c163d.js"
integrity="sha512-YFs7lCgY8KtacXrkRhNexGuO5aKtEq5W&#43;5DcKnbOMMOI&#43;f7IvMGNsVvUfj&#43;ooJ13n6EqqcGEz2FKMVvHLGwWPQ=="
data-blur-id="background-blur"
data-image-id="background-image"
data-image-url="/posts/k3s-architecture/featured_hu_79735d97c46e93a6.png"></script>
<header id="single_header" class="mt-5 max-w-prose"> <header id="single_header" class="mt-5 max-w-prose">
@ -1806,6 +1916,9 @@
</div> </div>
</section> </section>
@ -1955,7 +2068,7 @@
<div <div
id="search-wrapper" id="search-wrapper"
class="invisible fixed inset-0 flex h-screen w-screen cursor-default flex-col bg-neutral-500/50 p-4 backdrop-blur-sm dark:bg-neutral-900/50 sm:p-6 md:p-[10vh] lg:p-[12vh] z-500" class="invisible fixed inset-0 flex h-screen w-screen cursor-default flex-col bg-neutral-500/50 p-4 backdrop-blur-sm dark:bg-neutral-900/50 sm:p-6 md:p-[10vh] lg:p-[12vh] z-500"
data-url="http://192.168.11.190:1313/"> data-url="http://localhost:1313/">
<div <div
id="search-modal" id="search-modal"
class="flex flex-col w-full max-w-3xl min-h-0 mx-auto border rounded-md shadow-lg top-20 border-neutral-200 bg-neutral dark:border-neutral-700 dark:bg-neutral-800"> class="flex flex-col w-full max-w-3xl min-h-0 mx-auto border rounded-md shadow-lg top-20 border-neutral-200 bg-neutral dark:border-neutral-700 dark:bg-neutral-800">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 KiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

View File

@ -28,7 +28,7 @@
<link rel="canonical" href="http://192.168.11.190:1313/posts/k3s-infrastructure/"> <link rel="canonical" href="http://localhost:1313/posts/k3s-infrastructure/">
@ -68,7 +68,7 @@
<meta property="og:url" content="http://192.168.11.190:1313/posts/k3s-infrastructure/"> <meta property="og:url" content="http://localhost:1313/posts/k3s-infrastructure/">
<meta property="og:site_name" content="Олег Казанин"> <meta property="og:site_name" content="Олег Казанин">
<meta property="og:title" content="Подготовить инфраструктуру для K3s в Proxmox"> <meta property="og:title" content="Подготовить инфраструктуру для K3s в Proxmox">
<meta property="og:description" content="Создаём 5 VM в Proxmox с Debian 12, настраиваем сеть, отключаем swap и готовим систему для K3s. Пошаговая инструкция без сюрпризов."> <meta property="og:description" content="Создаём 5 VM в Proxmox с Debian 12, настраиваем сеть, отключаем swap и готовим систему для K3s. Пошаговая инструкция без сюрпризов.">
@ -83,13 +83,13 @@
<meta property="article:tag" content="Proxmox"> <meta property="article:tag" content="Proxmox">
<meta property="article:tag" content="Infrastructure"> <meta property="article:tag" content="Infrastructure">
<meta property="article:tag" content="Debian"> <meta property="article:tag" content="Debian">
<meta property="og:image" content="http://192.168.11.190:1313/posts/k3s-infrastructure/featured.png"> <meta property="og:image" content="http://localhost:1313/posts/k3s-infrastructure/featured.png">
<meta property="og:see_also" content="http://192.168.11.190:1313/posts/k3s-installation/"> <meta property="og:see_also" content="http://localhost:1313/posts/k3s-installation/">
<meta property="og:see_also" content="http://192.168.11.190:1313/posts/k3s-architecture/"> <meta property="og:see_also" content="http://localhost:1313/posts/k3s-part1-architecture/">
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="http://192.168.11.190:1313/posts/k3s-infrastructure/featured.png"> <meta name="twitter:image" content="http://localhost:1313/posts/k3s-infrastructure/featured.png">
<meta name="twitter:title" content="Подготовить инфраструктуру для K3s в Proxmox"> <meta name="twitter:title" content="Подготовить инфраструктуру для K3s в Proxmox">
<meta name="twitter:description" content="Создаём 5 VM в Proxmox с Debian 12, настраиваем сеть, отключаем swap и готовим систему для K3s. Пошаговая инструкция без сюрпризов."> <meta name="twitter:description" content="Создаём 5 VM в Proxmox с Debian 12, настраиваем сеть, отключаем swap и готовим систему для K3s. Пошаговая инструкция без сюрпризов.">
@ -217,6 +217,39 @@
<script type="module" src="/js/firebase.min.cad74e0625f72f359ec6d6fed579b87733749de70400e7614048050ed08832ee3f58983d5d139fb1ddc5f7f2f5047d45ed80ec923534a3660fc3a7965f936866.js" integrity="sha512-ytdOBiX3LzWextb&#43;1Xm4dzN0necEAOdhQEgFDtCIMu4/WJg9XROfsd3F9/L1BH1F7YDskjU0o2YPw6eWX5NoZg=="></script>
<script id="firebase-config"
type="application/json"
data-views="views_posts/k3s-infrastructure/index.md"
data-likes="likes_posts/k3s-infrastructure/index.md">
{
"config": {
"apiKey": "AIzaSyBBfzADrGgnwTIyW67gfZSrAtkoybxvmdI",
"authDomain": "oakazanin-hugo-blowfish.firebaseapp.com",
"projectId": "oakazanin-hugo-blowfish",
"storageBucket": "oakazanin-hugo-blowfish.firebasestorage.app",
"messagingSenderId": "945151844512",
"appId": "1:945151844512:web:22602cc010f5b7e0cca9c5",
"measurementId": ""
}
}
</script>
@ -241,7 +274,7 @@
"headline": "Подготовить инфраструктуру для K3s в Proxmox", "headline": "Подготовить инфраструктуру для K3s в Proxmox",
"description": "Создаём 5 VM в Proxmox с Debian 12, настраиваем сеть, отключаем swap и готовим систему для K3s. Пошаговая инструкция без сюрпризов.", "description": "Создаём 5 VM в Proxmox с Debian 12, настраиваем сеть, отключаем swap и готовим систему для K3s. Пошаговая инструкция без сюрпризов.",
"inLanguage": "ru", "inLanguage": "ru",
"url" : "http://192.168.11.190:1313/posts/k3s-infrastructure/", "url" : "http://localhost:1313/posts/k3s-infrastructure/",
"author" : { "author" : {
"@type": "Person", "@type": "Person",
"name": "Олег Казанин" "name": "Олег Казанин"
@ -445,6 +478,83 @@
<article> <article>
<div id="hero" class="h-[150px] md:h-[200px]"></div>
<div class="fixed inset-x-0 top-0 h-[800px] single_hero_background nozoom">
<img
id="background-image"
src="/posts/k3s-infrastructure/featured_hu_16a8e5a35380d2ac.png"
role="presentation"
loading="eager"
decoding="async"
fetchpriority="high"
class="absolute inset-0 w-full h-full object-cover"
>
<div
class="absolute inset-0 bg-gradient-to-t from-neutral dark:from-neutral-800 to-transparent mix-blend-normal"></div>
<div
class="absolute inset-0 opacity-60 bg-gradient-to-t from-neutral dark:from-neutral-800 to-neutral-100 dark:to-neutral-800 mix-blend-normal"></div>
</div>
<div
id="background-blur"
class="fixed opacity-0 inset-x-0 top-0 h-full single_hero_background nozoom backdrop-blur-xl bg-neutral-100/75 dark:bg-neutral-800/60"></div>
<script
type="text/javascript"
src="/js/background-blur.min.605b3b942818f0ab5a717ae446135ec46b8ee5a2ad12ae56fb90dc2a76ce30c388f9fec8bcc18db15bd47e3fa8a09d779fa12aa9c184cf614a315bc72c6c163d.js"
integrity="sha512-YFs7lCgY8KtacXrkRhNexGuO5aKtEq5W&#43;5DcKnbOMMOI&#43;f7IvMGNsVvUfj&#43;ooJ13n6EqqcGEz2FKMVvHLGwWPQ=="
data-blur-id="background-blur"
data-image-id="background-image"
data-image-url="/posts/k3s-infrastructure/featured_hu_16a8e5a35380d2ac.png"></script>
<header id="single_header" class="mt-5 max-w-prose"> <header id="single_header" class="mt-5 max-w-prose">
@ -569,14 +679,6 @@
<span class="ps-2"><span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Черновик
</span>
</span>
</span>
</div> </div>
@ -950,7 +1052,7 @@
<div <div
class="py-1 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600"> class="py-1 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600">
<a href="/posts/k3s-architecture/"> <a href="/posts/k3s-part1-architecture/">
Часть 1: Часть 1:
K3s HA для homelab: архитектура без боли K3s HA для homelab: архитектура без боли
</a> </a>
@ -1768,7 +1870,7 @@
<div <div
class="py-1 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600"> class="py-1 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600">
<a href="/posts/k3s-architecture/"> <a href="/posts/k3s-part1-architecture/">
Часть 1: Часть 1:
K3s HA для homelab: архитектура без боли K3s HA для homelab: архитектура без боли
</a> </a>
@ -1801,6 +1903,283 @@
<h2 class="mt-8 text-2xl font-extrabold mb-10">Статьи по теме</h2>
<section class="w-full grid gap-4 sm:grid-cols-2 md:grid-cols-3">
<article
class="article-link--related relative min-h-full min-w-full overflow-hidden rounded-lg border border-neutral-300 dark:border-neutral-600">
<div class="flex-none relative overflow-hidden thumbnail_card_related">
<img
src="/posts/k3s-part1-architecture/featured_hu_93e9342ed126d912.png"
role="presentation"
loading="lazy"
decoding="async"
fetchpriority="low"
class="not-prose absolute inset-0 w-full h-full object-cover">
</div>
<div class="p-4">
<header>
<a
href="/posts/k3s-part1-architecture/"
class="not-prose before:absolute before:inset-0 decoration-primary-500 dark:text-neutral text-xl font-bold text-neutral-800 hover:underline hover:underline-offset-2">
<h2>
K3s HA для homelab: архитектура без боли
</h2>
</a>
</header>
<div class="text-sm text-neutral-500 dark:text-neutral-400">
<div class="flex flex-row flex-wrap items-center">
<time datetime="2025-10-14T00:00:00&#43;00:00">14 октября 2025</time><span class="px-2 text-primary-500">&middot;</span><span title="Время чтения">8 минут</span><span class="px-2 text-primary-500">&middot;</span><span>
<span
id="views_posts/k3s-part1-architecture/index.md"
class="animate-pulse inline-block text-transparent max-h-3 rounded-full -mt-[2px] align-middle bg-neutral-300 dark:bg-neutral-400"
title="views"
>loading</span
>
<span class="inline-block align-text-bottom"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path fill="currentColor" d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z"/></svg></span></span>
</span>
<span class="px-2 text-primary-500">&middot;</span><span>
<span
id="likes_posts/k3s-part1-architecture/index.md"
class="animate-pulse inline-block text-transparent max-h-3 rounded-full -mt-[2px] align-middle bg-neutral-300 dark:bg-neutral-400"
title="likes"
>loading</span
>
<span class="inline-block align-text-bottom"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg></span></span>
</span>
</div>
<div class="flex flex-row flex-wrap items-center">
<a class="relative mt-[0.5rem] me-2" href="/tags/kubernetes/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Kubernetes
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/k3s/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
K3s
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/homelab/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Homelab
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/proxmox/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Proxmox
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/architecture/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Architecture
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/ha/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Ha
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/devops/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Devops
</span>
</span>
</a>
</div>
</div>
</div>
<div class="px-6 pt-4 pb-2"></div>
</article>
</section>
</div> </div>
</section> </section>
@ -1819,7 +2198,7 @@
<a <a
class="flex text-neutral-700 hover:text-primary-600 dark:text-neutral dark:hover:text-primary-400" class="flex text-neutral-700 hover:text-primary-600 dark:text-neutral dark:hover:text-primary-400"
href="/posts/k3s-architecture/"> href="/posts/k3s-part1-architecture/">
<span class="leading-6"> <span class="leading-6">
<span class="inline-block rtl:rotate-180">&larr;</span>&ensp;K3s HA для homelab: архитектура без боли <span class="inline-block rtl:rotate-180">&larr;</span>&ensp;K3s HA для homelab: архитектура без боли
</span> </span>
@ -1963,7 +2342,7 @@
<div <div
id="search-wrapper" id="search-wrapper"
class="invisible fixed inset-0 flex h-screen w-screen cursor-default flex-col bg-neutral-500/50 p-4 backdrop-blur-sm dark:bg-neutral-900/50 sm:p-6 md:p-[10vh] lg:p-[12vh] z-500" class="invisible fixed inset-0 flex h-screen w-screen cursor-default flex-col bg-neutral-500/50 p-4 backdrop-blur-sm dark:bg-neutral-900/50 sm:p-6 md:p-[10vh] lg:p-[12vh] z-500"
data-url="http://192.168.11.190:1313/"> data-url="http://localhost:1313/">
<div <div
id="search-modal" id="search-modal"
class="flex flex-col w-full max-w-3xl min-h-0 mx-auto border rounded-md shadow-lg top-20 border-neutral-200 bg-neutral dark:border-neutral-700 dark:bg-neutral-800"> class="flex flex-col w-full max-w-3xl min-h-0 mx-auto border rounded-md shadow-lg top-20 border-neutral-200 bg-neutral dark:border-neutral-700 dark:bg-neutral-800">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 KiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 741 KiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

View File

@ -28,7 +28,7 @@
<link rel="canonical" href="http://192.168.11.190:1313/posts/k3s-installation/"> <link rel="canonical" href="http://localhost:1313/posts/k3s-installation/">
@ -68,7 +68,7 @@
<meta property="og:url" content="http://192.168.11.190:1313/posts/k3s-installation/"> <meta property="og:url" content="http://localhost:1313/posts/k3s-installation/">
<meta property="og:site_name" content="Олег Казанин"> <meta property="og:site_name" content="Олег Казанин">
<meta property="og:title" content="Установить K3s HA кластер"> <meta property="og:title" content="Установить K3s HA кластер">
<meta property="og:description" content="Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер. Устанавливаем K3s на 3 master и 2 worker ноды, настраиваем kubectl."> <meta property="og:description" content="Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер. Устанавливаем K3s на 3 master и 2 worker ноды, настраиваем kubectl.">
@ -83,13 +83,13 @@
<meta property="article:tag" content="Installation"> <meta property="article:tag" content="Installation">
<meta property="article:tag" content="Ha"> <meta property="article:tag" content="Ha">
<meta property="article:tag" content="Etcd"> <meta property="article:tag" content="Etcd">
<meta property="og:image" content="http://192.168.11.190:1313/posts/k3s-installation/featured.png"> <meta property="og:image" content="http://localhost:1313/posts/k3s-installation/featured.png">
<meta property="og:see_also" content="http://192.168.11.190:1313/posts/k3s-infrastructure/"> <meta property="og:see_also" content="http://localhost:1313/posts/k3s-part2-infrastructure/">
<meta property="og:see_also" content="http://192.168.11.190:1313/posts/k3s-architecture/"> <meta property="og:see_also" content="http://localhost:1313/posts/k3s-part1-architecture/">
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="http://192.168.11.190:1313/posts/k3s-installation/featured.png"> <meta name="twitter:image" content="http://localhost:1313/posts/k3s-installation/featured.png">
<meta name="twitter:title" content="Установить K3s HA кластер"> <meta name="twitter:title" content="Установить K3s HA кластер">
<meta name="twitter:description" content="Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер. Устанавливаем K3s на 3 master и 2 worker ноды, настраиваем kubectl."> <meta name="twitter:description" content="Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер. Устанавливаем K3s на 3 master и 2 worker ноды, настраиваем kubectl.">
@ -217,6 +217,39 @@
<script type="module" src="/js/firebase.min.cad74e0625f72f359ec6d6fed579b87733749de70400e7614048050ed08832ee3f58983d5d139fb1ddc5f7f2f5047d45ed80ec923534a3660fc3a7965f936866.js" integrity="sha512-ytdOBiX3LzWextb&#43;1Xm4dzN0necEAOdhQEgFDtCIMu4/WJg9XROfsd3F9/L1BH1F7YDskjU0o2YPw6eWX5NoZg=="></script>
<script id="firebase-config"
type="application/json"
data-views="views_posts/k3s-installation/index.md"
data-likes="likes_posts/k3s-installation/index.md">
{
"config": {
"apiKey": "AIzaSyBBfzADrGgnwTIyW67gfZSrAtkoybxvmdI",
"authDomain": "oakazanin-hugo-blowfish.firebaseapp.com",
"projectId": "oakazanin-hugo-blowfish",
"storageBucket": "oakazanin-hugo-blowfish.firebasestorage.app",
"messagingSenderId": "945151844512",
"appId": "1:945151844512:web:22602cc010f5b7e0cca9c5",
"measurementId": ""
}
}
</script>
@ -241,7 +274,7 @@
"headline": "Установить K3s HA кластер", "headline": "Установить K3s HA кластер",
"description": "Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер. Устанавливаем K3s на 3 master и 2 worker ноды, настраиваем kubectl.", "description": "Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер. Устанавливаем K3s на 3 master и 2 worker ноды, настраиваем kubectl.",
"inLanguage": "ru", "inLanguage": "ru",
"url" : "http://192.168.11.190:1313/posts/k3s-installation/", "url" : "http://localhost:1313/posts/k3s-installation/",
"author" : { "author" : {
"@type": "Person", "@type": "Person",
"name": "Олег Казанин" "name": "Олег Казанин"
@ -445,6 +478,83 @@
<article> <article>
<div id="hero" class="h-[150px] md:h-[200px]"></div>
<div class="fixed inset-x-0 top-0 h-[800px] single_hero_background nozoom">
<img
id="background-image"
src="/posts/k3s-installation/featured_hu_535e29ddbb3baa24.png"
role="presentation"
loading="eager"
decoding="async"
fetchpriority="high"
class="absolute inset-0 w-full h-full object-cover"
>
<div
class="absolute inset-0 bg-gradient-to-t from-neutral dark:from-neutral-800 to-transparent mix-blend-normal"></div>
<div
class="absolute inset-0 opacity-60 bg-gradient-to-t from-neutral dark:from-neutral-800 to-neutral-100 dark:to-neutral-800 mix-blend-normal"></div>
</div>
<div
id="background-blur"
class="fixed opacity-0 inset-x-0 top-0 h-full single_hero_background nozoom backdrop-blur-xl bg-neutral-100/75 dark:bg-neutral-800/60"></div>
<script
type="text/javascript"
src="/js/background-blur.min.605b3b942818f0ab5a717ae446135ec46b8ee5a2ad12ae56fb90dc2a76ce30c388f9fec8bcc18db15bd47e3fa8a09d779fa12aa9c184cf614a315bc72c6c163d.js"
integrity="sha512-YFs7lCgY8KtacXrkRhNexGuO5aKtEq5W&#43;5DcKnbOMMOI&#43;f7IvMGNsVvUfj&#43;ooJ13n6EqqcGEz2FKMVvHLGwWPQ=="
data-blur-id="background-blur"
data-image-id="background-image"
data-image-url="/posts/k3s-installation/featured_hu_535e29ddbb3baa24.png"></script>
<header id="single_header" class="mt-5 max-w-prose"> <header id="single_header" class="mt-5 max-w-prose">
@ -569,14 +679,6 @@
<span class="ps-2"><span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Черновик
</span>
</span>
</span>
</div> </div>
@ -966,7 +1068,7 @@
<div <div
class="py-1 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600"> class="py-1 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600">
<a href="/posts/k3s-architecture/"> <a href="/posts/k3s-part1-architecture/">
Часть 1: Часть 1:
K3s HA для homelab: архитектура без боли K3s HA для homelab: архитектура без боли
</a> </a>
@ -976,7 +1078,7 @@
<div <div
class="py-1 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600"> class="py-1 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600">
<a href="/posts/k3s-infrastructure/"> <a href="/posts/k3s-part2-infrastructure/">
Часть 2: Часть 2:
Подготовить инфраструктуру для K3s в Proxmox Подготовить инфраструктуру для K3s в Proxmox
</a> </a>
@ -1827,7 +1929,7 @@
<div <div
class="py-1 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600"> class="py-1 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600">
<a href="/posts/k3s-architecture/"> <a href="/posts/k3s-part1-architecture/">
Часть 1: Часть 1:
K3s HA для homelab: архитектура без боли K3s HA для homelab: архитектура без боли
</a> </a>
@ -1837,7 +1939,7 @@
<div <div
class="py-1 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600"> class="py-1 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600">
<a href="/posts/k3s-infrastructure/"> <a href="/posts/k3s-part2-infrastructure/">
Часть 2: Часть 2:
Подготовить инфраструктуру для K3s в Proxmox Подготовить инфраструктуру для K3s в Proxmox
</a> </a>
@ -1860,6 +1962,552 @@
<h2 class="mt-8 text-2xl font-extrabold mb-10">Статьи по теме</h2>
<section class="w-full grid gap-4 sm:grid-cols-2 md:grid-cols-3">
<article
class="article-link--related relative min-h-full min-w-full overflow-hidden rounded-lg border border-neutral-300 dark:border-neutral-600">
<div class="flex-none relative overflow-hidden thumbnail_card_related">
<img
src="/posts/k3s-part1-architecture/featured_hu_93e9342ed126d912.png"
role="presentation"
loading="lazy"
decoding="async"
fetchpriority="low"
class="not-prose absolute inset-0 w-full h-full object-cover">
</div>
<div class="p-4">
<header>
<a
href="/posts/k3s-part1-architecture/"
class="not-prose before:absolute before:inset-0 decoration-primary-500 dark:text-neutral text-xl font-bold text-neutral-800 hover:underline hover:underline-offset-2">
<h2>
K3s HA для homelab: архитектура без боли
</h2>
</a>
</header>
<div class="text-sm text-neutral-500 dark:text-neutral-400">
<div class="flex flex-row flex-wrap items-center">
<time datetime="2025-10-14T00:00:00&#43;00:00">14 октября 2025</time><span class="px-2 text-primary-500">&middot;</span><span title="Время чтения">8 минут</span><span class="px-2 text-primary-500">&middot;</span><span>
<span
id="views_posts/k3s-part1-architecture/index.md"
class="animate-pulse inline-block text-transparent max-h-3 rounded-full -mt-[2px] align-middle bg-neutral-300 dark:bg-neutral-400"
title="views"
>loading</span
>
<span class="inline-block align-text-bottom"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path fill="currentColor" d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z"/></svg></span></span>
</span>
<span class="px-2 text-primary-500">&middot;</span><span>
<span
id="likes_posts/k3s-part1-architecture/index.md"
class="animate-pulse inline-block text-transparent max-h-3 rounded-full -mt-[2px] align-middle bg-neutral-300 dark:bg-neutral-400"
title="likes"
>loading</span
>
<span class="inline-block align-text-bottom"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg></span></span>
</span>
</div>
<div class="flex flex-row flex-wrap items-center">
<a class="relative mt-[0.5rem] me-2" href="/tags/kubernetes/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Kubernetes
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/k3s/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
K3s
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/homelab/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Homelab
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/proxmox/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Proxmox
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/architecture/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Architecture
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/ha/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Ha
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/devops/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Devops
</span>
</span>
</a>
</div>
</div>
</div>
<div class="px-6 pt-4 pb-2"></div>
</article>
<article
class="article-link--related relative min-h-full min-w-full overflow-hidden rounded-lg border border-neutral-300 dark:border-neutral-600">
<div class="flex-none relative overflow-hidden thumbnail_card_related">
<img
src="/posts/k3s-part2-infrastructure/featured_hu_fa55fce8dafb1970.png"
role="presentation"
loading="lazy"
decoding="async"
fetchpriority="low"
class="not-prose absolute inset-0 w-full h-full object-cover">
</div>
<div class="p-4">
<header>
<a
href="/posts/k3s-part2-infrastructure/"
class="not-prose before:absolute before:inset-0 decoration-primary-500 dark:text-neutral text-xl font-bold text-neutral-800 hover:underline hover:underline-offset-2">
<h2>
Подготовить инфраструктуру для K3s в Proxmox
</h2>
</a>
</header>
<div class="text-sm text-neutral-500 dark:text-neutral-400">
<div class="flex flex-row flex-wrap items-center">
<time datetime="2025-10-21T00:00:00&#43;00:00">21 октября 2025</time><span class="px-2 text-primary-500">&middot;</span><span title="Время чтения">10 минут</span><span class="px-2 text-primary-500">&middot;</span><span>
<span
id="views_posts/k3s-part2-infrastructure/index.md"
class="animate-pulse inline-block text-transparent max-h-3 rounded-full -mt-[2px] align-middle bg-neutral-300 dark:bg-neutral-400"
title="views"
>loading</span
>
<span class="inline-block align-text-bottom"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path fill="currentColor" d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z"/></svg></span></span>
</span>
<span class="px-2 text-primary-500">&middot;</span><span>
<span
id="likes_posts/k3s-part2-infrastructure/index.md"
class="animate-pulse inline-block text-transparent max-h-3 rounded-full -mt-[2px] align-middle bg-neutral-300 dark:bg-neutral-400"
title="likes"
>loading</span
>
<span class="inline-block align-text-bottom"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg></span></span>
</span>
</div>
<div class="flex flex-row flex-wrap items-center">
<a class="relative mt-[0.5rem] me-2" href="/tags/kubernetes/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Kubernetes
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/k3s/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
K3s
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/homelab/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Homelab
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/proxmox/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Proxmox
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/infrastructure/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Infrastructure
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/debian/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Debian
</span>
</span>
</a>
<a class="relative mt-[0.5rem] me-2" href="/tags/devops/">
<span class="flex cursor-pointer">
<span
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
Devops
</span>
</span>
</a>
</div>
</div>
</div>
<div class="px-6 pt-4 pb-2"></div>
</article>
</section>
</div> </div>
</section> </section>
@ -1878,7 +2526,7 @@
<a <a
class="flex text-neutral-700 hover:text-primary-600 dark:text-neutral dark:hover:text-primary-400" class="flex text-neutral-700 hover:text-primary-600 dark:text-neutral dark:hover:text-primary-400"
href="/posts/k3s-infrastructure/"> href="/posts/k3s-part2-infrastructure/">
<span class="leading-6"> <span class="leading-6">
<span class="inline-block rtl:rotate-180">&larr;</span>&ensp;Подготовить инфраструктуру для K3s в Proxmox <span class="inline-block rtl:rotate-180">&larr;</span>&ensp;Подготовить инфраструктуру для K3s в Proxmox
</span> </span>
@ -1892,6 +2540,19 @@
</span> </span>
<span class="flex flex-col items-end"> <span class="flex flex-col items-end">
<a
class="flex text-right text-neutral-700 hover:text-primary-600 dark:text-neutral dark:hover:text-primary-400"
href="/posts/blog-part-1-architecture/">
<span class="leading-6">
Блог на Hugo в K3s: часть 1 - архитектура и первый запуск&ensp;<span class="inline-block rtl:rotate-180">&rarr;</span>
</span>
</a>
<span class="me-6 mt-1 text-xs text-neutral-500 dark:text-neutral-400">
<time datetime="2026-01-03T00:00:00&#43;00:00">3 января 2026</time>
</span>
</span> </span>
</div> </div>
</div> </div>
@ -2009,7 +2670,7 @@
<div <div
id="search-wrapper" id="search-wrapper"
class="invisible fixed inset-0 flex h-screen w-screen cursor-default flex-col bg-neutral-500/50 p-4 backdrop-blur-sm dark:bg-neutral-900/50 sm:p-6 md:p-[10vh] lg:p-[12vh] z-500" class="invisible fixed inset-0 flex h-screen w-screen cursor-default flex-col bg-neutral-500/50 p-4 backdrop-blur-sm dark:bg-neutral-900/50 sm:p-6 md:p-[10vh] lg:p-[12vh] z-500"
data-url="http://192.168.11.190:1313/"> data-url="http://localhost:1313/">
<div <div
id="search-modal" id="search-modal"
class="flex flex-col w-full max-w-3xl min-h-0 mx-auto border rounded-md shadow-lg top-20 border-neutral-200 bg-neutral dark:border-neutral-700 dark:bg-neutral-800"> class="flex flex-col w-full max-w-3xl min-h-0 mx-auto border rounded-md shadow-lg top-20 border-neutral-200 bg-neutral dark:border-neutral-700 dark:bg-neutral-800">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 741 KiB

After

Width:  |  Height:  |  Size: 1.4 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

Some files were not shown because too many files have changed in this diff Show More