Compare commits
8 Commits
40e42fd8c6
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 7be5dd910b | |||
| 07bf07f78e | |||
| 132f194432 | |||
| 9138b45459 | |||
| 5f56173ae9 | |||
| 0f6bdfd02b | |||
| b8594dff65 | |||
| e7ddb7bc82 |
|
After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 9.2 KiB |
@@ -6,12 +6,12 @@
|
|||||||
# https://blowfish.page/docs/configuration/#theme-parameters
|
# https://blowfish.page/docs/configuration/#theme-parameters
|
||||||
|
|
||||||
colorScheme = "blowfish"
|
colorScheme = "blowfish"
|
||||||
defaultAppearance = "dark" # valid options: light or dark
|
defaultAppearance = "light" # valid options: light or dark
|
||||||
autoSwitchAppearance = false
|
autoSwitchAppearance = false
|
||||||
|
|
||||||
enableA11y = false
|
enableA11y = true
|
||||||
enableSearch = true
|
enableSearch = true
|
||||||
enableCodeCopy = false
|
enableCodeCopy = true
|
||||||
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)
|
||||||
|
|
||||||
@@ -20,18 +20,19 @@ replyByEmail = false
|
|||||||
# mainSections = ["section1", "section2"]
|
# mainSections = ["section1", "section2"]
|
||||||
# robots = ""
|
# robots = ""
|
||||||
|
|
||||||
disableImageOptimization = false
|
disableImageZoom = true
|
||||||
disableImageOptimizationMD = false
|
disableImageOptimization = true
|
||||||
|
# disableImageOptimizationMD = false
|
||||||
disableTextInHeader = true
|
disableTextInHeader = true
|
||||||
# backgroundImageWidth = 1200
|
# backgroundImageWidth = 1200
|
||||||
|
|
||||||
defaultBackgroundImage = "/img/background.png" # used as default for background images
|
defaultBackgroundImage = "/img/background.svg" # used as default for background images
|
||||||
defaultFeaturedImage = "/img/background.png" # used as default for featured images in all articles
|
# 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)
|
# defaultSocialImage = "/android-chrome-512x512.png" # used as default for social media sharing (Open Graph and Twitter)
|
||||||
hotlinkFeatureImage = false
|
hotlinkFeatureImage = false
|
||||||
# imagePosition = "50% 50%"
|
# imagePosition = "50% 50%"
|
||||||
|
|
||||||
# highlightCurrentMenuArea = true
|
highlightCurrentMenuArea = true
|
||||||
# smartTOC = true
|
# smartTOC = true
|
||||||
# smartTOCHideUnfocusedChildren = true
|
# smartTOCHideUnfocusedChildren = true
|
||||||
|
|
||||||
@@ -54,65 +55,65 @@ forgejoDefaultServer = "https://v11.next.forgejo.org"
|
|||||||
layout = "background" # 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
|
#homepageImage = "IMAGE.jpg" # used in: hero, and card
|
||||||
showRecent = true
|
showRecent = true
|
||||||
showRecentItems = 18
|
showRecentItems = 5
|
||||||
showMoreLink = true
|
showMoreLink = true
|
||||||
showMoreLinkDest = "/posts"
|
showMoreLinkDest = "/posts"
|
||||||
cardView = true
|
cardView = false
|
||||||
cardViewScreenWidth = false
|
cardViewScreenWidth = false
|
||||||
layoutBackgroundBlur = true # only used when layout equals background
|
layoutBackgroundBlur = true # only used when layout equals background
|
||||||
disableHeroImageFilter = false # only used when layout equals hero
|
disableHeroImageFilter = true # only used when layout equals hero
|
||||||
|
|
||||||
[article]
|
[article]
|
||||||
showDate = true
|
showDate = true
|
||||||
showViews = false
|
# showViews = false
|
||||||
showLikes = false
|
# showLikes = false
|
||||||
showDateOnlyInArticle = false
|
# showDateOnlyInArticle = false
|
||||||
showDateUpdated = false
|
# showDateUpdated = false
|
||||||
showAuthor = true
|
showAuthor = true
|
||||||
# showAuthorBottom = false
|
# showAuthorBottom = false
|
||||||
showHero = true
|
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
|
||||||
showDraftLabel = true
|
showDraftLabel = true
|
||||||
showEdit = false
|
#showEdit = false
|
||||||
# editURL = "https://github.com/username/repo/"
|
# editURL = "https://github.com/username/repo/"
|
||||||
editAppendPath = true
|
# editAppendPath = true
|
||||||
seriesOpened = true
|
seriesOpened = true
|
||||||
showHeadingAnchors = true
|
showHeadingAnchors = true
|
||||||
showPagination = true
|
showPagination = true
|
||||||
invertPagination = false
|
# invertPagination = false
|
||||||
showReadingTime = true
|
showReadingTime = true
|
||||||
showTableOfContents = true
|
showTableOfContents = true
|
||||||
showRelatedContent = true
|
showRelatedContent = true
|
||||||
relatedContentLimit = 6
|
relatedContentLimit = 3
|
||||||
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 = true # use showTaxonomies OR showCategoryOnly, not both
|
||||||
showAuthorsBadges = false
|
showAuthorsBadges = false
|
||||||
showWordCount = false
|
# showWordCount = false
|
||||||
# sharingLinks = [ "linkedin", "twitter", "bluesky", "mastodon", "reddit", "pinterest", "facebook", "email", "whatsapp", "telegram", "line"]
|
# sharingLinks = [ "linkedin", "twitter", "bluesky", "mastodon", "reddit", "pinterest", "facebook", "email", "whatsapp", "telegram", "line"]
|
||||||
sharingLinks = [ "reddit", "email", "telegram"]
|
sharingLinks = [ "reddit", "email", "telegram"]
|
||||||
showZenMode = false
|
# showZenMode = false
|
||||||
# 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)
|
||||||
# showComments = true
|
showComments = true
|
||||||
|
|
||||||
[list]
|
[list]
|
||||||
showHero = true
|
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
|
||||||
showSummary = false
|
showSummary = true
|
||||||
showViews = false
|
# showViews = false
|
||||||
showLikes = false
|
# showLikes = false
|
||||||
showTableOfContents = false
|
# showTableOfContents = false
|
||||||
showCards = false
|
showCards = true
|
||||||
orderByWeight = false
|
# orderByWeight = false
|
||||||
groupByYear = false
|
# groupByYear = false
|
||||||
cardView = false
|
# cardView = false
|
||||||
cardViewScreenWidth = false
|
# cardViewScreenWidth = false
|
||||||
constrainItemsWidth = false
|
# constrainItemsWidth = false
|
||||||
|
|
||||||
[sitemap]
|
[sitemap]
|
||||||
excludedKinds = ["taxonomy", "term"]
|
excludedKinds = ["taxonomy", "term"]
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ slug: "shortcodes"
|
|||||||
tags: ["shortcodes", "mermaid", "icon", "lead", "docs"]
|
tags: ["shortcodes", "mermaid", "icon", "lead", "docs"]
|
||||||
series: ["Документация"]
|
series: ["Документация"]
|
||||||
series_order: 8
|
series_order: 8
|
||||||
|
showComments: true
|
||||||
---
|
---
|
||||||
|
|
||||||
Hugo даёт [стандартные shortcodes](https://gohugo.io/content-management/shortcodes/). Blowfish добавляет свои — для типичных задач блога.
|
Hugo даёт [стандартные shortcodes](https://gohugo.io/content-management/shortcodes/). Blowfish добавляет свои — для типичных задач блога.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
@@ -4,7 +4,7 @@ date: 2026-01-03
|
|||||||
draft: false
|
draft: false
|
||||||
description: "Переезд с Jekyll на Hugo: выбор темы Blowfish, настройка K3s кластера и два окружения с нуля. Старт цикла о production блоге на Open Source стеке."
|
description: "Переезд с Jekyll на Hugo: выбор темы Blowfish, настройка K3s кластера и два окружения с нуля. Старт цикла о production блоге на Open Source стеке."
|
||||||
tags: ["hugo", "blowfish", "gitea", "homelab", "devops"]
|
tags: ["hugo", "blowfish", "gitea", "homelab", "devops"]
|
||||||
categories: ["Веб-разработка"]
|
categories: ["Мои проекты"]
|
||||||
series: ["Блог на Hugo в K3s"]
|
series: ["Блог на Hugo в K3s"]
|
||||||
series_order: 1
|
series_order: 1
|
||||||
---
|
---
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.5 MiB |
@@ -4,7 +4,7 @@ date: 2026-01-08
|
|||||||
draft: false
|
draft: false
|
||||||
description: "Деплой Hugo в K3s: NFS хранилище, Hugo Builder с webhook, Nginx с Prometheus exporter и автоматический SSL от Let's Encrypt."
|
description: "Деплой Hugo в K3s: NFS хранилище, Hugo Builder с webhook, Nginx с Prometheus exporter и автоматический SSL от Let's Encrypt."
|
||||||
tags: ["hugo", "k3s", "kubernetes", "nfs", "traefik", "cert-manager"]
|
tags: ["hugo", "k3s", "kubernetes", "nfs", "traefik", "cert-manager"]
|
||||||
categories: ["Kubernetes", "Веб-разработка"]
|
categories: ["Мои проекты"]
|
||||||
series: ["Блог на Hugo в K3s"]
|
series: ["Блог на Hugo в K3s"]
|
||||||
series_order: 2
|
series_order: 2
|
||||||
---
|
---
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 MiB |
@@ -4,7 +4,7 @@ date: 2026-01-15
|
|||||||
draft: false
|
draft: false
|
||||||
description: "Dev окружение для Hugo в K3s: отдельный Hugo Builder, Nginx и Basic Auth через Traefik. Проверяем статьи до публикации в production."
|
description: "Dev окружение для Hugo в K3s: отдельный Hugo Builder, Nginx и Basic Auth через Traefik. Проверяем статьи до публикации в production."
|
||||||
tags: ["hugo", "k3s", "kubernetes", "traefik", "basic-auth"]
|
tags: ["hugo", "k3s", "kubernetes", "traefik", "basic-auth"]
|
||||||
categories: ["Веб-разработка", "DevOps практики"]
|
categories: ["Мои проекты"]
|
||||||
series: ["Блог на Hugo в K3s"]
|
series: ["Блог на Hugo в K3s"]
|
||||||
series_order: 3
|
series_order: 3
|
||||||
---
|
---
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 MiB |
@@ -3,8 +3,9 @@ title: "Блог на Hugo в K3s: часть 4 - выбор Git workflow"
|
|||||||
date: 2026-02-16
|
date: 2026-02-16
|
||||||
draft: false
|
draft: false
|
||||||
description: "Одна папка или две для dev и production? Разбираем Git workflow для Hugo блога в K3s — переключение веток против отдельных директорий."
|
description: "Одна папка или две для dev и production? Разбираем Git workflow для Hugo блога в K3s — переключение веток против отдельных директорий."
|
||||||
|
summary: "Одна папка или две для dev и production? Разбираем Git workflow для Hugo блога в K3s — переключение веток против отдельных директорий."
|
||||||
tags: ["git", "workflow", "devops", "hugo"]
|
tags: ["git", "workflow", "devops", "hugo"]
|
||||||
categories: ["Веб-разработка", "DevOps практики"]
|
categories: ["Мои проекты"]
|
||||||
series: ["Блог на Hugo в K3s"]
|
series: ["Блог на Hugo в K3s"]
|
||||||
series_order: 4
|
series_order: 4
|
||||||
---
|
---
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 MiB |
@@ -4,7 +4,7 @@ date: 2026-02-17
|
|||||||
draft: false
|
draft: false
|
||||||
description: "Алгоритм диагностики Hugo блога в K3s за 5 минут: от DNS до пода. Сайт отдаёт 503 — находим причину без паники."
|
description: "Алгоритм диагностики Hugo блога в K3s за 5 минут: от DNS до пода. Сайт отдаёт 503 — находим причину без паники."
|
||||||
tags: ["kubernetes", "k3s", "traefik", "debugging", "nginx", "troubleshooting"]
|
tags: ["kubernetes", "k3s", "traefik", "debugging", "nginx", "troubleshooting"]
|
||||||
categories: ["Kubernetes", "DevOps практики"]
|
categories: ["Мои проекты"]
|
||||||
series: ["Блог на Hugo в K3s"]
|
series: ["Блог на Hugo в K3s"]
|
||||||
series_order: 5
|
series_order: 5
|
||||||
---
|
---
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 MiB |
@@ -4,7 +4,7 @@ date: 2026-02-18
|
|||||||
draft: false
|
draft: false
|
||||||
description: "Переносим сервисы между namespace в Kubernetes: экспорт манифестов, миграция NFS данных, новый SSL за 32 секунды. Реальный пример с Gitea."
|
description: "Переносим сервисы между namespace в Kubernetes: экспорт манифестов, миграция NFS данных, новый SSL за 32 секунды. Реальный пример с Gitea."
|
||||||
tags: ["kubernetes", "k3s", "gitea", "devops", "homelab", "namespace"]
|
tags: ["kubernetes", "k3s", "gitea", "devops", "homelab", "namespace"]
|
||||||
categories: ["Kubernetes", "DevOps практики"]
|
categories: ["Мои проекты"]
|
||||||
series: ["Блог на Hugo в K3s"]
|
series: ["Блог на Hugo в K3s"]
|
||||||
series_order: 6
|
series_order: 6
|
||||||
---
|
---
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 611 KiB |
@@ -3,8 +3,9 @@ title: "Debian 12: базовая настройка и hardening для product
|
|||||||
date: 2026-05-22
|
date: 2026-05-22
|
||||||
draft: false
|
draft: false
|
||||||
description: "Пошаговая настройка чистого Debian 12 (Bookworm) для production: SSH, firewall, fail2ban, swap, sysctl, автообновления, логи, бэкап конфигов."
|
description: "Пошаговая настройка чистого Debian 12 (Bookworm) для production: SSH, firewall, fail2ban, swap, sysctl, автообновления, логи, бэкап конфигов."
|
||||||
tags: ["debian 12", "security", "hardening", "ssh", "ufw", "fail2ban", "sysctl", "production", "linux"]
|
summary: "Пошаговая настройка чистого Debian 12 (Bookworm) для production: SSH, firewall, fail2ban, swap, sysctl, автообновления, логи, бэкап конфигов."
|
||||||
categories: ["Системное администрирование"]
|
tags: ["debian-12", "security", "hardening", "ssh", "ufw", "fail2ban", "sysctl", "production", "linux"]
|
||||||
|
categories: ["Безопасность"]
|
||||||
series: ["Linux Hardening"]
|
series: ["Linux Hardening"]
|
||||||
series_order: 1
|
series_order: 1
|
||||||
---
|
---
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 611 KiB |
@@ -3,8 +3,9 @@ title: "Debian 12: настройка Docker-хоста для production"
|
|||||||
date: 2026-05-22
|
date: 2026-05-22
|
||||||
draft: false
|
draft: false
|
||||||
description: "Пошаговая настройка Debian 12 как Docker-хоста для production: установка Docker, настройка daemon, UFW + Docker интеграция, изоляция контейнеров, docker.sock, ресурсные ограничения."
|
description: "Пошаговая настройка Debian 12 как Docker-хоста для production: установка Docker, настройка daemon, UFW + Docker интеграция, изоляция контейнеров, docker.sock, ресурсные ограничения."
|
||||||
tags: ["debian 12", "docker", "security", "hardening", "ufw", "production", "containers", "linux"]
|
summary: "Пошаговая настройка Debian 12 как Docker-хоста для production: установка Docker, настройка daemon, UFW + Docker интеграция, изоляция контейнеров, docker.sock, ресурсные ограничения."
|
||||||
categories: ["Системное администрирование"]
|
tags: ["debian-12", "docker", "security", "hardening", "ufw", "production", "containers", "linux"]
|
||||||
|
categories: ["Безопасность"]
|
||||||
series: ["Linux Hardening"]
|
series: ["Linux Hardening"]
|
||||||
series_order: 2
|
series_order: 2
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,454 @@
|
|||||||
|
---
|
||||||
|
title: "Диск забит на 100%: как Loki съел 24 ГБ логов, а df -i почти ввёл в заблуждение"
|
||||||
|
date: 2026-06-24
|
||||||
|
draft: false
|
||||||
|
description: "Расследование заполненного диска на Debian 12: от алерта Zabbix через df, du и docker system df до найденного виновника - логов Loki без лимита ротации. Чек-лист подозреваемых и настройка лимита логов Docker."
|
||||||
|
summary: "Расследование заполненного диска на Debian 12: от алерта Zabbix через df, du и docker system df до найденного виновника - логов Loki без лимита ротации."
|
||||||
|
tags: ["debian-12", "docker", "zabbix", "мониторинг", "диагностика", "логи", "xfs", "linux"]
|
||||||
|
categories: ["Файловые системы и хранилище"]
|
||||||
|
series: ["Linux: эксплуатация и обслуживание"]
|
||||||
|
series_order: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Диск забит на 100%: как Loki съел 24 ГБ логов, а df -i почти ввёл в заблуждение
|
||||||
|
|
||||||
|
Zabbix присылает алерт: на проде закончилось место в корне файловой системы. Сервер - Debian 12, что на нём крутится и почему диск кончился - неизвестно, в тикете только хостнейм и факт.
|
||||||
|
|
||||||
|
Эта статья - не теория про docker logs и не абстрактный гайд по очистке диска. Это реальное расследование: от первого `df -h` до настроенного лимита логов, со всеми тупиками по дороге, включая два моих собственных, а не системы. Один из них почти увёл диагностику в сторону несуществующей проблемы с инодами.
|
||||||
|
|
||||||
|
Если у вас диск забился по другой причине - в конце статьи отдельный чек-лист по всем проверенным подозреваемым. Он работает как самостоятельный справочник, независимо от того, что нашлось в этом конкретном случае.
|
||||||
|
|
||||||
|
## Старт: два предела сразу
|
||||||
|
|
||||||
|
"Место кончилось" в Linux бывает по двум разным причинам - не хватило байтов или не хватило инодов. Алерт Zabbix не уточняет, какая именно, так что смотрим сразу на обе.
|
||||||
|
|
||||||
|
Занятость по байтам на всех точках монтирования:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
df -h
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Filesystem Size Used Avail Use% Mounted on
|
||||||
|
/dev/mapper/vg01-root 29G 29G 16K 100% /
|
||||||
|
/dev/sda2 944M 147M 732M 17% /boot
|
||||||
|
/dev/mapper/vg02-opt 500G 22G 478G 5% /opt
|
||||||
|
```
|
||||||
|
|
||||||
|
Корень (`vg01-root`) забит под потолок - 29G из 29G, 16K свободно. `/opt` - отдельный том на 500G, занят на 5%, чист. Разделение на тома здесь сыграло на руку: не разведи их администратор раньше, искать причину было бы сложнее.
|
||||||
|
|
||||||
|
Та же проверка по инодам - те же 29G можно забить и десятком огромных файлов, и миллионом мелких, и метод диагностики для этих случаев разный:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
df -i
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Filesystem Inodes IUsed IFree IUse% Mounted on
|
||||||
|
/dev/mapper/vg01-root 67424 66740 684 99% /
|
||||||
|
/dev/mapper/vg02-opt 262141952 486209 261655743 1% /opt
|
||||||
|
```
|
||||||
|
|
||||||
|
Деталь, которая зацепила взгляд: всего 67424 инода на 29-гигабайтном томе - по привычке принял это за фиксированное значение, заданное при создании файловой системы, как это работает в ext4. При 99% занятости с таким скромным лимitom легко вылететь по инодам даже без аномального количества файлов. Отложил мысль на потом - и, как выяснится в финале статьи, отложил с неверной гипотезой в основе.
|
||||||
|
|
||||||
|
## Первая ошибка: du без -x
|
||||||
|
|
||||||
|
Чтобы понять, что внутри корня весит больше всего, логично пройтись `du` по верхним каталогам:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
du -h --max-depth=1 / 2>/dev/null | sort -rh
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
23G /
|
||||||
|
19G /opt
|
||||||
|
2.2G /usr
|
||||||
|
1.1G /home
|
||||||
|
765M /var
|
||||||
|
147M /boot
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат бессмысленный, и вот почему: без флага `-x` `du` пересекает точки монтирования. `/opt` и `/boot` - отдельные файловые системы, но `du` зашёл туда и насчитал их объём как часть `/`. Корень - это `vg01-root`, а в выводе сидят чужие 19G и 147M с других дисков.
|
||||||
|
|
||||||
|
Повторяем с `-x`, чтобы `du` не покидал текущую файловую систему:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
du -hx --max-depth=1 / 2>/dev/null | sort -rh
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
4.0G /
|
||||||
|
2.2G /usr
|
||||||
|
1.1G /home
|
||||||
|
765M /var
|
||||||
|
```
|
||||||
|
|
||||||
|
Сумма едва дотягивает до 4G, а `df -h` показывает 29G использовано. Разрыв в 25G - и тут я слишком быстро потянулся к экзотической гипотезе: удалённый, но открытый файл, который процесс держит дескриптором, а в дереве каталогов его уже нет.
|
||||||
|
|
||||||
|
## Слепая зона: du без root
|
||||||
|
|
||||||
|
Гипотеза была преждевременной. Команда выполнялась без sudo, а `2>/dev/null` заглушил не только мусор, а заодно все "Permission denied". Если `du` не смог зайти в каталог с ограниченными правами, он молча посчитал его как 0 байт - и 4.0G превращаются в недостоверное число, а не в доказательство призрачного файла.
|
||||||
|
|
||||||
|
Сначала проверяем явно, что именно `du` не смог прочитать - без скрытия ошибок:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo du -hx --max-depth=1 / 2>&1 1>/dev/null | grep -i denied
|
||||||
|
```
|
||||||
|
|
||||||
|
Вывод пустой - но это ничего не доказывает: команда уже шла через sudo, то есть не воспроизводила условия первого, проблемного запуска. Сама проверка получилась нерелевантной собственной ошибке, которую должна была вскрыть.
|
||||||
|
|
||||||
|
Пересчитываем с правами root, без слепых зон:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo du -hx --max-depth=1 / 2>/dev/null | sort -rh
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
29G /
|
||||||
|
26G /var
|
||||||
|
2.2G /usr
|
||||||
|
1.1G /home
|
||||||
|
```
|
||||||
|
|
||||||
|
26G + 2.2G + 1.1G с мелочью почти точно сходится с 29G из `df -h`. Гипотеза про удалённый-но-открытый файл не подтвердилась - фиксирую как часть истории, а не прячу. Разрыв был не призраком, а банальной слепой зоной из-за прав доступа в моей же команде.
|
||||||
|
|
||||||
|
**Золотое правило:** диагностику диска без `sudo` лучше не начинать. Любая Permission denied молча обнулит реальный объём каталога, и вы будете гоняться за несуществующей проблемой.
|
||||||
|
|
||||||
|
## /var/lib: Docker номер один
|
||||||
|
|
||||||
|
Раскрываем `/var` на уровень глубже:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
du -hx --max-depth=1 /var 2>/dev/null | sort -rh
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
26G /var/lib
|
||||||
|
242M /var/log
|
||||||
|
203M /var/cache
|
||||||
|
728K /var/backups
|
||||||
|
```
|
||||||
|
|
||||||
|
Весь вес - в `/var/lib`, остальное несущественно. `/var/lib` - общий контейнер для данных приложений, туда попадает и Docker, и базы данных, и системные служебные данные. Раскрываем ещё на уровень:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
du -hx --max-depth=1 /var/lib 2>/dev/null | sort -rh
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
25G /var/lib/docker
|
||||||
|
283M /var/lib/apt
|
||||||
|
18M /var/lib/dpkg
|
||||||
|
17M /var/lib/plocate
|
||||||
|
```
|
||||||
|
|
||||||
|
`/var/lib/docker` - 25G из 26G `/var/lib`. Остальное суммарно тянет на 350M. Виновник почти найден.
|
||||||
|
|
||||||
|
## docker system df врёт - не считает логи
|
||||||
|
|
||||||
|
У Docker есть встроенная команда, дающая разбиение по образам, контейнерам, volumes и build cache сразу:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker system df -v
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Images space usage:
|
||||||
|
REPOSITORY TAG SIZE
|
||||||
|
grafana/grafana latest 485MB
|
||||||
|
grafana/loki 2.9.2 74.6MB
|
||||||
|
hello-world latest 13.3kB
|
||||||
|
|
||||||
|
Containers space usage:
|
||||||
|
CONTAINER IMAGE CREATED STATUS SIZE
|
||||||
|
loki grafana/loki:2.9.2 4 months ago Up 4 months 0B
|
||||||
|
grafana grafana/grafana:latest 4 months ago Up 4 months 0B
|
||||||
|
|
||||||
|
Local Volumes space usage: (нет volumes)
|
||||||
|
Build cache usage: 0B
|
||||||
|
```
|
||||||
|
|
||||||
|
Суммарно - около 560MB. А `/var/lib/docker` весит 25G. Разница больше 24G, и `docker system df` её просто не видит.
|
||||||
|
|
||||||
|
Это известная засада: **`docker system df` не считает логи контейнеров**. Команда показывает только образы, writable-слои, volumes и build cache - JSON-логи, которые Docker пишет по умолчанию без лимита, в эту статистику не попадают вообще.
|
||||||
|
|
||||||
|
Проверяем напрямую, сколько весят лог-файлы контейнеров:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
du -h /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -rh
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
24G .../containers/<loki>/<loki>-json.log
|
||||||
|
257M .../containers/<grafana>/<grafana>-json.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Оба контейнера работают по 4 месяца без перезапуска - времени накопить логи без ротации хватало обоим. Но разница в 93 раза говорит не просто об "отсутствии ротации" - что-то заставляет именно Loki писать аномально много.
|
||||||
|
|
||||||
|
## Почему именно Loki
|
||||||
|
|
||||||
|
Сначала смотрим характер записей - последние строки лога:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CID=$(docker ps -qf "name=loki")
|
||||||
|
tail -n 20 /var/lib/docker/containers/$CID/$CID-json.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Все видимые строки - `level=info`, без единой ошибки. Но паттерн заметный: лог крутится по последовательным индексным таблицам компактора - `index_20407` → `index_20406` → `index_20405`, каждая обрабатывается за пару миллисекунд. Это не разовая фоновая задача, это что-то, что молотит постоянно и очень быстро.
|
||||||
|
|
||||||
|
Ищем настоящие ошибки, с ограничением по времени, чтобы не зависнуть на полном проходе по 24G:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
timeout 15 grep -m 1 'level=error' /var/lib/docker/containers/$CID/$CID-json.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Нашлась одна:
|
||||||
|
|
||||||
|
```
|
||||||
|
level=error ts=2026-01-26T12:36:53Z msg="error asking ring for who should run the compactor, will check again" err="at least 1 healthy replica required, could only find 0"
|
||||||
|
```
|
||||||
|
|
||||||
|
Классика для однонодового Loki: ring-based compactor рассчитан на кластер, а здесь единственный инстанс, и lifecycler не успел зарегистрироваться в ring при старте. Но дата - 26 января, почти 5 месяцев назад. Похоже на разовый сбой при старте, а не на текущую причину - нужно проверить, повторяется ли она сейчас.
|
||||||
|
|
||||||
|
Дальше - оценка темпа записи. Первая попытка была неудачной, признаю сразу: `timeout` после 10 секунд убил всю цепочку процессов сигналом, и `wc -l` не успел вывести даже ноль - результат недостоверен, метод сломан, аргумент в любую сторону строить на нём нельзя.
|
||||||
|
|
||||||
|
Первая попытка - через docker logs в реальном времени:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
timeout 10 docker logs -f --tail 0 $CID | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
|
Второй подход - без живого слежения, через временные метки в самом файле:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tail -n 2000 /var/lib/docker/containers/$CID/$CID-json.log | head -n 1
|
||||||
|
tail -n 1 /var/lib/docker/containers/$CID/$CID-json.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Разница между 2000-й-с-конца записью и последней - 7.48 секунды. 2000 строк за 7.48с - это около 267 строк в секунду. При среднем объёме 24G за 4 месяца (~200MB в день, ~2.3KB/сек, то есть 10-15 строк/сек) пойманное значение в 20+ раз выше среднего - это всплеск компактора, а не стабильный фон. Экстраполировать всплеск на все 4 месяца было бы нечестно по отношению к методу.
|
||||||
|
|
||||||
|
Проверяем ошибки за недавний период без скана всех 24G - берём последние 500MB через seek по байтам:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tail -c 500M /var/lib/docker/containers/$CID/$CID-json.log | grep -c 'level=error'
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
0
|
||||||
|
```
|
||||||
|
|
||||||
|
Ноль ошибок в недавней истории. Гипотеза "контейнер сломан и спамит ошибками" закрыта - дело не в этом.
|
||||||
|
|
||||||
|
## Главная причина: лимит логов никто не настраивал
|
||||||
|
|
||||||
|
Раз дело не в ошибках, причина - в объёме обычного info-логирования без всякого предела. Смотрим, ограничен ли размер логов хоть на каком-то уровне.
|
||||||
|
|
||||||
|
Глобальная настройка драйвера логов:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat /etc/docker/daemon.json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"registry-mirrors": ["https://mirror.example.com"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Настройка конкретного контейнера:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker inspect --format '{{json .HostConfig.LogConfig}}' $CID
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"Type":"json-file","Config":{}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Оба пустые. Используется дефолт Docker - драйвер `json-file` без `max-size`/`max-file`. Компактор болтлив на уровне info, лимита нет, контейнер живёт 4 месяца - 24G неизбежны. Причина подтверждена документально, не догадкой.
|
||||||
|
|
||||||
|
## Остальные подозреваемые - для полноты
|
||||||
|
|
||||||
|
Виновник найден, но по уговору формата добиваем весь список систематически - это даёт читателю с другой причиной полную картину.
|
||||||
|
|
||||||
|
| Подозреваемый | Команда | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| journald | `journalctl --disk-usage` | 245.9M, чисто |
|
||||||
|
| Старые ядра | `dpkg -l 'linux-image-*' \| grep ^ii` | 3 версии, но на отдельном `/boot`, не влияет на `/` |
|
||||||
|
| Кэш apt | `du -sh /var/cache/apt/archives` | 118M, чисто |
|
||||||
|
| Удалённые, но открытые файлы | `lsof +L1` | пустой вывод, suspect закрыт окончательно |
|
||||||
|
|
||||||
|
Последний пункт стоит выделить отдельно: пустой `lsof +L1` - это не "цифры совпали", а прямое подтверждение, что ни один процесс не держит открытым файл с нулевым числом ссылок. Гипотеза про "призрачный файл" была отвергнута на двух независимых основаниях - арифметикой `du`/`df` раньше и прямым инструментом сейчас.
|
||||||
|
|
||||||
|
## Лечение: освобождаем место
|
||||||
|
|
||||||
|
Truncate файла "на горячую" безопасен для драйвера `json-file` - Docker продолжит дописывать в тот же inode, ничего не упадёт:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
truncate -s 0 /var/lib/docker/containers/$CID/$CID-json.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверяем результат:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
df -h /
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Filesystem Size Used Avail Use% Mounted on
|
||||||
|
/dev/mapper/vg01-root 29G 5.1G 24G 18% /
|
||||||
|
```
|
||||||
|
|
||||||
|
Освободилось 23.9G - почти точное совпадение с размером усечённого файла. Это не просто "стало легче", а математическое подтверждение, что причина определена верно.
|
||||||
|
|
||||||
|
## Настройка лимита - и неожиданный нюанс
|
||||||
|
|
||||||
|
Чтобы это не повторилось ни с Loki, ни с любым другим контейнером, добавляем лимит в `daemon.json`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > /etc/docker/daemon.json << 'EOF'
|
||||||
|
{
|
||||||
|
"registry-mirrors": ["https://mirror.example.com"],
|
||||||
|
"log-driver": "json-file",
|
||||||
|
"log-opts": {
|
||||||
|
"max-size": "20m",
|
||||||
|
"max-file": "5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
systemctl restart docker
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверяем, применилось ли:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker inspect --format '{{json .HostConfig.LogConfig}}' $CID
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"Type":"json-file","Config":{}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Всё ещё пусто. **Глобальный `log-opts` действует только для новых контейнеров**, созданных после изменения - не для существующих. `LogConfig` фиксируется в момент создания контейнера и не обновляется при `restart docker`, только при пересоздании.
|
||||||
|
|
||||||
|
Чтобы пересоздать контейнер без потери параметров, сначала смотрим, как он был запущен - через лейблы compose-проекта:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker inspect --format '{{json .Config.Labels}}' $CID
|
||||||
|
```
|
||||||
|
|
||||||
|
В выводе - `com.docker.compose.project.working_dir` и `com.docker.compose.service: loki`. Контейнер управляется через compose, путь известен:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/deploy/docker
|
||||||
|
docker compose up -d --force-recreate loki
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
✔ Container loki Started
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверяем лимит на пересозданном контейнере:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker inspect --format '{{json .HostConfig.LogConfig}}' loki
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"Type":"json-file","Config":{"max-file":"5","max-size":"20m"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Применилось. Тот же compose-проект поднимает и `grafana` - у неё на момент инцидента всего 257M логов, но без лимита она повторила бы путь Loki за более долгий срок. Раз инструмент уже в руках, закрываем сразу, не оставляя половину работы:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d --force-recreate grafana
|
||||||
|
```
|
||||||
|
|
||||||
|
## Побочный квест: ложная тревога с инодами
|
||||||
|
|
||||||
|
Возвращаемся к инодовой детали, отложенной в начале. Сверяем `df -i` сейчас с замером в начале расследования:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
df -i /
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Filesystem Inodes IUsed IFree IUse% Mounted on
|
||||||
|
/dev/mapper/vg01-root 15224832 66741 15158091 1% /
|
||||||
|
```
|
||||||
|
|
||||||
|
| Замер | Inodes всего | IUsed | IFree | IUse% |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| В начале | 67424 | 66740 | 684 | 99% |
|
||||||
|
| Сейчас | 15224832 | 66741 | 15158091 | 1% |
|
||||||
|
|
||||||
|
`IUsed` почти не изменился, а общее число инодов выросло в 225 раз. В ext4 количество инодов фиксируется в суперблоке при `mkfs` и не меняется без явного `resize2fs` - а размер тома не менялся вообще. Гипотеза "том создан с искусственно малым лимитом инодов" построена на ext4-логике, и она оказалась неверной - я строил её, не проверив тип файловой системы.
|
||||||
|
|
||||||
|
Смотрим напрямую в суперблок:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tune2fs -l /dev/mapper/vg01-root | grep -i inode
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
tune2fs: Bad magic number in super-block while trying to open /dev/mapper/vg01-root
|
||||||
|
```
|
||||||
|
|
||||||
|
Это не ошибка диагностики - это разоблачение неверной гипотезы. `tune2fs` понимает только ext2/ext3/ext4. Если суперблок не найден - файловая система другая.
|
||||||
|
|
||||||
|
Подтверждаем тип ФС напрямую:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
findmnt -no FSTYPE /
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
xfs
|
||||||
|
```
|
||||||
|
|
||||||
|
Подтверждено: XFS. У XFS иноды выделяются динамически, а не фиксируются при создании. Столбец "Inodes" в `df -i` для XFS - это не константа, а оценка: текущие занятые иноды плюс расчёт, сколько ещё теоретически можно выделить исходя из свободного места на диске. При почти 100% занятости диска эта оценка падала почти до нуля, создавая видимость жёсткого лимита. После truncate освободилось 24G - и оценка свободных инодов резко выросла, потому что физически появилось место для их размещения.
|
||||||
|
|
||||||
|
Никакой аномалии не было - был артефакт интерпретации `df -i` на XFS при заполненном диске. Прячущая деталь: `IUsed` остался почти неизменным (66740 → 66741), и именно это надо было проверить раньше, чем строить гипотезу про лимит.
|
||||||
|
|
||||||
|
## Итог
|
||||||
|
|
||||||
|
Финальная сверка по диску:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
df -h /
|
||||||
|
df -i /
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Filesystem Size Used Avail Use% Mounted on
|
||||||
|
/dev/mapper/vg01-root 29G 4.9G 25G 17% /
|
||||||
|
|
||||||
|
Filesystem Inodes IUsed IFree IUse% Mounted on
|
||||||
|
/dev/mapper/vg01-root 15224832 66741 15158091 1% /
|
||||||
|
```
|
||||||
|
|
||||||
|
Было 29G из 29G, 100%. Стало 4.9G из 29G, 17%. Оба контейнера теперь ограничены лимитом логов - повторения с тем же сценарием не будет.
|
||||||
|
|
||||||
|
## Чек-лист: куда смотреть при заполненном диске
|
||||||
|
|
||||||
|
*Этот раздел - справочный.*
|
||||||
|
|
||||||
|
| Подозреваемый | Команда проверки | На что обратить внимание |
|
||||||
|
|---|---|---|
|
||||||
|
| Байты vs иноды | `df -h`, `df -i` | Какой именно лимит исчерпан; на XFS число инодов в `df -i` - оценка, а не константа |
|
||||||
|
| `du` без -x | `du -hx --max-depth=1 /` | Без `-x` команда пересечёт точки монтирования и даст мусорные числа |
|
||||||
|
| `du` без root | `sudo du -hx ...` | Без прав молча обнуляет недоступные каталоги |
|
||||||
|
| Docker логи контейнеров | `du -h /var/lib/docker/containers/*/*-json.log` | `docker system df` логи контейнеров не считает вообще |
|
||||||
|
| Лимит логов Docker | `docker inspect --format '{{json .HostConfig.LogConfig}}' <id>` | Пустой `Config{}` значит лимита нет; глобальный `daemon.json` не действует на уже созданные контейнеры |
|
||||||
|
| journald | `journalctl --disk-usage` | Может разрастись без `SystemMaxUse` в `journald.conf` |
|
||||||
|
| Старые ядра | `dpkg -l 'linux-image-*'` | Чистить через `apt autoremove --purge`, обычно живут в `/boot` отдельным томом |
|
||||||
|
| Кэш apt | `du -sh /var/cache/apt/archives` | `apt clean` освобождает мгновенно |
|
||||||
|
| Удалённые, но открытые файлы | `lsof +L1` | Процесс держит дескриптор - место не освободится без перезапуска процесса |
|
||||||
|
|
||||||
|
## Стек
|
||||||
|
|
||||||
|
Debian 12, XFS, Docker, Loki 2.9.2, Grafana, Docker Compose 2.29.7, Zabbix.
|
||||||
|
|
||||||
|
## Что дальше
|
||||||
|
|
||||||
|
Следующая статья серии - тот же сценарий для RHEL-подобных систем: dnf-кэш вместо apt, другие пути логов, и проверка, какие из подозреваемых из этого списка вообще не применимы на RHEL-like.
|
||||||
|
Before Width: | Height: | Size: 1.4 MiB |
@@ -4,7 +4,7 @@ date: 2025-10-14
|
|||||||
draft: false
|
draft: false
|
||||||
description: "K3s HA кластер для homelab: архитектура с 3 master нодами и embedded etcd. 50MB бинарник вместо 1.5GB зависимостей — почему это работает."
|
description: "K3s HA кластер для homelab: архитектура с 3 master нодами и embedded etcd. 50MB бинарник вместо 1.5GB зависимостей — почему это работает."
|
||||||
tags: ["kubernetes", "k3s", "homelab", "proxmox", "architecture", "ha", "devops"]
|
tags: ["kubernetes", "k3s", "homelab", "proxmox", "architecture", "ha", "devops"]
|
||||||
categories: ["Kubernetes", "Homelab"]
|
categories: ["devops-практики"]
|
||||||
series: ["K3s HA кластер для homelab"]
|
series: ["K3s HA кластер для homelab"]
|
||||||
series_order: 1
|
series_order: 1
|
||||||
---
|
---
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 MiB |
@@ -4,7 +4,7 @@ date: 2025-10-21
|
|||||||
draft: false
|
draft: false
|
||||||
description: "Готовим инфраструктуру для K3s в Proxmox: 5 VM с Debian 12, настройка сети, отключение swap. Пошаговая инструкция без сюрпризов."
|
description: "Готовим инфраструктуру для K3s в Proxmox: 5 VM с Debian 12, настройка сети, отключение swap. Пошаговая инструкция без сюрпризов."
|
||||||
tags: ["kubernetes", "k3s", "homelab", "proxmox", "infrastructure", "debian", "devops"]
|
tags: ["kubernetes", "k3s", "homelab", "proxmox", "infrastructure", "debian", "devops"]
|
||||||
categories: ["Kubernetes", "Homelab"]
|
categories: ["devops-практики"]
|
||||||
series: ["K3s HA кластер для homelab"]
|
series: ["K3s HA кластер для homelab"]
|
||||||
series_order: 2
|
series_order: 2
|
||||||
---
|
---
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 MiB |
@@ -4,7 +4,7 @@ date: 2025-11-02
|
|||||||
draft: false
|
draft: false
|
||||||
description: "Устанавливаем K3s HA кластер: 3 master и 2 worker ноды за 15 минут. Один curl-скрипт на ноду — и кластер готов к работе."
|
description: "Устанавливаем K3s HA кластер: 3 master и 2 worker ноды за 15 минут. Один curl-скрипт на ноду — и кластер готов к работе."
|
||||||
tags: ["kubernetes", "k3s", "homelab", "installation", "ha", "etcd", "devops"]
|
tags: ["kubernetes", "k3s", "homelab", "installation", "ha", "etcd", "devops"]
|
||||||
categories: ["Kubernetes", "Homelab", "DevOps практики"]
|
categories: ["devops-практики"]
|
||||||
series: ["K3s HA кластер для homelab"]
|
series: ["K3s HA кластер для homelab"]
|
||||||
series_order: 3
|
series_order: 3
|
||||||
---
|
---
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 777 KiB |
@@ -1,10 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: "Почтовый сервер на Debian 12: полное руководство от установки до production. Часть 1 - Начало"
|
title: "Свой почтовый сервер на Debian 12 вместо Gmail: полное руководство. Часть 1 - Начало"
|
||||||
date: 2026-03-05
|
date: 2026-03-05
|
||||||
draft: false
|
draft: false
|
||||||
description: "Зачем поднимать свой mail-сервер вместо Gmail. Анатомия почтового сервера: Postfix, Dovecot, антиспам, антивирус. Системные требования и чек-лист готовности."
|
description: "Зачем поднимать свой mail-сервер вместо Gmail. Анатомия почтового сервера: Postfix, Dovecot, антиспам, антивирус. Системные требования и чек-лист готовности."
|
||||||
tags: ["postfix", "dovecot", "smtp", "imap", "Self-hosting"]
|
summaty: "Зачем поднимать свой mail-сервер вместо Gmail. Анатомия почтового сервера: Postfix, Dovecot, антиспам, антивирус. Системные требования и чек-лист готовности."
|
||||||
categories: ["Системное администрирование", "Электронная почта"]
|
tags: ["postfix", "dovecot", "smtp", "imap", "self-hosting"]
|
||||||
|
categories: ["Почтовые системы"]
|
||||||
series: ["Почтовый сервер на Debian 12"]
|
series: ["Почтовый сервер на Debian 12"]
|
||||||
series_order: 1
|
series_order: 1
|
||||||
---
|
---
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 632 KiB |
@@ -3,8 +3,9 @@ title: "Почтовый сервер на Debian 12: полное руково
|
|||||||
date: 2026-03-10
|
date: 2026-03-10
|
||||||
draft: false
|
draft: false
|
||||||
description: "Установка и настройка Postfix и Dovecot. SMTP/IMAP, виртуальные ящики, авторизация через SASL. Первые тесты отправки и получения почты."
|
description: "Установка и настройка Postfix и Dovecot. SMTP/IMAP, виртуальные ящики, авторизация через SASL. Первые тесты отправки и получения почты."
|
||||||
tags: ["postfix", "dovecot", "smtp", "imap", "lmtp", "sasl", "Self-hosting"]
|
summary: "Установка и настройка Postfix и Dovecot. SMTP/IMAP, виртуальные ящики, авторизация через SASL. Первые тесты отправки и получения почты."
|
||||||
categories: ["Системное администрирование", "Электронная почта"]
|
tags: ["postfix", "dovecot", "smtp", "imap", "lmtp", "sasl", "self-hosting"]
|
||||||
|
categories: ["Почтовые системы"]
|
||||||
series: ["Почтовый сервер на Debian 12"]
|
series: ["Почтовый сервер на Debian 12"]
|
||||||
series_order: 2
|
series_order: 2
|
||||||
---
|
---
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 994 KiB |
@@ -3,8 +3,9 @@ title: "Почтовый сервер на Debian 12: полное руково
|
|||||||
date: 2026-03-16
|
date: 2026-03-16
|
||||||
draft: false
|
draft: false
|
||||||
description: "Настройка Amavis, ClamAV, SpamAssassin и Postgrey для защиты от вирусов и спама. Fail2ban для защиты от брутфорса SMTP/IMAP. Обучение антиспама, белые списки, мониторинг."
|
description: "Настройка Amavis, ClamAV, SpamAssassin и Postgrey для защиты от вирусов и спама. Fail2ban для защиты от брутфорса SMTP/IMAP. Обучение антиспама, белые списки, мониторинг."
|
||||||
|
summary: "Настройка Amavis, ClamAV, SpamAssassin и Postgrey для защиты от вирусов и спама. Fail2ban для защиты от брутфорса SMTP/IMAP. Обучение антиспама, белые списки, мониторинг."
|
||||||
tags: ["amavis", "clamav", "spamassassin", "postgrey", "fail2ban"]
|
tags: ["amavis", "clamav", "spamassassin", "postgrey", "fail2ban"]
|
||||||
categories: ["Системное администрирование", "Электронная почта"]
|
categories: ["Почтовые системы"]
|
||||||
series: ["Почтовый сервер на Debian 12"]
|
series: ["Почтовый сервер на Debian 12"]
|
||||||
series_order: 3
|
series_order: 3
|
||||||
---
|
---
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 790 KiB |
@@ -1,10 +1,12 @@
|
|||||||
---
|
---
|
||||||
title: "Почтовый сервер на Debian 12: полное руководство от установки до production. Часть 4 - Шифрование и репутация отправителя"
|
title: "Почтовый сервер на Debian 12: полное руководство от установки до production. Часть 4 - Шифрование и репутация отправителя"
|
||||||
|
slug: mailserver-part-4-tls-dkim-spf-dmarc
|
||||||
date: 2026-04-19
|
date: 2026-04-19
|
||||||
draft: false
|
draft: false
|
||||||
description: "Настройка TLS через Let's Encrypt, DKIM подписей, SPF и DMARC записей. Шифрование всех соединений SMTP/IMAP, повышение репутации отправителя для защиты от попадания в спам."
|
description: "Настройка TLS через Let's Encrypt, DKIM подписей, SPF и DMARC записей. Шифрование всех соединений SMTP/IMAP, повышение репутации отправителя для защиты от попадания в спам."
|
||||||
tags: ["tls", "ssl", "lets encrypt", "certbot", "dkim", "opendkim", "spf", "dmarc", "swaks"]
|
summary: "Настройка TLS через Let's Encrypt, DKIM подписей, SPF и DMARC записей. Шифрование всех соединений SMTP/IMAP, повышение репутации отправителя для защиты от попадания в спам."
|
||||||
categories: ["Системное администрирование", "Электронная почта"]
|
tags: ["tls", "ssl", "lets-encrypt", "certbot", "dkim", "opendkim", "spf", "dmarc", "swaks"]
|
||||||
|
categories: ["Почтовые системы"]
|
||||||
series: ["Почтовый сервер на Debian 12"]
|
series: ["Почтовый сервер на Debian 12"]
|
||||||
series_order: 4
|
series_order: 4
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ author:
|
|||||||
byline_title: "Автор"
|
byline_title: "Автор"
|
||||||
|
|
||||||
code:
|
code:
|
||||||
copy: "Копировать"
|
copy: "Копи."
|
||||||
copied: "Скопировано"
|
copied: "ОК"
|
||||||
|
|
||||||
error:
|
error:
|
||||||
404_title: "Страница не найдена: в замешательстве:"
|
404_title: "Страница не найдена: в замешательстве:"
|
||||||
|
|||||||
@@ -1,14 +1,3 @@
|
|||||||
<!-- VK Comments Widget -->
|
<!-- Comments Widget -->
|
||||||
<div class="vk-comments-wrap">
|
<script defer src="https://comments.oakazanin.ru/comentario.js"></script>
|
||||||
<div id="vk_comments"></div>
|
<comentario-comments no-fonts="true" lang="ru" theme="light"></comentario-comments>
|
||||||
</div>
|
|
||||||
<script type="text/javascript" src="https://vk.com/js/api/openapi.js?169"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
VK.init({apiId: 54459311, onlyWidgets: true});
|
|
||||||
VK.Widgets.Comments("vk_comments", {
|
|
||||||
limit: 10,
|
|
||||||
attach: "*",
|
|
||||||
autoPublish: 0,
|
|
||||||
pageUrl: "{{ .Permalink }}"
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||