diff --git a/README.md b/README.md index 8e72f50..2a60550 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -Production webhook test: Sat Feb 14 09:52:40 UTC 2026 +Webhook test: Sat Feb 14 09:49:12 UTC 2026 diff --git a/assets/img/background.png b/assets/img/background.png new file mode 100644 index 0000000..a34bcb3 Binary files /dev/null and b/assets/img/background.png differ diff --git a/assets/img/logo.png b/assets/img/logo.png new file mode 100644 index 0000000..4746e27 Binary files /dev/null and b/assets/img/logo.png differ diff --git a/assets/img/logo_inverted.png b/assets/img/logo_inverted.png new file mode 100644 index 0000000..0ed1b4a Binary files /dev/null and b/assets/img/logo_inverted.png differ diff --git a/assets/img/profile.png b/assets/img/profile.png new file mode 100644 index 0000000..40dfc16 Binary files /dev/null and b/assets/img/profile.png differ diff --git a/config/_default/hugo.toml b/config/_default/hugo.toml index 7224251..ccb6d31 100644 --- a/config/_default/hugo.toml +++ b/config/_default/hugo.toml @@ -2,11 +2,11 @@ # Refer to the theme docs for more details about each of these parameters. # https://blowfish.page/docs/getting-started/ -theme = "blowfish" # UNCOMMENT THIS LINE +theme = "blowfish" baseURL = "https://oakazanin.ru/" defaultContentLanguage = "ru" -# pluralizeListTitles = "true" # hugo function useful for non-english languages, find out more in https://gohugo.io/getting-started/configuration/#pluralizelisttitles +pluralizeListTitles = "true" # hugo function useful for non-english languages, find out more in https://gohugo.io/getting-started/configuration/#pluralizelisttitles enableRobotsTXT = true summaryLength = 0 diff --git a/config/_default/languages.ru.toml b/config/_default/languages.ru.toml index 2809481..c428715 100644 --- a/config/_default/languages.ru.toml +++ b/config/_default/languages.ru.toml @@ -9,16 +9,16 @@ title = "Олег Казанин" isoCode = "ru" rtl = false dateFormat = "2 January 2006" - # logo = "img/logo.png" - # secondaryLogo = "img/secondary-logo.png" + logo = "img/logo.png" + secondaryLogo = "img/logo_inverted.png" # description = "My awesome website" # copyright = "Copy, _right?_ :thinking_face:" -# [params.author] + [params.author] name = "Олег Казанин" email = "oakazanin@ya.ru" -# image = "img/blowfish_logo.png" -# imageQuality = 96 + image = "img/profile.png" + # imageQuality = 96 headline = "Infrastructure Engineer | Linux & Open Source" bio = "Строю полезную инфраструктуру на Open Source стеке. Документирую грабли, чтобы вы на них не наступали." links = [ diff --git a/config/_default/menus.ru.toml b/config/_default/menus.ru.toml index c1514fe..599460b 100644 --- a/config/_default/menus.ru.toml +++ b/config/_default/menus.ru.toml @@ -11,30 +11,30 @@ # по weight от меньшего к большему. -[[main]] - name = "О блоге" - pageRef = "about" - weight = 10 +#[[main]] +# name = "О блоге" +# pageRef = "about" +# weight = 10 -[[main]] - name = "Статьи" - pageRef = "posts" - weight = 20 +#[[main]] +# name = "Статьи" +# pageRef = "posts" +# weight = 20 -[[main]] - name = "Проекты" - pageRef = "projects" - weight = 30 +#[[main]] +# name = "Проекты" +# pageRef = "projects" +# weight = 30 -[[main]] - name = "Резюме" - pageRef = "resume" - weight = 40 +#[[main]] +# name = "Резюме" +# pageRef = "resume" +# weight = 40 -[[main]] - name = "Музыка" - pageRef = "music" - weight = 50 +#[[main]] +# name = "Музыка" +# pageRef = "music" +# weight = 50 #[[main]] # name = "Blog" diff --git a/config/_default/params.toml b/config/_default/params.toml index 8b0bf26..8c9920f 100644 --- a/config/_default/params.toml +++ b/config/_default/params.toml @@ -6,12 +6,12 @@ # https://blowfish.page/docs/configuration/#theme-parameters colorScheme = "blowfish" -defaultAppearance = "light" # valid options: light or dark -autoSwitchAppearance = true +defaultAppearance = "dark" # valid options: light or dark +autoSwitchAppearance = false enableA11y = false enableSearch = true -enableCodeCopy = false +enableCodeCopy = true enableStructuredBreadcrumbs = false # enableStyledScrollbar = true # disable to use native scrollbar style (defaults to true) @@ -22,11 +22,11 @@ replyByEmail = false disableImageOptimization = false disableImageOptimizationMD = false -disableTextInHeader = false +disableTextInHeader = true # backgroundImageWidth = 1200 -# defaultBackgroundImage = "IMAGE.jpg" # used as default for background images -# defaultFeaturedImage = "IMAGE.jpg" # used as default for featured images in all articles +defaultBackgroundImage = "/img/background.png" # used as default for background images +defaultFeaturedImage = "/img/background.png" # used as default for featured images in all articles # defaultSocialImage = "/android-chrome-512x512.png" # used as default for social media sharing (Open Graph and Twitter) hotlinkFeatureImage = false # imagePosition = "50% 50%" @@ -41,7 +41,7 @@ giteaDefaultServer = "https://git.fsfe.org" forgejoDefaultServer = "https://v11.next.forgejo.org" [header] - layout = "basic" # valid options: basic, fixed, fixed-fill, fixed-gradient, fixed-fill-blur + layout = "fixed" # valid options: basic, fixed, fixed-fill, fixed-gradient, fixed-fill-blur [footer] showMenu = true @@ -51,21 +51,21 @@ forgejoDefaultServer = "https://v11.next.forgejo.org" showScrollToTop = true [homepage] - layout = "profile" # valid options: page, profile, hero, card, background, custom + layout = "background" # valid options: page, profile, hero, card, background, custom #homepageImage = "IMAGE.jpg" # used in: hero, and card - showRecent = false - showRecentItems = 5 - showMoreLink = false + showRecent = true + showRecentItems = 18 + showMoreLink = true showMoreLinkDest = "/posts/" - cardView = false + cardView = true cardViewScreenWidth = false - layoutBackgroundBlur = false # only used when layout equals background + layoutBackgroundBlur = true # only used when layout equals background disableHeroImageFilter = false # only used when layout equals hero [article] showDate = true - showViews = false - showLikes = false + showViews = true + showLikes = true showDateOnlyInArticle = false showDateUpdated = false showAuthor = true @@ -79,18 +79,18 @@ forgejoDefaultServer = "https://v11.next.forgejo.org" showEdit = false # editURL = "https://github.com/username/repo/" editAppendPath = true - seriesOpened = false + seriesOpened = true showHeadingAnchors = true showPagination = true invertPagination = false showReadingTime = true - showTableOfContents = false + showTableOfContents = true # showRelatedContent = false # relatedContentLimit = 3 - showTaxonomies = false # use showTaxonomies OR showCategoryOnly, not both + showTaxonomies = true # use showTaxonomies OR showCategoryOnly, not both showCategoryOnly = false # use showTaxonomies OR showCategoryOnly, not both showAuthorsBadges = false - showWordCount = true + showWordCount = false # sharingLinks = [ "linkedin", "twitter", "bluesky", "mastodon", "reddit", "pinterest", "facebook", "email", "whatsapp", "telegram", "line"] showZenMode = false # externalLinkForceNewTab = false # disable to allow external links in the same tab (defaults to true) diff --git a/content/posts/hello-world/index.md b/content/posts/hello-world/index.md deleted file mode 100644 index 725b54b..0000000 --- a/content/posts/hello-world/index.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: "Hello World" -date: 2026-02-13 -draft: false -description: "Первый пост на новом сайте" -tags: ["test"] ---- - -## Привет! - -Это первый пост на новом сайте **oakazanin.ru**. - -Сайт работает на Hugo + Blowfish теме. \ No newline at end of file diff --git a/content/posts/k3s-architecture/etcd.svg b/content/posts/k3s-architecture/etcd.svg new file mode 100644 index 0000000..7cb0d79 --- /dev/null +++ b/content/posts/k3s-architecture/etcd.svg @@ -0,0 +1 @@ +

Embedded etcd (K3s) — 3 VM

raft

raft

raft

Master 1
API+Scheduler+Controller
+ etcd

Master 2
API+Scheduler+Controller
+ etcd

Master 3
API+Scheduler+Controller
+ etcd

External etcd — 6 VM

etcd Cluster (3 VM)

Control Plane (3 VM)

etcd-1

Master 1
API+Scheduler+Controller

etcd-2

etcd-3

Master 2
API+Scheduler+Controller

Master 3
API+Scheduler+Controller

\ No newline at end of file diff --git a/content/posts/k3s-architecture/featured.png b/content/posts/k3s-architecture/featured.png new file mode 100644 index 0000000..71196ef Binary files /dev/null and b/content/posts/k3s-architecture/featured.png differ diff --git a/content/posts/k3s-architecture/final_arch.svg b/content/posts/k3s-architecture/final_arch.svg new file mode 100644 index 0000000..20713a6 --- /dev/null +++ b/content/posts/k3s-architecture/final_arch.svg @@ -0,0 +1 @@ +

Workers

Control Plane (HA)

etcd raft

etcd raft

etcd raft

API :6443

API :6443

API :6443

API :6443

API :6443

API :6443

k3s-master-1
192.168.11.201
─────────
API Server
etcd
Scheduler
Controller
─────────
2 vCPU / 8GB

k3s-master-2
192.168.11.202
─────────
API Server
etcd
Scheduler
Controller
─────────
2 vCPU / 8GB

k3s-master-3
192.168.11.203
─────────
API Server
etcd
Scheduler
Controller
─────────
2 vCPU / 8GB

k3s-worker-1
192.168.11.210
─────────
Kubelet
Ваши приложения
─────────
4 vCPU / 16GB

k3s-worker-2
192.168.11.211
─────────
Kubelet
Ваши приложения
─────────
4 vCPU / 16GB

\ No newline at end of file diff --git a/content/posts/k3s-architecture/index.md b/content/posts/k3s-architecture/index.md new file mode 100644 index 0000000..d9bdc06 --- /dev/null +++ b/content/posts/k3s-architecture/index.md @@ -0,0 +1,374 @@ +--- +title: "K3s HA для homelab: архитектура без боли" +date: 2025-10-14 +draft: false +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"] +series: ["K3s HA кластер для homelab"] +series_order: 1 +# seriesOpened: false +# showTableOfContents: true +showAuthor: true +--- + +Kubernetes слишком тяжёлый, Docker Swarm мёртв, а хочется нормальный кластер для экспериментов. Знакомо? K3s решает эту проблему - полноценный Kubernetes в бинарнике на 50MB вместо 1.5GB зависимостей. Но без правильного планирования вы получите нестабильную конструкцию, которая падает в самый неподходящий момент. + +В этой статье разберём архитектуру K3s HA кластера: почему именно 3 master ноды, зачем embedded etcd и сколько ресурсов закладывать. В конце - готовый план для установки. + +**Результат:** понимание архитектуры + таблица ресурсов + сетевая схема. Всё, что нужно перед тем, как создавать VM. + +--- + +## Для кого это + +**Подходит:** +- Знаком с базовыми концепциями Kubernetes (pod, service, deployment) +- Есть Proxmox с 14+ vCPU и 56GB+ RAM +- Хочешь понять *что* устанавливать, прежде чем устанавливать + +**Не подходит:** +- Нужна одна нода для экспериментов - достаточно docker-compose или K3s single-node +- Ищешь managed Kubernetes для бизнеса - смотри в сторону Yandex Cloud или VK Cloud +- Хочешь сразу команды без теории - переходи к статье 2 + +--- + +## K3s vs Kubernetes: в чём разница + +**Kubernetes (K8s)** - оркестратор контейнеров, стандарт индустрии. Добро пожаловать в enterprise, где для запуска трёх контейнеров нужно поддерживать шесть виртуальных машин. + +**K3s** - тот же Kubernetes, но кто-то в Rancher (теперь SUSE) задумался: "А что если выкинуть всё, что нужно только Сберу и Yandex Cloud?" + +![Kubernetes](k8s-architecture.svg "**Kubernetes**") +![K3s](k3s-architecture.svg "**K3s**") + + +**Что выкинули:** + +- Интеграции с облачными провайдерами (вы же не в VK Cloud) +- Legacy API (вы же не мигрируете кластер 2016 года) +- Встроенные драйверы хранилищ на все случаи жизни (вы же не используете 47 типов СХД) +- Альфа/бета-функции (нестабильные эксперименты) + +**Что осталось:** полноценный Kubernetes, сертифицированный CNCF (Cloud Native Computing Foundation - организация, которая решает, что считать "настоящим" Kubernetes). Все манифесты работают. Helm работает. kubectl работает. Ответы со StackOverflow работают. + +### Сравнение в цифрах + +| Характеристика | Kubernetes | K3s | +|----------------|------------|-----| +| Размер | ~1.5GB образы | 50MB бинарник | +| RAM на control plane | ~2GB на ноду | ~500MB на ноду | +| Установка | kubeadm, 10+ шагов | один curl-скрипт | +| etcd | Отдельный кластер (3+ VM) | Встроенный | +| CNI | Нужно устанавливать | Flannel из коробки | +| Совместимость | 100% | 100% | + +*"Но я потеряю гибкость!"* - скажете вы. Да, вы не сможете заменить сетевой плагин Flannel без пересборки. Это критично примерно для одного проекта из тысячи, и ваш homelab в их число не входит. + +**Вердикт:** для homelab K3s - очевидный выбор. Теряем 5% гибкости, получаем 90% простоты. + +--- + +## Что такое High Availability и зачем оно вам + +**HA (High Availability)** - способность системы продолжать работу при отказе компонентов. Звучит как enterprise-термин для больших компаний? На практике это разница между "кластер упал в субботу, но я починил в понедельник" и "кластер сам пережил падение ноды, пока я спал". + +### Без HA (single node) + +``` +┌─────────────────┐ +│ K3s Master │ ← Единственная точка отказа +│ + Worker │ +└─────────────────┘ + +Нода упала → кластер мёртв → ваши сервисы недоступны +``` + +### С HA (3+ master nodes) + +``` +┌──────────┐ ┌──────────┐ ┌──────────┐ +│ Master 1 │ │ Master 2 │ │ Master 3 │ +│ + etcd │ │ + etcd │ │ + etcd │ +└──────────┘ └──────────┘ └──────────┘ + ↓ ↓ ↓ +┌──────────┐ ┌──────────┐ +│ Worker 1 │ │ Worker 2 │ +└──────────┘ └──────────┘ + +Одна master упала → кластер работает +Один worker упал → поды переехали на другой +``` + +### Сколько master нод нужно + +Вот тут начинается интересное. Интуиция подсказывает: одна нода - плохо, две - уже лучше. Логично? Логично. И неправильно. + +| Master нод | Выдержит отказов | Кворум | Вердикт | +|------------|------------------|--------|---------| +| 1 | 0 | 1/1 | Нет HA, но честно | +| 2 | 0 | **Ловушка!** | ⛔ Хуже, чем 1 | +| 3 | 1 | 2/3 | ✅ Минимум для HA | +| 5 | 2 | 3/5 | Для критичных систем | + +### Почему 2 master ноды хуже, чем 1 + +etcd (база данных кластера, где хранится вообще всё) работает по принципу голосования. Чтобы записать данные, нужно согласие большинства нод. Не "хотя бы одной" - именно большинства. + +Считаем: + +- **1 нода:** большинство = 1. Упала - кластер мёртв. Честная игра, вы знали на что шли. +- **2 ноды:** большинство = 2. Упала одна - кворума нет, кластер мёртв. Сюрприз! +- **3 ноды:** большинство = 2. Одна упала - две оставшиеся продолжают работать. + +Это как договор, требующий подписи обоих директоров - заболел один, и компания парализована. + +С двумя нодами вы не получили отказоустойчивость. Вы удвоили количество точек отказа и назвали это "высокой доступностью". + +![High Availability](k3s-ha.svg) + +**Правило:** или 1 нода (и честное понимание рисков), или 3+ (и настоящий HA). Двойка - ловушка для тех, кто не дочитал документацию. + +--- + +## Embedded etcd vs External etcd + +**etcd** - распределённое key-value хранилище. Единственный источник истины для всего состояния Kubernetes: все объекты (поды, сервисы, секреты), конфигурации, сетевые политики. Без etcd кластер не работает. Точка. + +Есть два варианта архитектуры: + +### External etcd (классический Kubernetes) + +``` +Control Plane (3 VM) etcd кластер (3 VM) +┌────────────────┐ ┌──────────────┐ +│ API Server │ │ etcd-1 │ +│ Scheduler │ ──────────────>│ (только etcd)│ +│ Controller │ └──────────────┘ +└────────────────┘ ┌──────────────┐ +┌────────────────┐ │ etcd-2 │ +│ API Server │ ──────────────>│ (только etcd)│ +│ Scheduler │ └──────────────┘ +│ Controller │ ┌──────────────┐ +└────────────────┘ │ etcd-3 │ +┌────────────────┐ │ (только etcd)│ +│ API Server │ ──────────────>└──────────────┘ +│ Scheduler │ +│ Controller │ +└────────────────┘ + +Итого: 6 виртуальных машин +``` + +### Embedded etcd (K3s) + +``` +┌─────────────────────────┐ +│ K3s Master 1 │ +│ ┌───────────────────┐ │ +│ │ API + Scheduler │ │ +│ │ + Controller │ │ +│ └───────────────────┘ │ +│ ┌───────────────────┐ │ +│ │ etcd (встроенный) │◄─┼──┐ +│ └───────────────────┘ │ │ +└─────────────────────────┘ │ Raft protocol +┌─────────────────────────┐ │ (синхронизация) +│ K3s Master 2 │ │ +│ etcd ◄────────────────────┤ +└─────────────────────────┘ │ +┌─────────────────────────┐ │ +│ K3s Master 3 │ │ +│ etcd ◄────────────────────┘ +└─────────────────────────┘ + +Итого: 3 виртуальные машины +``` + +![etcd](etcd.svg) + +### Сравнение подходов + +| Критерий | External etcd | Embedded etcd | +|----------|---------------|---------------| +| Количество VM | 6 (3 master + 3 etcd) | 3 (всё вместе) | +| Сложность настройки | Высокая | Один флаг `--cluster-init` | +| Сложность обновления | Отдельно etcd и K8s | Одна команда | +| Производительность | Чуть лучше | Достаточно для homelab | +| Масштаб | >500 нод | До 100-200 нод | + +**Для homelab embedded etcd - очевидный выбор.** Теряем 5-10% производительности etcd, экономим 3 VM и часы настройки. + +*"А если мне понадобится масштаб?"* - официально embedded etcd поддерживает до 100 нод и 5000 подов. Для homelab это как ограничение скорости 300 км/ч на велосипеде. + +--- + +## Зачем отдельные worker ноды + +**Worker ноды** - машины для запуска ваших приложений (подов). На них не запускаются компоненты control plane. + +*"А можно запускать приложения прямо на master нодах?"* + +Технически - да. K3s не ставит ограничений на master ноды (в отличие от обычного Kubernetes). Но это плохая идея: + +- **Control plane должен быть стабильным.** Ваше приложение съело всю память → API server упал → кластер недоступен. +- **etcd чувствителен к диску.** База данных на той же ноде создаёт I/O нагрузку → etcd тормозит → весь кластер тормозит. +- **Изоляция отказов.** Проблема с приложением не должна убивать control plane. + +**2 worker ноды - минимум для HA приложений:** +- Можно запускать 2 реплики (на разных нодах) +- При падении одного worker'а второй держит нагрузку +- Легко добавить третью, четвёртую ноду потом + +--- + +## Архитектура нашего кластера + +Вот что мы будем строить: + +![Архитектура нашего кластера](final_arch.svg) + +**Ключевые моменты:** + +1. **Все master ноды равны** - нет "главной", kubectl подключается к любой. +2. **etcd синхронизируется через Raft** - алгоритм консенсуса, гарантирует согласованность данных. +3. **Workers знают только про API** - они не подключаются к etcd напрямую. +4. **Flannel создаёт overlay-сеть** - все поды получают IP из 10.42.0.0/16, видят друг друга. + +--- + +## Планирование ресурсов + +### Таблица VM + +| Hostname | VM ID | IP | vCPU | RAM | Disk | Роль | +|----------|-------|------------|------|-----|------|------| +| k3s-master-1 | 201 | 192.168.11.201 | 2 | 8GB | 32GB | Control Plane + etcd | +| k3s-master-2 | 202 | 192.168.11.202 | 2 | 8GB | 32GB | Control Plane + etcd | +| k3s-master-3 | 203 | 192.168.11.203 | 2 | 8GB | 32GB | Control Plane + etcd | +| k3s-worker-1 | 210 | 192.168.11.210 | 4 | 16GB | 50GB | Workloads | +| k3s-worker-2 | 211 | 192.168.11.211 | 4 | 16GB | 50GB | Workloads | +| **Итого** | - | - | **14** | **56GB** | **196GB** | - | + +### Почему именно такие ресурсы + +**Master ноды (2 vCPU / 8GB RAM / 32GB Disk):** + +Реальное потребление в idle: +- API server: ~200-300MB RAM +- etcd: ~100-200MB RAM (растёт со временем) +- Scheduler + Controller: ~150MB RAM +- Системные поды: ~100-200MB RAM +- **Итого:** ~600-900MB используется + +*"Зачем тогда 8GB?"* - запас для burst-нагрузки. Когда вы деплоите 50 подов одновременно, API server временно съедает больше. etcd при большом кластере может вырасти до 1-2GB. Golang GC работает лучше с запасом памяти. + +**Worker ноды (4 vCPU / 16GB RAM / 50GB Disk):** + +Здесь будут ваши приложения. При 16GB можно запустить: +- 5-10 средних приложений (256MB-2GB каждое) +- Или 2-3 базы данных (PostgreSQL любит память) +- Или комбинацию + +50GB диска - под образы контейнеров (10-20GB), логи (5-10GB), временные данные. + +### Можно ли меньше? + +**Минимальная конфигурация (для экспериментов):** +- Master: 1 vCPU / 4GB RAM / 20GB Disk +- Worker: 2 vCPU / 8GB RAM / 30GB Disk +- **Итого:** 9 vCPU / 36GB RAM + +**Риски:** +- Медленная работа API server +- OOM killer при нагрузке +- Нет запаса для burst + +Для production-like homelab рекомендую таблицу выше. Комфортный запас стоит дешевле, чем отладка странных падений. + +--- + +## Сетевая схема + +### IP-адреса (адаптируй под свою сеть) + +``` +192.168.11.0/24 - Локальная сеть + 192.168.11.1 - Gateway (роутер) + 192.168.11.201-203 - Master ноды + 192.168.11.210-211 - Worker ноды + 192.168.11.220-230 - Резерв для MetalLB (статья 2) +``` + +### Kubernetes внутренние сети (создаются автоматически) + +``` +10.42.0.0/16 - Pod network (Flannel VXLAN overlay) +10.43.0.0/16 - Service network (ClusterIP) +10.43.0.10 - CoreDNS +``` + +![Сети](k3s_network.svg) + +### Порты между нодами + +| Порт | Протокол | Направление | Назначение | +|------|----------|-------------|------------| +| 6443 | TCP | Master ← Worker | Kubernetes API | +| 2379-2380 | TCP | Master ↔ Master | etcd (client + peer) | +| 10250 | TCP | Master ↔ All | Kubelet API | +| 8472 | UDP | All ↔ All | Flannel VXLAN | + +--- + +## Требования к железу и софту + +### Железо (Proxmox хост) + +**Минимум:** +- CPU: 14 vCPU свободных +- RAM: 56GB свободных +- Disk: 200GB на SSD +- Network: 1 Gbit + +**Рекомендуется:** +- CPU: 18+ vCPU (запас для приложений) +- RAM: 64GB+ (базы данных прожорливые) +- Disk: NVMe для etcd +- Network: 2.5 Gbit (для NFS, если будете использовать) + +### Софт + +| Компонент | Версия | +|-----------|--------| +| Proxmox VE | 7.x или 8.x | +| K3s | v1.31+ (stable) | +| ОС на нодах | Debian 12 или Ubuntu 22.04+ | +| Kernel | 5.15+ (для cgroup v2) | + +## Итог + +**Что мы спроектировали:** +- 5 VM: 3 master + 2 worker +- K3s с embedded etcd (HA без лишних VM) +- Отказоустойчивость: выдерживает падение 1 master и любого worker +- Ресурсы: 14 vCPU / 56GB RAM / 196GB Disk + +**Что НЕ входит в эту серию** (отдельные статьи): +- LoadBalancer (MetalLB) +- Ingress (Traefik) +- SSL (cert-manager) +- Мониторинг (Prometheus/Grafana) + +--- + +## Что дальше + +**👉 "Подготовить инфраструктуру для K3s в Proxmox"** + +Там мы: +- Создадим template VM с Debian 12 +- Склонируем 5 VM с правильными ресурсами +- Настроим статические IP +- Подготовим ОС (swap, cgroup v2, firewall) \ No newline at end of file diff --git a/content/posts/k3s-architecture/k3s-architecture.svg b/content/posts/k3s-architecture/k3s-architecture.svg new file mode 100644 index 0000000..05bd46e --- /dev/null +++ b/content/posts/k3s-architecture/k3s-architecture.svg @@ -0,0 +1 @@ +

K3s
Размер: 50MB
Один бинарник

Встроенные компоненты

Control Plane:
API Server
Scheduler
Controller Manager

Data Store:
etcd
встроенный

Сеть:
Flannel CNI
kube-proxy

Дополнения:
CoreDNS
Metrics Server
Local Storage
kubelet

\ No newline at end of file diff --git a/content/posts/k3s-architecture/k3s-ha.svg b/content/posts/k3s-architecture/k3s-ha.svg new file mode 100644 index 0000000..a65d2f5 --- /dev/null +++ b/content/posts/k3s-architecture/k3s-ha.svg @@ -0,0 +1 @@ +

⛔ 2 ноды

Master 1: 🟢
Master 2: 🔴

1 / 2 = 50%

Кворума нет ✗

Мёртв

✅ 3 ноды

Master 1: 🟢
Master 2: 🔴
Master 3: 🟢

2 / 3 = 66%

Кворум ✓

Работает

\ No newline at end of file diff --git a/content/posts/k3s-architecture/k3s_network.svg b/content/posts/k3s-architecture/k3s_network.svg new file mode 100644 index 0000000..c973b00 --- /dev/null +++ b/content/posts/k3s-architecture/k3s_network.svg @@ -0,0 +1 @@ +

Kubernetes внутренние сети

Физическая сеть 192.168.11.0/24

Gateway
192.168.11.1

Master-1
.201

Master-2
.202

Master-3
.203

Worker-1
.210

Worker-2
.211

Pod Network
10.42.0.0/16
(Flannel VXLAN)

Service Network
10.43.0.0/16
(ClusterIP)

CoreDNS
10.43.0.10

\ No newline at end of file diff --git a/content/posts/k3s-architecture/k8s-architecture.svg b/content/posts/k3s-architecture/k8s-architecture.svg new file mode 100644 index 0000000..e34a9f8 --- /dev/null +++ b/content/posts/k3s-architecture/k8s-architecture.svg @@ -0,0 +1 @@ +

Kubernetes
Размер: ~1GB+

Control Plane

Data Store

Сеть

Дополнения

API Server

Scheduler

Controller Manager

etcd cluster
3+ отдельных VM

CNI plugin
установка отдельно

kube-proxy

CoreDNS

Metrics Server

kubelet

\ No newline at end of file diff --git a/content/posts/k3s-infrastructure/featured.png b/content/posts/k3s-infrastructure/featured.png new file mode 100644 index 0000000..48e6a49 Binary files /dev/null and b/content/posts/k3s-infrastructure/featured.png differ diff --git a/content/posts/k3s-infrastructure/index.md b/content/posts/k3s-infrastructure/index.md new file mode 100644 index 0000000..b014367 --- /dev/null +++ b/content/posts/k3s-infrastructure/index.md @@ -0,0 +1,619 @@ +--- +title: "Подготовить инфраструктуру для K3s в Proxmox" +date: 2025-10-21 +draft: false +description: "Создаём 5 VM в Proxmox с Debian 12, настраиваем сеть, отключаем swap и готовим систему для K3s. Пошаговая инструкция без сюрпризов." +summary: "Архитектура спланирована, ресурсы посчитаны - пора создавать виртуальные машины. Подготовим 5 VM в Proxmox и настроим ОС так, чтобы K3s установился без сюрпризов." +tags: ["kubernetes", "k3s", "homelab", "proxmox", "infrastructure", "debian", "devops"] +series: ["K3s HA кластер для homelab"] +series_order: 2 +# seriesOpened: true +showTableOfContents: true +--- + +Архитектура спланирована, ресурсы посчитаны - пора создавать виртуальные машины. В этой статье подготовим 5 VM в Proxmox и настроим ОС так, чтобы K3s установился без сюрпризов. + +Звучит просто? В теории - да. На практике: забытый swap, cgroup v1 вместо v2, закрытые порты firewall - и вы тратите час на отладку того, что должно было работать "из коробки". + +**Результат:** 5 VM (3 master + 2 worker) с Debian 12, настроенной сетью, отключённым swap и правильными параметрами ядра. SSH доступ работает, ноды видят друг друга. + +--- + +## Для кого это + +**Подходит:** +- Прочитал первую статью (или понимаешь архитектуру K3s HA) +- Есть Proxmox с 14+ vCPU и 56GB+ RAM свободных +- Умеешь работать в терминале Proxmox (или готов учиться) + +**Не подходит:** +- Proxmox ещё не установлен - сначала разберись с ним +- Хочешь использовать LXC вместо VM - K3s в контейнерах работает, но с нюансами (не покрываем) + +--- + +## Что понадобится + +| Компонент | Значение | +|-----------|----------| +| Proxmox VE | 7.x или 8.x | +| Storage pool | local-lvm или другой (минимум 200GB свободно) | +| Сетевой bridge | vmbr0 (или ваш) | +| SSH-ключ | Публичный ключ для доступа к VM | +| ОС для VM | Debian 12 cloud image | + +--- + +## Шаг 1: Скачать cloud image Debian 12 + +Cloud image - готовый образ с поддержкой cloud-init. Не нужно проходить установщик вручную: задаёшь параметры (IP, пользователь, SSH-ключ) - VM стартует уже настроенной. + +**На Proxmox хосте (SSH или Shell в Web UI):** + +```bash +# Перейти в директорию для образов +cd /var/lib/vz/template/iso + +# Скачать Debian 12 cloud image +wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2 + +# Проверить +ls -lh debian-12-generic-amd64.qcow2 +``` + +**Ожидаемый результат:** +``` +-rw-r--r-- 1 root root 521M ... debian-12-generic-amd64.qcow2 +``` + + + +**Если скачивание медленное:** образ ~500MB, на слабом канале может занять время. Альтернатива - скачать на локальную машину и загрузить через Proxmox Web UI (Datacenter → Storage → Upload). + +--- + +## Шаг 2: Создать template VM + +Template - шаблон VM, из которого будем клонировать все 5 нод. Настраиваем один раз, клонируем пять. + +### 2.1. Задать переменные + +```bash +# Настрой под свою конфигурацию +TEMPLATE_ID=9000 # ID для template (любой свободный) +STORAGE=local-lvm # Твой storage pool +BRIDGE=vmbr0 # Сетевой bridge +SSH_KEY_PATH=~/.ssh/id_rsa.pub # Путь к публичному SSH-ключу +``` + +**Как узнать имя storage:** +```bash +pvesm status +``` +**Ожидаемый результат** +``` +Name Type Status Total Used Available % +local dir active 229199360 9095552 220103808 3.97% +local-lvm pool active 220103964 96 220103868 0.00% +``` + + + +### 2.2. Создать VM и импортировать диск + +```bash +# 1. Создать пустую VM +qm create $TEMPLATE_ID \ + --name debian-12-template \ + --memory 2048 \ + --cores 2 \ + --net0 virtio,bridge=$BRIDGE + +# 2. Импортировать скачанный образ как диск +qm importdisk $TEMPLATE_ID \ + /var/lib/vz/template/iso/debian-12-generic-amd64.qcow2 \ + $STORAGE +``` + +**Ожидаемый результат:** +``` +importing disk '/var/lib/vz/template/iso/debian-12-generic-amd64.qcow2' to VM 9000 ... +Successfully imported disk as 'unused0:local-lvm:vm-9000-disk-0' +``` + +### 2.3. Настроить диск и загрузку + +```bash +# 3. Подключить диск к VM +qm set $TEMPLATE_ID \ + --scsihw virtio-scsi-pci \ + --scsi0 $STORAGE:vm-$TEMPLATE_ID-disk-0 + +# 4. Настроить загрузку и cloud-init +qm set $TEMPLATE_ID \ + --boot c \ + --bootdisk scsi0 \ + --ide2 $STORAGE:cloudinit \ + --serial0 socket \ + --vga serial0 +``` + +### 2.4. Настроить cloud-init + +```bash +# 5. Пользователь, пароль, SSH-ключ +qm set $TEMPLATE_ID \ + --ciuser k3s \ + --cipassword "ВашНадёжныйПароль" \ + --sshkeys $SSH_KEY_PATH \ + --ipconfig0 ip=dhcp + +# 6. Увеличить диск до 32GB (базовый размер для master) +qm resize $TEMPLATE_ID scsi0 32G +``` + +**Замени:** +- `ВашНадёжныйПароль` - пароль для пользователя k3s (резервный доступ, если SSH не работает) +- `$SSH_KEY_PATH` - путь к твоему публичному ключу + +### 2.5. Превратить в template + +```bash +# 7. Конвертировать VM в template +qm template $TEMPLATE_ID +``` + +После этого VM 9000 станет шаблоном - её нельзя запустить, только клонировать. + + + +### Checkpoint: Template создан + +```bash +# Проверить что template существует +qm list | grep template +``` + +**Ожидаемый результат:** +``` +9000 debian-12-template stopped 2048 32.00 0 +``` + +**Если ошибка "disk import failed":** +- Проверь свободное место: `pvesm status` +- Проверь путь к образу: `ls -l /var/lib/vz/template/iso/` + +--- + +## Шаг 3: Клонировать master ноды + +Теперь создаём 3 master ноды из template. Каждая получит свой IP, имя и ресурсы. + +```bash +TEMPLATE_ID=9000 + +# ───────────────────────────────────────────── +# Master 1 +# ───────────────────────────────────────────── +qm clone $TEMPLATE_ID 201 --name k3s-master-1 --full +qm set 201 --cores 2 --memory 8192 +qm set 201 --ipconfig0 ip=192.168.11.201/24,gw=192.168.11.1 +qm set 201 --nameserver 8.8.8.8 +qm resize 201 scsi0 32G + +# ───────────────────────────────────────────── +# Master 2 +# ───────────────────────────────────────────── +qm clone $TEMPLATE_ID 202 --name k3s-master-2 --full +qm set 202 --cores 2 --memory 8192 +qm set 202 --ipconfig0 ip=192.168.11.202/24,gw=192.168.11.1 +qm set 202 --nameserver 8.8.8.8 +qm resize 202 scsi0 32G + +# ───────────────────────────────────────────── +# Master 3 +# ───────────────────────────────────────────── +qm clone $TEMPLATE_ID 203 --name k3s-master-3 --full +qm set 203 --cores 2 --memory 8192 +qm set 203 --ipconfig0 ip=192.168.11.203/24,gw=192.168.11.1 +qm set 203 --nameserver 8.8.8.8 +qm resize 203 scsi0 32G +``` + +**Параметры:** +- `--full` - полное клонирование (не linked clone), VM независима от template +- `--cores 2 --memory 8192` - 2 vCPU, 8GB RAM (как планировали) +- `--ipconfig0` - статический IP через cloud-init +- `--nameserver` - DNS сервер (можешь указать свой) + +**Адаптируй под свою сеть:** +- `192.168.11.0/24` → твоя подсеть +- `192.168.11.1` → твой gateway + +--- + +## Шаг 4: Клонировать worker ноды + +Workers получают больше ресурсов - здесь будут работать приложения. + +```bash +# ───────────────────────────────────────────── +# Worker 1 +# ───────────────────────────────────────────── +qm clone $TEMPLATE_ID 210 --name k3s-worker-1 --full +qm set 210 --cores 4 --memory 16384 +qm set 210 --ipconfig0 ip=192.168.11.210/24,gw=192.168.11.1 +qm set 210 --nameserver 8.8.8.8 +qm resize 210 scsi0 50G + +# ───────────────────────────────────────────── +# Worker 2 +# ───────────────────────────────────────────── +qm clone $TEMPLATE_ID 211 --name k3s-worker-2 --full +qm set 211 --cores 4 --memory 16384 +qm set 211 --ipconfig0 ip=192.168.11.211/24,gw=192.168.11.1 +qm set 211 --nameserver 8.8.8.8 +qm resize 211 scsi0 50G +``` + +**Отличия от master:** +- 4 vCPU вместо 2 +- 16GB RAM вместо 8GB +- 50GB диск вместо 32GB + +--- + +## Шаг 5: Запустить все VM + +```bash +# Запустить все 5 VM +for vmid in 201 202 203 210 211; do + qm start $vmid + echo "Запущена VM $vmid" + sleep 3 +done + +# Проверить статус +qm list | grep k3s +``` + +**Ожидаемый результат:** +``` + 201 k3s-master-1 running 8192 32.00 12345 + 202 k3s-master-2 running 8192 32.00 12346 + 203 k3s-master-3 running 8192 32.00 12347 + 210 k3s-worker-1 running 16384 50.00 12348 + 211 k3s-worker-2 running 16384 50.00 12349 +``` + + + +### Checkpoint: VM работают + +Подожди 1-2 минуты (cloud-init применяет настройки при первом запуске), затем проверь SSH: + +```bash +# Проверить доступность всех нод +for ip in 192.168.11.201 192.168.11.202 192.168.11.203 192.168.11.210 192.168.11.211; do + echo -n "Проверяю $ip... " + ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no k3s@$ip "hostname" 2>/dev/null && echo "OK" || echo "FAIL" +done +``` + +**Ожидаемый результат:** +``` +Проверяю 192.168.11.201... k3s-master-1 +OK +Проверяю 192.168.11.202... k3s-master-2 +OK +... +``` + +**Если SSH не работает:** + +| Симптом | Причина | Решение | +|---------|---------|---------| +| Connection refused | VM не загрузилась или SSH не запущен | Открой консоль в Proxmox, проверь загрузку | +| Connection timeout | Неправильный IP или firewall | Проверь IP в консоли: `ip addr` | +| Permission denied | Неправильный SSH-ключ | Проверь `~/.ssh/authorized_keys` на VM | +| Host key verification failed | Первое подключение | Добавь `-o StrictHostKeyChecking=no` | + + + +--- + +## Шаг 6: Подготовить ОС на всех нодах + +Теперь нужно настроить каждую ноду: обновить пакеты, отключить swap, настроить ядро. Команды одинаковые для всех 5 нод. + +### 6.1. Обновить систему + +**На каждой ноде (или через цикл):** + +```bash +# Вариант 1: по одной +ssh k3s@192.168.11.201 + +sudo apt update +sudo apt upgrade -y +sudo apt install -y curl wget vim htop iptables + +# Вариант 2: массово (с локальной машины) +for ip in 192.168.11.{201..203} 192.168.11.{210..211}; do + echo "=== Обновляю $ip ===" + ssh k3s@$ip "sudo apt update && sudo apt upgrade -y && sudo apt install -y curl wget vim htop iptables" +done +``` + +### 6.2. Отключить swap + +Kubernetes не любит swap. При включённом swap поды ведут себя непредсказуемо - OOMKiller срабатывает не тогда, когда ожидаешь. + +**На всех нодах:** + +```bash +# Отключить swap сейчас +sudo swapoff -a + +# Отключить навсегда (закомментировать в fstab) +sudo sed -i '/swap/s/^/#/' /etc/fstab + +# Проверить +free -h | grep Swap +``` + +**Ожидаемый результат:** +``` +Swap: 0B 0B 0B +``` + +### 6.3. Загрузить kernel-модули + +K3s использует overlay filesystem и bridge netfilter. Без этих модулей - ошибки при старте. + +**На всех нодах:** + +```bash +# Загрузить модули +sudo modprobe overlay +sudo modprobe br_netfilter + +# Настроить автозагрузку +cat </dev/null 2>&1 && echo "✓" || echo "✗ ОШИБКА" +``` + +**Ожидаемый результат:** +``` +=== Проверка готовности ноды === +1. Swap отключён: ✓ +2. Модуль overlay: ✓ +3. Модуль br_netfilter: ✓ +4. IP forwarding: ✓ +5. bridge-nf-call-iptables: ✓ +6. cgroup v2: ✓ +7. UFW активен: ✓ +8. Пинг k3s-master-1: ✓ +``` + +Если где-то ✗ - вернись к соответствующему шагу. + +--- + +## Troubleshooting + +| Симптом | Причина | Решение | +|---------|---------|---------| +| VM не получает IP | cloud-init не отработал | Проверь консоль, жди 2-3 минуты, перезагрузи VM | +| SSH connection refused | sshd не запущен | Открой консоль, проверь `systemctl status ssh` | +| Swap не отключается | Строка не закомментирована в fstab | `cat /etc/fstab`, проверь swap строку, `reboot` | +| cgroup v1 после reboot | GRUB не обновился | Проверь `/proc/cmdline`, повтори `update-grub` | +| UFW блокирует всё | Забыл разрешить SSH до включения | Через консоль Proxmox: `ufw allow 22/tcp` | +| Ноды не пингуются | UFW или неправильный IP | Проверь `ip addr`, проверь правила UFW | + +--- + +## Итог + +**Что сделано:** +- ✅ Скачан Debian 12 cloud image +- ✅ Создан template VM с cloud-init +- ✅ Склонированы 5 VM (3 master + 2 worker) +- ✅ Настроены статические IP +- ✅ Подготовлена ОС (swap, modules, sysctl, cgroup v2) +- ✅ Настроен firewall с нужными портами +- ✅ SSH работает на все ноды + +**Что дальше:** + +👉 **Следующая статья: "Установить K3s HA кластер"** + +Там мы: +- Сгенерируем token для кластера +- Установим K3s на первую master ноду +- Добавим ещё 2 master ноды (HA) +- Подключим worker ноды +- Настроим kubectl +- Проверим работу кластера diff --git a/content/posts/k3s-infrastructure/mermaid-diagram-4x.png b/content/posts/k3s-infrastructure/mermaid-diagram-4x.png new file mode 100644 index 0000000..48d6026 Binary files /dev/null and b/content/posts/k3s-infrastructure/mermaid-diagram-4x.png differ diff --git a/content/posts/k3s-infrastructure/mermaid-diagram-5x.png b/content/posts/k3s-infrastructure/mermaid-diagram-5x.png new file mode 100644 index 0000000..542e7d6 Binary files /dev/null and b/content/posts/k3s-infrastructure/mermaid-diagram-5x.png differ diff --git a/content/posts/k3s-infrastructure/mermaid-diagram.svg b/content/posts/k3s-infrastructure/mermaid-diagram.svg new file mode 100644 index 0000000..99b1715 --- /dev/null +++ b/content/posts/k3s-infrastructure/mermaid-diagram.svg @@ -0,0 +1 @@ +

Worker ноды

Master ноды

2379-2380
etcd

2379-2380
etcd

2379-2380
etcd

6443
API

6443
API

6443
API

6443
API

6443
API

6443
API

10250
Kubelet

10250
Kubelet

10250
Kubelet

10250
Kubelet

10250
Kubelet

10250
Kubelet

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

8472/udp
Flannel

master-1

master-2

master-3

worker-1

worker-2

\ No newline at end of file diff --git a/content/posts/k3s-installation/featured.png b/content/posts/k3s-installation/featured.png new file mode 100644 index 0000000..be48e8b Binary files /dev/null and b/content/posts/k3s-installation/featured.png differ diff --git a/content/posts/k3s-installation/index.md b/content/posts/k3s-installation/index.md new file mode 100644 index 0000000..8c95126 --- /dev/null +++ b/content/posts/k3s-installation/index.md @@ -0,0 +1,650 @@ +--- +title: "Установить K3s HA кластер" +date: 2025-11-02 +draft: false +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"] +series: ["K3s HA кластер для homelab"] +series_order: 3 +# seriesOpened: true +showTableOfContents: true +--- + +Инфраструктура готова: 5 VM работают, ОС настроена, порты открыты. Пора устанавливать K3s. Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер. + +Звучит слишком просто? Потому что сложная часть уже позади - в предыдущих статьях. Теперь осталось не перепутать флаги и порядок установки. + +**Результат:** 5 нод в статусе Ready, etcd кластер 3/3 healthy, kubectl работает с локальной машины. + +--- + +## Для кого это + +**Подходит:** +- Прошёл статьи 1 и 2 (или имеешь готовые VM с настроенной ОС) +- Все 5 нод доступны по SSH +- Понимаешь разницу между master и worker + +**Не подходит:** +- VM ещё не созданы → статья 2 +- Не понимаешь зачем 3 master ноды → статья 1 + +--- + +## Что понадобится + +| Компонент | Значение | +|-----------|----------| +| K3s версия | v1.31.4+k3s1 (или актуальная stable) | +| Token | Сгенерируем на первом шаге | +| SSH доступ | Ко всем 5 нодам | +| Время | ~15-20 минут | + +--- + +## Шаг 1: Сгенерировать token + +Token - общий секрет для всех нод кластера. Без правильного token нода не присоединится. + +**На локальной машине:** + +```bash +# Сгенерировать случайный token +openssl rand -base64 32 +``` + +**Пример вывода:** +``` +K10f8c9a7b6e5d4c3b2a1f0e9d8c7b6a5e4d3c2b1a0f9e8d7c6b5a4== +``` + +**Сохрани этот token** - он понадобится для каждой ноды. Положи в менеджер паролей или временный файл. + +```bash +# Для удобства - сохранить в переменную (на время сессии) +export K3S_TOKEN="твой_сгенерированный_token" +echo $K3S_TOKEN +``` + +--- + +## Шаг 2: Установить K3s на первую master ноду + +Первая нода инициализирует etcd кластер. Она особенная - использует флаг `--cluster-init`. + +**SSH на k3s-master-1:** + +```bash +ssh k3s@192.168.11.201 +``` + +**Установка:** + +```bash +# Задать переменные +export K3S_TOKEN="твой_сгенерированный_token" +export INSTALL_K3S_VERSION="v1.31.4+k3s1" + +# Установить K3s +curl -sfL https://get.k3s.io | sh -s - server \ + --cluster-init \ + --tls-san=192.168.11.201 \ + --disable=traefik \ + --disable=servicelb \ + --write-kubeconfig-mode=644 +``` + +**Разбор флагов:** + +| Флаг | Зачем | +|------|-------| +| `server` | Режим control plane (не agent) | +| `--cluster-init` | **Ключевой!** Инициализирует etcd. Только на первой ноде | +| `--tls-san=192.168.11.201` | Добавить IP в сертификат API server | +| `--disable=traefik` | Отключить встроенный Traefik (установим свой через Helm) | +| `--disable=servicelb` | Отключить встроенный LB (установим MetalLB) | +| `--write-kubeconfig-mode=644` | Разрешить чтение kubeconfig без sudo | + +**Установка займёт 1-2 минуты.** K3s скачает бинарник (~50MB) и запустит все компоненты. + +### Проверка + +```bash +# 1. Статус сервиса +sudo systemctl status k3s +``` + +**Ожидаемый результат:** +``` +● k3s.service - Lightweight Kubernetes + Loaded: loaded + Active: active (running) since ... +``` + +```bash +# 2. Статус ноды +sudo k3s kubectl get nodes +``` + +**Ожидаемый результат:** +``` +NAME STATUS ROLES AGE VERSION +k3s-master-1 Ready control-plane,etcd,master 45s v1.31.4+k3s1 +``` + + + +### Checkpoint: Первая master работает + +```bash +# Быстрая проверка +sudo systemctl is-active k3s && \ +sudo k3s kubectl get nodes | grep -q "Ready" && \ +echo "✓ Master-1 готов" || echo "✗ Проблема" +``` + +**Если статус NotReady или сервис не запустился:** + +```bash +# Смотреть логи +sudo journalctl -u k3s -f --no-pager | tail -50 +``` + +| Ошибка в логах | Причина | Решение | +|----------------|---------|---------| +| `cgroup v1 is not supported` | Нужен cgroup v2 | Вернись к статье 2, шаг 6.5 | +| `port 6443 already in use` | Что-то занимает порт | `sudo ss -tlnp \| grep 6443` | +| `etcd failed to start` | Мало места на диске | `df -h`, увеличь диск | + +--- + +## Шаг 3: Добавить вторую master ноду + +Теперь присоединяем вторую master. Она подключается к существующему кластеру - **без** `--cluster-init`. + +**SSH на k3s-master-2:** + +```bash +ssh k3s@192.168.11.202 +``` + +**Установка:** + +```bash +export K3S_TOKEN="твой_сгенерированный_token" +export INSTALL_K3S_VERSION="v1.31.4+k3s1" + +curl -sfL https://get.k3s.io | sh -s - server \ + --server=https://192.168.11.201:6443 \ + --tls-san=192.168.11.202 \ + --disable=traefik \ + --disable=servicelb \ + --write-kubeconfig-mode=644 +``` + +**Ключевое отличие:** +- ❌ Нет `--cluster-init` - кластер уже инициализирован +- ✅ Есть `--server=https://192.168.11.201:6443` - адрес существующего кластера + +### Проверка + +```bash +# На master-2 +sudo k3s kubectl get nodes +``` + +**Ожидаемый результат:** +``` +NAME STATUS ROLES AGE VERSION +k3s-master-1 Ready control-plane,etcd,master 3m v1.31.4+k3s1 +k3s-master-2 Ready control-plane,etcd,master 30s v1.31.4+k3s1 +``` + +Две ноды - но кворума ещё нет. etcd требует большинство, а 2 из 3 - это ещё не "большинство от трёх". + +--- + +## Шаг 4: Добавить третью master ноду + +**SSH на k3s-master-3:** + +```bash +ssh k3s@192.168.11.203 +``` + +**Установка (аналогично master-2):** + +```bash +export K3S_TOKEN="твой_сгенерированный_token" +export INSTALL_K3S_VERSION="v1.31.4+k3s1" + +curl -sfL https://get.k3s.io | sh -s - server \ + --server=https://192.168.11.201:6443 \ + --tls-san=192.168.11.203 \ + --disable=traefik \ + --disable=servicelb \ + --write-kubeconfig-mode=644 +``` + +### Checkpoint: Control Plane HA готов + +**На любой master ноде:** + +```bash +# 1. Все 3 master ноды Ready +sudo k3s kubectl get nodes +``` + +**Ожидаемый результат:** +``` +NAME STATUS ROLES AGE VERSION +k3s-master-1 Ready control-plane,etcd,master 5m v1.31.4+k3s1 +k3s-master-2 Ready control-plane,etcd,master 3m v1.31.4+k3s1 +k3s-master-3 Ready control-plane,etcd,master 1m v1.31.4+k3s1 +``` + +```bash +# 2. etcd кластер здоров +sudo k3s kubectl exec -n kube-system \ + $(sudo k3s kubectl get pods -n kube-system -l component=etcd -o name | head -1) \ + -- etcdctl endpoint health --cluster +``` + +**Ожидаемый результат:** +``` +https://192.168.11.201:2379 is healthy: successfully committed proposal: took = 2.1ms +https://192.168.11.202:2379 is healthy: successfully committed proposal: took = 1.8ms +https://192.168.11.203:2379 is healthy: successfully committed proposal: took = 2.3ms +``` + + + +**Теперь у вас настоящий HA.** Можете остановить любую master ноду - кластер продолжит работать. + +### Тест отказоустойчивости (опционально) + +Хотите убедиться, что HA работает? Проверьте: + +```bash +# С локальной машины - остановить master-2 +ssh k3s@192.168.11.202 "sudo systemctl stop k3s" + +# Подождать 30-40 секунд, затем на master-1: +ssh k3s@192.168.11.201 "sudo k3s kubectl get nodes" +# master-2 станет NotReady, но кластер работает + +# Проверить etcd - 2/3 кворум есть +ssh k3s@192.168.11.201 "sudo k3s kubectl exec -n kube-system \ + \$(sudo k3s kubectl get pods -n kube-system -l component=etcd -o name | head -1) \ + -- etcdctl endpoint health --cluster" +# 2 из 3 healthy - кворум есть + +# Вернуть master-2 +ssh k3s@192.168.11.202 "sudo systemctl start k3s" +``` + +--- + +## Шаг 5: Добавить worker ноды + +Worker ноды устанавливаются как **agent** - они не участвуют в etcd и не запускают control plane. + +### 5.1. Установить K3s agent на worker-1 + +**SSH на k3s-worker-1:** + +```bash +ssh k3s@192.168.11.210 +``` + +**Установка:** + +```bash +export K3S_TOKEN="твой_сгенерированный_token" +export INSTALL_K3S_VERSION="v1.31.4+k3s1" + +curl -sfL https://get.k3s.io | sh -s - agent \ + --server=https://192.168.11.201:6443 +``` + +**Отличия от master:** +- `agent` вместо `server` - режим worker +- Нет флагов `--disable` и `--tls-san` - они не нужны для worker +- Только `--server` - куда подключаться + +### 5.2. Установить K3s agent на worker-2 + +**SSH на k3s-worker-2:** + +```bash +ssh k3s@192.168.11.211 +``` + +**Установка:** + +```bash +export K3S_TOKEN="твой_сгенерированный_token" +export INSTALL_K3S_VERSION="v1.31.4+k3s1" + +curl -sfL https://get.k3s.io | sh -s - agent \ + --server=https://192.168.11.201:6443 +``` + +### Checkpoint: Все ноды в кластере + +**На любой master ноде:** + +```bash +sudo k3s kubectl get nodes -o wide +``` + +**Ожидаемый результат:** +``` +NAME STATUS ROLES AGE VERSION INTERNAL-IP +k3s-master-1 Ready control-plane,etcd,master 10m v1.31.4+k3s1 192.168.11.201 +k3s-master-2 Ready control-plane,etcd,master 8m v1.31.4+k3s1 192.168.11.202 +k3s-master-3 Ready control-plane,etcd,master 6m v1.31.4+k3s1 192.168.11.203 +k3s-worker-1 Ready 2m v1.31.4+k3s1 192.168.11.210 +k3s-worker-2 Ready 1m v1.31.4+k3s1 192.168.11.211 +``` + + + +**Обрати внимание:** +- Master: роли `control-plane,etcd,master` +- Worker: роли `` - только выполнение workloads + +![K3S HA Cluster](mermaid-diagram.svg) + +--- + +## Шаг 6: Настроить kubectl на локальной машине + +Сейчас kubectl работает только на master нодах через `sudo k3s kubectl`. Настроим доступ с вашей рабочей машины. + +### 6.1. Скопировать kubeconfig + +**На локальной машине (не на ноде):** + +```bash +# 1. Создать директорию +mkdir -p ~/.kube + +# 2. Скопировать конфиг с master-1 +scp k3s@192.168.11.201:/etc/rancher/k3s/k3s.yaml ~/.kube/config + +# 3. Заменить localhost на реальный IP +sed -i 's/127.0.0.1/192.168.11.201/g' ~/.kube/config + +# Для macOS: +# sed -i '' 's/127.0.0.1/192.168.11.201/g' ~/.kube/config + +# 4. Права доступа +chmod 600 ~/.kube/config +``` + +### 6.2. Установить kubectl (если нет) + +**Linux:** + +```bash +curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" +sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl +rm kubectl +``` + +**macOS:** + +```bash +brew install kubectl +``` + +### 6.3. Проверить подключение + +```bash +# Версия +kubectl version + +# Ноды +kubectl get nodes + +# Все поды +kubectl get pods -A +``` + +**Ожидаемый результат `kubectl get pods -A`:** +``` +NAMESPACE NAME READY STATUS RESTARTS AGE +kube-system coredns-xxx 1/1 Running 0 10m +kube-system local-path-provisioner-xxx 1/1 Running 0 10m +kube-system metrics-server-xxx 1/1 Running 0 10m +``` + + + +**Если kubectl не подключается:** + +| Симптом | Причина | Решение | +|---------|---------|---------| +| `connection refused` | k3s не запущен | `ssh k3s@192.168.11.201 "sudo systemctl status k3s"` | +| `connection timeout` | Firewall блокирует | Проверь UFW на master: `sudo ufw status \| grep 6443` | +| `certificate signed by unknown authority` | Неправильный kubeconfig | Скопируй заново с master ноды | + +### 6.4. Настроить автодополнение (опционально) + +```bash +# Bash +echo 'source <(kubectl completion bash)' >> ~/.bashrc +echo 'alias k=kubectl' >> ~/.bashrc +echo 'complete -o default -F __start_kubectl k' >> ~/.bashrc +source ~/.bashrc + +# Zsh +echo 'source <(kubectl completion zsh)' >> ~/.zshrc +source ~/.zshrc +``` + +Теперь работает `k get nodes` и Tab-автодополнение. + +--- + +## Финальная проверка + +Полный чеклист работоспособности кластера: + +```bash +echo "=== Проверка K3s HA кластера ===" + +echo -n "1. Все ноды Ready: " +[ $(kubectl get nodes --no-headers | grep -c "Ready") -eq 5 ] && echo "✓ (5/5)" || echo "✗" + +echo -n "2. Master ноды: " +kubectl get nodes --no-headers | grep -c "control-plane" | xargs -I {} echo "✓ ({}/3)" + +echo -n "3. Worker ноды: " +kubectl get nodes --no-headers | grep -c "" | xargs -I {} echo "✓ ({}/2)" + +echo -n "4. etcd healthy: " +kubectl exec -n kube-system \ + $(kubectl get pods -n kube-system -l component=etcd -o name | head -1) \ + -- etcdctl endpoint health --cluster 2>/dev/null | grep -c "is healthy" | xargs -I {} echo "✓ ({}/3)" + +echo -n "5. CoreDNS Running: " +kubectl get pods -n kube-system -l k8s-app=kube-dns --no-headers | grep -q "Running" && echo "✓" || echo "✗" + +echo -n "6. Metrics Server Running: " +kubectl get pods -n kube-system -l k8s-app=metrics-server --no-headers | grep -q "Running" && echo "✓" || echo "✗" + +echo "" +echo "=== Информация о кластере ===" +kubectl cluster-info +``` + +**Ожидаемый результат:** +``` +=== Проверка K3s HA кластера === +1. Все ноды Ready: ✓ (5/5) +2. Master ноды: ✓ (3/3) +3. Worker ноды: ✓ (2/2) +4. etcd healthy: ✓ (3/3) +5. CoreDNS Running: ✓ +6. Metrics Server Running: ✓ + +=== Информация о кластере === +Kubernetes control plane is running at https://192.168.11.201:6443 +CoreDNS is running at https://192.168.11.201:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy +Metrics-server is running at https://192.168.11.201:6443/api/v1/namespaces/kube-system/services/https:metrics-server:https/proxy +``` + +--- + +## Тестовый деплой + +Убедимся, что кластер может запускать приложения: + +```bash +# 1. Создать тестовый под +kubectl run nginx-test --image=nginx:alpine --port=80 + +# 2. Подождать запуска +kubectl wait --for=condition=Ready pod/nginx-test --timeout=60s + +# 3. Проверить на какой ноде запустился +kubectl get pod nginx-test -o wide +``` + +**Ожидаемый результат:** +``` +NAME READY STATUS RESTARTS AGE IP NODE +nginx-test 1/1 Running 0 30s 10.42.1.5 k3s-worker-1 +``` + +Под запустился на worker ноде - как и должно быть. + +```bash +# 4. Проверить доступность изнутри кластера +kubectl run -it --rm debug --image=busybox --restart=Never -- wget -qO- nginx-test +``` + +**Ожидаемый результат:** HTML-страница nginx. + +```bash +# 5. Удалить тестовые ресурсы +kubectl delete pod nginx-test +``` + +--- + +## Troubleshooting + +### Нода не присоединяется к кластеру + +**Симптом:** После установки нода не появляется в `kubectl get nodes`. + +| Причина | Диагностика | Решение | +|---------|-------------|---------| +| Неправильный token | Логи: `sudo journalctl -u k3s-agent -f` | Проверь token, переустанови | +| Firewall блокирует | `curl -k https://192.168.11.201:6443` | Открой порт 6443 на masters | +| DNS не резолвит | `ping k3s-master-1` | Проверь /etc/hosts или DNS | + +### etcd не формирует кворум + +**Симптом:** `etcdctl endpoint health` показывает unhealthy. + +```bash +# Проверить членов etcd +sudo k3s kubectl exec -n kube-system \ + $(sudo k3s kubectl get pods -n kube-system -l component=etcd -o name | head -1) \ + -- etcdctl member list --write-out=table +``` + +| Причина | Диагностика | Решение | +|---------|-------------|---------| +| Порты 2379-2380 закрыты | `sudo ufw status` | `sudo ufw allow 2379:2380/tcp` | +| Нода недоступна по сети | `ping 192.168.11.20X` | Проверь сеть, UFW | +| etcd ещё стартует | `uptime` на ноде | Подожди 2-3 минуты | + +### Поды не запускаются на workers + +**Симптом:** Все поды на master нодах, workers пустые. + +```bash +# Проверить taints +kubectl describe node k3s-worker-1 | grep Taints +``` + +Если есть taints - убрать: +```bash +kubectl taint nodes k3s-worker-1 node.kubernetes.io/not-ready:NoSchedule- +``` + +--- + +## Откат: удаление K3s + +Если нужно начать заново: + +**На master ноде:** +```bash +sudo /usr/local/bin/k3s-uninstall.sh +``` + +**На worker ноде:** +```bash +sudo /usr/local/bin/k3s-agent-uninstall.sh +``` + +**Что удаляется:** +- Бинарники K3s +- Systemd сервисы +- Данные из `/var/lib/rancher/k3s/` +- etcd данные (на masters) +- Контейнеры и образы + +**⚠️ Внимание:** etcd снапшоты тоже удаляются. Если нужен бэкап: +```bash +# Перед удалением - сохранить снапшоты +sudo cp -r /var/lib/rancher/k3s/server/db/snapshots/ ~/k3s-backup/ +``` + +--- + +## Итог + +**Что сделано:** +- ✅ Сгенерирован token для кластера +- ✅ Установлен K3s на 3 master ноды с embedded etcd +- ✅ Добавлены 2 worker ноды +- ✅ Настроен kubectl на локальной машине +- ✅ Проверена работоспособность кластера + +**Что имеем:** +- HA Control Plane - выдерживает падение 1 master ноды +- 2 worker ноды для приложений +- kubectl доступ с локальной машины +- Встроенные компоненты: CoreDNS, metrics-server, local-path storage + +**Что ещё не настроено** (следующие статьи): +- LoadBalancer (MetalLB) - для доступа к сервисам извне +- Ingress Controller (Traefik) - для HTTP/HTTPS routing +- SSL сертификаты (cert-manager) - для автоматического HTTPS +- Мониторинг (Prometheus/Grafana) - для наблюдения за кластером + +--- + +## Что дальше + +Кластер готов, но пока он изолирован от внешнего мира. Чтобы запускать реальные приложения с доступом извне, нужны: + +1. **MetalLB** - выдаёт IP-адреса для LoadBalancer сервисов +2. **Traefik** - маршрутизирует HTTP/HTTPS трафик +3. **cert-manager** - автоматически получает SSL сертификаты + +Это темы для следующей серии статей. + +**А пока можно:** +- Поэкспериментировать с kubectl +- Задеплоить тестовые приложения +- Изучить как работает scheduling между нодами + diff --git a/content/posts/k3s-installation/mermaid-diagram.svg b/content/posts/k3s-installation/mermaid-diagram.svg new file mode 100644 index 0000000..6494228 --- /dev/null +++ b/content/posts/k3s-installation/mermaid-diagram.svg @@ -0,0 +1 @@ +
K3s HA Cluster
API
Control Plane (HA)
etcd
etcd
etcd
✅ k3s-master-1
Ready
✅ k3s-master-2
Ready
✅ k3s-master-3
Ready
Workers
✅ k3s-worker-1
Ready
✅ k3s-worker-2
Ready
\ No newline at end of file diff --git a/i18n/ru.yaml b/i18n/ru.yaml new file mode 100644 index 0000000..b52ef20 --- /dev/null +++ b/i18n/ru.yaml @@ -0,0 +1,80 @@ +global: + language: "RU" + +article: + anchor_label: "Якорь" + date: "{{ .Date }}" + date_updated: "Обновлено: {{ .Date }}" + draft: "Черновик" + edit_title: "Редактировать содержимое" + reading_time: + one: "{{ .Count }} минута" + other: "{{ .Count }} минут" + reading_time_title: "Время чтения" + table_of_contents: "Оглавление" + word_count: + one: "{{ .Count }} слово" + other: "{{ .Count }} слов" + views: + one: "{{ .Count }} просмотр" + other: "{{ .Count }} просмотров" + likes: + one: "{{ .Count }} нравится" + other: "{{ .Count }} нравится" + part_of_series: "Эта статья — часть серии." + part: "Часть" + this_article: "Читаешь сейчас" + related_articles: "Статьи по теме" + reply_by_email: "Ответить по электронной почте" + +a11y: + title: "Настройки доступности" + disable_blur: "Отключить размытие" + disable_images: "Отключить изображения" + show_link_underline: "Показать подчеркивание ссылок" + font_size: "Размер шрифта" + +author: + byline_title: "Автор" + +code: + copy: "Копировать" + copied: "Скопировано" + +error: + 404_title: "Страница не найдена: в замешательстве:" + 404_error: "Ошибка 404" + 404_description: "Похоже, запрошенная вами страница не существует." + +footer: + dark_appearance: "Переключить на темный вид" + light_appearance: "Переключить на светлый вид" + powered_by: "Работает на {{ .Hugo }} & {{ .Theme }}" + +list: + externalurl_title: "Ссылка на внешний сайт" + no_articles: "Здесь пока нет статей." + +nav: + scroll_to_top_title: "Пролистать наверх" + skip_to_main: "Перейти к основному содержимому" + +search: + open_button_title: "Поиск (/)" + close_button_title: "Закрыть (Esc)" + input_placeholder: "Поиск" + +sharing: + email: "Отправить по электронной почте" + facebook: "Поделиться через Facebook" + line: "Поделиться через LINE" + linkedin: "Поделиться через LinkedIn" + pinterest: "Добавить в Pinterest" + reddit: "Отправить в Reddit" + twitter: "Твитнуть в Twitter" + +shortcode: + recent_articles: "Недавние" + +recent: + show_more: "Показать еще" \ No newline at end of file diff --git a/public/404.html b/public/404.html index 0c394d6..0bd10e5 100644 --- a/public/404.html +++ b/public/404.html @@ -3,8 +3,8 @@ lang="ru" dir="ltr" class="scroll-smooth" - data-default-appearance="light" - data-auto-appearance="true"> + data-default-appearance="dark" + data-auto-appearance="false"> @@ -29,6 +29,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -92,8 +124,8 @@ + src="/js/appearance.min.a0c4d367419d691bf95fc98ffcaf55ce81db3412c3dfbd6c4fbe968f56f77347f5a8512b0916a65a5f496dbec1ef0590dbadcf2fbd0de3c919e525f11c32d0e3.js" + integrity="sha512-oMTTZ0GdaRv5X8mP/K9VzoHbNBLD371sT76Wj1b3c0f1qFErCRamWl9Jbb7B7wWQ263PL70N48kZ5SXxHDLQ4w=="> @@ -113,6 +145,8 @@ + + @@ -122,8 +156,8 @@ defer type="text/javascript" id="script-bundle" - src="/js/main.bundle.min.858f7f82734cfae08d59fcf8d0eb186f01706a84e2f7d20d39edfd7bc8bed6166e02d5c65ecce1de82b1ac52d1e01d77bd1a82d19186fdae5fe6e12d867fcf68.js" - integrity="sha512-hY9/gnNM+uCNWfz40OsYbwFwaoTi99INOe39e8i+1hZuAtXGXszh3oKxrFLR4B13vRqC0ZGG/a5f5uEthn/PaA==" + src="/js/main.bundle.min.bdda7dece6cbaf08deef7d254f7f842f3261c2524d247905127c9a20decc03f1011a2950048464c79272c1ce0705a49a41147f39f2b95163bb71d404b33263ef.js" + integrity="sha512-vdp97ObLrwje730lT3+ELzJhwlJNJHkFEnyaIN7MA/EBGilQBIRkx5Jywc4HBaSaQRR/OfK5UWO7cdQEszJj7w==" data-copy="Копировать" data-copied="Скопировано"> @@ -197,7 +231,7 @@ "url" : "http://192.168.11.190:1313/404.html", "author" : { "@type": "Person", - "name": "" + "name": "Олег Казанин" }, @@ -233,11 +267,19 @@ + + + + + + + + @@ -256,109 +298,36 @@ -