Compare commits
54 Commits
bf15c4fb7b
...
master
Author | SHA1 | Date | |
---|---|---|---|
f2583a89cc | |||
948dbe28b0 | |||
be40e3764d | |||
cc4a4add8c | |||
0af69580e7 | |||
fa0eb3188f | |||
adbc6c4072 | |||
c70eb60c8a | |||
f7203aabdc | |||
026a03de3d | |||
54b743e2ad | |||
60e8e24ed7 | |||
dad496edcb | |||
010d85a0ce | |||
d6c747f7c6 | |||
e32b702d7c | |||
08042c99b2 | |||
39a731f369 | |||
af3da4aca3 | |||
356a6d72bb | |||
da9a92f9c6 | |||
f94b1bd679 | |||
b594271e03 | |||
f04d593f53 | |||
75b24bbdab | |||
f94d7e42ed | |||
fc924e196b | |||
af40c9c716 | |||
ce7cea9b0f | |||
c80f4fa469 | |||
88b7e81b84 | |||
8d5b6d1306 | |||
ffb24ad816 | |||
e62371bb52 | |||
76cf200847 | |||
625b6c0b0a | |||
19ba8e07e4 | |||
920cf30bae | |||
18d396991e | |||
92ad28bc58 | |||
1abeffd72c | |||
c3d4600662 | |||
9629b85011 | |||
475a294d61 | |||
7bfc9001e0 | |||
ceb94eb151 | |||
df7777655c | |||
1abefba930 | |||
9f713ba93a | |||
60650b69d2 | |||
2fb12824e2 | |||
93548f786e | |||
e12e8a5b80 | |||
819c122a2b |
13
README.md
@@ -15,13 +15,19 @@
|
||||
* [Подключение менеджера сертификатов (cert-manager) Let's Encrypt](kubernetes/k3s-lets-encrypt-cert-manager.md) к k3s
|
||||
* [Под с 3X-UI](kubernetes/k3s-3xui-pod.md) в k3s
|
||||
* [Проксирование внешнего хоста через Traefik (Ingress-контроллер)](kubernetes/k3s-proxy.md)
|
||||
* [Перенос контейнера Docker в k3s](kubernetes/k3s-migrating-container-from-docker-to-kubernetes.md)
|
||||
* [Перенос контейнера Docker в k3s](kubernetes/k3s-migrating-container-from-docker-to-kubernetes.md) (на примере Gitea)
|
||||
* [Резервное копирование k3s](kubernetes/k3s-backup.md)
|
||||
* [Настройка доступа к панелям управления](kubernetes/k3s-setting-up-web-access-to-dashboard.md) Longhorn и Traefik
|
||||
* [Под с SmokePing](kubernetes/k3s_smokeping.md) для мониторинга доступности хостов
|
||||
* [PostgeSQL в K3s](kubernetes/k3s-postresql.md)
|
||||
* [ChartDB в K3s](kubernetes/k3s-chartdb.md) — графический редактор схем баз данных
|
||||
* [Развертывание Metabase в K3s](kubernetes/k3s-metabase.md) — BI-платформа для анализа данных
|
||||
|
||||
## Python
|
||||
* [Устранение проблем при установке Python-коннектора mysqlclient (MySQL/MariaDB)](python/python-mysql.md)
|
||||
* [Python-скрипт как служба Linux](python/python_as_service.md)
|
||||
|
||||
## Orange Pi / Raspberry Pi
|
||||
## Linux (возможно в специфике Orange Pi / Raspberry Pi)
|
||||
* [Установка (перенос) системы на NVMe или eMMC (для Orange Pi 5 Plus)](raspberry-and-orange-pi/opi5plus-move-system-to-nvme-or-emmc.md)
|
||||
* [Измерение производительности накопителей](raspberry-and-orange-pi/measuring-performance-storage-devices.md)
|
||||
* [Установка Docker и Docker Compose](raspberry-and-orange-pi/install-docker-compose.md)
|
||||
@@ -29,7 +35,8 @@
|
||||
* [k8s (кubernetes) на Orange Pi (драфт...)](raspberry-and-orange-pi/k8s.md)
|
||||
* [k3s (кubernetes) на Orange Pi](raspberry-and-orange-pi/k3s.md)
|
||||
* [Перекомпиляция ядра Linux (включение пподдержки iSCSI в Orange Pi 5 Plus](raspberry-and-orange-pi/opi5plus-rebuilding-linux-kernel-for-iscsi.md)
|
||||
|
||||
* [Защита хоста с помощью CrowdSec](raspberry-and-orange-pi/host-protection-with-crowdsec.md), включая GeoIP блокировки
|
||||
*
|
||||
## Nginx / Apache
|
||||
* [Ограничение доступа по User-Agent (на примере GPTBot)](nginx/nginx-ban-user-agent.md)
|
||||
* [Настройка nginx как прямого прокси](nginx/nginx_as_direct_proxy.md)
|
||||
|
BIN
images/crowdsec--security-engine-registration.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
images/crowdsec--security-panel.png
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
images/k3s--chartdb-ui.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
images/k3s--longhorn-ui.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
images/k3s--pods-smokeping.png
Normal file
After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
281
kubernetes/k3s-backup.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# Резервное копирование и восстановление K3s
|
||||
|
||||
У меня все манифесты хранятся в домашнем каталоге в папке `~/k3s`, но сохранение манифестов не обеспечит резервного
|
||||
копирования (хотя и будет хорошим подспорьем). Но в k3s есть еще настройки развертывания, маршруты, секреты,
|
||||
данные etcd (базы данных, в котрой хранится и синхронизируется вся информация k3s) и тома блочного хранилища
|
||||
PersistentVolumeClaims (PVC). Хочется сделать резервную копию всего этого, на случай сбоя и фактора "кривых рук".
|
||||
|
||||
```bash
|
||||
mkdir -p ~/script
|
||||
nano ~/script/backup-k3s.sh
|
||||
```
|
||||
|
||||
И вставить туда вот такой скрипт (не забудьте заменить `<secret-password>`, `<NAS-IP>` и `<FOLDER>` на свои значения):
|
||||
```bash
|
||||
#!/usr/bin/bash
|
||||
# Скрипт для резервного копирования компонентов K3s (снапшоты etcd, манифесты, секреты)
|
||||
# на сетевой ресурс SAMBA.
|
||||
|
||||
# --- Конфигурация ---
|
||||
# Локальная точка монтирования для SAMBA
|
||||
MOUNT_POINT="/media/backup"
|
||||
# Сетевой ресурс SAMBA
|
||||
SAMBA_USER="<USER>"
|
||||
SAMBA_PASSWORD="<secret-password>" # Лучше использовать файл credentials: credentials=/путь/к/.smbcreds
|
||||
SAMBA_SHARE="//<NAS-IP>/<FOLDER>"
|
||||
# Каталог для резервных копий на SAMBA
|
||||
BACKUP_DIR="${MOUNT_POINT}/k3s-backup"
|
||||
# Каталог с манифестами K3s
|
||||
MANIFESTS_DIR="/home/opi/k3s"
|
||||
# Каталог со снапшотами etcd K3s
|
||||
ETCD_SNAPSHOT_DIR="/var/lib/rancher/k3s/server/db/snapshots"
|
||||
# Домашний каталог пользователя (используется для cd)
|
||||
USER_HOME="/home/opi"
|
||||
# Сколько дней хранить старые резервные копии
|
||||
RETENTION_DAYS=14
|
||||
# Формат даты для имен файлов и записей в журнале
|
||||
DATE_FORMAT='%F--%H-%M-%S'
|
||||
# Файл журнала на SAMBA
|
||||
LOG_FILE="${BACKUP_DIR}/-backup---"$(date +${DATE_FORMAT})".log"
|
||||
|
||||
# --- Вспомогательные функции ---
|
||||
# Функция для записи сообщения в журнал и на консоль
|
||||
log_message() {
|
||||
local message="$1"
|
||||
local timestamp
|
||||
timestamp=$(date +'%F %R:%S')
|
||||
# Выводим на консоль и дописываем в файл журнала (если он уже доступен)
|
||||
# Добавляем проверку, существует ли каталог для лога, на случай ошибки монтирования
|
||||
if [ -d "$(dirname "${LOG_FILE}")" ]; then
|
||||
echo -e "${timestamp} - ${message}" | tee -a "${LOG_FILE}"
|
||||
else
|
||||
# Если каталог недоступен (например, до монтирования или после размонтирования), пишем только в консоль
|
||||
echo -e "${timestamp} - ${message}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Функция для вывода разделителя в журнал и на консоль
|
||||
log_separator() {
|
||||
local timestamp
|
||||
timestamp=$(date +'%F %R:%S')
|
||||
if [ -d "$(dirname "${LOG_FILE}")" ]; then
|
||||
echo -e "${timestamp} - =========================" | tee -a "${LOG_FILE}"
|
||||
else
|
||||
echo -e "${timestamp} - ========================="
|
||||
fi
|
||||
}
|
||||
|
||||
# Функция для завершения скрипта и размонтирования SAMBA
|
||||
cleanup_and_exit() {
|
||||
local exit_code=$? # Захватываем код завершения последней команды
|
||||
local timestamp
|
||||
timestamp=$(date +'%F %R:%S')
|
||||
|
||||
# Логируем код завершения *до* попытки размонтирования, пока лог-файл (возможно) доступен
|
||||
log_message "Скрипт завершился с кодом ${exit_code}."
|
||||
|
||||
# Пытаемся размонтировать SAMBA, если она примонтирована
|
||||
if mountpoint -q "${MOUNT_POINT}"; then
|
||||
log_message "Размонтирование SAMBA ресурса '${MOUNT_POINT}'..." # Это сообщение еще (возможно) попадет в лог
|
||||
log_separator # И это тоже
|
||||
|
||||
if umount "${MOUNT_POINT}"; then
|
||||
# <<< РЕСУРС УСПЕШНО РАЗМОНТИРОВАН >>>
|
||||
# Выводим сообщение только в консоль, так как лог-файл уже недоступен
|
||||
echo "${timestamp} - SAMBA ресурс успешно размонтирован."
|
||||
else
|
||||
# Ошибка размонтирования. Лог-файл может быть еще доступен, а может и нет.
|
||||
# Надежнее вывести ошибку в консоль.
|
||||
echo "${timestamp} - ОШИБКА: Не удалось размонтировать SAMBA ресурс '${MOUNT_POINT}'."
|
||||
fi
|
||||
else
|
||||
# Ресурс не был примонтирован, лог-файл на нем недоступен
|
||||
echo "${timestamp} - SAMBA ресурс '${MOUNT_POINT}' не примонтирован или уже размонтирован."
|
||||
fi
|
||||
exit "${exit_code}"
|
||||
}
|
||||
|
||||
# Перехватываем сигнал EXIT для запуска функции очистки
|
||||
trap cleanup_and_exit EXIT
|
||||
|
||||
# --- Основной скрипт ---
|
||||
|
||||
echo "Запуск скрипта резервного копирования K3s..." # Это сообщение только в консоль
|
||||
|
||||
# Проверяем, что скрипт запущен от имени root (нужно для mount, доступа к /var/lib/rancher)
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "ОШИБКА: Этот скрипт должен быть запущен от имени root (используй sudo)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 1. Подготовка точки монтирования
|
||||
echo "Проверка и создание локальной точки монтирования '${MOUNT_POINT}'..." # Только консоль
|
||||
if [ ! -d "${MOUNT_POINT}" ]; then
|
||||
if mkdir -p "${MOUNT_POINT}"; then
|
||||
echo "Точка монтирования '${MOUNT_POINT}' создана." # Только консоль
|
||||
else
|
||||
echo "ОШИБКА: Не удалось создать точку монтирования '${MOUNT_POINT}'."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo "=========================" # Только консоль
|
||||
|
||||
# 2. Монтирование SAMBA ресурса
|
||||
echo "Монтирование SAMBA ресурса '${SAMBA_SHARE}' в '${MOUNT_POINT}'..." # Только консоль
|
||||
# Для безопасности лучше использовать файл credentials: -o credentials=/путь/к/.smbcreds,uid=1000,gid=1000 и т.д.
|
||||
if ! mount -t cifs -o username="${SAMBA_USER}",password="${SAMBA_PASSWORD}" "${SAMBA_SHARE}" "${MOUNT_POINT}"; then
|
||||
echo "ОШИБКА: Не удалось примонтировать SAMBA ресурс."
|
||||
exit 1
|
||||
fi
|
||||
log_message "SAMBA ресурс успешно примонтирован."
|
||||
log_separator
|
||||
|
||||
# 3. Подготовка каталога для резервных копий на SAMBA
|
||||
# Теперь можно использовать log_message, т.к. каталог BACKUP_DIR должен быть доступен
|
||||
log_message "Проверка и создание каталога для резервных копий '${BACKUP_DIR}' на SAMBA..."
|
||||
if [ ! -d "${BACKUP_DIR}" ]; then
|
||||
if mkdir -p "${BACKUP_DIR}"; then
|
||||
log_message "Каталог для резервных копий '${BACKUP_DIR}' создан."
|
||||
else
|
||||
log_message "ОШИБКА: Не удалось создать каталог '${BACKUP_DIR}' на SAMBA ресурсе."
|
||||
exit 1 # Выходим, так как некуда сохранять резервные копии
|
||||
fi
|
||||
fi
|
||||
log_separator
|
||||
|
||||
# Начинаем полноценное логирование в файл на примонтированном ресурсе
|
||||
log_message "Начало процесса резервного копирования (лог: ${LOG_FILE})..."
|
||||
log_separator
|
||||
|
||||
|
||||
# Переходим в домашний каталог пользователя (если нужно для относительных путей, хотя сейчас используются абсолютные)
|
||||
cd "${USER_HOME}" || { log_message "ОШИБКА: Не удалось перейти в каталог ${USER_HOME}"; exit 1; }
|
||||
|
||||
# 4. Резервное копирование снапшотов etcd
|
||||
log_message "Резервное копирование снапшотов etcd из '${ETCD_SNAPSHOT_DIR}'..."
|
||||
etcd_backup_file="${BACKUP_DIR}/etcd-------$(date +"${DATE_FORMAT}").zip"
|
||||
if /usr/bin/zip -r "${etcd_backup_file}" "${ETCD_SNAPSHOT_DIR}"; then
|
||||
log_message "Снапшоты etcd сохранены в ${etcd_backup_file}."
|
||||
else
|
||||
log_message "ОШИБКА: Не удалось создать резервную копию снапшотов etcd."
|
||||
# Решите, является ли это критической ошибкой или скрипт может продолжаться
|
||||
fi
|
||||
log_separator
|
||||
|
||||
# 5. Резервное копирование манифестов
|
||||
log_message "Резервное копирование манифестов из '${MANIFESTS_DIR}'..."
|
||||
manifests_backup_file="${BACKUP_DIR}/manifests--$(date +"${DATE_FORMAT}").zip"
|
||||
if /usr/bin/zip -r "${manifests_backup_file}" "${MANIFESTS_DIR}"; then
|
||||
log_message "Манифесты сохранены в ${manifests_backup_file}."
|
||||
else
|
||||
log_message "ОШИБКА: Не удалось создать резервную копию манифестов."
|
||||
fi
|
||||
log_separator
|
||||
|
||||
# 6. Резервное копирование секретов Kubernetes
|
||||
log_message "Резервное копирование секретов Kubernetes..."
|
||||
secrets_backup_file="${BACKUP_DIR}/secrets----$(date +"${DATE_FORMAT}").zip"
|
||||
# Безопасно создаем временный каталог
|
||||
tmp_secrets_dir=$(mktemp -d -t k8s-secrets-backup-XXXXXX)
|
||||
if [[ -z "$tmp_secrets_dir" || ! -d "$tmp_secrets_dir" ]]; then
|
||||
log_message "ОШИБКА: Не удалось создать временный каталог для резервной копии секретов."
|
||||
else
|
||||
log_message "Создан временный каталог для секретов: ${tmp_secrets_dir}"
|
||||
secrets_exported=false
|
||||
# Получаем все пространства имен, исключая некоторые системные (при необходимости)
|
||||
namespaces=$(kubectl get ns -o jsonpath='{.items[*].metadata.name}')
|
||||
# Если нужно, настройте исключаемые из резервного копирования пространства имен
|
||||
# namespaces=$(kubectl get ns -o jsonpath='{.items[*].metadata.name}' --field-selector metadata.name!=kube-system,metadata.name!=kube-public,metadata.name!=kube-node-lease,metadata.name!=default,metadata.name!=longhorn-system,metadata.name!=cert-manager)
|
||||
|
||||
for ns in $namespaces; do
|
||||
log_message "Экспорт секретов из пространства имен: ${ns}"
|
||||
# Определяем путь к файлу вывода во временном каталоге
|
||||
secret_file="${tmp_secrets_dir}/secrets-${ns}.yaml"
|
||||
# Экспортируем секреты во временный файл
|
||||
if kubectl get secret -n "${ns}" -o yaml > "${secret_file}"; then
|
||||
# Проверяем, не пустой ли файл (если в namespace нет секретов)
|
||||
if [[ -s "${secret_file}" ]]; then
|
||||
log_message "Успешно экспортированы секреты для пространства имен ${ns} в ${secret_file}"
|
||||
secrets_exported=true
|
||||
else
|
||||
log_message "В пространстве имен ${ns} нет секретов, пропускаем."
|
||||
rm "${secret_file}" # Удаляем пустой файл
|
||||
fi
|
||||
else
|
||||
log_message "ПРЕДУПРЕЖДЕНИЕ: Не удалось экспортировать секреты для пространства имен ${ns}. Возможно, оно пустое или недоступно."
|
||||
# Удаляем файл, если он был создан, но команда завершилась с ошибкой
|
||||
[ -f "${secret_file}" ] && rm "${secret_file}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Архивируем собранные секреты из временного каталога
|
||||
if [ "$secrets_exported" = true ]; then
|
||||
# Используем флаг -j, чтобы не сохранять структуру временного каталога в архиве
|
||||
if /usr/bin/zip -j "${secrets_backup_file}" "${tmp_secrets_dir}"/*; then
|
||||
log_message "Секреты сохранены в ${secrets_backup_file}."
|
||||
else
|
||||
log_message "ОШИБКА: Не удалось заархивировать экспортированные секреты."
|
||||
fi
|
||||
else
|
||||
log_message "Секреты для экспорта не найдены, создание архива пропущено."
|
||||
fi
|
||||
|
||||
# Очищаем временный каталог
|
||||
log_message "Удаление временного каталога секретов: ${tmp_secrets_dir}"
|
||||
rm -rf "${tmp_secrets_dir}"
|
||||
fi
|
||||
log_separator
|
||||
|
||||
|
||||
# 7. Резервное копирование PVC (Заглушка - Требуется отдельная стратегия, например, Velero или бэкап Longhorn)
|
||||
log_message "Секция резервного копирования PVC - Заглушка."
|
||||
log_message "Примечание: Резервное копирование данных PVC требует специальной стратегии, такой как Velero (velero.io) или встроенные функции резервного копирования Longhorn."
|
||||
# Пример использования бэкапа Longhorn (концептуально - требует настройки Longhorn):
|
||||
# longhorn backup create my-pvc-backup --dest s3://my-backup-bucket/longhorn/
|
||||
log_separator
|
||||
|
||||
|
||||
# 8. Очистка старых резервных копий
|
||||
log_message "Очистка старых резервных копий старше ${RETENTION_DAYS} дней в '${BACKUP_DIR}'..."
|
||||
# Ищем и удаляем старые zip-файлы и файлы журналов, соответствующие шаблонам
|
||||
# Используем -maxdepth 1, чтобы случайно не удалить файлы во вложенных каталогах
|
||||
# Обновляем шаблон для лог-файлов: backup-*.log
|
||||
deleted_files=$(/usr/bin/find "${BACKUP_DIR}" -maxdepth 1 -type f \( -name "etcd-------*.zip" -o -name "manifests--*.zip" -o -name "secrets----*.zip" -o -name "-backup---*.log" \) -mtime +"${RETENTION_DAYS}" -print -delete)
|
||||
|
||||
if [[ -n "$deleted_files" ]]; then
|
||||
log_message "Удалены старые файлы резервных копий:"
|
||||
echo "$deleted_files" | while IFS= read -r file; do log_message " - $file"; done
|
||||
else
|
||||
log_message "Старые файлы резервных копий для удаления не найдены."
|
||||
fi
|
||||
log_separator
|
||||
|
||||
# 9. Список текущих резервных копий
|
||||
log_message "Текущие резервные копии в '${BACKUP_DIR}':"
|
||||
ls -alhcrt "${BACKUP_DIR}" >> "${LOG_FILE}" # Записываем подробный список в журнал
|
||||
ls -alhcrt "${BACKUP_DIR}" # Показываем список на консоли
|
||||
log_separator
|
||||
|
||||
# 10. Размонтирование и выход (Обрабатывается через trap)
|
||||
log_message "Процесс резервного копирования завершен. Размонтирование произойдет при выходе."
|
||||
log_separator
|
||||
|
||||
# Явный выход с кодом 0, если мы дошли до сюда без ошибок
|
||||
exit 0
|
||||
```
|
||||
|
||||
Добавим скрипт в системный cron (root):
|
||||
```bash
|
||||
sudo crontab -e
|
||||
```
|
||||
|
||||
Например, добавим в cron запуск скрипта каждый день в 2:10:
|
||||
```text
|
||||
# Резервное копирование K3S
|
||||
10 2 * * * /usr/bin/bash /home/opi/script/backup-k3s.sh
|
||||
```
|
||||
|
||||
## Настройка резервного копирования томов блочного хранилища Longhorn
|
||||
|
||||
Наиболее удобный способ резервного копирования томов блочного хранилища Longhorn - встроенная в него панель управления.
|
||||
О том как настроить доступ к ней из браузера, читайте в заметке [Настройка доступа к панели управления Longhorn](k3s-setting-up-web-access-to-dashboard.md).
|
127
kubernetes/k3s-chartdb.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Под с ChartDB
|
||||
|
||||
ChatBD ([chartdb.io](https://chartdb.io/)) - это визуальный редактор структуры базы данных и инструмент проектирования
|
||||
схем (ER-диаграмм), мини-конструктор для работы с локальной псевдо-БД. Все это доступно в веб-интерфейсе. Пока ChatBD
|
||||
не позволяет подключаться к реальным базам данных, но в будущем планируется поддержка. Зато в нее можно импортировать
|
||||
схемы рабочих баз данных из PostgreSQL, MySQL, MariaDB, SQLite, MSSQL, ClickHouse и CockroachDB. Так же поддерживается
|
||||
экспорт из JSON и DBML. Импорт готовых схем возможен в любую из поддерживаемых СУБД (плюс JSON и DBML).
|
||||
|
||||
Можно работать с таблицами, колонками, индексами, ключами... но в документации прямо заявлено:
|
||||
> «Самостоятельная версия не поддерживает удаленные подключения к базе данных или аутентификацию.
|
||||
> Для полной функциональности используйте chartdb.io».
|
||||
|
||||
Но самое печальное, в нем нет никаких инструментов для создания запросов.
|
||||
|
||||
_Из интересных фишек ChatDB -- к нему можно подключить LLM (через API OpenAI или локальный Ollama) и тогда он,
|
||||
похоже, сможет генерировать SQL-запросы по текстовым описаниям. Но это я пока пока не проверил (ждите обновления этой
|
||||
инструкции)._
|
||||
|
||||
Манифест для развертывания пода с ChartDB в k3s, который предоставляет веб-интерфейс по адресу `http://chartdb.local`:
|
||||
```yaml
|
||||
# ~/k3s/chartdb/chartdb.yaml
|
||||
# Все манифесты для ChartDB
|
||||
|
||||
# 1. Манифест создания пространства имён `chartdb`. Если оно уже есть — kubectl apply ничего не изменит
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: chartdb
|
||||
|
||||
---
|
||||
# 2. Манифест PVC (Longhorn) -- том в блочном хранилище, в котором будут храниться данные ChartDB.
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: chartdb-data
|
||||
namespace: chartdb
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 320Mi # Более чем достаточно для хранения схем и данных ChartDB, даже 150Mi хватит
|
||||
|
||||
---
|
||||
# 3. Deployment: развёртывание ChartDB
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: chartdb
|
||||
namespace: chartdb
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: chartdb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: chartdb
|
||||
spec:
|
||||
containers:
|
||||
- name: chartdb
|
||||
image: ghcr.io/chartdb/chartdb:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
env:
|
||||
- name: TZ # Часовой пояс, который будет в поде
|
||||
value: Europe/Moscow
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
volumeMounts: # Монтируем том:
|
||||
- name: chartdb-storage # ... имя PVC-тома
|
||||
mountPath: /data # ...путь внутри контейнера, куда будет смонтирован PVC-том
|
||||
volumes: # Используемые том:
|
||||
- name: chartdb-storage # ... c именем
|
||||
persistentVolumeClaim: # ... PVC (Longhorn)
|
||||
claimName: chartdb-data
|
||||
|
||||
---
|
||||
# 4. Service: внутренний доступ к контейнеру ChartDB
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: chartdb
|
||||
namespace: chartdb
|
||||
spec:
|
||||
selector:
|
||||
app: chartdb
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
type: ClusterIP
|
||||
|
||||
---
|
||||
# 5. IngressRoute для Traefik (под твою конфигурацию)
|
||||
# Это публикует ChartDB по адресу http://chartdb.local (заменить на свой домен)
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: chartdb
|
||||
namespace: chartdb
|
||||
spec:
|
||||
entryPoints:
|
||||
- web # это должен быть один из entrypoints в Traefik (обычно "web" = порт 80)
|
||||
routes:
|
||||
- match: Host("chartdb.local") # доменное имя, по которому будет доступен сервис
|
||||
kind: Rule
|
||||
services:
|
||||
- name: chartdb
|
||||
port: 80
|
||||
```
|
||||
|
||||
Применим манифесты командой:
|
||||
```shell
|
||||
kubectl apply -f ~/k3s/chartdb/chartdb.yaml
|
||||
```
|
||||
|
||||
После этого ChartDB будет доступен по адресу `http://chartdb.local`. и в нем можно будет создавать и редактировать
|
||||
схемы, например:
|
||||
|
||||

|
@@ -149,8 +149,8 @@ kubectl get certificate -A
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
NAMESPACE NAME READY SECRET AGE
|
||||
ab-shelf audiobookshelf-tls False audiobookshelf-tls 4h
|
||||
gitea gitea-tls True gitea-tls 14m
|
||||
ab-shelf audiobookshelf-tls False audiobookshelf-tls 90m
|
||||
gitea gitea-tls True gitea-tls 66s
|
||||
```
|
||||
|
||||
Тут можно заметить, что сертификат `audiobookshelf-tls` не готов (False), а `gitea-tls` готов (True).
|
||||
@@ -194,19 +194,62 @@ Status:
|
||||
```
|
||||
|
||||
Как видно, причина неготовности сертификата `audiobookshelf-tls` в том, что уже превышен лимит на количество сертификатов
|
||||
(5) для данного доменного имени за последние 168 часов (7 дней). Указано время после которого можно повторить запрос.
|
||||
(пять запросов) для данного доменного имени за последние 168 часов (семь дней). Указано время после которого можно
|
||||
повторить запрос.
|
||||
|
||||
По идее запрос на повторный сертификат будет отправлен автоматически, но это может произойти спустя несколько часов
|
||||
после разрешенного времени (учтите что время указывается в UTC, делайте поправку по своему локальному времени).
|
||||
(три-четыре или более) после разрешенного времени. Кстати, время указывается в UTC, делайте поправку на свой часовой
|
||||
пояс.
|
||||
|
||||
Чтобы ускорить процесс, можно удалить сертификат и создать его заново (на примере `audiobookshelf-tls`):
|
||||
```bash
|
||||
kubectl delete certificate audiobookshelf-tls -n ab-shelf
|
||||
```
|
||||
|
||||
А затем повторно принять манифест, в котором у вас находится `kind: Certificate`
|
||||
А затем повторно принять манифест, в котором у вас находится `kind: Certificate`.
|
||||
|
||||
| Заметка |
|
||||
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Пока Let's Encrypt не выдал сертификат, Traefik будет работать по SSL (https) на самоподписанном сертификате. Можно открыть анонимное окно браузера, согласится с предупреждениями безопасности и пользоваться сайтом. |
|
||||
| |
|
||||
|
||||
Когда же все хорошо (на примере `gitea-tls` для домены `git.cube2.ru`, сайт которого вы сейчас читаете):
|
||||
```bash
|
||||
kubectl describe certificate gitea-tls -n gitea
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
Name: gitea-tls
|
||||
Namespace: gitea
|
||||
...
|
||||
...
|
||||
...
|
||||
Spec:
|
||||
Dns Names:
|
||||
git.cube2.ru
|
||||
Issuer Ref:
|
||||
Kind: ClusterIssuer
|
||||
Name: letsencrypt-prod
|
||||
Secret Name: gitea-tls
|
||||
Status:
|
||||
Conditions:
|
||||
Last Transition Time: 2025-04-27T18:43:02Z
|
||||
Message: Certificate is up to date and has not expired
|
||||
Observed Generation: 1
|
||||
Reason: Ready
|
||||
Status: True
|
||||
Type: Ready
|
||||
Not After: 2025-07-26T17:44:29Z
|
||||
Not Before: 2025-04-27T17:44:30Z
|
||||
Renewal Time: 2025-06-26T17:44:29Z
|
||||
...
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
Видим что `Status: True`, `Reason: Ready`, а также время дату/время с которого сертификат действителен
|
||||
(время `Not Before`) и до которого он действителен (время `Not After`), а также дату/время, когда сертификат
|
||||
будет автоматически обновлен (время `Renewal Time`).
|
||||
|
||||
**ВАЖНО**: _время указывается в UTC, делайте поправку на свой часовой пояс_.
|
178
kubernetes/k3s-metabase.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# Развертывание Metabase в K3s
|
||||
|
||||
Metabase ([metabase.com](https://www.metabase.com/)) — это мощный и дружелюбный инструмент BI (Business Intelligence).
|
||||
Он работает через веб-интерфейс, позволяет подключаться к реальным БД, делать запросы (SQL и визуальный конструктор),
|
||||
строить графики, дашборды, отчёты (и отправлять эти отчеты по email-рассылки) и многое другое.
|
||||
|
||||
Metabase немного похож на [Power BI](https://www.microsoft.com/en-us/power-platform/products/power-bi/) или
|
||||
[Tableau](https://www.tableau.com/), но Open-Source.
|
||||
|
||||
Поддерживаются основные СУБД: PostgreSQL, MySQL/MariaDB, SQLite, ClickHouse, MongoDB, Microsoft SQL Server, BigQuery
|
||||
и многие другие.
|
||||
|
||||
Metabase — монолит (Java JAR), и работает на сервере (или любом компьютере) как один процесс Java Virtual Machine.
|
||||
Для хранения своей конфигурации использует встроенную базу данных H2, но его можно подключить и внешней СУБД
|
||||
(например, PostgreSQL или MariaDB). И главное, для меня, он поддерживает ARM64? (а значит заработает [на моем k3s
|
||||
на базе Orange Pi 5](../raspberry-and-orange-pi/k3s.md)).
|
||||
|
||||
|
||||
## Подготовка базы данных
|
||||
|
||||
В deployment-манифесте Metabase будут указаны параметры подключения к PostgreSQL. Он у меня тоже развернут как под k3s
|
||||
(см.: [развертывание PostgeSQL в K3s](k3s-postresql.md).
|
||||
|
||||
Создадим пользователя PostgreSQL, базу данных и права пользователя для Metabase. Это можно сделать через `psql` или
|
||||
любой другой клиент PostgreSQL. Нужно выполнить следующие SQL-команды (не забудьте заменить пароль на свой):
|
||||
```sql
|
||||
CREATE USER metabase_user WITH ENCRYPTED PASSWORD 'очень-секретный-пароль-123!!';
|
||||
CREATE DATABASE metabase OWNER metabase_user;
|
||||
GRANT ALL PRIVILEGES ON DATABASE metabase TO metabase_user;
|
||||
GRANT ALL ON SCHEMA public TO metabase_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO metabase_user;
|
||||
```
|
||||
|
||||
Что здесь происходит:
|
||||
- Создаем пользователя `metabase_user` с паролем `очень-секретный-пароль-123!!`.
|
||||
- Создаем базу данных `metabase` и назначаем владельцем этого пользователя.
|
||||
- Предоставляем все привилегии на базу данных `metabase` этому пользователю (можно не делать, т.к. мы уже указали владельца).
|
||||
- Схема public создаётся автоматически в базе, но чтобы metabase_user мог работать с таблицами предоставляем все права
|
||||
на схему `public` базы данных (это стандартная схема PostgreSQL).:
|
||||
- Чтобы пользователь мог создавать таблицы, функции и прочее в схеме...
|
||||
|
||||
# Манифесты для развертывания Metabase в K3s
|
||||
|
||||
У меня Metabase будет доступен по адресу `http://mb.local` (через VIP-адрес Keeepalive). Замените доменное имя на свое,
|
||||
и не забудьте настроить DNS или файл `/etc/hosts`.
|
||||
|
||||
Для развертывания Metabase в k3s нам понадобятся следующие манифесты Kubernetes (не забудьте поменять пароль
|
||||
пользователя PostgreSQL на свой):
|
||||
```yaml
|
||||
# ~/k3s/metabase/metabase.yaml
|
||||
# Все манифесты для Metabase в k3s
|
||||
|
||||
# 1. Namespace: создаём пространство имен `metabase`
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: metabase
|
||||
|
||||
---
|
||||
# 2. Secret: храним пароль к PostgreSQL в Kubernetes-секрете (это безопаснее, чем указывать его прямо в Deployment)
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: metabase-db-secret
|
||||
namespace: metabase
|
||||
type: Opaque
|
||||
stringData:
|
||||
MB_DB_PASS: 'очень-секретный-пароль-123!!' # Пароль. Не закодирован, но kubectl хранит его в base64.
|
||||
|
||||
---
|
||||
# 3. PVC: том для временных данных Metabase
|
||||
# Metabase хранит всё важное в PostgreSQL, но PVC-том нужен для кеша, логов и временных файлов
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: metabase-data
|
||||
namespace: metabase
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 512Mi # Достаточно для большинства задач
|
||||
|
||||
---
|
||||
# 4. Deployment: контейнер с Metabase (+указываем переменные окружения для подключения к PostgreSQL)
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: metabase
|
||||
namespace: metabase
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: metabase
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: metabase
|
||||
spec:
|
||||
containers:
|
||||
- name: metabase
|
||||
image: metabase/metabase:latest # ARM64-совместимый образ
|
||||
ports:
|
||||
- containerPort: 3000 # Стандартный порт Metabase внутри контейнера
|
||||
env:
|
||||
- name: MB_DB_TYPE
|
||||
value: postgres
|
||||
- name: MB_DB_DBNAME
|
||||
value: metabase
|
||||
- name: MB_DB_PORT
|
||||
value: "5432" # В кавычках: безопаснее в YAML
|
||||
- name: MB_DB_USER
|
||||
value: metabase_user
|
||||
- name: MB_DB_HOST
|
||||
value: postgres.postgresql.svc.cluster.local # Берем из Service-монифеста PostgreSQL (или хост `pg.local`, доступный внутри сети)
|
||||
- name: MB_DB_PASS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: metabase-db-secret # Секрет, созданный выше
|
||||
key: MB_DB_PASS
|
||||
- name: JAVA_TIMEZONE
|
||||
value: Europe/Moscow
|
||||
volumeMounts:
|
||||
- name: metabase-storage
|
||||
mountPath: /metabase-data # Временные файлы, кеши, логи
|
||||
volumes:
|
||||
- name: metabase-storage
|
||||
persistentVolumeClaim:
|
||||
claimName: metabase-data
|
||||
|
||||
---
|
||||
# 5. Сервис: внутренняя точка доступа (не публикуется наружу)
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: metabase
|
||||
namespace: metabase
|
||||
spec:
|
||||
selector:
|
||||
app: metabase
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 3000 # Проксируем внешний порт 80 → контейнерный 3000
|
||||
type: ClusterIP
|
||||
|
||||
---
|
||||
# 6. IngressRoute: Traefik CRD для публикации Metabase по адресу http://mb.local
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: metabase
|
||||
namespace: metabase
|
||||
spec:
|
||||
entryPoints:
|
||||
- web # HTTP-порт Traefik (обычно 80)
|
||||
routes:
|
||||
- match: Host("mb.local")
|
||||
kind: Rule
|
||||
services:
|
||||
- name: metabase
|
||||
port: 80
|
||||
```
|
||||
|
||||
Применим манифесты командой:
|
||||
```bash
|
||||
kubectl apply -f ~/k3s/metabase/metabase.yaml
|
||||
```
|
||||
|
||||
Все должно заработать, и Metabase будет доступен по адресу `http://mb.local` (или по тому доменному имени, которое
|
||||
вы указали). Если что-то не так, то скорее всего проблемы с подключением к PostgreSQL. Удалите базу, пользователя,
|
||||
k3s-деплоймент, и создайте заново. Диагностировать причину неполадок можно посмотрев логи пода:
|
||||
```bash
|
||||
kubectl -n metabase logs deploy/metabase
|
||||
```
|
||||
|
@@ -44,9 +44,9 @@ docker stop docker stop gitea-new
|
||||
```
|
||||
### Архивируем данные gitea
|
||||
|
||||
Находясь на хосте в Docker, cоздадим архив с данными gitea:
|
||||
Находясь на хосте в Docker, создадим архив с данными gitea:
|
||||
```bash
|
||||
tar -czf /home/orangepi/gitea-data.tar.gz -C /home/orangepi/docker-data/gitea-new .
|
||||
с
|
||||
```
|
||||
|
||||
Проверим, что архив создался:
|
||||
@@ -82,7 +82,7 @@ ls -alh ~/gitea-data.tar.gz
|
||||
|
||||
Создадим пространство имен для gitea в k3s (чтобы все было аккуратно):
|
||||
```bash
|
||||
sudo kubectl create namespace gitea
|
||||
kubectl create namespace gitea
|
||||
```
|
||||
|
||||
Создаем папку для хранения манифестов gitea:
|
||||
@@ -99,7 +99,7 @@ mkdir -p ~/k3s/gitea
|
||||
|
||||
Создадим манифест для PersistentVolumeClaim (PVC) и PersistentVolume (PV):
|
||||
```bash
|
||||
nano ~/k3s/gitea/longhorn-pvc.yaml
|
||||
nano ~/k3s/gitea/gitea-pvc.yaml
|
||||
```
|
||||
|
||||
Вставляем в него следующее содержимое:
|
||||
@@ -115,17 +115,17 @@ spec:
|
||||
storageClassName: longhorn # Используем Longhorn как класс хранения
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi # Размер под данные Gitea 10 Гб (максимальный объем)
|
||||
storage: 8Gi # Размер под данные Gitea 10 Гб (максимальный объем)
|
||||
```
|
||||
|
||||
Применим манифест:
|
||||
```bash
|
||||
sudo kubectl apply -f ~/k3s/gitea/longhorn-pvc.yaml
|
||||
kubectl apply -f ~/k3s/gitea/gitea-pvc.yaml
|
||||
```
|
||||
|
||||
Проверим, что PVC создан и доступен:
|
||||
```bash
|
||||
sudo kubectl get pvc -n gitea -o wide
|
||||
kubectl get pvc -n gitea -o wide
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
@@ -151,20 +151,21 @@ spec:
|
||||
containers:
|
||||
- name: init-data
|
||||
image: alpine:latest
|
||||
command: ["/bin/sh", "-c", "tar -xzf /mnt/gitea-data.tar.gz -C /data && chmod -R 777 /data && ls -la /data && sleep 3600"]
|
||||
command: ["/bin/sh", "-c", "tar -xzf /mnt/gitea-data.tar.gz -C /data && chown -R root:root /data && chmod -R 777 /data && ls -la /data && sleep 14400"]
|
||||
# Для каталога `/data` заданы такие "злые" права из-за системных хуков, которые запускает Gitea. Точечно найти и дать права -- не получилось.
|
||||
volumeMounts:
|
||||
- name: gitea-data
|
||||
mountPath: /data
|
||||
- name: tmp-data
|
||||
mountPath: /mnt
|
||||
mountPath: /mnt/gitea-data.tar.gz
|
||||
volumes:
|
||||
- name: gitea-data
|
||||
persistentVolumeClaim:
|
||||
claimName: gitea-pvc
|
||||
- name: tmp-data
|
||||
hostPath:
|
||||
path: /home/<USER-NAME>
|
||||
type: Directory # Указываем, что это папка
|
||||
path: /home/<USER-NAME>/gitea-data.tar.gz
|
||||
type: File # Указываем, что это файл
|
||||
restartPolicy: Never
|
||||
```
|
||||
|
||||
@@ -174,7 +175,7 @@ spec:
|
||||
- `containers` — задаёт контейнер с именем `init-data`, который использует образ `alpine:latest`.
|
||||
- `command` — выполняет команду в контейнере:
|
||||
- `tar -xzf /mnt/gitea-data.tar.gz -C /data` — распаковывает архив в `/data`.
|
||||
- `chmod -R 777 /data` — задаёт права на папку `/data`.
|
||||
- `chown -R root:root /data && chmod -R 777 /data` — задаёт права на папку `/data`.
|
||||
- `ls -la /data` — выводит содержимое `/data` в логи.
|
||||
- `sleep 3600` — держит под живым 1 час, чтобы ты можно было зайти в sh и проверить, что всё распаковалось.
|
||||
- `volumeMounts` — монтирует два тома:
|
||||
@@ -188,12 +189,12 @@ spec:
|
||||
|
||||
Применим манифест:
|
||||
```bash
|
||||
sudo kubectl apply -f ~/k3s/gitea/gitea-init-data.yaml
|
||||
kubectl apply -f ~/k3s/gitea/gitea-init-data.yaml
|
||||
```
|
||||
|
||||
Проверим, что под создан и работает:
|
||||
```bash
|
||||
sudo kubectl get pod -n gitea -o wide
|
||||
kubectl get pod -n gitea -o wide
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
@@ -204,7 +205,7 @@ gitea-init-data 1/1 Running 0 4m 10.42.2.64 opi5plus-1 <
|
||||
|
||||
Проверим логи пода:
|
||||
```bash
|
||||
sudo kubectl logs -n gitea gitea-init-data
|
||||
kubectl logs -n gitea gitea-init-data
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
@@ -221,7 +222,7 @@ drwxrwxrwx 2 root root 4096 Apr 17 13:01 ssh
|
||||
Как видим, данные благополучно распаковались в `/data` внутри пода, и это Longhorn PVC `gitea-pvc`. Можно также "зайти"
|
||||
в под и посмотреть, что там внутри:
|
||||
```bash
|
||||
sudo kubectl exec -it -n gitea gitea-init-data -- /bin/sh
|
||||
kubectl exec -it -n gitea gitea-init-data -- /bin/sh
|
||||
```
|
||||
|
||||
Внутри пода дать команду, например:
|
||||
@@ -250,13 +251,13 @@ SSD быстрее (если интересно, то вот [заметка о
|
||||
и потому если контейнер с Gitea будет работать на SSD, то он будет работать быстрее. Как настроить предпочтение узлов
|
||||
описано в [заметке о аффинити](k3s-affinate.md), поэтому кратко: присваиваем узлам метки, например `disk=ssd`:
|
||||
```bash
|
||||
sudo kubectl label nodes opi5plus-1 disk=ssd
|
||||
sudo kubectl label nodes opi5plus-3 disk=ssd
|
||||
kubectl label nodes opi5plus-1 disk=ssd
|
||||
kubectl label nodes opi5plus-3 disk=ssd
|
||||
```
|
||||
|
||||
Проверяем, что метки добавлены:
|
||||
```bash
|
||||
sudo kubectl get nodes --show-labels | grep "disk=ssd"
|
||||
kubectl get nodes --show-labels | grep "disk=ssd"
|
||||
```
|
||||
|
||||
Будут показаны только узлы с меткой `disk=ssd`. У каждого узда очень много меток.
|
||||
@@ -333,12 +334,12 @@ spec:
|
||||
|
||||
Применим манифест:
|
||||
```bash
|
||||
sudo kubectl apply -f ~/k3s/gitea/gitea-deployment.yaml
|
||||
kubectl apply -f ~/k3s/gitea/gitea-deployment.yaml
|
||||
```
|
||||
|
||||
Проверим, что под создан и работает:
|
||||
```bash
|
||||
sudo kubectl get pod -n gitea -o wide
|
||||
kubectl get pod -n gitea -o wide
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
@@ -356,7 +357,7 @@ gitea-init-data 0/1 Completed 0 2h 10.42.2.64 opi5p
|
||||
|
||||
Проверим логи пода `gitea`:
|
||||
```bash
|
||||
sudo kubectl logs -n gitea deployment/gitea
|
||||
kubectl logs -n gitea deployment/gitea
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
@@ -388,7 +389,7 @@ Server listening on 0.0.0.0 port 22.
|
||||
|
||||
Можно устроить более глубокую проверку, зайдя внутр пода и посмотреть как там всё устроено:
|
||||
```bash
|
||||
sudo kubectl exec -n gitea -it deployment/gitea -- /bin/bash
|
||||
kubectl exec -n gitea -it deployment/gitea -- /bin/bash
|
||||
```
|
||||
|
||||
Внутри пода проверим, что PVC `gitea-pvc` подключен и данные gitea и база на месте:
|
||||
@@ -445,81 +446,93 @@ exit
|
||||
|
||||
## Создание сервиса и IngressRoute для доступа к Gitea снаружи
|
||||
|
||||
|
||||
|
||||
```bash
|
||||
nano ~/k3s/gitea/gitea-service.yaml
|
||||
```
|
||||
|
||||
Вставляем в него следующее содержимое:
|
||||
Добавим в предыдущий файл (или создадим новый манифест `~/k3s/gitea/gitea-service.yaml`) сервис для доступа к Gitea
|
||||
снаружи кластера. Вставляем в него следующее содержимое:
|
||||
```yaml
|
||||
---
|
||||
# Манифест для создания сервиса (Service)
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: gitea
|
||||
namespace: gitea
|
||||
name: gitea # имя сервиса `gitea`
|
||||
namespace: gitea # в пространстве имен `gitea`
|
||||
spec:
|
||||
selector:
|
||||
app: gitea
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 3000
|
||||
protocol: TCP
|
||||
#- name: ssh
|
||||
# port: 22
|
||||
# targetPort: 22
|
||||
# protocol: TCP
|
||||
app: gitea # выбираем поды с меткой `app: gitea`
|
||||
ports: # определяем порты, которые будут открыты в сервисе
|
||||
- name: http # имя порта 'http'
|
||||
port: 80 # порт 80 (внешний)
|
||||
targetPort: 3000 # порт 3000 (внутренний, в контейнере)
|
||||
protocol: TCP # протокол TCP
|
||||
- name: ssh # имя порта 'http'
|
||||
port: 22 # порт 22 (внешний)
|
||||
targetPort: 22 # порт 22 (внутренний, в контейнере)
|
||||
protocol: TCP # протокол TCP
|
||||
type: ClusterIP # тип сервиса -- ClusterIP (только внутри кластера)
|
||||
```
|
||||
Объяснение:
|
||||
|
||||
selector: app: gitea — находит поды из Deployment gitea.
|
||||
port: 80 — внешний порт сервиса (Traefik будет слать трафик сюда).
|
||||
targetPort: 3000 — порт контейнера Gitea.
|
||||
Объяснение:
|
||||
* `selector: app: gitea` — находит поды из Deployment gitea.
|
||||
* `port: 80` — внешний порт сервиса (Traefik будет слать трафик сюда).
|
||||
* `targetPort: 3000` — порт контейнера внутри пода Gitea.
|
||||
|
||||
Применим манифест:
|
||||
```bash
|
||||
sudo kubectl apply -f ~/k3s/gitea/gitea-service.yaml
|
||||
kubectl apply -f ~/k3s/gitea/gitea-service.yaml
|
||||
```
|
||||
|
||||
sudo kubectl get svc -n gitea -o wide
|
||||
Проверим, что сервис создан:
|
||||
```bash
|
||||
kubectl get svc -n gitea -o wide
|
||||
```
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
|
||||
gitea ClusterIP 10.43.211.8 <none> 80/TCP 115s app=gitea
|
||||
|
||||
nano ~/k3s/gitea/https-redirect-middleware.yaml
|
||||
|
||||
```yaml
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: https-redirect
|
||||
namespace: gitea
|
||||
spec:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
```
|
||||
|
||||
Объяснение:
|
||||
Можно проверить, что под отвечает c IP-адреса сервиса (укажите IP-адрес сервиса):
|
||||
```bash
|
||||
curl -v http://10.43.211.8:80
|
||||
```
|
||||
|
||||
redirectScheme: scheme: https — перенаправляет запросы на HTTPS.
|
||||
permanent: true — возвращает 301 (постоянный редирект) для SEO и кэширования.
|
||||
Размещаем в gitea, чтобы не затрагивать другие сервисы.
|
||||
## Создадим манифест для Middleware (перенаправление HTTP на HTTPS внутри Traefik):
|
||||
|
||||
Добавим в предыдущий файл (или создадим новый манифест `~/k3s/gitea/https-redirect-middleware.yaml`):
|
||||
|
||||
```yaml
|
||||
---
|
||||
# Манифест для Middleware (редирект HTTP → HTTPS внутри Traefik)
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: https-redirect # Имя Middleware `https-redirect`
|
||||
namespace: gitea # Размещаем в пространстве имен gitea, чтобы не затрагивать другие сервисы.
|
||||
spec:
|
||||
redirectScheme: # Схема редиректа
|
||||
scheme: https # Перенаправлять на HTTPS
|
||||
permanent: true # Постоянный редирект (HTTP 301) для SEO и кэширования
|
||||
```
|
||||
|
||||
Что тут происходит:
|
||||
* `redirectScheme: scheme: https` — перенаправляет запросы на HTTPS.
|
||||
* `permanent: true` — возвращает 301 (постоянный редирект) для SEO и кэширования.
|
||||
* Размещаем в gitea, чтобы не затрагивать другие сервисы.
|
||||
|
||||
Применить:
|
||||
|
||||
```bash
|
||||
sudo kubectl apply -f ~/k3s/gitea/https-redirect-middleware.yaml
|
||||
kubectl apply -f ~/k3s/gitea/https-redirect-middleware.yaml
|
||||
```
|
||||
|
||||
### Подключаем сертификат Let's Encrypt
|
||||
|
||||
У нас уже настроена выдача сертификатов Let’s Encrypt в подах cert-managerб cert-manager-cainjector и
|
||||
cert-manager-webhook, в пронстве имен cert-manager. Это нестандартный способ (см. [заметку о cert-manager](k3s-cert-manager.md)).
|
||||
У нас уже настроена выдача сертификатов Let’s Encrypt в подах `cert-manager`, `cert-manager-cainjector` и
|
||||
`cert-manager-webhook`, в баронстве имен `cert-manager`. Это нестандартный способ
|
||||
(см. [заметку о cert-manager](k3s-lets-encrypt-cert-manager.md)).
|
||||
|
||||
Проверим, что cert-manager работает:
|
||||
```bash
|
||||
sudo kubectl get pods -n cert-manager
|
||||
kubectl get pods -n cert-manager
|
||||
```
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
@@ -533,7 +546,7 @@ cert-manager-webhook-544c988c49-zxdxc 1/1 Running 0 19d
|
||||
|
||||
Проверим наличие ClusterIssuer:
|
||||
```bash
|
||||
sudo kubectl get clusterissuer -A -o wide
|
||||
kubectl get clusterissuer -A -o wide
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
@@ -544,7 +557,7 @@ letsencrypt-prod True The ACME account was registered with the ACME server
|
||||
|
||||
Проверим, что работает и Let's Encrypt знает о нас:
|
||||
```bash
|
||||
sudo kubectl describe clusterissuer letsencrypt-prod
|
||||
kubectl describe clusterissuer letsencrypt-prod
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
@@ -573,14 +586,8 @@ Events: <none>
|
||||
|
||||
Важно чтобы `Status: Conditions: Ready:` был `True`.
|
||||
|
||||
|
||||
|
||||
Создадим манифест для получения сертификата Let's Encrypt:
|
||||
```bash
|
||||
nano ~/k3s/gitea/gitea-certificate.yaml
|
||||
```
|
||||
|
||||
и вставим в него следующее содержимое:
|
||||
Добавим в предыдущий файл (или создадим новый манифест `~/k3s/gitea/gitea-certificate.yaml`) для получения сертификата
|
||||
Let's Encrypt:
|
||||
```yaml
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
@@ -596,18 +603,92 @@ spec:
|
||||
kind: ClusterIssuer
|
||||
```
|
||||
|
||||
secretName: gitea-tls: Сертификат сохраняется в Secret gitea-tls в gitea.
|
||||
dnsNames: Домен git.cube2.ru.
|
||||
issuerRef: Ссылается на ClusterIssuer letsencrypt-prod.
|
||||
Что тут происходит:
|
||||
* `secretName: gitea-tls` -- Сертификат сохраняется в Secret с именем `gitea-tls` в пространстве имен `gitea`.
|
||||
* `dnsNames` -- домен git.cube2.ru.
|
||||
* `issuerRef` -- эмитент отвечающий за выдачу сертификата. В данном случае это `letsencrypt-prod`, который мы
|
||||
создали ранее.
|
||||
|
||||
Применим манифест:
|
||||
Пока не будем применять манифест, так как это сразу создаст запрос к Let’s Encrypt на получение сертификата, а у нас
|
||||
пока нет IngressRoute, который сможет обеспечить подтверждение владение доменом через _HTTP-01 challenge_
|
||||
(запрашивающий сертификат размещает специальный временный файл в папке `/.well-known/acme-challenge/` на web-сервере,
|
||||
который доступен по HTTP, и Let’s Encrypt проверяет его наличие для подтверждения владения доменом).
|
||||
|
||||
У Let’s Encrypt есть лимиты по количеству запросов на получение сертификатов (пять запросов в неделю), и исчерпать
|
||||
лимит довольно неприятно. Как минимум полутора суток не получится запросить и получить новый сертификат. Поэтому
|
||||
и не надо сейчас применять манифест.
|
||||
|
||||
## Создание IngressRoute для доступа к Gitea
|
||||
|
||||
Настроим IngressRoute для маршрутизации git.cube2.ru через HTTPS с Let’s Encrypt и редиректом HTTP.
|
||||
```bash
|
||||
sudo kubectl apply -f ~/k3s/gitea/gitea-certificate.yaml
|
||||
nano ~/k3s/gitea/gitea-ingressroute.yaml
|
||||
```
|
||||
|
||||
Проверим, что секрет создан:
|
||||
```yaml
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: gitea-http
|
||||
namespace: gitea
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host("git.cube2.ru")
|
||||
kind: Rule
|
||||
services:
|
||||
- name: gitea
|
||||
port: 80
|
||||
middlewares:
|
||||
- name: https-redirect
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: gitea-https
|
||||
namespace: gitea
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host("git.cube2.ru")
|
||||
kind: Rule
|
||||
services:
|
||||
- name: gitea
|
||||
port: 80
|
||||
tls:
|
||||
secretName: gitea-tls
|
||||
# Если у вас стандартная настройка Traefik, то раскомментируйте следующую строку (а строку выше закомментируйте)
|
||||
# certResolver: letsencrypt
|
||||
```
|
||||
Объяснение:
|
||||
* Первая IngressRoute (`gitea-http`):
|
||||
* `entryPoints: web` — слушает порт 80 (HTTP).
|
||||
* `match: Host("git.cube2.ru")` — обрабатывает запросы к git.cube2.ru.
|
||||
* `services: gitea` — направляет трафик в Service gitea (порт 80 → 3000).
|
||||
* `middlewares: https-redirect` — редирект на HTTPS.
|
||||
* Вторая IngressRoute (gitea-https):
|
||||
* `entryPoints: websecure` — слушает порт 443 (HTTPS).
|
||||
* `tls: certResolver: gitea-tls` — включает Let’s Encrypt для автоматического получения сертификата через
|
||||
cert-manager в секрет gitea-tls. Трафик идёт в тот же Service gitea.
|
||||
|
||||
Применим манифест для IngressRoute и сертификата Let’s Encrypt:
|
||||
```bash
|
||||
sudo kubectl get secret -n gitea gitea-tls -o wide
|
||||
kubectl apply -f ~/k3s/gitea/gitea-ingressroute.yaml
|
||||
kubectl apply -f ~/k3s/gitea/gitea-certificate.yaml
|
||||
```
|
||||
Все долно работать.
|
||||
|
||||
#### Проверим, что IngressRoute создан
|
||||
|
||||
|
||||
|
||||
#### Проверим, что сертификат Let’s Encrypt создан
|
||||
|
||||
Проверим секрет:
|
||||
```bash
|
||||
kubectl get secret -n gitea gitea-tls -o wide
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
@@ -618,7 +699,7 @@ gitea-tls kubernetes.io/tls 2 46s
|
||||
|
||||
Проверим, что сертификат выдан:
|
||||
```bash
|
||||
sudo kubectl describe certificate -n gitea gitea-tls
|
||||
kubectl describe certificate -n gitea gitea-tls
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
@@ -654,69 +735,6 @@ Events:
|
||||
up to date and has not expired` в `Message`.
|
||||
|
||||
|
||||
## Создание IngressRoute для доступа к Gitea
|
||||
|
||||
Создать IngressRoute для HTTPS
|
||||
Настроим IngressRoute для маршрутизации git.cube2.ru через HTTPS с Let’s Encrypt и редиректом HTTP.
|
||||
```bash
|
||||
nano ~/k3s/gitea/gitea-ingressroute.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: gitea-http
|
||||
namespace: gitea
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host("git.cube2.ru")
|
||||
kind: Rule
|
||||
services:
|
||||
- name: gitea
|
||||
port: 80
|
||||
middlewares:
|
||||
- name: https-redirect
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: gitea-https
|
||||
namespace: gitea
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host("git.cube2.ru")
|
||||
kind: Rule
|
||||
services:
|
||||
- name: gitea
|
||||
port: 80
|
||||
tls:
|
||||
secretName: gitea-tls
|
||||
# Если у вас стандартная настройка Traefik, то раскомментируйте следующую строку (а строку выше закомментируйте)
|
||||
# certResolver: letsencrypt
|
||||
```
|
||||
Объяснение:
|
||||
* Первая IngressRoute (`gitea-http`):
|
||||
* `entryPoints: web` — слушает порт 80 (HTTP).
|
||||
* `match: Host("git.cube2.ru")` — обрабатывает запросы к git.cube2.ru.
|
||||
* `services: gitea` — направляет трафик в Service gitea (порт 80 → 3000).
|
||||
* `middlewares: https-redirect` — редирект на HTTPS.
|
||||
* Вторая IngressRoute (gitea-https):
|
||||
* `entryPoints: websecure` — слушает порт 443 (HTTPS).
|
||||
* `tls: certResolver: gitea-tls` — включает Let’s Encrypt для автоматического получения сертификата через
|
||||
cert-manager в секрет gitea-tls. Трафик идёт в тот же Service gitea.
|
||||
|
||||
Применим манифест:
|
||||
```bash
|
||||
sudo kubectl apply -f ~/k3s/gitea/gitea-ingressroute.yaml
|
||||
```
|
||||
|
||||
Все долно работать. Проверим, что IngressRoute создан:
|
||||
|
||||
## Подключение SSH к Gitea (опционально)
|
||||
|
||||
Кроме http-доступа и идентификации по логину, паролю и токенами, к к Gitea можно подключаться по SSH ([git://]). Выше
|
||||
@@ -747,7 +765,7 @@ kubectl apply -f ~/k3s/gitea/gitea-service.yaml
|
||||
|
||||
Теперь Traefik сможет маршрутизировать SSH-трафик. Проверим, что сервис обновился:
|
||||
```bash
|
||||
sudo kubectl get svc -n gitea -o wide
|
||||
kubectl get svc -n gitea -o wide
|
||||
```
|
||||
|
||||
Увидим что теперь в сервисе gitea есть порт 22:
|
||||
@@ -760,10 +778,10 @@ gitea ClusterIP 10.43.211.8 <none> 80/TCP,22/TCP 5h app=gite
|
||||
|
||||
Traefik должен слушать порт 2222. У меня Traefik настроен через `Helm values` в `HelmChartConfig`, через дополнительные
|
||||
параметры `additionalArguments` . Чтобы Traefik начал слушать порт 2222, добавим новый entryPoint с именем `ssh` в
|
||||
`~/k3s/traefik/traefik-config.yaml`. Добавим в конец файла: `- --entrypoints.ssh.address=:2222`. Полностью манифест
|
||||
`~/k3s/traefik/traefik-ssh.yaml`. Добавим в конец файла: `- --entrypoints.ssh.address=:2222`. Полностью манифест
|
||||
у меня выглядит так:
|
||||
```yaml
|
||||
aapiVersion: helm.cattle.io/v1
|
||||
apiVersion: helm.cattle.io/v1
|
||||
kind: HelmChartConfig
|
||||
metadata:
|
||||
name: traefik
|
||||
@@ -771,20 +789,24 @@ metadata:
|
||||
spec:
|
||||
valuesContent: |-
|
||||
additionalArguments:
|
||||
- --entrypoints.web-custom.address=:2055 # Слушаем HTTP на 2055 (для web-панели 3x-ui)
|
||||
- --entrypoints.ssh.address=:2222 # Слушаем TCP на 2222 (для SSH)
|
||||
- --log.level=DEBUG
|
||||
- --entrypoints.ssh.address=:2222
|
||||
ports:
|
||||
ssh:
|
||||
port: 2222
|
||||
expose:
|
||||
default: true
|
||||
exposedPort: 2222
|
||||
protocol: TCP
|
||||
```
|
||||
|
||||
Применим изменения и перезапустим Traefik чтобы изменения вступили в силу:
|
||||
```bash
|
||||
sudo kubectl apply -f ~/k3s/traefik/traefik-config.yaml
|
||||
sudo kubectl rollout restart deployment -n kube-system traefik
|
||||
kubectl apply -f ~/k3s/traefik/traefik-ssh.yaml
|
||||
```
|
||||
|
||||
Проверим, что Traefik перезапустился:
|
||||
```bash
|
||||
sudo kubectl get pod -n kube-system -o wide
|
||||
kubectl get pod -n kube-system -o wide
|
||||
```
|
||||
|
||||
Увидим что-то вроде (время жизни пода Traefik небольшое, так как он недавно перезапустился):
|
||||
@@ -792,10 +814,39 @@ sudo kubectl get pod -n kube-system -o wide
|
||||
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
|
||||
...
|
||||
...
|
||||
traefik-6b96fd9d85-8txb9 1/1 Running 0 119s 10.42.0.93 opi5plus-2 <none> <none>
|
||||
svclb-traefik-0d6fad03-gqj8k 3/3 Running 0 119s 10.42.0.42 opi5plus-2 <none> <none>
|
||||
traefik-6dd77bc5b-924kw 1/1 Running 0 119s 10.42.0.41 opi5plus-2 <none> <none>
|
||||
...
|
||||
```
|
||||
|
||||
Проверим, что аргументы применились и Traefik слушает порт 2222:
|
||||
```bash
|
||||
kubectl describe pod -n kube-system -l app.kubernetes.io/name=traefik | grep -A 15 Args
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
Args:
|
||||
--global.checknewversion
|
||||
--global.sendanonymoususage
|
||||
--entryPoints.metrics.address=:9100/tcp
|
||||
--entryPoints.ssh.address=:2222/tcp
|
||||
--entryPoints.traefik.address=:8080/tcp
|
||||
--entryPoints.web.address=:8000/tcp
|
||||
--entryPoints.websecure.address=:8443/tcp
|
||||
--api.dashboard=true
|
||||
--ping=true
|
||||
--metrics.prometheus=true
|
||||
--metrics.prometheus.entrypoint=metrics
|
||||
--providers.kubernetescrd
|
||||
--providers.kubernetescrd.allowEmptyServices=true
|
||||
--providers.kubernetesingress
|
||||
--providers.kubernetesingress.allowEmptyServices=true
|
||||
```
|
||||
|
||||
Если вы видите аргумент `--entryPoints.ssh.address=:2222/tcp`, то всё работает.
|
||||
|
||||
|
||||
### Добавим IngressRouteTCP для Gitea
|
||||
|
||||
Чтобы маршрутизировать трафик с порта 2222 на Service Gitea (порт 22), создадим манифест IngressRouteTCP:
|
||||
@@ -806,7 +857,7 @@ nano ~/k3s/gitea/gitea-ingressroute-tcp.yaml
|
||||
И вставим в него следующее содержимое:
|
||||
```yaml
|
||||
# IngressRouteTCP для маршрутизации SSH через Traefik
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRouteTCP
|
||||
metadata:
|
||||
name: gitea-ssh
|
||||
@@ -823,12 +874,12 @@ spec:
|
||||
|
||||
Применим манифест:
|
||||
```bash
|
||||
sudo kubectl apply -f ~/k3s/gitea/gitea-ingressroute-tcp.yaml
|
||||
kubectl apply -f ~/k3s/gitea/gitea-ingressroute-tcp.yaml
|
||||
```
|
||||
|
||||
Проверим, что IngressRouteTCP создан:
|
||||
```bash
|
||||
sudo kubectl get ingressroutetcp -n gitea
|
||||
kubectl get ingressroutetcp -n gitea
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
@@ -836,7 +887,7 @@ sudo kubectl get ingressroutetcp -n gitea
|
||||
NAME AGE
|
||||
gitea-ssh 16s
|
||||
```
|
||||
### Изменим Traefik Service для доступа к SSH по порту 2222 через балансировщик
|
||||
### Изменим Traefik Service для доступа к SSH по порту 2222 через балансировщик (не обязательно, если только один узел, ПРОВЕРИТЬ)
|
||||
|
||||
Если у вас уже есть манифест Traefik Service (например `~/k3s/traefik/traefik-service.yaml`), то нужно добавить в него
|
||||
обработку порта:
|
||||
@@ -861,13 +912,13 @@ spec:
|
||||
|
||||
И, само собой, применить манифест:
|
||||
```bash
|
||||
sudo kubectl apply -f ~/k3s/traefik/traefik-service.yaml
|
||||
kubectl apply -f ~/k3s/traefik/traefik-service.yaml
|
||||
```
|
||||
|
||||
Если у вас нет манифеста Traefik Service, то можно отредактировать его непосредственно в кластере через `kubectl edit`
|
||||
(будьте готовы редактировать в **vim**):
|
||||
```bash
|
||||
sudo kubectl edit svc -n kube-system traefik -o yaml
|
||||
kubectl edit svc -n kube-system traefik -o yaml
|
||||
```
|
||||
|
||||
Важно: значение `nodePort` для SSH не нужно указывать, так как оно будет сгенерировано автоматически сразу после
|
||||
@@ -875,7 +926,7 @@ sudo kubectl edit svc -n kube-system traefik -o yaml
|
||||
|
||||
Проверим, что Service обновился:
|
||||
```bash
|
||||
sudo kubectl get svc -n kube-system traefik
|
||||
kubectl get svc -n kube-system traefik
|
||||
```
|
||||
|
||||
Увидим что-то вроде (орт 2222 добавился в Service Traefik):
|
||||
@@ -884,6 +935,8 @@ NAME TYPE CLUSTER-IP EXTERNAL-IP
|
||||
traefik LoadBalancer 10.43.164.48 192.168.1.26,192.168.1.27,192.168.1.28 80:32171/TCP,443:30329/TCP,9000:31317/TCP,2055:31675/TCP,2222:31963/TCP 6d
|
||||
```
|
||||
|
||||
### Проверим доступность SSH в Gitea
|
||||
|
||||
Проверим, что порт 2222 доступен на VIP-адресе keepalived (не забудьте заменить `<VIP>` на ваш Virtual IP):
|
||||
```bash
|
||||
nc -zv <VIP> 2222
|
||||
@@ -921,13 +974,13 @@ Connection to <VIP> closed.
|
||||
|
||||
Можно удалить временный под, который мы использовали для переноса данных gitea в блочное хранилище Longhorn PVC:
|
||||
```bash
|
||||
sudo kubectl delete pod gitea-init-data -n gitea
|
||||
kubectl delete pod gitea-init-data -n gitea
|
||||
```
|
||||
|
||||
И удалить временный архив с данными gitea которые мы перенесли из Docker-контейнера и положили в корень домашнего
|
||||
каталога. Теперь данные gitea уже в Longhorn PVC и арихив не нужен:
|
||||
```bash
|
||||
sudo rm ~/gitea-data.tar.gz
|
||||
rm ~/gitea-data.tar.gz
|
||||
```
|
||||
|
||||
## Выводы
|
||||
|
1251
kubernetes/k3s-postresql.md
Normal file
@@ -261,3 +261,98 @@ NAME ENDPOINTS AGE
|
||||
sudo kubectl describe certificate -n <NAME-SPACE> <SERVICE-NAME>-tls
|
||||
sudo kubectl get secret -n <NAME-SPACE> <SERVICE-NAME>-tls
|
||||
```
|
||||
|
||||
## UPD
|
||||
|
||||
Почему-то SSL-сертификат Let's Encrypt через положенный срок не перевыпустился. Для начала выяснилось, что на
|
||||
моем роутере перестал работать NAT-loopback, и внутрення проверка Certbot перестала работать. Похоже роутер получил
|
||||
обновление прошивки, и все сломалось. В результате DNS кластера (coredns) разрешал домен во внешний IP-адрес роутера,
|
||||
а не во внутренний IP-адрес кластера (VIP через keepalived), а без NAT-loopback запросы к домену не доходили до
|
||||
кластера.
|
||||
|
||||
Как чинил:
|
||||
|
||||
Во-первых, руками поменял на всех узлах кластера (включая роутер) в файле `/etc/resolv.conf` IP-адрес DNS.
|
||||
Обновление испортило и DHCP-сервер на роутере, и он стал раздавать свой IP-адреса как DNS-сервер, вместо моего
|
||||
домшнего DNS-сервера (работат на Synology NAS).
|
||||
|
||||
Во-вторых, в кластере k3s обновил конфигурацию coredns:
|
||||
```bash
|
||||
kubectl edit configmap -n kube-system coredns
|
||||
```
|
||||
|
||||
Отчего-то там было навернуты хосты 'hosts /etc/coredns/NodeHosts` внутри `.:53 ` блока, и это мешало. В результате
|
||||
получалось следующее:
|
||||
```yaml
|
||||
# Please edit the object below. Lines beginning with a '#' will be ignored,
|
||||
# and an empty file will abort the edit. If an error occurs while saving this file will be
|
||||
# reopened with the relevant failures.
|
||||
#
|
||||
apiVersion: v1
|
||||
data:
|
||||
Corefile: |
|
||||
.:53 {
|
||||
errors
|
||||
health
|
||||
ready
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
pods insecure
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
}
|
||||
prometheus :9153
|
||||
forward . /etc/resolv.conf
|
||||
cache 30
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
import /etc/coredns/custom/*.override
|
||||
}
|
||||
import /etc/coredns/custom/*.server
|
||||
...
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
После сохранения конфигурации манифет coredns (т.к. его редактируем пряма в кластере) применяется автоматически.
|
||||
После пеускаем coredns:
|
||||
```bash
|
||||
kubectl rollout restart deployment -n kube-system coredns
|
||||
```
|
||||
|
||||
Проверяем логи:
|
||||
```bash
|
||||
kubectl logs -n kube-system -l k8s-app=kube-dns
|
||||
```
|
||||
|
||||
Проверяем статусы подов CoreDNS:
|
||||
```shell
|
||||
kubectl get pods -n kube-system -l k8s-app=kube-dns -o wide
|
||||
```
|
||||
|
||||
Поды должны быть в статусе `Running`:
|
||||
```text
|
||||
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
|
||||
coredns-646d9c4784-wsbbg 1/1 Running 0 58m 10.42.2.127 opi5plus-1 <none> <none>
|
||||
coredns-646d9c4784-z5zqv 1/1 Running 0 58m 10.42.0.125 opi5 <none> <none>
|
||||
```
|
||||
|
||||
И наконец, в-третьих, обновил сертификат Let's Encrypt. Сначала удалил старый сертификат и секрет:
|
||||
```bash
|
||||
kubectl delete certificate -n <NAME-SPACE> <SERVICE-NAME>-tls
|
||||
kubectl delete secret -n <NAME-SPACE> <SERVICE-NAME>-tls
|
||||
```
|
||||
|
||||
И снова применил манифест с сертификатом:
|
||||
```bash
|
||||
kubectl apply -f ~/k3s/<SERVICE-NAME>/<SERVICE-NAME>.yaml
|
||||
```
|
||||
|
||||
Проверяем, что сертификат обновился:
|
||||
```bash
|
||||
kubectl describe certificate -n <NAME-SPACE> <SERVICE-NAME>-tls
|
||||
kubectl get secret -n <NAME-SPACE> <SERVICE-NAME>-tls
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
302
kubernetes/k3s-setting-up-web-access-to-dashboard.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# Настройка доступа к панелям управления Lobghorn и Traefik
|
||||
|
||||
В k3s есть несколько панелей управления, которые можно настроить для доступа извне кластера.
|
||||
|
||||
# Панель управления блочный хранилищем (Persistent Volume) Longhorn
|
||||
|
||||
Панель управления Longhorn позволяет управлять блочными хранилищами (Persistent Volume) в k3s. Полезнейшая вещь!
|
||||
Через нее можно не только отслеживать работу тома, но и создавать, удалять и изменять PVC-хранилища, и, главное,
|
||||
делать бэкапы томов и восстанавливать их.
|
||||
|
||||
Проверим, поды Longhorn, и в нем есть UI (интерфейс управления):
|
||||
```shell
|
||||
kubectl get pod -n longhorn-system
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
...
|
||||
...
|
||||
longhorn-ui-f7ff9c74-7bbsw 1/1 Running 2 (26h ago) 21h
|
||||
longhorn-ui-f7ff9c74-b4svq 1/1 Running 3 (26h ago) 21h
|
||||
...
|
||||
```
|
||||
|
||||
Поды longhorn-ui работают -- интерфейс управления Longhorn -- доступен.
|
||||
|
||||
Проверим доступные сервисы Longhorn:
|
||||
```shell
|
||||
kubectl get svc -n longhorn-system
|
||||
```
|
||||
|
||||
Увидим что-то типа:
|
||||
```text
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
longhorn-admission-webhook ClusterIP 10.43.123.226 <none> 9502/TCP 21h
|
||||
longhorn-backend ClusterIP 10.43.226.46 <none> 9500/TCP 21h
|
||||
longhorn-conversion-webhook ClusterIP 10.43.243.121 <none> 9501/TCP 21h
|
||||
longhorn-frontend ClusterIP 10.43.152.91 <none> 80/TCP 21h
|
||||
longhorn-recovery-backend ClusterIP 10.43.205.78 <none> 9503/TCP 21h
|
||||
```
|
||||
|
||||
Как видим, есть работающий сервис `longhorn-frontend` с типом `ClusterIP` (внутренний IP-адрес кластера) и портом 80. Это
|
||||
и есть интерфейс управления Longhorn. Проверим, что он доступен по этому адресу:
|
||||
```shell
|
||||
curl -v http://10.43.152.91
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
* Trying 10.43.152.91:80...
|
||||
* Connected to 10.43.152.91 (10.43.152.91) port 80 (#0)
|
||||
> GET / HTTP/1.1
|
||||
> Host: 10.43.152.91
|
||||
> User-Agent: curl/7.81.0
|
||||
> Accept: */*
|
||||
>
|
||||
* Mark bundle as not supporting multiuse
|
||||
< HTTP/1.1 200 OK
|
||||
< Server: nginx/1.21.5
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
Как видим, Longhorn доступен, и выдает 200 OK.
|
||||
|
||||
### Манифес IngressRoute для доступа к панели управления Longhorn
|
||||
|
||||
Я настраиваю панель управления Longhorn на доступ по адресу `pvc.local` (достигается через соответствующий DNS-запись
|
||||
в локальном DNS-сервере или редактированием файла `/etc/hosts`). Создадим IngressRoute-манифест для доступа
|
||||
к дашборду Longhorn по домену `pvc.local` (или какому вы там сами пожелаете).
|
||||
|
||||
```yaml
|
||||
# IngressRoute-манифест, для доступа к панели управления Longhorn по адресу http://pvc.local
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: longhorn-ui # имя ресурса (пода)
|
||||
namespace: longhorn-system
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host("pvc.local") # маршрутизируем запросы с хоста pvc.local
|
||||
kind: Rule
|
||||
services:
|
||||
- name: longhorn-frontend # целевой сервис
|
||||
port: 80
|
||||
```
|
||||
|
||||
Что туту происходит:
|
||||
* `apiVersion: traefik.io/v1alpha1` — стандартный CRD для Traefik в k3s.
|
||||
* `kind: IngressRoute` — ресурс Traefik для маршрутизации.
|
||||
* `metadata`:
|
||||
* `name: longhorn-ui` — имя ресурса (пода) longhorn-ui.
|
||||
* `namespace: longhorn-system` — в пространстве имен longhorn-system.
|
||||
* `spec:`
|
||||
* `entryPoints: web` — используем порт 80 в сервисе traefik
|
||||
* `routes:` — маршруты.
|
||||
* `match: Host("pvc.local")` — маршрутизируем запросы с хоста `pvc.local`.
|
||||
* `kind: Rule` — правило маршрутизации.
|
||||
* `services:`
|
||||
* `name: longhorn-frontend` — целевой сервис.
|
||||
* `port: 80` — порт на котором работает сервис longhorn-frontend.
|
||||
|
||||
Применим манифест и проверим, что он применился:
|
||||
```shell
|
||||
kubectl apply -f <путь_к_файлу_с_манифестом>
|
||||
kubectl get ingressroute -n longhorn-system
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
NAME AGE
|
||||
longhorn-ui 124m
|
||||
```
|
||||
|
||||
Проверим, что панель управления Longhorn доступна по адресу `pvc.local`:
|
||||
```shell
|
||||
curl -v http://pvc.local
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
* Trying <IP>:80...
|
||||
* Connected to pvc.local (<IP>) port 80 (#0)
|
||||
> GET / HTTP/1.1
|
||||
> Host: pvc.local
|
||||
> User-Agent: curl/7.81.0
|
||||
> Accept: */*
|
||||
> GET / HTTP/1.1
|
||||
>
|
||||
* Mark bundle as not supporting multiuse
|
||||
< HTTP/1.1 200 OK
|
||||
< Server: nginx/1.21.5
|
||||
```
|
||||
|
||||
Как видим, Longhorn теперь доступен по адресу `pvc.local` и выдает 200 OK.
|
||||
|
||||
Можно открыть в браузере `http://pvc.local` и увидеть панель управления Longhorn:
|
||||
|
||||

|
||||
|
||||
|
||||
### Изменение числа реплик Longhorn (не обязательно)
|
||||
|
||||
Если у вас всего одна нода, то в панели управления Longhorn вы можете увидеть, что тома находятся в состоянии
|
||||
`degraded` (деградированное). Это связано с тем, что Longhorn не может создать реплики на других нодах, так как их нет.
|
||||
Исправить это можно, изменив число глобальное число реплик Longhorn с 3 до 1. Это можно сделать через команду:
|
||||
```shell
|
||||
kubectl edit settings.longhorn.io -n longhorn-system default-replica-count
|
||||
```
|
||||
|
||||
Найти и отредактировать:
|
||||
```yaml
|
||||
value: "3"
|
||||
```
|
||||
|
||||
Тоже самое, кстати, можно проделать и через панель управления Longhorn **"Setting" → "General" → "Default Replica Count"**.
|
||||
|
||||
Это изменит число реплик для вновь создаваемых томов, но не изменит значение для уже существующих (они останутся
|
||||
с тремя репликами, пока не измените вручную в UI). Измените в панели управления Longhorn число реплик для каждого
|
||||
тома с 3 до 1, и все тома перейдут в состоянии `healthy` (здоровое).
|
||||
|
||||
## Панель управления Traefik
|
||||
|
||||
Дашборд Traefik позволяет визуализировать маршрутизацию и состояние сервисов. Не так чтоб сильно полезная вещь,
|
||||
но с ней можно поиграться и к ней есть [https://plugins.traefik.io/plugins](множество плагинов и расширений).
|
||||
|
||||
Я настраиваю панель управления Traefik на доступ по адресу `traefik.local` (достигается через соответствующий DNS-запись
|
||||
в локальном DNS-сервере или редактированием файла `/etc/hosts`).
|
||||
|
||||
### Изменение конфигурации Traefik (через Helm)
|
||||
|
||||
По умолчанию панель управления Traefik недоступна извне кластера. Чтобы это исправить, нужно создать нужно изменить
|
||||
конфигурацию Traefik, чтобы проверить, что панель управления включена и разрешить доступ к ней по HTTP. Это можно
|
||||
сделать через Helm, используя HelmChartConfig. Если у вас уже есть манифест HelmChartConfig для traefik, то просто
|
||||
добавьте в него в блок `spec: valuesContent: additionalArguments:` дополнительные аргументы: `--api.dashboard=true` и
|
||||
`--api.insecure=true`.
|
||||
|
||||
Если у вас нет HelmChartConfig, то создайте его:
|
||||
```shell
|
||||
mkdir -p ~/k3s/traefik
|
||||
nano ~/k3s/traefik/traefik-helm-config.yaml
|
||||
```
|
||||
|
||||
И вставьте в него следующее содержимое:
|
||||
```yaml
|
||||
apiVersion: helm.cattle.io/v1
|
||||
kind: HelmChartConfig
|
||||
metadata:
|
||||
name: traefik
|
||||
namespace: kube-system
|
||||
spec:
|
||||
valuesContent: |
|
||||
additionalArguments:
|
||||
- --api.dashboard=true # включает панель управления (dashboard) Traefik (обычно он уже включен)
|
||||
- --api.insecure=true # разрешает доступ к dashboard Traefik по HTTP
|
||||
```
|
||||
|
||||
Применим манифест:
|
||||
```shell
|
||||
kubectl apply -f ~/k3s/traefik/traefik-helm-config.yaml
|
||||
```
|
||||
|
||||
Пезезапустим Traefik, чтобы изменения вступили в силу:
|
||||
```shell
|
||||
kubectl rollout restart deployment -n kube-system traefik
|
||||
```
|
||||
|
||||
### Создание IngressRoute для доступа к панели управления Traefik с http
|
||||
|
||||
Создадим манифест IngressRoute для доступа к панели управления Traefik по домену `traefik.local` (или какому вы там
|
||||
сами пожелаете):
|
||||
|
||||
```yaml
|
||||
# IngressRoute-манифест, для доступа к панели управления Traefik по адресу http://traefik.local
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: traefik-dashboard
|
||||
namespace: kube-system
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host("traefik.local") && PathPrefix("/dashboard") # доступ к панели управления
|
||||
kind: Rule
|
||||
services:
|
||||
- name: api@internal # имя встроенного в k3s сервиса Traefik для доступа к панели управления
|
||||
kind: TraefikService # тип сервиса
|
||||
- match: Host("traefik.local") && PathPrefix("/api") # доступ к API
|
||||
kind: Rule
|
||||
services:
|
||||
- name: api@internal
|
||||
kind: TraefikService
|
||||
- match: Host("traefik.local") && Path("/") # переадресация чтобы не вызывать по полному пути (`/dashboard`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: api@internal
|
||||
kind: TraefikService
|
||||
```
|
||||
|
||||
Применим манифест и проверим, что он применился:
|
||||
```shell
|
||||
kubectl get ingressroute -n kube-system
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
NAME AGE
|
||||
traefik-dashboard 4m
|
||||
```
|
||||
|
||||
Проверим, что панель управления Traefik доступна по адресу `traefik.local`:
|
||||
```shell
|
||||
curl -v http://traefik.local/dashboard/
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
Trying <IP>:80...
|
||||
* Connected to traefik.local (<IP>) port 80 (#0)
|
||||
> GET /dashboard/ HTTP/1.1
|
||||
> Host: traefik.local
|
||||
> User-Agent: curl/7.81.0
|
||||
> Accept: */*
|
||||
>
|
||||
* Mark bundle as not supporting multiuse
|
||||
< HTTP/1.1 200 OK
|
||||
< Content-Security-Policy: frame-src 'self' https://traefik.io https://*.traefik.io;
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
Как видим, статус 200 OK, значит панель доступна и работает.
|
||||
|
||||
```shell
|
||||
curl -v http://traefik.local/
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
Trying <IP>:80...
|
||||
* Connected to traefik.local (<IP>) port 80 (#0)
|
||||
> GET / HTTP/1.1
|
||||
> Host: traefik.local
|
||||
> User-Agent: curl/7.81.0
|
||||
> Accept: */*
|
||||
>
|
||||
* Mark bundle as not supporting multiuse
|
||||
< HTTP/1.1 302 Found
|
||||
< Location: /dashboard/
|
||||
< Date: Sat, 03 May 2025 11:59:19 GMT
|
||||
< Content-Length: 0
|
||||
```
|
||||
|
||||
Как видим, статус 302 Found, значит переадресация тоже работает.
|
||||
|
||||
Откроем в браузере `http://traefik.local/dashboard/` и видим панель управления Traefik:
|
||||
|
||||

|
362
kubernetes/k3s-traefik-custom-error-pages.md
Normal file
@@ -0,0 +1,362 @@
|
||||
# Кастомная страница ошибки 404 (и других) в Traefik
|
||||
|
||||
Страницы ошибок Traefik по умолчанию выглядят скучно. Это даже не страницы, а просто текстовые сообщения. Например,
|
||||
404 выглядит как `404 page not found`. Это позволяет Traefik быть лёгким и быстрым.
|
||||
|
||||
Если хочется сделать страницы статусоы 4xx и 5xx более привлекательными, то кастомизация страниц ошибок — отличная идея!
|
||||
Для каждого HTTP-сервиса внутри можно сделать свои страницы ошибок на уровне приложения (как например, на Gitea,
|
||||
на которой ты сейчас сидишь). И это наиболее правильный способ. Но если http-запрос не привязан ни к какому сервису,
|
||||
и Traefik не знает куда его отправить, то он выдаёт свои страницы ошибок. Например, при обращении по IP.
|
||||
|
||||
|
||||
Traefik позволяет кастомизировать страницы ошибок через **middleware** типа `errors`, который перенаправляет запросы
|
||||
с определёнными кодами ошибок (например, 404) на кастомный сервис, возвращающий нужную html-страницу. И все это
|
||||
излишество в `k3s` нужно настраивать глобально, с помощью `Middleware` и `IngressRoute`, и применять ко всем маршрутам.
|
||||
|
||||
Чтобы кастомная страница 404 работала для всех запросов в кластере, нужно:
|
||||
|
||||
# Создать сервис, который возвращает кастомную страницу (например, контейнер с Nginx или простой HTTP-сервер).
|
||||
# Настроить middleware `errors` для перехвата ошибок 404.
|
||||
# Применить middleware глобально через `IngressRoute` или конфигурацию Traefik.
|
||||
|
||||
Самый простой подход — развернуть лёгкий контейнер (например, Nginx) с HTML-файлом для страницы 404 и настроить
|
||||
Traefik для перенаправления ошибок на этот контейнер. Ну или, как альтернатива, использовать внешний сервис (по URL),
|
||||
но это сложнее для глобальной настройки, и создаст зависимость от этого URL.
|
||||
|
||||
#### 2. План действий
|
||||
- Создать кастомную страницу 404 (HTML-файл).
|
||||
- Развернуть контейнер с Nginx, который будет отдавать эту страницу.
|
||||
- Настроить Traefik middleware `errors` для перехвата 404.
|
||||
- Применить middleware глобально для всех маршрутов в `k3s`.
|
||||
- Проверить результат.
|
||||
|
||||
---
|
||||
|
||||
### Настройка кастомной страницы 404
|
||||
|
||||
#### 1. Создать кастомную страницу 404
|
||||
- Создай HTML-файл для страницы 404 на ноде `opi5`:
|
||||
```bash
|
||||
mkdir -p ~/k3s/error-pages
|
||||
cat > ~/k3s/error-pages/404.html <<EOF
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>404 Not Found</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
|
||||
h1 { color: #ff5555; }
|
||||
p { font-size: 18px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>404 - Page Not Found</h1>
|
||||
<p>Oops! Looks like you're lost in the void.</p>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
```
|
||||
|
||||
#### 2. Развернуть Nginx для отдачи страницы
|
||||
- Создай манифест для Nginx, который будет отдавать `404.html`:
|
||||
```bash
|
||||
cat > ~/k3s/error-pages/error-pages.yaml <<EOF
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: error-pages
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: error-pages
|
||||
namespace: error-pages
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: error-pages
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: error-pages
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- containerPort: 80
|
||||
volumeMounts:
|
||||
- name: error-pages
|
||||
mountPath: /usr/share/nginx/html
|
||||
volumes:
|
||||
- name: error-pages
|
||||
configMap:
|
||||
name: error-pages
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: error-pages
|
||||
namespace: error-pages
|
||||
spec:
|
||||
selector:
|
||||
app: error-pages
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: error-pages
|
||||
namespace: error-pages
|
||||
data:
|
||||
404.html: |
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>404 Not Found</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
|
||||
h1 { color: #ff5555; }
|
||||
p { font-size: 18px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>404 - Page Not Found</h1>
|
||||
<p>Oops! Looks like you're lost in the void.</p>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
```
|
||||
|
||||
- Примени:
|
||||
```bash
|
||||
kubectl apply -f ~/k3s/error-pages/error-pages.yaml
|
||||
```
|
||||
|
||||
- Проверь поды и сервис:
|
||||
```bash
|
||||
kubectl get pod -n error-pages
|
||||
kubectl get svc -n error-pages
|
||||
```
|
||||
|
||||
#### 3. Настроить Traefik middleware для ошибок
|
||||
- Создай манифест для middleware `errors`:
|
||||
```bash
|
||||
cat > ~/k3s/traefik/error-middleware.yaml <<EOF
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: error-pages
|
||||
namespace: kube-system
|
||||
spec:
|
||||
errors:
|
||||
status:
|
||||
- "404"
|
||||
service:
|
||||
name: error-pages
|
||||
namespace: error-pages
|
||||
port: 80
|
||||
query: /404.html
|
||||
EOF
|
||||
```
|
||||
|
||||
- Примени:
|
||||
```bash
|
||||
kubectl apply -f ~/k3s/traefik/error-middleware.yaml
|
||||
```
|
||||
|
||||
- Проверь:
|
||||
```bash
|
||||
kubectl get middleware -n kube-system
|
||||
```
|
||||
|
||||
#### 4. Применить middleware глобально
|
||||
- В `k3s` дефолтный Traefik обрабатывает маршруты через `IngressRoute` или `Ingress`. Чтобы middleware применялся ко всем маршрутам, нужно либо:
|
||||
- Добавить middleware к каждому `IngressRoute` вручную.
|
||||
- Настроить Traefik для глобального применения middleware через `defaultMiddlewares`.
|
||||
|
||||
- Для простоты создадим глобальный `IngressRoute` для всех доменов:
|
||||
```bash
|
||||
cat > ~/k3s/traefik/global-error-route.yaml <<EOF
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: global-error-route
|
||||
namespace: kube-system
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
- websecure
|
||||
routes:
|
||||
- match: HostRegexp(`{host:.+}`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: noop
|
||||
namespace: kube-system
|
||||
port: 9999
|
||||
middlewares:
|
||||
- name: error-pages
|
||||
namespace: kube-system
|
||||
EOF
|
||||
```
|
||||
|
||||
- Примени:
|
||||
```bash
|
||||
kubectl apply -f ~/k3s/traefik/global-error-route.yaml
|
||||
```
|
||||
|
||||
- **Примечание**:
|
||||
- Сервис `noop:9999` — это заглушка, так как `IngressRoute` требует сервис, но middleware `errors` перехватит 404 до обращения к сервису.
|
||||
- Это обеспечивает, что любой запрос с кодом 404 (для любого домена) будет перенаправлен на `error-pages`.
|
||||
|
||||
#### 5. Проверить кастомную страницу 404
|
||||
- Попробуй открыть несуществующий путь:
|
||||
```bash
|
||||
curl -v https://git.cube2.ru/nonexistent
|
||||
```
|
||||
|
||||
- Ожидаемый ответ:
|
||||
- Код: `404 Not Found`.
|
||||
- HTML-страница:
|
||||
```html
|
||||
<h1>404 - Page Not Found</h1>
|
||||
<p>Oops! Looks like you're lost in the void.</p>
|
||||
```
|
||||
|
||||
- Проверь для другого домена (например, `Bitwarden`):
|
||||
```bash
|
||||
curl -v https://<bitwarden-domain>/nonexistent
|
||||
```
|
||||
|
||||
- Проверь логи Traefik:
|
||||
```bash
|
||||
kubectl logs -n kube-system -l app.kubernetes.io/name=traefik | tail -n 20
|
||||
```
|
||||
|
||||
#### 6. (Опционально) Настроить другие ошибки
|
||||
- Чтобы добавить кастомные страницы для других кодов (например, 403, 500), обнови middleware:
|
||||
```bash
|
||||
cat > ~/k3s/traefik/error-middleware.yaml <<EOF
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: error-pages
|
||||
namespace: kube-system
|
||||
spec:
|
||||
errors:
|
||||
status:
|
||||
- "403"
|
||||
- "404"
|
||||
- "500-503"
|
||||
service:
|
||||
name: error-pages
|
||||
namespace: error-pages
|
||||
port: 80
|
||||
query: /{status}.html
|
||||
EOF
|
||||
```
|
||||
|
||||
- Создай дополнительные файлы (`403.html`, `500.html`) в `ConfigMap`:
|
||||
```bash
|
||||
kubectl edit configmap -n error-pages error-pages
|
||||
```
|
||||
- Добавь, например:
|
||||
```yaml
|
||||
403.html: |
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>403 Forbidden</title></head>
|
||||
<body><h1>403 - Forbidden</h1><p>Access denied!</p></body>
|
||||
</html>
|
||||
```
|
||||
|
||||
- Примени:
|
||||
```bash
|
||||
kubectl apply -f ~/k3s/traefik/error-middleware.yaml
|
||||
kubectl delete pod -n error-pages -l app=error-pages
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Ответ на твой вопрос
|
||||
> По умолчанию Traefik выдаёт скучные текстовые страницы для 404 и других ошибок. Как сделать кастомные 404 для всего `k3s` без привязки к домену? Где они лежат, и есть ли простой способ их переопределить?
|
||||
|
||||
- **Где лежат дефолтные страницы**:
|
||||
- Они встроены в бинарник Traefik и генерируются как текст (не HTML), например: `404 page not found`.
|
||||
- Физически их нет в виде файлов в кластере.
|
||||
|
||||
- **Почему не HTML**:
|
||||
- Traefik использует текстовые ответы для минимизации ресурсов.
|
||||
|
||||
- **Как переопределить**:
|
||||
- Использовать middleware `errors`, который перенаправляет ошибки (например, 404) на кастомный сервис с HTML-страницей.
|
||||
- Развернуть контейнер (например, Nginx) с кастомной страницей.
|
||||
- Настроить глобальный `IngressRoute` для применения middleware ко всем доменам.
|
||||
|
||||
- **Простой способ**:
|
||||
1. Создать `ConfigMap` с HTML-файлом (`404.html`).
|
||||
2. Развернуть Nginx в namespace `error-pages` для отдачи страницы.
|
||||
3. Настроить middleware `errors` для перехвата 404.
|
||||
4. Применить middleware через глобальный `IngressRoute`.
|
||||
|
||||
- **Для всего `k3s`**:
|
||||
- Глобальный `IngressRoute` с `HostRegexp` перехватывает все запросы и применяет middleware `errors` для ошибок 404.
|
||||
|
||||
---
|
||||
|
||||
### Рекомендации
|
||||
1. Создать и применить страницу 404:
|
||||
```bash
|
||||
kubectl apply -f ~/k3s/error-pages/error-pages.yaml
|
||||
```
|
||||
|
||||
2. Настроить middleware:
|
||||
```bash
|
||||
kubectl apply -f ~/k3s/traefik/error-middleware.yaml
|
||||
```
|
||||
|
||||
3. Применить глобальный маршрут:
|
||||
```bash
|
||||
kubectl apply -f ~/k3s/traefik/global-error-route.yaml
|
||||
```
|
||||
|
||||
4. Проверить:
|
||||
```bash
|
||||
curl -v https://git.cube2.ru/nonexistent
|
||||
curl -v https://<bitwarden-domain>/nonexistent
|
||||
```
|
||||
|
||||
5. Проверить логи:
|
||||
```bash
|
||||
kubectl logs -n kube-system -l app.kubernetes.io/name=traefik | tail -n 20
|
||||
```
|
||||
|
||||
6. (Опционально) Добавить другие ошибки:
|
||||
- Обновить `ConfigMap` и middleware для 403, 500 и т.д.
|
||||
|
||||
---
|
||||
|
||||
### Итог
|
||||
Дефолтные страницы ошибок Traefik — это встроенные текстовые ответы, которые можно переопределить с помощью middleware `errors` и кастомного сервиса (например, Nginx с HTML-страницей). Для глобальной настройки в `k3s` мы развернули контейнер с `404.html`, настроили middleware для перехвата ошибок 404, и применили его ко всем доменам через `IngressRoute` с `HostRegexp`. Это простой и универсальный способ сделать страницы ошибок яркими и весёлыми! 😄 Теперь твои 404 будут выглядеть стильно, и ты можешь добавить такие же для других ошибок.
|
||||
|
||||
**Действия**:
|
||||
1. Применить:
|
||||
```bash
|
||||
kubectl apply -f ~/k3s/error-pages/error-pages.yaml
|
||||
kubectl apply -f ~/k3s/traefik/error-middleware.yaml
|
||||
kubectl apply -f ~/k3s/traefik/global-error-route.yaml
|
||||
```
|
||||
2. Проверить:
|
||||
```bash
|
||||
curl -v https://git.cube2.ru/nonexistent
|
||||
```
|
||||
|
||||
**Напиши**:
|
||||
1. Получилась ли кастомная страница 404? (`curl -v https://git.cube2.ru/nonexistent`)
|
||||
2. Работает ли для других доменов? (`curl -v https://<bitwarden-domain>/nonexistent`)
|
||||
3. Хочешь настроить страницы для других ошибок (403, 500)?
|
||||
|
||||
Теперь можно расслабиться и наслаждаться яркими страницами ошибок! 🚀
|
208
kubernetes/k3s_smokeping.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# Контейнер со smokeping в k3s
|
||||
|
||||
Полная жопа с домашним интеренетом? А поддержка провайдера, типа МГТС или Ростелеком, отвечает: "Проблем на линии нет,
|
||||
у нас все работает"? А может быть, вы хотите следить за доступностью и сетевыми задержками в доступности своих серверов
|
||||
в разных локациях, или в домашней сети?.. Smokeping -- это то, что вам нужно! Не такой громоздкий, как Zabbix,
|
||||
и не такой сложный в настройке, как Grafana (хотя к Grafana его тоже можно подключить), и главное супер-компактный
|
||||
и легкий.
|
||||
|
||||
И так, все манифесты в одном файле. Только поменяйте в манифесте `smokeping-config` меню, название и IP-адреса
|
||||
на свои, и укажите на какое доменное имя публикуете веб-панель (у меня -- http://sp.local). Ну и читайте пояснения
|
||||
и комментарии внутри манифестов:
|
||||
```yaml
|
||||
# ~/k3s/smokeping/smokeping.yaml
|
||||
# Все манифесты для smokeping
|
||||
|
||||
# Манифест создания пространства имён `smokeping`. Если оно уже есть — kubectl apply ничего не изменит (т.е. безопасно.)
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: smokeping
|
||||
|
||||
---
|
||||
# Манифест ConfigMap с конфигурацией Targets для smokeping
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: smokeping-config
|
||||
namespace: smokeping
|
||||
data:
|
||||
# Это содержимое файла Targets. Здесь указываем, кого пинговать.
|
||||
Targets: |
|
||||
*** Targets ***
|
||||
|
||||
probe = FPing
|
||||
|
||||
menu = Top
|
||||
title = Network Latency Grapher
|
||||
remark = Привет тебе, путник. SmokePing website of Campany.\
|
||||
Тут ты узнаешь все о задержках в вашей сети.
|
||||
|
||||
+ HOME
|
||||
menu = HOME
|
||||
title = HOME
|
||||
|
||||
++ Router
|
||||
menu = Router
|
||||
title = Router
|
||||
alerts = someloss
|
||||
host = 192.168.1.1
|
||||
|
||||
++ NAS
|
||||
menu = NAS
|
||||
title = NAS
|
||||
alerts = someloss
|
||||
host = 192.168.1.xxx
|
||||
|
||||
++ K3S_VIP
|
||||
menu = K3S_VIP
|
||||
title = K3S_VIP
|
||||
alerts = someloss
|
||||
host = 192.168.1.xxx
|
||||
|
||||
++ YANDEX_ALISA
|
||||
menu = YANDEX_ALISA
|
||||
title = YANDEX_ALISA
|
||||
alerts = someloss
|
||||
host = 192.168.1.xxx
|
||||
|
||||
|
||||
+ INTERNET
|
||||
menu = INTERNET
|
||||
title = INTERNET
|
||||
|
||||
++ HOSTING_RU
|
||||
menu = Russia
|
||||
title = MasterHost_ru
|
||||
alerts = someloss
|
||||
host = xxx.xxx.xxx.xxx
|
||||
|
||||
++ HOSTING_EU
|
||||
menu = Sweden
|
||||
title = xxxxxxx
|
||||
alerts = someloss
|
||||
host = xxx.xxx.xxx.xxx
|
||||
|
||||
++ HOSTING_AS
|
||||
menu = Tureky
|
||||
title = xxxxxxx
|
||||
alerts = someloss
|
||||
host = xxx.xxx.xxx.xxx
|
||||
|
||||
|
||||
---
|
||||
# Манифест PVC (Longhorn) -- том для хранения данных графиков, чтоб при перезапуске пода данные не пропадали
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: smokeping-data # Имя PVC-хранилища
|
||||
namespace: smokeping # Пространство имен `smokeping`
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn # Используем Longhorn как класс хранения
|
||||
resources:
|
||||
requests:
|
||||
storage: 256Mi # Хватит на мониторинг 20-30 узлов глубиной 1.5-2 года (!)
|
||||
|
||||
|
||||
---
|
||||
# Манифест для развертывания smokeping (Deployment)
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: smokeping
|
||||
namespace: smokeping
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: smokeping
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: smokeping
|
||||
spec:
|
||||
containers:
|
||||
- name: smokeping
|
||||
# image: ghcr.io/linuxserver-arm64v8/smokeping # dля arm64v8
|
||||
image: linuxserver/smokeping # оригинальный образ smokeping, и он заработал на amd64
|
||||
env:
|
||||
- name: TZ # Указываем временную зону
|
||||
value: Europe/Moscow # ...чтобы на графиках не было UTC
|
||||
ports:
|
||||
- containerPort: 80
|
||||
volumeMounts: # Монтируем файл Targets из ConfigMap в нужное место в контейнере
|
||||
- name: config
|
||||
mountPath: /config/Targets # mountPath указывает, куда будет "вставлен" файл
|
||||
subPath: Targets # subPath = берём только один файл из configMap
|
||||
- name: data
|
||||
mountPath: /data # Данные графиков в Longhorn (PVC)
|
||||
volumes:
|
||||
- name: config # Используем том ConfigMap с конфигурацией
|
||||
configMap:
|
||||
name: smokeping-config
|
||||
- name: data # Используем PVC (Longhorn) для хранения данных
|
||||
persistentVolumeClaim:
|
||||
claimName: smokeping-data
|
||||
|
||||
|
||||
---
|
||||
# Service — внутренний сервис для доступа к smokeping по сети внутри кластера
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: smokeping
|
||||
namespace: smokeping
|
||||
spec:
|
||||
selector:
|
||||
app: smokeping
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80 # порт внутри кластера
|
||||
targetPort: 80 # порт, на котором работает контейнер
|
||||
type: ClusterIP # только для доступа внутри кластера (Ingress подключится к нему)
|
||||
|
||||
|
||||
---
|
||||
# IngressRoute для Traefik (под твою конфигурацию)
|
||||
# Это публикует smokeping по адресу http://sp.local (заменить на свой домен)
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: smokeping
|
||||
namespace: smokeping
|
||||
spec:
|
||||
entryPoints:
|
||||
- web # это должен быть один из entrypoints в Traefik (обычно "web" = порт 80)
|
||||
routes:
|
||||
- match: Host("sp.local") # доменное имя, по которому будет доступен сервис
|
||||
kind: Rule
|
||||
services:
|
||||
- name: smokeping
|
||||
port: 80
|
||||
|
||||
---
|
||||
######## Это я пытался сделать редирект на favicon, но не заработало. У самого smokeping нет favicon, и это бесило.
|
||||
# Манифест Middleware для перенаправление всех запросов к /favicon.ico на другой URL (к сожалению у smokeping нет favicon.ico).
|
||||
#apiVersion: traefik.io/v1alpha1
|
||||
#kind: Middleware
|
||||
#metadata:
|
||||
# name: favicon-redirect
|
||||
# namespace: smokeping
|
||||
#spec:
|
||||
# redirectRegex:
|
||||
# regex: "^/favicon\\.ico$"
|
||||
# replacement: "http://ai.local/_graphmagnifier_118081.ico"
|
||||
# permanent: true
|
||||
```
|
||||
|
||||
Сохраним файл в `~/k3s/smokeping/smokeping.yaml` (или в другом месте, где вам удобно), и применим манифесты:
|
||||
```bash
|
||||
kubectl apply -f ~/k3s/smokeping/smokeping.yaml
|
||||
```
|
||||
|
||||
После этого smokeping будет доступен по адресу http://sp.local (или по тому доменному имени, которое вы указали
|
||||
в манифесте IngressRoute) и увидите занятные графики (только дождитесь пока данные соберутся, ну часок-другой):
|
||||
|
||||

|
744
raspberry-and-orange-pi/host-protection-with-crowdsec.md
Normal file
@@ -0,0 +1,744 @@
|
||||
# Защита хоста с помощью CrowdSec
|
||||
|
||||
Вы наверняка использовали (или как минимум слышали) о Fail2Ban. Он очень широко распространён для защиты SSH на хостах,
|
||||
противодействия сканированию сайтов, легких DDoS-атак и "фонового bot-трафика". Fail2Ban существует с 2004 года
|
||||
и давно стал стандартом для защиты серверов. Но он слабо подходит для защиты кластеров Kubernetes, так поды
|
||||
обслуживающие внешний трафик (Ingress-контроллеры Traefik в случае k3s) могут находиться на разных узлах кластера.
|
||||
Если Fail2Ban заблокирует IP-адрес на одной ноде, то он не сможет защитить другие узлы кластера, так как они ничего
|
||||
не узнают о блокировках.
|
||||
|
||||
Для защиты распределённых систем (в том числе кластеров Kubernetes) набирает популярность CrowdSec. Это проект
|
||||
с открытым исходным кодом, который, кроме обмена информацией об атаках между узлами (за периметром), использует
|
||||
и внешний краудсорсинг (Community Blocklist) для защиты от атак. Он собирает данные о блокировках и позволяет
|
||||
обмениваться этой информацией между всеми участниками сети (это отключаемая опция, и по умолчанию она отключена).
|
||||
Таким образом, CrowdSec может не только защитить все узлы кластера (благодаря обмену информацией за периметром),
|
||||
о блокировать IP-адреса, еще до их атаки на ваш сервер (если данные IP уже заблокированы другими участниками CrowdSec).
|
||||
А еще CrowdSec модульный, поддерживает сценарии (http-cms-scanning, sshd-bf и тому-подобное),в 60 раз быстрее
|
||||
Fail2Ban (он написан на Golang), работает с IPv6 и имеет интеграции с Traefik, Cloudflare, Nginx, k3s, Docker и другими
|
||||
инструментами. CrowdSec активно растёт в нише DevOps, облаков, контейнеров и кластеров Kubernetes. А еще он не
|
||||
требовательный по ресурсам (~100 МБ RAM) и подходит для Orange Pi.
|
||||
|
||||
----
|
||||
## Утановка CrowdSec
|
||||
|
||||
В принципе, СrowdSec можно установить в кластер через Helm. Тогда он сам развернется на всех узлах кластера и это
|
||||
отличный вариант для защиты Traefik (HTTP-запросы, сценарии http-cms-scanning, http-probing) и контейнеризированных
|
||||
приложений (в моем случае [Gitea](../kubernetes/k3s-migrating-container-from-docker-to-kubernetes.md), [3x-ui](../kubernetes/k3s-3xui-pod.md)
|
||||
и тому подобного). Но мне нужно защитить еще и SSH самих узлов (узла) кластера. Поэтому план такой:
|
||||
|
||||
* Хостовый CrowdSec (на одном или всех узлах кластера) использует тот же Local API (LAPI) через виртуальный IP (VIP)
|
||||
Keepalived для получения бан-листа и применяет его к SSH (через Firewall Bouncer) и через тот же LAPI сообщает
|
||||
о банах ssh-bt в CrowdSec Agent внутри k3s.
|
||||
* Кластерный CrowdSec Agent, внутри k3s, анализирует логи Traefik и создаёт решения (decisions) о бане IP.
|
||||
* Traefik Bouncer в k3s, также подключается к LAPI для защиты HTTP (для git.cube2.ru и других web-приложений).
|
||||
|
||||
----
|
||||
### CrowdSec на первом узле и защита SSH на хосте
|
||||
|
||||
----
|
||||
#### Подготовка к установке CrowdSec
|
||||
|
||||
Делаем обновляем список пактов и систему:
|
||||
```shell
|
||||
sudo apt update
|
||||
sudo apt upgrade
|
||||
```
|
||||
|
||||
Добавляем репозиторий CrowdSec и ключи репозитория:
|
||||
```shell
|
||||
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
|
||||
```
|
||||
|
||||
----
|
||||
#### Установка CrowdSec и проверка
|
||||
|
||||
Устанавливаем CrowdSec:
|
||||
```shell
|
||||
sudo apt install crowdsec
|
||||
```
|
||||
|
||||
Увидим, в число прочего:
|
||||
```text
|
||||
...
|
||||
...
|
||||
reating /etc/crowdsec/acquis.yaml
|
||||
INFO[2025-xx-xx xx:xx:xx] crowdsec_wizard: service 'ssh': /var/log/auth.log
|
||||
INFO[2025-xx-xx xx:xx:xx] crowdsec_wizard: using journald for 'smb'
|
||||
INFO[2025-xx-xx xx:xx:xx] crowdsec_wizard: service 'linux': /var/log/syslog /var/log/kern.log
|
||||
Machine 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' successfully added to the local API.
|
||||
API credentials written to '/etc/crowdsec/local_api_credentials.yaml'.
|
||||
Updating hub
|
||||
Downloading /etc/crowdsec/hub/.index.json
|
||||
Action plan:
|
||||
🔄 check & update data files
|
||||
|
||||
|
||||
INFO[2025-05-17 17:56:45] crowdsec_wizard: Installing collection 'crowdsecurity/linux'
|
||||
downloading parsers:crowdsecurity/syslog-logs
|
||||
downloading parsers:crowdsecurity/geoip-enrich
|
||||
downloading https://hub-data.crowdsec.net/mmdb_update/GeoLite2-City.mmdb
|
||||
downloading https://hub-data.crowdsec.net/mmdb_update/GeoLite2-ASN.mmdb
|
||||
downloading parsers:crowdsecurity/dateparse-enrich
|
||||
downloading parsers:crowdsecurity/sshd-logs
|
||||
downloading scenarios:crowdsecurity/ssh-bf
|
||||
downloading scenarios:crowdsecurity/ssh-slow-bf
|
||||
downloading scenarios:crowdsecurity/ssh-cve-2024-6387
|
||||
downloading scenarios:crowdsecurity/ssh-refused-conn
|
||||
downloading contexts:crowdsecurity/bf_base
|
||||
downloading collections:crowdsecurity/sshd
|
||||
downloading collections:crowdsecurity/linux
|
||||
enabling parsers:crowdsecurity/syslog-logs
|
||||
enabling parsers:crowdsecurity/geoip-enrich
|
||||
enabling parsers:crowdsecurity/dateparse-enrich
|
||||
enabling parsers:crowdsecurity/sshd-logs
|
||||
enabling scenarios:crowdsecurity/ssh-bf
|
||||
enabling scenarios:crowdsecurity/ssh-slow-bf
|
||||
enabling scenarios:crowdsecurity/ssh-cve-2024-6387
|
||||
enabling scenarios:crowdsecurity/ssh-refused-conn
|
||||
enabling contexts:crowdsecurity/bf_base
|
||||
enabling collections:crowdsecurity/sshd
|
||||
enabling collections:crowdsecurity/linux
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
Как видим, CrowdSec сам определил, что у нас есть SSH и Linux (syslog и kern.log). Создан локальный API (LAPI)
|
||||
м логин/пароль для него записан в `/etc/crowdsec/local_api_credentials.yaml`.
|
||||
|
||||
Далее CrowdSec загрузил парсеры, сценарии и коллекции для настройки защиты SSH и Linux.
|
||||
|
||||
Проверим, что CrowdSec работает:
|
||||
```shell
|
||||
sudo systemctl status crowdsec
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
● crowdsec.service - Crowdsec agent
|
||||
Loaded: loaded (/lib/systemd/system/crowdsec.service; enabled; vendor preset: enabled)
|
||||
Active: active (running) since Sat xxxx-xx-xx xx:xx:xx XXX; 51min ago
|
||||
Main PID: 3357651 (crowdsec)
|
||||
Tasks: 14 (limit: 18978)
|
||||
Memory: 30.7M
|
||||
CPU: 18.233s
|
||||
CGroup: /system.slice/crowdsec.service
|
||||
├─3357651 /usr/bin/crowdsec -c /etc/crowdsec/config.yaml
|
||||
└─3357715 journalctl --follow -n 0 _SYSTEMD_UNIT=smb.service
|
||||
|
||||
Xxx xx xx:xx:xx xxxx systemd[1]: Starting Crowdsec agent...
|
||||
Xxx xx xx:xx:xx xxxx systemd[1]: Started Crowdsec agent.
|
||||
```
|
||||
|
||||
Проверим версию CrowdSec:
|
||||
```shell
|
||||
sudo cscli version
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
version: v1.6.8-debian-pragmatic-arm64-f209766e
|
||||
Codename: alphaga
|
||||
BuildDate: 2025-03-25_14:50:57
|
||||
GoVersion: 1.24.1
|
||||
Platform: linux
|
||||
libre2: C++
|
||||
User-Agent: crowdsec/v1.6.8-debian-pragmatic-arm64-f209766e-linux
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
Проверим список установленных парсеров:
|
||||
```shell
|
||||
sudo cscli parsers list
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
──────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
PARSERS
|
||||
──────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
Name 📦 Status Version Local Path
|
||||
──────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
crowdsecurity/dateparse-enrich ✔️ enabled 0.2 /etc/crowdsec/parsers/s02-enrich/dateparse-enrich.yaml
|
||||
crowdsecurity/geoip-enrich ✔️ enabled 0.5 /etc/crowdsec/parsers/s02-enrich/geoip-enrich.yaml
|
||||
crowdsecurity/smb-logs ✔️ enabled 0.2 /etc/crowdsec/parsers/s01-parse/smb-logs.yaml
|
||||
crowdsecurity/sshd-logs ✔️ enabled 3.0 /etc/crowdsec/parsers/s01-parse/sshd-logs.yaml
|
||||
crowdsecurity/syslog-logs ✔️ enabled 0.8 /etc/crowdsec/parsers/s00-raw/syslog-logs.yaml
|
||||
crowdsecurity/whitelists ✔️ enabled 0.3 /etc/crowdsec/parsers/s02-enrich/whitelists.yaml
|
||||
──────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
```
|
||||
|
||||
Как видим `crowdsecurity/sshd-logs` доступны, а значит CrowdSec может парсить логи SSH. Проверим список
|
||||
установленных коллекций:
|
||||
```shell
|
||||
sudo cscli collections list
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
─────────────────────────────────────────────────────────────────────────────────
|
||||
COLLECTIONS
|
||||
─────────────────────────────────────────────────────────────────────────────────
|
||||
Name 📦 Status Version Local Path
|
||||
─────────────────────────────────────────────────────────────────────────────────
|
||||
crowdsecurity/linux ✔️ enabled 0.2 /etc/crowdsec/collections/linux.yaml
|
||||
crowdsecurity/smb ✔️ enabled 0.1 /etc/crowdsec/collections/smb.yaml
|
||||
crowdsecurity/sshd ✔️ enabled 0.6 /etc/crowdsec/collections/sshd.yaml
|
||||
─────────────────────────────────────────────────────────────────────────────────
|
||||
```
|
||||
|
||||
Видим, что `crowdsecurity/sshd` доступны. Проверим список установленных сценариев:
|
||||
```shell
|
||||
sudo cscli scenarios list
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
SCENARIOS
|
||||
───────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
Name 📦 Status Version Local Path
|
||||
───────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
crowdsecurity/smb-bf ✔️ enabled 0.2 /etc/crowdsec/scenarios/smb-bf.yaml
|
||||
crowdsecurity/ssh-bf ✔️ enabled 0.3 /etc/crowdsec/scenarios/ssh-bf.yaml
|
||||
crowdsecurity/ssh-cve-2024-6387 ✔️ enabled 0.2 /etc/crowdsec/scenarios/ssh-cve-2024-6387.yaml
|
||||
crowdsecurity/ssh-refused-conn ✔️ enabled 0.1 /etc/crowdsec/scenarios/ssh-refused-conn.yaml
|
||||
crowdsecurity/ssh-slow-bf ✔️ enabled 0.4 /etc/crowdsec/scenarios/ssh-slow-bf.yaml
|
||||
───────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
```
|
||||
|
||||
Сценарии `ssh-bf`, `crowdsecurity/ssh-slow-bf` (брутфорсинг и медленный брутфорсинг SSH),
|
||||
`crowdsecurity/ssh-cve-2024-6387` (защита от regreSSHion-атак на старые SSH-сервера) и
|
||||
crowdsecurity/ssh-refused-conn` (отказ соединения SSH) доступны.
|
||||
|
||||
Кстати, обновлять все это богачество (парсеры, сценарии, коллекции и т.п.) можно командой:
|
||||
```shell
|
||||
sudo cscli hub update
|
||||
```
|
||||
|
||||
Проверим конфиги CrowdSec, и убедимся, что он анализирует логи SSH:
|
||||
```shell
|
||||
sudo cat /etc/crowdsec/acquis.yaml
|
||||
```
|
||||
|
||||
Должны увидеть вот такой блок:
|
||||
```yaml
|
||||
filenames:
|
||||
- /var/log/auth.log
|
||||
labels:
|
||||
type: syslog
|
||||
---
|
||||
```
|
||||
|
||||
Если, вдруг, такого блока нет, добавьте его (лучше в начало) и перезапустим CrowdSec. Но обычно все уже настроено.
|
||||
|
||||
Проверим, что CrowdSec анализирует логи SSH:
|
||||
```shell
|
||||
sudo cscli metrics
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Acquisition Metrics │
|
||||
├────────────────────────┬────────────┬──────────────┬────────────────┬────────────────────────┬───────────────────┤
|
||||
│ Source │ Lines read │ Lines parsed │ Lines unparsed │ Lines poured to bucket │ Lines whitelisted │
|
||||
├────────────────────────┼────────────┼──────────────┼────────────────┼────────────────────────┼───────────────────┤
|
||||
│ file:/var/log/auth.log │ 628 │ - │ 628 │ - │ - │
|
||||
│ file:/var/log/kern.log │ 2.78k │ - │ 2.78k │ - │ - │
|
||||
│ file:/var/log/syslog │ 3.46k │ - │ 3.46k │ - │ - │
|
||||
╰────────────────────────┴────────────┴──────────────┴────────────────┴────────────────────────┴───────────────────╯
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
Как видим, CrowdSec читает `/var/log/auth.log` (логи SSH).
|
||||
|
||||
----
|
||||
#### Установка CrowdSec Firewall Bouncer -- блокировщик IP-адресов
|
||||
|
||||
По мне, блокировки CrowdSec довольно беззубые. К счастью через "вышибалу" Firewall Bouncer можно блокировать
|
||||
IP-адреса по iptables (или nftables) и сделать CrowdSec злее fail2ban. Для этого нужно установить
|
||||
`crowdsec-firewall-bouncer-iptables`:
|
||||
```shell
|
||||
sudo apt-get install crowdsec-firewall-bouncer-iptables
|
||||
```
|
||||
Проверим, что "вышибала" запустилась:
|
||||
```shell
|
||||
sudo systemctl status crowdsec-firewall-bouncer
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
● crowdsec-firewall-bouncer.service - The firewall bouncer for CrowdSec
|
||||
Loaded: loaded (/etc/systemd/system/crowdsec-firewall-bouncer.service; enabled; vendor preset: enabled)
|
||||
Active: active (running) since Sun 2025-05-18 14:47:10 MSK; 723ms ago
|
||||
Process: 621537 ExecStartPre=/usr/bin/crowdsec-firewall-bouncer -c /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml -t (code=exited, status=0/SUCCESS)
|
||||
Process: 621674 ExecStartPost=/bin/sleep 0.1 (code=exited, status=0/SUCCESS)
|
||||
Main PID: 621622 (crowdsec-firewa)
|
||||
Tasks: 10 (limit: 18978)
|
||||
Memory: 7.4M
|
||||
CPU: 401ms
|
||||
CGroup: /system.slice/crowdsec-firewall-bouncer.service
|
||||
└─621622 /usr/bin/crowdsec-firewall-bouncer -c /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml
|
||||
|
||||
May 18 14:47:04 opi5 systemd[1]: Starting The firewall bouncer for CrowdSec...
|
||||
May 18 14:47:10 opi5 systemd[1]: Started The firewall bouncer for CrowdSec.
|
||||
```
|
||||
|
||||
Подключить его в CrowdSec:
|
||||
```shell
|
||||
sudo cscli bouncers add firewall-bounce
|
||||
```
|
||||
|
||||
Проверим, что "вышибала" добавлен:
|
||||
```shell
|
||||
sudo cscli bouncers list
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
Name IP Address Valid Last API pull Type Version Auth Type
|
||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
cs-firewall-bouncer-xxxx 127.0.0.1 ✔️ xxxx-xx-xxTxx:xx:xxZ crowdsec-firewall-bouncer v0.0.31-debian-pragmatic-xxxxxx... api-key
|
||||
firewall-bouncer ✔️ api-key
|
||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
```
|
||||
|
||||
----
|
||||
#### Подключаем наш CrowdSec к обмену данными об атаках
|
||||
|
||||
CrowdSec может обмениваться данными об атаках с другими участниками сети. Чтобы это сделать, нужно пойти [на сайт
|
||||
CrowdSec](https://crowdsec.net/) и зарегистрироваться. После подтверждения регистрации по email, в личном кабинете
|
||||
в самом низу, увидим строчку команды, типа:
|
||||
```shell
|
||||
sudo cscli console enroll -e context хеш-идентификатор-вашего-аккаунта
|
||||
```
|
||||
|
||||
Скопируем эту команду и выполняем в терминале. Увидим что-то вроде:
|
||||
```text
|
||||
INFO manual set to true
|
||||
INFO context set to true
|
||||
INFO Enabled manual : Forward manual decisions to the console
|
||||
INFO Enabled tainted : Forward alerts from tainted scenarios to the console
|
||||
INFO Enabled context : Forward context with alerts to the console
|
||||
INFO Watcher successfully enrolled. Visit https://app.crowdsec.net to accept it.
|
||||
INFO Please restart crowdsec after accepting the enrollment.
|
||||
```
|
||||
|
||||
Как видим, нужно перезапустить CrowdSec:
|
||||
```shell
|
||||
sudo systemctl restart crowdsec
|
||||
```
|
||||
|
||||
Теперь нужно снова зайти в личный кабинет CrowdSec и подтвердить подключение Security Engine.
|
||||
|
||||
Все! Подключение локального CrowdSec к Community Blocklist завершено. В личном кабинете можно посмотреть статистику
|
||||
(по каждому Security Engine, ведь на один аккаунт можно подключить несколько хостов с CrowdSec) и даже управлять
|
||||
фильтрами и сценариями (это не точно).
|
||||
|
||||

|
||||
|
||||
Проверим, что CrowdSec получает блокировки через Community Blocklist API (CAPI):
|
||||
```shell
|
||||
sudo cscli metrics
|
||||
```
|
||||
|
||||
Увидим что-то типа:
|
||||
```text
|
||||
...
|
||||
...
|
||||
╭──────────────────────────────────────────╮
|
||||
│ Local API Decisions │
|
||||
├────────────────┬────────┬────────┬───────┤
|
||||
│ Reason │ Origin │ Action │ Count │
|
||||
├────────────────┼────────┼────────┼───────┤
|
||||
│ generic:scan │ CAPI │ ban │ 3222 │
|
||||
│ smb:bruteforce │ CAPI │ ban │ 427 │
|
||||
│ ssh:bruteforce │ CAPI │ ban │ 10033 │
|
||||
│ ssh:exploit │ CAPI │ ban │ 1315 │
|
||||
╰────────────────┴────────┴────────┴───────╯
|
||||
...
|
||||
```
|
||||
|
||||
Как видим, CrowdSec получает блокировки. Если очень интересно, можно посмотреть, что именно и почему блокируется
|
||||
(например, `ssh:bruteforce`):
|
||||
```shell
|
||||
sudo cscli decisions list --origin CAPI
|
||||
```
|
||||
|
||||
Увидим длиннющий список, примерно такого содержания:
|
||||
```text
|
||||
╭───────┬────────┬────────────────────────────────────┬────────────────┬────────┬─────────┬────┬────────┬────────────┬──────────╮
|
||||
│ ID │ Source │ Scope:Value │ Reason │ Action │ Country │ AS │ Events │ expiration │ Alert ID │
|
||||
├───────┼────────┼────────────────────────────────────┼────────────────┼────────┼─────────┼────┼────────┼────────────┼──────────┤
|
||||
..... .... ...................... .............. ... . ......... .
|
||||
│ ..... │ CAPI │ Ip:129.211.204.27 │ ssh:bruteforce │ ban │ │ │ 0 │ 79h15m46s │ 1 │
|
||||
│ ..... │ CAPI │ Ip:128.199.124.27 │ ssh:bruteforce │ ban │ │ │ 0 │ -1h44m14s │ 1 │
|
||||
│ ..... │ CAPI │ Ip:Ip:2602:80d:1006::76 │ ssh:bruteforce │ ban │ │ │ 0 │ 48h15m46s │ 1 │
|
||||
│ ..... │ CAPI │ Ip:123.58.213.127 │ ssh:bruteforce │ ban │ │ │ 0 │ 160h15m46s │ 1 │
|
||||
╰───────┴────────┴────────────────────────────────────┴────────────────┴────────┴─────────┴────┴────────┴────────────┴──────────╯
|
||||
```
|
||||
|
||||
----
|
||||
#### Настройка Whitelist (белого списка)
|
||||
|
||||
Чтобы не заблокировать себя (случайно) нужно создать в Whitelist (белый список). Например, сделаем `home_whitelist`
|
||||
(имя списка, таких списков может быть несколько, и
|
||||
```shell
|
||||
sudo cscli allowlist create home_whitelist -d 'Мой домашний whitelist'
|
||||
```
|
||||
|
||||
Теперь добавим в него свои домашнюю подсеть или IP-адрес (через пробел можно указать несколько адресов или подсетей):
|
||||
```shell
|
||||
sudo cscli allowlist add home_whitelist 192.168.1.0/24 XXX.XXX.XXX.XXX
|
||||
````
|
||||
|
||||
Проверим, что все добавилось:
|
||||
```shell
|
||||
sudo cscli allowlist inspect home_whitelist
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
──────────────────────────────────────────────
|
||||
Allowlist: home_whitelist
|
||||
──────────────────────────────────────────────
|
||||
Name home_whitelist
|
||||
Description Мой домашний whitelist
|
||||
Created at 2025-05-17T21:00:13.042Z
|
||||
Updated at 2025-05-17T21:01:29.090Z
|
||||
Managed by Console no
|
||||
──────────────────────────────────────────────
|
||||
|
||||
───────────────────────────────────────────────────────────────
|
||||
Value Comment Expiration Created at
|
||||
───────────────────────────────────────────────────────────────
|
||||
192.168.1.0/24 never 2025-05-17T21:00:13.042Z
|
||||
XXX.XXX.XXX.XXX never 2025-05-17T21:00:13.042Z
|
||||
XXX.XXX.XXX.XXX never 2025-05-17T21:00:13.042Z
|
||||
...
|
||||
...
|
||||
───────────────────────────────────────────────────────────────
|
||||
```
|
||||
|
||||
Еще один способ отредактировать (создать) Whitelist-конфиг парсера, который мы получили командой
|
||||
`sudo cscli parsers list`. Конфиг `/etc/crowdsec/parsers/s02-enrich/whitelists.yaml` довольно простой, если его
|
||||
отредактировать (добавить нужные IP-адреса, подсети или даже доменные имена), а затем перезапустить CrowdSec -- получим
|
||||
тот же результат. Только управлять через списки (allowlist) удобнее.
|
||||
[См. документацию](https://doc.crowdsec.net/u/getting_started/post_installation/whitelists/).
|
||||
|
||||
----
|
||||
#### Настройка Firewall Bouncer (блокировщик IP-адресов)
|
||||
|
||||
----
|
||||
##### Сценарии блокировок
|
||||
|
||||
Когда мы проверяли установку CrowdSec, и проверим список сценариев `shell sudo cscli scenarios list`, то нам был
|
||||
показан список yaml-манифестов c конфигурациями сценариев блокировок. Эти **сценарии занимаются распознаванием атак**,
|
||||
в частности касающихся SSH:
|
||||
* `/etc/crowdsec/scenarios/ssh-bf.yaml` -- брутфорс SSH
|
||||
* `/etc/crowdsec/scenarios/ssh-slow-bf.yaml` -- медленный брутфорс SSH
|
||||
* `/etc/crowdsec/scenarios/ssh-cve-2024-6387.yaml` -- regreSSHion-атака (атаки уязвимости SSH-серверов старых версий)
|
||||
* `/etc/crowdsec/scenarios/ssh-refused-conn.yaml` -- отказ соединения SSH, защищает от сканеров, которые ищут
|
||||
открытые SSH-порты (на очень актуально, если у вас SSH открыт по стандартном 22-порту).
|
||||
|
||||
В некоторых манифестах может быть несколько блоков конфигурации блокировок для разных сценариев атак "зловредов".
|
||||
Например, в `ssh-bf.yaml` есть блоки `crowdsecurity/ssh-bf` (для тупого брутфорса) и `crowdsecurity/ssh-bf_user-enum`
|
||||
(для перебора пользователей).
|
||||
|
||||
Меняем "беззубые" параметры, на что-то более серьезное. Открываем на редактирование, например, `ssh-bf.yaml`:
|
||||
```shell
|
||||
sudo nano /etc/crowdsec/scenarios/ssh-bf.yaml
|
||||
```
|
||||
|
||||
Увидим что-то типа:
|
||||
```yaml
|
||||
# ssh bruteforce
|
||||
type: leaky
|
||||
name: crowdsecurity/ssh-bf
|
||||
description: "Detect ssh bruteforce"
|
||||
filter: "evt.Meta.log_type == 'ssh_failed-auth'"
|
||||
leakspeed: "10s"
|
||||
references:
|
||||
- http://wikipedia.com/ssh-bf-is-bad
|
||||
capacity: 5
|
||||
groupby: evt.Meta.source_ip
|
||||
blackhole: 1m
|
||||
reprocess: true
|
||||
labels:
|
||||
service: ssh
|
||||
confidence: 3
|
||||
spoofable: 0
|
||||
classification:
|
||||
- attack.T1110
|
||||
label: "SSH Bruteforce"
|
||||
behavior: "ssh:bruteforce"
|
||||
remediation: true
|
||||
---
|
||||
# ssh user-enum
|
||||
type: leaky
|
||||
name: crowdsecurity/ssh-bf_user-enum
|
||||
description: "Detect ssh user enum bruteforce"
|
||||
filter: evt.Meta.log_type == 'ssh_failed-auth'
|
||||
groupby: evt.Meta.source_ip
|
||||
distinct: evt.Meta.target_user
|
||||
leakspeed: 10s
|
||||
capacity: 5
|
||||
blackhole: 1m
|
||||
labels:
|
||||
service: ssh
|
||||
remediation: true
|
||||
confidence: 3
|
||||
spoofable: 0
|
||||
classification:
|
||||
- attack.T1589
|
||||
behavior: "ssh:bruteforce"
|
||||
label: "SSH User Enumeration"
|
||||
```
|
||||
|
||||
Что тут происходит:
|
||||
|
||||
* Сценарий `crowdsecurity/ssh-bf`:
|
||||
* Тип: `leaky` -- leaky bucket — алгоритм "дырявое ведро", считающий события в окне времени. Метафора "дырявого
|
||||
ведра" в том, что из дырок на дне идет утечка со скоростью одна попытка за `leakspeed`. Емкость ведра равна
|
||||
`capacity`. Когда "ведро" было пустм, в него можно было поместить `capacity` событий, и после по одому событию
|
||||
в `leakspeed`. Если ведро переполнено событиями, то включается `blackhole` (черная дыра) и события игнорируются
|
||||
в течении `blackhole` времени.
|
||||
* Фильтр: `evt.Meta.log_type == 'ssh_failed-auth'` -- ловит неудачные попытки входа по SSH из `/var/log/auth.log`.
|
||||
* Логика:
|
||||
* `groupby: evt.Meta.source_ip` -- группирует события по IP атакующего.
|
||||
* `leakspeed: 10s` -- "окно времени" — 10 секунд (каждые 10 сек разрешена одна попытка).
|
||||
* `capacity: 5` -- Бан после 5 неудачных попыток.
|
||||
* `blackhole: 1m` -- Бан на 1 минуту.
|
||||
* Сценарий `crowdsecurity/ssh-bf_user-enum`:
|
||||
* Тип тот же.
|
||||
* Фильтр тот же.
|
||||
* Логика:
|
||||
* `distinct: evt.Meta.target_user` -- считает попытки с разными пользователями (root, admin, pi, orangepi и т.д.).
|
||||
* `leakspeed: 10s` -- "окно времени" — 10 секунд.
|
||||
* `capacity: 5` -- Бан после 5 разных пользователей за 10 секунд.
|
||||
* `blackhole: 1m` -- Бан на 1 минуту.
|
||||
|
||||
Как видим в обоих случаях бан срабатывает после пяти попыток за десять секунд, и блокировка всего на минуту. Конечно,
|
||||
брутфорсеры -- это быстрые атаки, но "быстрота" понятие относительное. Я выставляю:
|
||||
* `leakspeed: 10m`
|
||||
* `capacity: 2`
|
||||
* `blackhole: 1h`
|
||||
|
||||
И считаю, что это довольно мягко. Но чтоб случайно не заблокировать себя, когда буду подключаться с внешнего IP
|
||||
не из белого списка (например, по мобильному интернету) -- это разумный компромисс.
|
||||
|
||||
После редактирования файла, нужно перезапустить CrowdSec, чтоб он применил изменения:
|
||||
```shell
|
||||
sudo systemctl restart crowdsec
|
||||
sudo systemctl restart crowdsec-firewall-bouncer
|
||||
```
|
||||
|
||||
Другие сценарии можно настроить по аналогии. "Злость" управляется параметрами `leakspeed`, `capacity` и `blackhole`.
|
||||
Но имейте в виду: не стоит менять много параметров одновременно. Настройки разных сценариев могут конфликтовать
|
||||
друг другом, и тогда CrowdSec не запустится.
|
||||
|
||||
После перезапуска CrowdSec:
|
||||
```shell
|
||||
sudo systemctl restart crowdsec
|
||||
```
|
||||
|
||||
И еще, экспериментально я обнаружил, что настройки дней, например `2d` недопустимы. Надо указывать `48h` (48 часов),
|
||||
и в целом не нужно сразу месть настройки сразу во всех сценариях. Они могут конфликтовать друг с другом, и CrowdSec
|
||||
не перезапуститься.
|
||||
|
||||
Проверим, что CrowdSec начал банить на основании настроенных правил (особо ждать не придется, зловреды попадутся уже через
|
||||
пару минут):
|
||||
```shell
|
||||
sudo cscli decisions list
|
||||
```
|
||||
|
||||
Увидим что-то типа:
|
||||
```text
|
||||
╭───────┬──────────┬───────────────────┬────────────────────────────────┬─────┬────┬────────────────────────┬────────┬────────────┬──────────╮
|
||||
│ ID │ Source │ Scope:Value │ Reason │ Act │ Co │ AS │ Events │ expiration │ Alert ID │
|
||||
├───────┼──────────┼───────────────────┼────────────────────────────────┼─────┼────┼────────────────────────┼────────┼────────────┼──────────┤
|
||||
│ 30004 │ crowdsec │ Ip:39.98.38.186 │ crowdsecurity/ssh-slow-bf │ ban │ CN │ 37963 Hangzhou Alibaba │ 11 │ 3h54m49s │ 6 │
|
||||
│ 30002 │ crowdsec │ Ip:165.246.104.64 │ crowdsecurity/ssh-bf │ ban │ KR │ 9317 INHA UNIVERSITY │ 3 │ 3h50m0s │ 4 │
|
||||
│ 90210 │ crowdsec │ Ip:180.10.143.248 │ crowdsecurity/ssh-bf_user-enum │ ban │ CN │ 4134 Chinanet │ 3 │ 3h6m38s │ 216 │
|
||||
╰───────┴──────────┴───────────────────┴────────────────────────────────┴─────┴────┴────────────────────────┴────────┴────────────┴──────────╯
|
||||
```
|
||||
|
||||
----
|
||||
##### Время блокировок
|
||||
|
||||
Сценарии занимаются распознаванием угроз, но самими блокировками они не занимаются. Блокировки настроены по умолчанию
|
||||
на четыре часа, и это указано в профилях `/etc/crowdsec/profiles.yaml`. Чтобы изменить время, на которое "зловред"
|
||||
отправляется в бан, нужно отредактировать этот файл. По умолчанию он вот такой:
|
||||
```yaml
|
||||
name: default_ip_remediation
|
||||
#debug: true
|
||||
filters:
|
||||
- Alert.Remediation == true && Alert.GetScope() == "Ip"
|
||||
decisions:
|
||||
- type: ban
|
||||
duration: 4h
|
||||
#duration_expr: Sprintf('%dh', (GetDecisionsCount(Alert.GetValue()) + 1) * 4)
|
||||
# notifications:
|
||||
# - slack_default # Set the webhook in /etc/crowdsec/notifications/slack.yaml before enabling this.
|
||||
# - splunk_default # Set the splunk url and token in /etc/crowdsec/notifications/splunk.yaml before enabling this.
|
||||
# - http_default # Set the required http parameters in /etc/crowdsec/notifications/http.yaml before enabling this.
|
||||
# - email_default # Set the required email parameters in /etc/crowdsec/notifications/email.yaml before enabling this.
|
||||
on_success: break
|
||||
---
|
||||
name: default_range_remediation
|
||||
#debug: true
|
||||
filters:
|
||||
- Alert.Remediation == true && Alert.GetScope() == "Range"
|
||||
decisions:
|
||||
- type: ban
|
||||
duration: 4h
|
||||
#duration_expr: Sprintf('%dh', (GetDecisionsCount(Alert.GetValue()) + 1) * 4)
|
||||
# notifications:
|
||||
# - slack_default # Set the webhook in /etc/crowdsec/notifications/slack.yaml before enabling this.
|
||||
# - splunk_default # Set the splunk url and token in /etc/crowdsec/notifications/splunk.yaml before enabling this.
|
||||
# - http_default # Set the required http parameters in /etc/crowdsec/notifications/http.yaml before enabling this.
|
||||
# - email_default # Set the required email parameters in /etc/crowdsec/notifications/email.yaml before enabling this.
|
||||
on_success: break
|
||||
```
|
||||
|
||||
Как видим, по умолчанию блокировка на 4 часа. Чтобы изменить время блокировок, нужно отредактировать `duration: 4h` на
|
||||
нужное. Но в конфигурации есть "заготовка" для динамического времени блокировок:
|
||||
`duration_expr: Sprintf('%dh', (GetDecisionsCount(Alert.GetValue()) + 1) * 4)` -- каждый раз, когда зловред
|
||||
попадает в бан, время блокировки увеличивается на 4 часа. То есть, если зловред попался в бан 5 раз, то его блокировка
|
||||
будет 20 часов. И так далее (формулу, при желании, можно изменить). Это то, что нам нужно. Имейте в виду, что
|
||||
подключение `duration_expr` исключает возможность указать `duration` (время блокировки) в секции `decisions`. Таким
|
||||
образом получаем вот такой конфиг:
|
||||
```yaml
|
||||
name: default_ip_remediation
|
||||
#debug: true
|
||||
filters:
|
||||
- Alert.Remediation == true && Alert.GetScope() == "Ip"
|
||||
decisions:
|
||||
- type: ban
|
||||
# duration: 4h
|
||||
duration_expr: Sprintf('%dh', (GetDecisionsCount(Alert.GetValue()) + 1) * 4)
|
||||
on_success: break
|
||||
---
|
||||
name: default_range_remediation
|
||||
#debug: true
|
||||
filters:
|
||||
- Alert.Remediation == true && Alert.GetScope() == "Range"
|
||||
decisions:
|
||||
- type: ban
|
||||
duration: 5h
|
||||
on_success: break
|
||||
```
|
||||
|
||||
Можно добавлять и свои правила. Например, для более длительных блокировок медленных брутфорсов, добавим в конце:
|
||||
```yaml
|
||||
---
|
||||
name: ssh_slow_bf_remediation
|
||||
filters:
|
||||
- Alert.Remediation == true && Alert.Scenario == "crowdsecurity/ssh-slow-bf"
|
||||
decisions:
|
||||
- type: ban
|
||||
duration: 10h
|
||||
on_success: break
|
||||
```
|
||||
|
||||
После сохранения конфига, перезапустим CrowdSec:
|
||||
```shell
|
||||
sudo systemctl restart crowdsec
|
||||
```
|
||||
|
||||
И убедимся, что время блокировки увеличилось:
|
||||
```shell
|
||||
sudo cscli decisions list
|
||||
```
|
||||
```text
|
||||
╭────────┬──────────┬───────────────────┬──────────────────────┬────────┬─────────┬──────────────────────┬────────┬────────────┬──────────╮
|
||||
│ ID │ Source │ Scope:Value │ Reason │ Action │ Country │ AS │ Events │ expiration │ Alert ID │
|
||||
├────────┼──────────┼───────────────────┼──────────────────────┼────────┼─────────┼──────────────────────┼────────┼────────────┼──────────┤
|
||||
│ 165247 │ crowdsec │ Ip:165.246.104.64 │ crowdsecurity/ssh-bf │ ban │ KR │ 9317 INHA UNIVERSITY │ 3 │ 91h25m24s │ 258 │
|
||||
╰────────┴──────────┴───────────────────┴──────────────────────┴────────┴─────────┴──────────────────────┴────────┴────────────┴──────────╯
|
||||
```
|
||||
|
||||
----
|
||||
#### Web-панель
|
||||
|
||||
Плюсом CrowdSec является то, что благодаря обмену информацией о блокировках, в личном кабинете на сайте CrowdSec можно
|
||||
посмотреть ваши локальные блокировки через веб-интерфейсе:
|
||||
|
||||

|
||||
|
||||
----
|
||||
#### Управление блокировками
|
||||
|
||||
Можно добавить бан вручную (по умолчанию: `duration:4h` и `type:ban`):
|
||||
```shell
|
||||
sudo cscli decisions add -i xxx.xxx.xxx.xxx
|
||||
sudo cscli decisions add --ip xxx.xxx.xxx.xxx --duration 24h --reason "любопытный безопасник"
|
||||
sudo cscli decisions add --ip xxx.xxx.xxx.xxx --reason "web bruteforce" --type captcha
|
||||
```
|
||||
|
||||
Снять блокировку отдельного IP, подсети (диапазона) или вообще все:
|
||||
```shell
|
||||
sudo cscli decisions delete --ip xxx.xxx.xxx.xxx
|
||||
sudo cscli decisions delete --range yyy.yyy.yyyy.yyy/24
|
||||
sudo cscli decisions delete --all
|
||||
```
|
||||
|
||||
#### Блокировки по GeoIP
|
||||
|
||||
Проверим, что у нас есть парсер на основе GeoIP:
|
||||
```shell
|
||||
sudo cscli parsers lis
|
||||
```
|
||||
|
||||
Увидим в числе прочих:
|
||||
```text
|
||||
PARSERS
|
||||
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
Name 📦 Status Version Local Path
|
||||
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
crowdsecurity/geoip-enrich ✔️ enabled 0.5 /etc/crowdsec/parsers/s02-enrich/geoip-enrich.yaml
|
||||
```
|
||||
|
||||
Он обогащает события (events) GeoIP-информацией: страна, город, ASN, континент и так далее, но сам ничего не блокирует —
|
||||
он просто добавляет поля к событиям. Но это делает возможным создание собственных фильтров и сценариев,
|
||||
завязанных на GeoIP. Используется двухбуквенный код страны (стандарт ISO-3166-1 alpha-2).
|
||||
|
||||
У меня на хосте половина зловредов из Китая и Кореи. За ними, с большим отрывом, Индия и Индонезия. Вот честно,
|
||||
ничего против этих стран не имею, от из этих регионов 80% всех атак на мой SSH. Создаем свой сценарий:
|
||||
```shell
|
||||
sudo nano /etc/crowdsec/scenarios/ban-cn--geoip.yaml
|
||||
```
|
||||
|
||||
И вставим туда:
|
||||
```yaml
|
||||
# /etc/crowdsec/scenarios/ban-cn--geoip.yaml
|
||||
# Бан по GeoIP для Китая и Кореи
|
||||
type: trigger
|
||||
name: local/ban-russian-ips
|
||||
description: "Ban any IP from China & Korea"
|
||||
filter: evt.Meta.geoip_country in ['CN', 'KR'] and evt.Meta.service == 'ssh'
|
||||
groupby: evt.Meta.source_ip
|
||||
labels:
|
||||
country_ban: CN
|
||||
remediation: true
|
||||
classification:
|
||||
- abuse
|
||||
behavior: "geoip:ban"
|
||||
confidence: 5
|
||||
label: "GeoIP Country Ban"
|
||||
manual: true
|
||||
```
|
||||
|
||||
Как видно, это `trigger`-сценарий, он срабатывает при одиночном совпадении, без необходимости "накопить
|
||||
события", как в `leaky`. И бан срабатывает если "зловред" лезет в сервис `ssh`.
|
||||
|
||||
Перезапустим CrowdSec:
|
||||
```shell
|
||||
sudo systemctl restart crowdsec
|
||||
```
|
||||
|
||||
Теперь CrowdSec будет автоматически блокировать все новые IP из указанных стран при появлении их в логах.
|
||||
И если честно, можно вообще все страны забанить, кроме тех, где бываю в отпуске. Нечего им делать на моем сервере. :)
|
||||
|
@@ -75,9 +75,59 @@ sudo service k3s status
|
||||
…
|
||||
```
|
||||
|
||||
Посмотрим сколько нод в кластере:
|
||||
## Перемещение конфигурации kubectl
|
||||
|
||||
При выполнении команд `kubectl` используется файл конфигурации из переменной `KUBECONFIG`. При установке k3s создает
|
||||
его в `/etc/rancher/k3s/k3s.yaml` и у него права `600` (только для root). Чтобы использовать `kubectl` от имени
|
||||
пользователя хоста, нужно скопировать файл конфигурации в домашнюю директорию и изменить права доступа.
|
||||
|
||||
```bash
|
||||
sudo kubectl get nodes
|
||||
mkdir -p ~/.kube/config
|
||||
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
|
||||
```
|
||||
|
||||
Дадим права:
|
||||
```bash
|
||||
sudo chown opi:opi ~/.kube
|
||||
chmod 755 ~/.kube
|
||||
sudo chown opi:opi ~/.kube/config
|
||||
chmod 755 ~/.kube/config
|
||||
sudo chown opi:opi ~/.kube/config/k3s.yaml
|
||||
chmod 755 ~/.kube/config/k3s.yaml
|
||||
```
|
||||
|
||||
Проверим:
|
||||
```bash
|
||||
ls -ld ~/.kube ~/.kube/config ~/.kube/config/k3s.yaml
|
||||
```
|
||||
|
||||
Должны увидеть что-то вроде:
|
||||
```text
|
||||
drwxr-xr-x 4 opi opi 4096 Apr 26 11:32 /home/opi/.kube
|
||||
drwxr-xr-x 2 opi opi 4096 Apr 26 11:09 /home/opi/.kube/config
|
||||
-rwxr-xr-x 1 opi opi 2961 Apr 27 16:31 /home/opi/.kube/config/k3s.yaml
|
||||
```
|
||||
|
||||
Установить переменную KUBECONFIG:
|
||||
```bash
|
||||
export KUBECONFIG=~/.kube/config/k3s.yaml
|
||||
```
|
||||
|
||||
И добавь её в `~/.bashrc` чтобы не вводить каждый раз после входа в систему или перезагрузки:
|
||||
```bash
|
||||
echo 'export KUBECONFIG=~/.kube/config/k3s.yaml' >> ~/.bashrc
|
||||
```
|
||||
|
||||
Проверим, что переменная установлена:
|
||||
```bash
|
||||
echo $KUBECONFIG
|
||||
```
|
||||
|
||||
## Проверим установку k3s
|
||||
|
||||
Посмотрим сколько нод в кластере (теперь можно не использовать `sudo`):
|
||||
```bash
|
||||
kubectl get nodes
|
||||
```
|
||||
|
||||
И, та-да! Увидим одну ноду:
|
||||
@@ -91,7 +141,7 @@ opi5plus-2 Ready control-plane,etcd,master 31m v1.31.5+k3s1
|
||||
|
||||
А что там внутри? Посмотрим на поды:
|
||||
```bash
|
||||
sudo kubectl get pods -A
|
||||
kubectl get pod -A
|
||||
```
|
||||
|
||||
Целых семь подов (минималистичная установка k3s):
|
||||
@@ -106,9 +156,17 @@ kube-system svclb-traefik-4f8c2580-jddgz 2/2 Running 0
|
||||
kube-system traefik-5d45fc8cc9-t5d58 1/1 Running 0 12m
|
||||
```
|
||||
|
||||
Тут статус X/Y в выводе kubectl get pods показывает:
|
||||
* Y — сколько контейнеров должно быть в поде (по спецификации).
|
||||
* X — сколько из них сейчас работает (running).
|
||||
* READY `X/Y` в выводе `kubectl get pod` показывает:
|
||||
* Y — сколько контейнеров должно быть в поде (по спецификации).
|
||||
* X — сколько из них сейчас работает (running).
|
||||
* STATUS показывает состояние пода. Основные статусы которые могут быть:
|
||||
* `Running` — под работает и все контейнеры в нем запущены.
|
||||
* `Completed` — под завершил свою работу (например, Job или CronJob).
|
||||
* `Error` — под завершился с ошибкой.
|
||||
* `ContainerCreating` — под в процессе создания, контейнеры в нем еще не запущены.
|
||||
* `CrashLoopBackOff` — под перезапускается из-за ошибки.
|
||||
* `ImagePullBackOff` — не удалось загрузить образ контейнера.
|
||||
* `Pending` — под ожидает ресурсов (например, памяти или CPU).
|
||||
|
||||
Представлены следующие поды:
|
||||
1. `coredns` — это DNS-сервер для кластера. Он отвечает за разрешение имен внутри Kubernetes (например, чтобы поды
|
||||
@@ -141,9 +199,9 @@ kube-system traefik-5d45fc8cc9-t5d58 1/1 Running 0
|
||||
Структура имени — `<имя-приложения>-<хеш-ревизии>-<случайный-суффикс>`. Впрочем, `<хеш-ревизии>` может отсутствовать,
|
||||
если под не имеет контроллера репликации (например, Job или CronJob).
|
||||
|
||||
Можно проверить, что API нашего узла (кластера) отвечает:
|
||||
Можно проверить, что API нашего узла (кластера) отвечает на порту `6443` (порт по умолчанию):
|
||||
```bash
|
||||
curl -k https://192.168.1.27
|
||||
curl -k https://192.168.1.27:6443
|
||||
```
|
||||
|
||||
Здесь ключ `-k` означает, что мы не проверяем сертификаты (нам важно только, что сервер отвечает). Должны получить
|
||||
@@ -160,8 +218,98 @@ Unauthorized JSON-ответ от API. Что-то вроде:
|
||||
}
|
||||
```
|
||||
|
||||
## Подключение второго узла (мастер)
|
||||
ВАЖНО: Надо проверить версию Traefik, которая установилась. Например версия 3.3.2 отличается от 3.3.6 значением
|
||||
`apiVersion` в которые надо указывать в манифесте:
|
||||
* `traefik.io/v1alpha1` -- для Traefik v3.3.6
|
||||
* `traefik.containo.us/v1alpha1` -- для Traefik v3.3.2
|
||||
|
||||
Проверить версию можно так (подставьте вместо `<хеш-ревизии>-<случайный-суффикс>` свой, из вывода `kubectl get pod`):
|
||||
```bash
|
||||
kubectl get pod -n kube-system traefik-<хеш-ревизии>-<случайный-суффикс> -o jsonpath='{.spec.containers[0].image}'
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
rancher/mirrkubectl exec -n kube-system traefik-<хеш-ревизии>-<случайный-суффикс> -- traefik versionik-67bfb46dcb-prvjd -- traefik version
|
||||
Version: 3.3.2
|
||||
Codename: saintnectaire
|
||||
Go version: go1.23.4
|
||||
Built: 2025-01-14T15:52:27Z
|
||||
OS/Arch: linux/arm64
|
||||
```
|
||||
|
||||
## Установка блочного хранилища (PVC -- Persistent Volume Claim) Longhorn
|
||||
|
||||
Longhorn -- это блочное хранилище k3s, которое позволяет создавать и управлять блочными томами в кластере
|
||||
для обеспечения высокой доступности и отказоустойчивости. Если узел, на котором находится том, выходит из строя,
|
||||
Longhorn автоматически перемещает том на другой узел и контейнер продолжает работу с томом, как будто ничего
|
||||
не произошло (с некоторой задержкой, конечно).
|
||||
|
||||
Если установить Longhorn сразу, то при добавлении новых узлов в кластер Longhorn автоматически будет устанавливаться
|
||||
на них. Но если вы хотите установить Longhorn позже, то нужно будет вручную установить его на новых узлах.
|
||||
|
||||
| **ВАЖНО** |
|
||||
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Longhorn требует наличия iSCSI на узлах. Убедитесь, что молуль iSCSI включен или собран в ядро Linux на вашем хосте. Для Orange Pi 5 Plus (Ubuntu на ядре 6.1.43-rockchip-rk3588) [потребует перекомпиляцию ядра](opi5plus-rebuilding-linux-kernel-for-iscsi.md), на Orange Pi 5 (Ubuntu ядро 5.10.160-rockchip-rk3588) iSCSI разрешен к подключению как модуль. |
|
||||
|
||||
Установим Longhorn на первый узел (мастер):
|
||||
```bash
|
||||
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml
|
||||
```
|
||||
|
||||
Проверим, что все поды Longhorn запустились:
|
||||
```bash
|
||||
kubectl get pod -n longhorn-system
|
||||
```
|
||||
|
||||
Увидим что-то вроде:
|
||||
```text
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
csi-attacher-5d68b48d9-6wsff 1/1 Running 0 9m18s
|
||||
csi-attacher-5d68b48d9-t9wht 1/1 Running 0 9m19s
|
||||
csi-attacher-5d68b48d9-w772m 1/1 Running 0 9m18s
|
||||
csi-provisioner-6fcc6478db-bd26m 1/1 Running 0 9m18s
|
||||
csi-provisioner-6fcc6478db-vg9g9 1/1 Running 0 9m18s
|
||||
csi-provisioner-6fcc6478db-xc8ks 1/1 Running 0 9m18s
|
||||
csi-resizer-6c558c9fbc-47pw9 1/1 Running 0 9m18s
|
||||
csi-resizer-6c558c9fbc-n5ndj 1/1 Running 0 9m18s
|
||||
csi-resizer-6c558c9fbc-xjflz 1/1 Running 0 9m18s
|
||||
csi-snapshotter-874b9f887-2cbn8 1/1 Running 0 9m18s
|
||||
csi-snapshotter-874b9f887-5x9fd 1/1 Running 0 9m18s
|
||||
csi-snapshotter-874b9f887-79dgn 1/1 Running 0 9m18s
|
||||
engine-image-ei-b907910b-2jgjr 1/1 Running 0 10m
|
||||
instance-manager-fba73d00a7ff718b4ddabef450cfe759 1/1 Running 0 9m34s
|
||||
longhorn-csi-plugin-h92s8 3/3 Running 0 9m18s
|
||||
longhorn-driver-deployer-5f44b4dc59-z7tlc 1/1 Running 0 9m34s
|
||||
longhorn-manager-k2gtm 2/2 Running 0 10m
|
||||
longhorn-ui-f7ff9c74-7bbsw 1/1 Running 0 10m
|
||||
longhorn-ui-f7ff9c74-b4svq 1/1 Running 0 10m
|
||||
```
|
||||
|
||||
Что это за поды? Longhorn состоит из нескольких компонентов, каждый из которых отвечает за свою задачу:
|
||||
* CSI-компоненты (attacher, provisioner, resizer, snapshotter) интегрируют Longhorn с Kubernetes для работы
|
||||
с Persistent Volumes (PV) и Persistent Volume Claims (PVC).
|
||||
* `csi-attacher` -- (3 пода) для присоединения (attaches) тома Longhorn к подам Kubernetes, когда PVC монтируется.
|
||||
Три реплики CSI -- для отказоустойчивости. На одном узле достаточно одной, но Longhorn по умолчанию разворачивает 3.
|
||||
* `csi-provisioner` -- (3 пода) для сздания новыех PV (томов) при запросе PVC. Отвечает за динамическое
|
||||
выделение хранилища.
|
||||
* `csi-resizer` -- (3 пода) позволяет изменять размер томов (можно только увеличивать PVC) без остановки приложений.
|
||||
* `csi-snapshotter` -- (3 пода): управля.т созданием и восстановлением снапшотов томов.
|
||||
* `engine-image` -- хранит бинарники движка Longhorn, используемые для работы томов (чтение/запись данных).
|
||||
Один под на узел, содержит образ движка для всех томов на этом узле и запускается на каждом узле.
|
||||
* `instance-manager` -- (1 под) Управляет движками и репликами томов на узле (например, запускает процессы для
|
||||
чтения/записи данных). Один под на узел для локального управления томами.
|
||||
* `longhorn-csi-plugin` -- (1 под, 3 контейнера) интерфейс между Kubernetes CSI и Longhorn. Обрабатывает
|
||||
монтирование/управление томами на узле.
|
||||
* `longhorn-driver-deployer` -- (1 под) устанавливает CSI-драйвер и регистрирует его в Kubernetes.
|
||||
Нужен для инициализации CSI-интеграции и он обычно один в кластере.
|
||||
* `longhorn-manager` -- (1 под, 2 контейнера) Основной компонент Longhorn. Управляет узлами, томами, репликами,
|
||||
снапшотами и синхронизацией данных. Один под на узел, 2 контейнера (основной + kube-rbac-proxy для авторизации).
|
||||
* `longhorn-ui` -- (2 реплики) Предоставляют веб-интерфейс для управления Longhorn (графики, настройка томов,
|
||||
* мониторинг). Две реплики для отказоустойчивости, хотя на одном узле это избыточно.
|
||||
|
||||
|
||||
## Подключение второго узла (мастер)
|
||||
|
||||
Для начала, на первой ноде получим токен для подключения нового узла к кластеру:
|
||||
```bash
|
||||
@@ -276,10 +424,9 @@ metrics-server ClusterIP 10.43.248.208 <none>
|
||||
traefik LoadBalancer 10.43.164.48 192.168.1.26,192.168.1.27,192.168.1.28 80:31941/TCP,443:30329/TCP,9000:32185/TCP 3d
|
||||
```
|
||||
|
||||
Можем проверить доступность панели `Traefik` через браузер через IP-адрес нового узла и (в нашем случае `http://192.168.1.26:9000/dashboard/#/`)
|
||||
и увидим, что балаансировщик работает и перенаправляет трафик и с ноды воркера.
|
||||
Можем так же настроить [web-панель Traefik](../kubernetes/k3s-setting-up-web-access-to-dashboard.md).
|
||||
|
||||

|
||||

|
||||
|
||||
Что ж, теперь у нас есть кластер k3s с тремя нодами: двумя мастерами и одним воркером. Но, как я уже говорил, это не
|
||||
идеальная конфигурация, так как у нас четное количество мастер-узлов.
|
||||
@@ -681,39 +828,6 @@ helm repo add traefik https://helm.traefik.io/traefik
|
||||
Обновить репозитории:
|
||||
helm repo update
|
||||
|
||||
mkdir ~/.kube/config
|
||||
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
|
||||
|
||||
sudo chown opi:opi ~/.kube
|
||||
chmod 755 ~/.kube
|
||||
sudo chown opi:opi ~/.kube/config
|
||||
chmod 755 ~/.kube/config
|
||||
sudo chown opi:opi ~/.kube/config/k3s.yaml
|
||||
chmod 755 ~/.kube/config/k3s.yaml
|
||||
|
||||
или
|
||||
chmod 755 ~/.kube ~/.kube/config
|
||||
chmod 755 ~/.kube ~/.kube
|
||||
chmod 600 ~/.kube/config/k3s.yaml
|
||||
|
||||
ls -ld ~/.kube ~/.kube/config ~/.kube/config/k3s.yaml
|
||||
|
||||
```text
|
||||
drwxr-xr-x 4 opi opi 4096 Apr 26 11:32 /home/opi/.kube
|
||||
drwxr-xr-x 2 opi opi 4096 Apr 26 11:09 /home/opi/.kube/config
|
||||
-rw------- 1 opi opi 2961 Apr 26 11:09 /home/opi/.kube/config/k3s.yaml
|
||||
```
|
||||
|
||||
Установить переменную KUBECONFIG:
|
||||
```bash
|
||||
export KUBECONFIG=~/.kube/config/k3s.yaml
|
||||
```
|
||||
|
||||
И добавь её в `~/.bashrc` для постоянства:
|
||||
```bash
|
||||
echo 'export KUBECONFIG=~/.kube/config/k3s.yaml' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
|
||||
Установить Traefik:
|
||||
|