Compare commits
87 Commits
0a6c1591ab
...
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 | |||
bf15c4fb7b | |||
effe549f75 | |||
205e8b9c53 | |||
c18a5ffe43 | |||
9e1a414f45 | |||
138bb728fc | |||
dc04658e70 | |||
5560ed2d8e | |||
3b64bc44ac | |||
0b4bc455bf | |||
e2c18fd572 | |||
3bc414ca98 | |||
b6b54e136c | |||
f50b509440 | |||
3a71c76f89 | |||
20fa88fa19 | |||
c54ab2ebc1 | |||
f3a296f9e4 | |||
09d32c8cb1 | |||
3a1ce1370b | |||
558653d7e9 | |||
433a3b102f | |||
1571e7118d | |||
ed9159335e | |||
c7a5241d33 | |||
0a90d4a444 | |||
81fbf2ffa6 | |||
a49d432419 | |||
84a6ba4c92 | |||
b470dcdd5d | |||
86c51d33ce | |||
cbf3ae56bb | |||
7b0853da9c |
17
README.md
@@ -11,21 +11,32 @@
|
|||||||
|
|
||||||
## Kubernetes (k3s/k8s)
|
## Kubernetes (k3s/k8s)
|
||||||
* [Установка k3s на Orange Pi 5 Plus](raspberry-and-orange-pi/k3s.md)
|
* [Установка k3s на Orange Pi 5 Plus](raspberry-and-orange-pi/k3s.md)
|
||||||
* [Под с Shadowsocks-клиент](kubernetes/k3s-shadowsocks-client.md) (k3s)
|
* [Под с Shadowsocks-клиент](kubernetes/k3s-shadowsocks-client.md) в k3s
|
||||||
|
* [Подключение менеджера сертификатов (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) (на примере 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
|
||||||
* [Устранение проблем при установке Python-коннектора mysqlclient (MySQL/MariaDB)](python/python-mysql.md)
|
* [Устранение проблем при установке Python-коннектора mysqlclient (MySQL/MariaDB)](python/python-mysql.md)
|
||||||
* [Python-скрипт как служба Linux](python/python_as_service.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)
|
* [Установка (перенос) системы на 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)
|
* [Измерение производительности накопителей](raspberry-and-orange-pi/measuring-performance-storage-devices.md)
|
||||||
* [Установка Docker и Docker Compose](raspberry-and-orange-pi/install-docker-compose.md)
|
* [Установка Docker и Docker Compose](raspberry-and-orange-pi/install-docker-compose.md)
|
||||||
* [Резервное копирование и восстановление](raspberry-and-orange-pi/backup-restore.md)
|
* [Резервное копирование и восстановление](raspberry-and-orange-pi/backup-restore.md)
|
||||||
* [k8s (кubernetes) на Orange Pi (драфт...)](raspberry-and-orange-pi/k8s.md)
|
* [k8s (кubernetes) на Orange Pi (драфт...)](raspberry-and-orange-pi/k8s.md)
|
||||||
* [k3s (кubernetes) на Orange Pi](raspberry-and-orange-pi/k3s.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
|
## Nginx / Apache
|
||||||
* [Ограничение доступа по User-Agent (на примере GPTBot)](nginx/nginx-ban-user-agent.md)
|
* [Ограничение доступа по User-Agent (на примере GPTBot)](nginx/nginx-ban-user-agent.md)
|
||||||
* [Настройка nginx как прямого прокси](nginx/nginx_as_direct_proxy.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--3x-ui-welcome.png
Normal file
After Width: | Height: | Size: 58 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 |
687
kubernetes/k3s-3xui-pod.md
Normal file
@@ -0,0 +1,687 @@
|
|||||||
|
# Установка 3X-UI как под в K3s (Kubernetes)
|
||||||
|
|
||||||
|
3x-ui — это симпатичный веб-интерфейс для управления VPN-серверами, такими как WireGuard, Shadowsocks, Xray, V2Ray
|
||||||
|
и тому подобное. Он позволяет настраивать и мониторить VPN-соединения и клиентов через браузер. Мы будем запускать
|
||||||
|
его как контейнер (под) внутри K3s кластера на Orange Pi 5.
|
||||||
|
|
||||||
|
Мне нужен 3x-ui, для безопасного доступа к домашней сети из любой точки мира, а также для безопасного доступа
|
||||||
|
к интернету через домашний сервер.
|
||||||
|
|
||||||
|
### Создание namespace (не обязательно)
|
||||||
|
|
||||||
|
Для удобства организации рекомендую создать отдельное пространство имён (`namespace`) для 3x-ui. Пространство имен --
|
||||||
|
это способ организовать ресурсы в кластере. Оно работает как виртуальная "папка", которая помогает разделять
|
||||||
|
(изолировать) и управлять объектами, такими как поды, сервисы, конфигурации и т.д. Объекты в одном _namespace_ не видят
|
||||||
|
объекты из другого namespace (если не настроено обратное), что помогает избежать путаницы. Несколько приложений
|
||||||
|
с одинаковыми именами могут без проблем существовать в разных пространствах имен. Кроме того, можно настроить
|
||||||
|
права доступа (RBAC) отдельно для каждого namespace.
|
||||||
|
|
||||||
|
Выполним в терминале:
|
||||||
|
```bash
|
||||||
|
sudo kubectl create namespace x-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверим, что пространство имён создано:
|
||||||
|
```bash
|
||||||
|
kubectl get namespaces
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим x-ui в списке:
|
||||||
|
```text
|
||||||
|
NAME STATUS AGE
|
||||||
|
... ... ...
|
||||||
|
... ... ...
|
||||||
|
x-ui Active 6s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Простое развёртывание 3X-UI в поде
|
||||||
|
|
||||||
|
Cоздадим манифест развертывания пода (этого YAML-файл с инструкциями для K3s, что и как запустить). Мы будем
|
||||||
|
использовать SQLite как внутреннюю базу данных 3x-ui, и пока эта бызы будет храниться внутри пода. Позже сможем
|
||||||
|
переключиться на `Longhorn` (опционально).
|
||||||
|
|
||||||
|
Создадим `deployment.yaml` в каталоге `~/k3s/vpn/x-ui/` (см. [структуру каталогов для хранения конфигураций и манифестов](k3s-shadowsocks-client.md)
|
||||||
|
принятую в моем проекте):
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/k3s/vpn/x-ui
|
||||||
|
nano ~/k3s/vpn/x-ui/deployment.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Вставим в него следующий код:
|
||||||
|
```yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: x-ui # имя развертывания (имя пода)
|
||||||
|
namespace: x-ui # пространство имён, в котором будет создан под
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: x-ui
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: x-ui
|
||||||
|
spec:
|
||||||
|
hostNetwork: true # использовать сетевой стек хоста
|
||||||
|
containers:
|
||||||
|
- name: x-ui # имя контейнера
|
||||||
|
image: ghcr.io/mhsanaei/3x-ui:latest
|
||||||
|
# image: enwaiax/x-ui:latest # альтернативный облегчённый: меньше способов шифрования и китайский интерфейс
|
||||||
|
```
|
||||||
|
В этом манифесте примечательно следующее:
|
||||||
|
- `hostNetwork: true` — позволяет контейнеру использовать сетевой стек хоста и значит работать
|
||||||
|
с сетевыми интерфейсами и портами хоста напрямую. Это полезно для приложений, которые требуют прямого доступа
|
||||||
|
к сети, например, VPN-серверы.
|
||||||
|
- `spec.replicas: 1` — количество реплик (экземпляров) пода, которые будут запущены. В данном случае -- оин под.
|
||||||
|
- `spec.selector` — селектор, который используется для выбора подов, которые будут управляться этим
|
||||||
|
развертыванием. Он определяет, какие поды будут обновлены или удалены при изменении конфигурации развертывания.
|
||||||
|
- `matchLabels` — метки, которые должны совпадать с метками подов, чтобы они были выбраны селектором.
|
||||||
|
В данном случае мы используем метку `app: x-ui`, чтобы выбрать поды, которые относятся к приложению x-ui.
|
||||||
|
|
||||||
|
|
||||||
|
Применим манифест:
|
||||||
|
```bash
|
||||||
|
sudo kubectl apply -f ~/k3s/vpn/x-ui/deployment.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверим, что под запустился:
|
||||||
|
```bash
|
||||||
|
sudo k3s kubectl get pods -n x-ui -o wide
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим что-то вроде:
|
||||||
|
```text
|
||||||
|
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
|
||||||
|
x-ui-bb97f6894-h7zj8 1/1 Running 0 11s 10.42.1.50 opi5plus-3 <none> <none>
|
||||||
|
|
||||||
|
Видим, что нода на которой запустился 3x-ui это `opi5plus-3`, а имя пода `x-ui-bb97f6894-h7zj8`. Проверим логи пода,
|
||||||
|
используя его имя:
|
||||||
|
```bash
|
||||||
|
sudo kubectl logs -n x-ui x-ui-bb97f6894-h7zj8
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим что-то вроде:
|
||||||
|
```text
|
||||||
|
Server ready
|
||||||
|
(0x291e4e8,0x40001657b0)
|
||||||
|
2025/03/28 13:28:34 Starting x-ui 2.5.6
|
||||||
|
(0x291e4e8,0x40001658e0)
|
||||||
|
INFO - Web server running HTTP on [::]:2053
|
||||||
|
INFO - XRAY: infra/conf/serial: Reading config: &{Name:bin/config.json Format:json}
|
||||||
|
WARNING - XRAY: core: Xray 25.3.6 started
|
||||||
|
```
|
||||||
|
|
||||||
|
Теперь мы знаем порт, на котором работает 3x-ui (`2053`), и значит можем получить доступ к веб-интерфейсу через браузер
|
||||||
|
по адресу `http://opi5plus-3:2053` или `http://<IP_адрес_вашего_узла>:2053`.
|
||||||
|
|
||||||
|
<img src="../images/k3s--3x-ui-welcome.png" alt="3x-ui welcome page (RU)" width="70%" />
|
||||||
|
|
||||||
|
После первого логирования (по умолчанию логин и пароль `admin`/`admin`) можно настаивать VPN-подключения, создавать
|
||||||
|
пользователей, менять логин и пароль на вход и т.д. Веб-интерфейс 3x-ui интуитивно понятен, так что разбираться
|
||||||
|
не составит труда.
|
||||||
|
|
||||||
|
## Развертывание Kubernetes пода 3x-ui с постоянным хранилищем (PVC)
|
||||||
|
|
||||||
|
Есть, конечно, у 3x-ui под k3s минусы. В частности, внутри пода (`sudo kubectl exec -it -n x-ui x-ui-... -- /bin/sh`)
|
||||||
|
не будет работать командный интерфейс 3x-ui (`x-ui admin`). Поды k3s работают на **Alpine**, а там некоторые команды
|
||||||
|
отличаются (например, нет `bash`, а только `ash`). Но web-панель работает как положено, и всё управление удобнее
|
||||||
|
делать через веб-интерфейс, так что лезть в консоль подов не обязательно.
|
||||||
|
|
||||||
|
Но есть ещё другой минус, более критичный. При рестарте пода, все настройки будут сброшены, так как они хранятся
|
||||||
|
во внутреннем хранилище пода, а при остановке пода хранилище удаляется.
|
||||||
|
|
||||||
|
Чтобы этого избежать нужно использовать постоянное хранилище (Persistent Volume). Для его работы требуется установить
|
||||||
|
`Longhorn` (или другой менеджер хранилищ). K3s поддерживает `Longhorn` из коробки, так как в операционной системе на
|
||||||
|
Orange Pi 5 нет поддержки `iSCSI`, включение его потребует компиляции ядра (если вы этого еще не сделали, [смотрите
|
||||||
|
инструкцию](../raspberry-and-orange-pi/opi5plus-rebuilding-linux-kernel-for-iscsi.md).
|
||||||
|
|
||||||
|
Если `Longhorn` уже установлен, создадим не его базе постоянное хранилище для -- _PersistentVolumeClaim_ (**PVC**).
|
||||||
|
Манифест PVC создадим в каталоге `~/k3s/vpn/x-ui/`, рядом с `deployment.yaml`:
|
||||||
|
```bash
|
||||||
|
nano ~/k3s/vpn/x-ui/pvc-db.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Вставим в него следующий код:
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: x-ui-db-pvc
|
||||||
|
namespace: x-ui
|
||||||
|
spec:
|
||||||
|
storageClassName: longhorn # Указываем Longhorn как класс хранилища
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce # Доступ для чтения и записи одним подом
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 512Mi # Запрашиваемое хранилище, размер можно увеличить, если нужно
|
||||||
|
```
|
||||||
|
|
||||||
|
Обратите внимание:
|
||||||
|
- `metadata.name` и `metadata.namespace` — имя хранилища (и это имя мы должны использовать в манифесте
|
||||||
|
развертывания пода, чтобы указать, какое хранилище использовать) и пространство имён, в котором оно будет создано.
|
||||||
|
- `spec.storageClassName` — класс хранилища, который будет использоваться для создания постоянного хранилища.
|
||||||
|
В данном случае -- `longhorn`.
|
||||||
|
- `spec.accessModes` — режим доступа к хранилищу. `ReadWriteOnce` означает, что хранилище может быть смонтировано
|
||||||
|
только одним подом для чтения и записи. У нас один под и база на SQLite, так что этого достаточно.
|
||||||
|
- `spec.resources.requests.storage` — запрашиваемый размер хранилища. Мы запрашиваем 1 ГБ и не означает, что
|
||||||
|
хранилище будет занимать 1 ГБ на диске. Это предельный размер, который сможет занять хранилище.
|
||||||
|
|
||||||
|
Применим pvc-манифест:
|
||||||
|
```bash
|
||||||
|
sudo kubectl apply -f ~/k3s/vpn/x-ui/pvc-db.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
После этого Longhorn создаст том, который будет привязан к этому PVC.
|
||||||
|
|
||||||
|
Теперь нам нужно изменить манифест развертывания пода, и подключить к нему созданный PVC. Теперь наш
|
||||||
|
`~/k3s/vpn/x-ui/deployment.yaml` будет выглядеть так:
|
||||||
|
```yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: x-ui
|
||||||
|
namespace: x-ui
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: x-ui
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: x-ui
|
||||||
|
spec:
|
||||||
|
hostNetwork: true
|
||||||
|
containers:
|
||||||
|
- name: x-ui
|
||||||
|
image: ghcr.io/mhsanaei/3x-ui:latest
|
||||||
|
# image: enwaiax/x-ui:latest # альтернативный облегчённый: меньше способов шифрования и китайский интерфейс
|
||||||
|
volumeMounts:
|
||||||
|
- name: db-storage # Имя тома, в который будет смонтирован...
|
||||||
|
mountPath: /etc/x-ui # ...в путь к базе данных внутри контейнера
|
||||||
|
volumes:
|
||||||
|
- name: db-storage # Имя тома, которое...
|
||||||
|
persistentVolumeClaim: # ...должно быть постоянным хранилищем
|
||||||
|
claimName: x-ui-db-pvc # ...и размещаться в PVC с именем 'x-ui-db-pvc'
|
||||||
|
```
|
||||||
|
|
||||||
|
Применим обновлённый манифест:
|
||||||
|
```bash
|
||||||
|
sudo kubectl apply -f ~/k3s/vpn/x-ui/deployment.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Под перезапустится, и теперь база данных будет храниться в постоянном хранилище Longhorn. При перезапуске пода или его
|
||||||
|
"переезде" на другой узел, база данных останется доступной и не потеряется. Следует отметить, что при сбое узла
|
||||||
|
процесс перемещения пода занимает некоторое время. В кластере на Orange Pi 5, где проверки связности не очень
|
||||||
|
агрессивные, это может занять до 5 минут. В общем, это нормально.
|
||||||
|
|
||||||
|
## Единая точка входа VPN-соединений через под 3x-ui
|
||||||
|
|
||||||
|
Под с 3x-ui может быть запущен k3s на произвольном узле, и может быть произвольно перемещён в кластере на другой узел.
|
||||||
|
Таким образом, если мы хотим предоставить доступ к VPN-соединениям из интернета, нам нужно настроить доступ через
|
||||||
|
единый IP-адрес. Это можно сделать несколькими способами.
|
||||||
|
|
||||||
|
### Доступ через VIP (виртуальный IP) c перенаправлял трафика через Keepalived на узел с подом с 3x-ui
|
||||||
|
|
||||||
|
При [развертывании k3s](../raspberry-and-orange-pi/k3s.md) на Orange Pi 5 Plus мы уже настраивали Keepalived. Теперь
|
||||||
|
надо настроить его так, чтобы узел с подом 3x-ui получал больший приоритет в Keepalived, и тогда виртуальный IP
|
||||||
|
будет получать трафик с этого узла.
|
||||||
|
|
||||||
|
**Лучшим решением будет динамическая настройка приоритета в Keepalived.**
|
||||||
|
|
||||||
|
Лучший способ — настроить так, чтобы приоритет Keepalived ноды автоматически повышался, если под 3x-ui запущен на ней.
|
||||||
|
Это можно сделать с помощью механизма `track_script`, который будет проверять наличие пода и динамически менять
|
||||||
|
приоритет. Такой подход сохранит текущую работу K3s API и [подов Shadowsocks](k3s-shadowsocks-client.md), добавив
|
||||||
|
поддержку 3x-ui.
|
||||||
|
|
||||||
|
Создадим проверочный скрипт (на каждом узле), который будет проверять наличие пода 3x-ui. Скрипт будет
|
||||||
|
расположен в `~/scripts/check_xui.sh`:
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/scripts
|
||||||
|
nano ~/scripts/check_xui.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
И вставим в него следующий код (на каждой ноде):
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/bash
|
||||||
|
NODE_NAME=$(hostname) # Получаем имя текущей ноды
|
||||||
|
POD_NAME=$(kubectl get pods -n x-ui -o jsonpath="{.items[?(@.spec.nodeName=='$NODE_NAME')].metadata.name}")
|
||||||
|
if [ -n "$POD_NAME" ]; then
|
||||||
|
exit 0 # Под есть на этой ноде
|
||||||
|
else
|
||||||
|
exit 1 # Пода нет
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт использует `kubectl`, чтобы проверить, есть ли под `3x-ui` в _namespace_ `x-ui` на текущей ноде. Использование
|
||||||
|
`sudo` не требуется, так как скрипт будет запускаться `keepalived`, который работает от `root`.
|
||||||
|
Убедись, что kubectl доступен на всех нодах и настроен для работы с кластером (например, через kubeconfig).
|
||||||
|
|
||||||
|
Сделаем скрипт исполняемым (на каждой ноде):
|
||||||
|
```bash
|
||||||
|
sudo chmod +x ~/scripts/check_xui.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Обновим конфиг `reepalived`, добавив `vrrp_script` и привязку к нему через `track_script`. Теперь мы переведем все
|
||||||
|
ноды в **BACKUP** (чтобы избежать конфликтов), а приоритет будет динамически меняться в зависимости от наличия пода.
|
||||||
|
|
||||||
|
На перовой мастер-ноде:
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/keepalived/keepalived.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
И теперь там будет вот такой конфиг (не забудь указать правильное имя пользователя `<user>` в пути к скрипту):
|
||||||
|
```pycon
|
||||||
|
vrrp_script check_xui {
|
||||||
|
script "/home/<user>/scripts/check_xui.sh"
|
||||||
|
interval 2 # Проверять каждые 2 секунды
|
||||||
|
weight 50 # Добавить 50 к приоритету, если под есть
|
||||||
|
}
|
||||||
|
|
||||||
|
vrrp_instance VI_1 {
|
||||||
|
# state MASTER
|
||||||
|
state BACKUP # Все ноды стартуют как BACKUP
|
||||||
|
interface enP4p65s0
|
||||||
|
virtual_router_id 51
|
||||||
|
priority 100 # Базовый приоритет
|
||||||
|
advert_int 1
|
||||||
|
unicast_src_ip 192.168.1.26
|
||||||
|
unicast_peer {
|
||||||
|
192.168.1.27
|
||||||
|
192.168.1.28
|
||||||
|
}
|
||||||
|
virtual_ipaddress {
|
||||||
|
192.168.1.200
|
||||||
|
}
|
||||||
|
track_script {
|
||||||
|
check_xui # Привязка к скрипту
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Перезапустим Keepalived:
|
||||||
|
```bash
|
||||||
|
sudo service keepalived restart
|
||||||
|
```
|
||||||
|
|
||||||
|
Аналогичным образом настроим конфиги на других узлах (добавить блок `vrrp_script` сверху, и добавить `track_script` в
|
||||||
|
`vrrp_instance`). Не забудь указать проверить `unicast_src_ip` для каждой ноды и перезапустить Keepalived на всех узлах.
|
||||||
|
|
||||||
|
Теперь на каждой ноде cкрипт `~/scripts/check_xui.sh` проверяет наличие пода `x-ui` каждые 2 секунды. Если под есть,
|
||||||
|
Keepalived добавляет 50 к базовому приоритету ноды (например, 100 → 150). Если пода нет, приоритет остаётся базовым
|
||||||
|
(100, 90 или 80). Нода с наивысшим приоритетом становится MASTER и получает виртуальный IP. Таким образом, VIP всегда
|
||||||
|
будет указывать на ноду с подом 3x-ui.
|
||||||
|
|
||||||
|
Теперь панель 3x-ui будет доступна с виртуального IP (192.168.1.200). Все VPN-соединения будут работать через него.
|
||||||
|
Так что если на домашнем роутере настроить перенаправление портов (для 2053-порта веб-панели 3x-ui, и портов которые
|
||||||
|
будем выбирать для VPN-соединений), то можно будет подключаться к 3x-ui и VPN-соединениям из любой точки мира.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Доступ через Ingress Controller по имени домена (http).
|
||||||
|
|
||||||
|
Сейчас web-панель 3x-ui доступна через VIP по порту `2053` по http. _В принципе, так можно и оставить_. Но если мы хотим
|
||||||
|
иметь доступ по https, да еще чтобы это работало через доменное имя, и чтобы k3s автоматически получал и обновлял
|
||||||
|
сертификаты, то можно использовать Ingress-контроллер. Он будет брать трафик с порта VIP, по порту `2055`, через
|
||||||
|
балансировщик svclb-traefik направлять его на Ingress-контроллер Traefik и перенаправлять его на под с 3x-ui (тоже
|
||||||
|
через VIP но уже по порту `2053`). Дополнительно, есть [заметка про настройку Traefik в качестве прокси](k3s-proxy.md).
|
||||||
|
|
||||||
|
#### Манифест для Ingress-контроллера Traefik
|
||||||
|
|
||||||
|
По умолчанию Ingress-контроллер Traefik в k3s слушает на портах 80 и 443 (HTTP и HTTPS) и перенаправляет трафик
|
||||||
|
на соответствующие поды. В моем случае порты 80 и 443 на моем роутере уже перенаправляются на другой хост.
|
||||||
|
В будущем я это, возможно, изменю, и сейчас я не могу перенаправить эти порты на VIP. Поэтому мне нужно настроить
|
||||||
|
Traefik так, чтобы он слушал http/https на другом порту (например, 2055, и порт, обратите внимание, стандартный
|
||||||
|
443-порт от продолжит слушать как и раньше) и перенаправлял трафик на под с 3x-ui (это только для http/https, то есть
|
||||||
|
для доступа в веб-интерфейсу 3x-ui, а не для VPN-соединений). Этот манифест задаёт глобальную конфигурацию Traefik
|
||||||
|
для всего кластера, а не только к 3x-ui, и потому лучше положить его в "общую" папку для Traefik, например:
|
||||||
|
`~/k3s/traefik/traefik-config.yaml`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/k3s/traefik
|
||||||
|
nano ~/k3s/traefik/traefik-config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
И вставим в него следующий код:
|
||||||
|
```yaml
|
||||||
|
apiVersion: helm.cattle.io/v1
|
||||||
|
kind: HelmChartConfig
|
||||||
|
metadata:
|
||||||
|
name: traefik
|
||||||
|
namespace: kube-system
|
||||||
|
spec:
|
||||||
|
valuesContent: |-
|
||||||
|
additionalArguments:
|
||||||
|
- --entrypoints.web-custom.address=:2055 # Слушаем HTTP на 2055
|
||||||
|
- --log.level=DEBUG
|
||||||
|
```
|
||||||
|
Что тут происходит: Для изменения настройки Traefik, создаётся HelmChartConfig (этот такой аналог пакетного менеджера
|
||||||
|
для Kubernetes, который позволяет управлять приложениями и сервисами в кластере). Этот манифест указывает Traefik,
|
||||||
|
в пространство имён `kube-system`, а аргумент `--entrypoints.web-custom.address=:2055` в конфигурацию -- инструкция:
|
||||||
|
_Слушай порт 2055 и назови эту точку входа **web-custom**_). После применения Traefik начнёт принимать запросы на порту
|
||||||
|
2055. Поскольку мой роутер пробрасывает 2055 на VIP-адрес (тоже 2055 порт), Traefik на ноде с VIP увидит этот трафик.
|
||||||
|
|
||||||
|
Применим манифест:
|
||||||
|
```bash
|
||||||
|
sudo kubectl apply -f ~/k3s/traefik/traefik-config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Теперь Traefik будет слушать http еще и на порту 2055.
|
||||||
|
|
||||||
|
#### Манифест для маршрутизации трафика на под с 3x-ui через Ingress-контроллер
|
||||||
|
|
||||||
|
Теперь нужно сказать Traefik, что запросы на домен `v.home.cube2.ru` через порт `2055` — это HTTP, и их надо
|
||||||
|
перенаправить на порт 2053, где работает 3x-ui. Для этого в каталоге с манифестами 3x-ui `~/k3s/vpn/x-ui/`
|
||||||
|
(ведь это касается подa с 3x-ui) создадим манифест IngressRoute:
|
||||||
|
```bash
|
||||||
|
nano ~/k3s/vpn/x-ui/ingressroute.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
И вставим в него следующий код (не забудь указать свой домен):
|
||||||
|
```yaml
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: x-ui-ingress
|
||||||
|
namespace: x-ui
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- web-custom # ендпоинт, который "слушает" порт 2055
|
||||||
|
routes:
|
||||||
|
- match: Host("v.home.cube2.ru")
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: x-ui-external # имя сервиса, на который будет перенаправлен трафик
|
||||||
|
port: 2053 # порт, на который будет перенаправлен трафик
|
||||||
|
```
|
||||||
|
|
||||||
|
Что тут происходит? Мы создаём объект `IngressRoute`, который определяет маршрут для входящего трафика. Параметры:
|
||||||
|
- `kind` — тип объекта, который мы создаём. В данном случае это `IngressRoute`, который используется для
|
||||||
|
маршрутизации трафика в Traefik.
|
||||||
|
- `metadata` — метаданные объекта, такие как имя и пространство имён. Мы указываем имя `x-ui-ingress` и
|
||||||
|
пространство имён `x-ui`, в котором будет создан объект (то же пространство, что и у пода с 3x-ui).
|
||||||
|
- `entryPoints` — точка входа, которая будет использоваться для маршрутизации трафика. В данном случае это `web-custom`,
|
||||||
|
который мы настроили в предыдущем шаге.
|
||||||
|
- `routes` — определяет правила маршрутизации. В данном случае мы указываем, что если запрос приходит на домен
|
||||||
|
`v.home.cube2.ru` (`match` — условие, которое должно быть выполнено для маршрутизации), то он будет перенаправлен
|
||||||
|
на сервис `x-ui-external` (который мы создадим ниже) на порт `2053`.
|
||||||
|
|
||||||
|
Теперь создадим сервис `x-ui-external`, который будет использоваться для маршрутизации трафика на под с 3x-ui.
|
||||||
|
|
||||||
|
Создадим манифест сервиса в каталоге `~/k3s/vpn/x-ui/`:
|
||||||
|
```bash
|
||||||
|
nano ~/k3s/vpn/x-ui/x-ui-service.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
И вставим в него следующий код:
|
||||||
|
```yaml
|
||||||
|
# Service для 3x-ui с hostNetwork: true, использующего VIP 192.168.1.200
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service # Тип объекта, который мы создаём. В данном случае это Service
|
||||||
|
metadata:
|
||||||
|
name: x-ui-external
|
||||||
|
namespace: x-ui
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 2053
|
||||||
|
targetPort: 2053
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
# Endpoints указывает на VIP, так как под не в сетевом пространстве Kubernetes
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Endpoints # Тип объекта, который мы создаём. В данном случае это Endpoints
|
||||||
|
metadata:
|
||||||
|
name: x-ui-external
|
||||||
|
namespace: x-ui
|
||||||
|
subsets:
|
||||||
|
- addresses:
|
||||||
|
- ip: 192.168.1.200 # IP-адрес (VIP), на который будет перенаправлен трафик
|
||||||
|
ports:
|
||||||
|
- port: 2053
|
||||||
|
protocol: TCP
|
||||||
|
```
|
||||||
|
|
||||||
|
Что тут происходит? Мы создаём два объекта: `Service` и `Endpoints`. `Service` — это абстракция, которая предоставляет
|
||||||
|
единый IP-адрес и DNS-имя для доступа к группе подов. `Endpoints` — это объект, который указывает конечные точки
|
||||||
|
для перенаправления трафика. В нашем случае это VIP:2053, так как под 3x-ui использует `hostNetwork: true`
|
||||||
|
и недоступен через внутренние IP Kubernetes. Но обычно `Endpoints` указывают на имена подов, на которые отправляется
|
||||||
|
трафик.
|
||||||
|
|
||||||
|
Для `Service` мы указываем:
|
||||||
|
- `kind` — тип объекта, который мы создаём. В данном случае это `Service`.
|
||||||
|
- `metadata` — метаданные объекта, такие как имя и пространство имён. Мы указываем имя `x-ui-external` и
|
||||||
|
пространство имён `x-ui`, в котором будет создан объект (то же пространство, что и у пода с 3x-ui).
|
||||||
|
- `spec` — спецификация объекта, которая определяет его поведение. Мы указываем, что сервис будет слушать внешний трафик
|
||||||
|
на порту `2053` и перенаправлять на тот же порт внутри кластера.
|
||||||
|
- `ports` — определяет порты, на которых будет слушать сервис. Мы указываем, что сервис будет слушать
|
||||||
|
на порту `2053` и перенаправлять трафик на тот же порт внутри кластера, и будем использоваться TCP.
|
||||||
|
|
||||||
|
Для `Endpoints` мы указываем:
|
||||||
|
- `kind` — тип объекта, который мы создаём. В данном случае это `Endpoints`.
|
||||||
|
- `metadata` — метаданные объекта, такие как имя и пространство имён. Мы указываем имя `x-ui-external` (то же,
|
||||||
|
что и у сервиса) и пространство имён `x-ui` (то же, что и у пода с 3x-ui).
|
||||||
|
- `subsets` — подмножество конечных точек, которые будут использоваться для маршрутизации трафика. Мы указываем, что
|
||||||
|
в подмножестве есть одна конечная точка с IP 192.168.1.200 и портом 2053 (TCP).
|
||||||
|
|
||||||
|
Применим манифесты:
|
||||||
|
```bash
|
||||||
|
sudo kubectl apply -f ~/k3s/vpn/x-ui/ingressroute.yaml
|
||||||
|
sudo kubectl apply -f ~/k3s/vpn/x-ui/x-ui-service.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Перезагрузим Traefik, чтобы он увидел изменения:
|
||||||
|
```bash
|
||||||
|
kubectl rollout restart deployment traefik -n kube-system
|
||||||
|
```
|
||||||
|
|
||||||
|
Или для надёжности вовсе удалим поды с traefik и svclb-traefik, тогда они должны создастся заново, и гарантированно
|
||||||
|
примут новые настройки:
|
||||||
|
```bash
|
||||||
|
kubectl delete pod -n kube-system -l app.kubernetes.io/name=traefik
|
||||||
|
kubectl delete pod -n kube-system -l svccontroller.k3s.cattle.io/svcname=traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверим, что поды создались и запустились:
|
||||||
|
```bash
|
||||||
|
sudo kubectl get pods -n kube-system -o wide | grep traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим что-то вроде (поды стартовали недавно):
|
||||||
|
```text
|
||||||
|
helm-install-traefik-c4vlp 0/1 Completed 0 148m 10.42.0.84 opi5plus-2 <none> <none>
|
||||||
|
svclb-traefik-4f8c2580-8pfdg 4/4 Running 0 4m 10.42.2.62 opi5plus-1 <none> <none>
|
||||||
|
svclb-traefik-4f8c2580-9tldj 4/4 Running 0 4m 10.42.1.93 opi5plus-3 <none> <none>
|
||||||
|
svclb-traefik-4f8c2580-pmbqj 4/4 Running 0 4m 10.42.0.83 opi5plus-2 <none> <none>
|
||||||
|
traefik-5db7d4fd45-45gj6 1/1 Running 0 4m 10.42.0.82 opi5plus-2 <none> <none>
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверим, что сервисы создались и запустились:
|
||||||
|
```bash
|
||||||
|
sudo kubectl get svc -n kube-system -o wide | grep traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим что-то вроде (есть обработка через порт 2055:ххххх):
|
||||||
|
```text
|
||||||
|
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,2055:32627/TCP 53d app.kubernetes.io/instance=traefik-kube-system,app.kubernetes.io/name=traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверим, что созданный сервис `x-ui-external` доступен:
|
||||||
|
```bash
|
||||||
|
sudo kubectl get svc -n x-ui -o wide
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим что-то вроде (сервис создан и слушает на порту 2053):
|
||||||
|
```text
|
||||||
|
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
|
||||||
|
x-ui-external ClusterIP 10.43.73.106 <none> 2053/TCP 2h <none>
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверим, что созданный IngressRoute доступен:
|
||||||
|
```bash
|
||||||
|
sudo kubectl get ingressroutes -n x-ui -o wide
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим что-то вроде (IngressRoute создан):
|
||||||
|
```text
|
||||||
|
NAME AGE
|
||||||
|
x-ui-ingress 14h
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверим логи Traefik (не зря же мы включали отладку в манифесте)
|
||||||
|
```bash
|
||||||
|
kubectl get pods -n kube-system | grep traefik
|
||||||
|
sudo kubectl logs -n kube-system traefik-<hash> --since=5m
|
||||||
|
```
|
||||||
|
Ищем: `"web-custom": {"address": ":2055"}` и маршрут `x-ui-x-ui-ingress` с `Host("v.home.cube2.ru")`,
|
||||||
|
|
||||||
|
И наконец, проверим, что под с 3x-ui доступен по нашему доменному на порту 2055 через VIP-адрес (возможно, придется
|
||||||
|
сделать запись в `/etc/hosts`, если ваш роутер не может разрешить внешний домен внутрь домашней сети, и поставить
|
||||||
|
в соответствие домен и VIP):
|
||||||
|
```bash
|
||||||
|
curl -v http://v.home.cube2.ru:2055
|
||||||
|
```
|
||||||
|
|
||||||
|
**Все заработало**, мы видим, что запросы на домен `v.home.cube2.ru` через порт `2055` перенаправляются на под с 3x-ui
|
||||||
|
|
||||||
|
Если не получилось, то можно дополнительно проверить, что с сервисом `traefik` всё в порядке. Посмотрим его текущие
|
||||||
|
настройки:
|
||||||
|
```bash
|
||||||
|
sudo kubectl get service -n kube-system traefik -o yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Мы должны увидеть в блоке `spec:ports` что-то типа:
|
||||||
|
```yaml
|
||||||
|
- name: web-custom
|
||||||
|
nodePort: тут-будет-номер-порта-внутри-балансировщика
|
||||||
|
port: 2055
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 2055
|
||||||
|
```
|
||||||
|
|
||||||
|
Если блока нет, добавьте его через редактор (по умолчанию откроется `vim`, используйте `:wq` для сохранения и выхода):
|
||||||
|
```bash
|
||||||
|
sudo kubectl edit service -n kube-system traefik -o yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Найти в `spec:ports` блок:
|
||||||
|
```yaml
|
||||||
|
- name: web
|
||||||
|
nodePort: 31941
|
||||||
|
port: 80
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: web
|
||||||
|
- name: websecure
|
||||||
|
nodePort: 30329
|
||||||
|
port: 443
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: websecure
|
||||||
|
```
|
||||||
|
|
||||||
|
И добавить под ним новый блок:
|
||||||
|
```yaml
|
||||||
|
- name: web-custom
|
||||||
|
port: 2055
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 2055
|
||||||
|
```
|
||||||
|
|
||||||
|
После сохранения изменений и выхода из редактора, сервис будет обновлён автоматически. Можно проверить, что ему присвоен
|
||||||
|
новый номер порта внутри балансировщика (см. выше) и возможно все заработает. Но скорее всего придется удалить манифесты
|
||||||
|
`ingressroute.yaml`, `x-ui-service.yaml` и все настраивать заново, проверять логи и т.д.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Доступ через Ingress Controller c https и перенаправлением трафика на узел с подом с 3x-ui
|
||||||
|
|
||||||
|
Установим Cert-Manager для автоматического получения сертификатов Let's Encrypt. Это позволит нам использовать
|
||||||
|
HTTPS для доступа к 3x-ui (и другим подам). Cert-Manager автоматически обновляет сертификаты, когда они истекают.
|
||||||
|
```bash
|
||||||
|
sudo kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
В результате у нас появится три новых пода в пространстве имён `cert-manager`:
|
||||||
|
```bash
|
||||||
|
sudo k3s kubectl get pods -n cert-manager -o wide
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим что-то вроде:
|
||||||
|
```text
|
||||||
|
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
|
||||||
|
cert-manager-64478b89d5-p4msl 1/1 Running 0 8m36s 10.42.1.55 opi5plus-3 <none> <none>
|
||||||
|
cert-manager-cainjector-65559df4ff-t7rj4 1/1 Running 0 8m36s 10.42.1.54 opi5plus-3 <none> <none>
|
||||||
|
cert-manager-webhook-544c988c49-zxdxc 1/1 Running 0 8m36s 10.42.1.56 opi5plus-3 <none> <none>
|
||||||
|
```
|
||||||
|
|
||||||
|
Cert-Manager состоит из трёх основных компонентов, каждый из которых запускается в своём поде:
|
||||||
|
* `cert-manager` -- основной контроллер. Он следит за ресурсами вроде Certificate и Issuer, запрашивает сертификаты
|
||||||
|
у провайдеров (например, Let’s Encrypt) и обновляет их при необходимости.
|
||||||
|
* `cert-manager-cainjector` -- внедряет CA (Certificate Authority) в вебхуки и другие ресурсы Kubernetes, чтобы
|
||||||
|
они могли доверять сертификатам, выданным Cert-Manager.
|
||||||
|
* `cert-manager-webhook` -- отвечает за валидацию и мутацию запросов на создание или обновление ресурсов, связанных
|
||||||
|
с сертификатами. Он проверяет их на соответствие правилам.
|
||||||
|
|
||||||
|
#### Манифест для ClusterIssuer
|
||||||
|
|
||||||
|
Создадим манифест ClusterIssuer (эмитент кластера) для Cert-Manager и относится ко всему кластеру. В нем описываются
|
||||||
|
правила для получения сертификатов от внешнего поставщика (в нашем случае Let's Encrypt) и укажем твм адрес сервера
|
||||||
|
ACME, email для уведомлений, способы подтверждения владения доменом (например, через HTTP-01 или DNS-01).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir ~/k3s/cert-manager
|
||||||
|
nano ~/k3s/cert-manager/clusterissuer.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
И вставим в него следующий код (не забудь указать свой email):
|
||||||
|
```yaml
|
||||||
|
piVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
acme:
|
||||||
|
server: https://acme-v02.api.letsencrypt.org/directory
|
||||||
|
email: ваш@емейл.где-то
|
||||||
|
privateKeySecretRef:
|
||||||
|
name: letsencrypt-prod
|
||||||
|
solvers:
|
||||||
|
- http01:
|
||||||
|
ingress:
|
||||||
|
class: traefik # Ingress-контроллер, например, traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
Применим манифест, чтобы cert-manager принял конфигурацию:
|
||||||
|
```bash
|
||||||
|
sudo kubectl apply -f ~/k3s/cert-manager/clusterissuer.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Это будет работать для всего кластера и для всех подов (текущих и будущих). Cert-Manager будет автоматически
|
||||||
|
запрашивать и обновлять сертификаты. Let's Encrypt при проверке прав владения доменом посредством правила HTTP-01
|
||||||
|
использует http (порт 80) и нужно настроить в роутере перенаправление трафика на кластер (лучше через VIP) для этого
|
||||||
|
порта.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------
|
||||||
|
У меня развернут k3s-кластер из трех узлов (Orange Pi 5 Plus). Там есть под 3x-ui для внешнего доступа к VPN-соединениям.
|
||||||
|
Под настроен как hostNetwork: true и использует `Longhorn` для хранения базы данных. На всех узлах настроен keepalived так,
|
||||||
|
чтобы виртуальный IP-адрес (VIP) всегда указывал на узел с подом 3x-ui. Это позволяет поду ловить VPN-соединения из интернета
|
||||||
|
(благодаря пробросу портов на роутере). Веб-интерфейс 3x-ui по порту 2053 тоже доступен по VIP-адресу (192.168.1.200) благодаря
|
||||||
|
`hostNetwork: true` и виден из интернета по адресу `http://v.home.cube2.ru:2053`.
|
||||||
|
|
||||||
|
Я хочу настроить доступ к веб-интерфейсу 3x-ui через svclb-traefik по порту 2055 так, чтобы он стал доступен через VIP
|
||||||
|
и по 2055 порту и будущем можно было настроить доступ по https.
|
||||||
|
|
||||||
|
|
||||||
|
Кроме того у меня еще есть внешний хост с audiobookshelf по адресу http://192.168.1.15:8000 и я хотелбы настроить доступ
|
||||||
|
к нему через svclb-traefik по адресу http://zvuk.cube2.ru:2055.
|
||||||
|
|
||||||
|
Как это сделать? Какие конфиги тебе нужны чтобы ты смог мне помочь?
|
198
kubernetes/k3s-affinate.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# Аффинность узлов в K3s
|
||||||
|
|
||||||
|
Аффинность узлов (node affinity) в K3s позволяет управлять размещением подов на определённых узлах кластера.
|
||||||
|
Это полезно, когда нужно обеспечить запуск приложения на узле с определёнными характеристиками или метками.
|
||||||
|
|
||||||
|
?Привязка узлов может быть и жесткой и не жесткой.
|
||||||
|
|
||||||
|
## Под с предпочтительной привязкой к узлу
|
||||||
|
|
||||||
|
У меня есть [VPN-под на 3x-ui](k3s-3xui-pod.md), и предпочтительно, чтобы он при старте k3s запускался на узле
|
||||||
|
c наивысшем приоритетом в keepalive (у меня это хост `opi5plus-1`), но если нода "упадет" и под 3x-ui "переедет"
|
||||||
|
на другой узел, то keepalive повысит приоритет т ноды При этом возвращать под на исходный узел после его восстановления не обязательно.
|
||||||
|
|
||||||
|
(например, opi5plus-1), но мог переезжать на другие узлы (например, opi5plus-2 или opi5plus-3), если что-то пойдёт не так с основным узлом. При этом возвращать под на исходный узел после его восстановления не обязательно.
|
||||||
|
|
||||||
|
Ответ: Да, это возможно! В Kubernetes для этого используется механизм node affinity (аффинность к узлам). Он позволяет указать "мягкое" предпочтение для запуска пода на определённом узле, но не запрещает поду перемещаться, если нужного узла нет.
|
||||||
|
|
||||||
|
Как настроить node affinity
|
||||||
|
Допустим, ты хочешь, чтобы под запускался на opi5plus-1 по умолчанию, но мог переехать на другие узлы при необходимости.
|
||||||
|
|
||||||
|
Шаги:
|
||||||
|
|
||||||
|
Проверь или добавь метки узлам: Убедись, что твои узлы имеют метки (labels). Например:
|
||||||
|
bash
|
||||||
|
|
||||||
|
Collapse
|
||||||
|
|
||||||
|
Wrap
|
||||||
|
|
||||||
|
Copy
|
||||||
|
kubectl label nodes opi5plus-1 preferred=true
|
||||||
|
kubectl label nodes opi5plus-2 preferred=false
|
||||||
|
kubectl label nodes opi5plus-3 preferred=false
|
||||||
|
Здесь preferred=true — это метка, которая указывает предпочтительный узел.
|
||||||
|
Обнови манифест деплоя 3x-ui: Добавь секцию affinity в спецификацию пода. Пример:
|
||||||
|
yaml
|
||||||
|
|
||||||
|
Collapse
|
||||||
|
|
||||||
|
Wrap
|
||||||
|
|
||||||
|
Copy
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: x-ui
|
||||||
|
namespace: x-ui
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: x-ui
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: x-ui
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- weight: 1
|
||||||
|
preference:
|
||||||
|
matchExpressions:
|
||||||
|
- key: preferred
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- "true"
|
||||||
|
containers:
|
||||||
|
- name: x-ui
|
||||||
|
image: ghcr.io/mhsanaei/3x-ui:latest
|
||||||
|
# Остальные настройки контейнера...
|
||||||
|
Что здесь происходит:
|
||||||
|
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution — это "мягкое" правило. Kubernetes постарается запустить под на узле с меткой preferred=true, но если это невозможно (например, узел недоступен), под запустится на любом другом доступном узле.
|
||||||
|
weight: 1 — вес предпочтения. Если у тебя будет несколько правил, можно задавать приоритеты, но для одного правила это не так важно.
|
||||||
|
Как это работает:
|
||||||
|
|
||||||
|
Если узел opi5plus-1 доступен, под запустится там.
|
||||||
|
Если opi5plus-1 упадёт, под автоматически переедет на другой узел (например, opi5plus-2).
|
||||||
|
После восстановления opi5plus-1 под останется на текущем узле и не вернётся автоматически, что соответствует твоему запросу.
|
||||||
|
Как это сочетается с Keepalived?
|
||||||
|
Твой скрипт проверки в Keepalived гарантирует, что виртуальный IP будет следовать за подом 3x-ui. Если ты задашь opi5plus-1 как предпочтительный узел через affinity и дашь ему высокий приоритет в Keepalived (например, 100), то в нормальной ситуации под и IP будут на одном узле. А если под переедет из-за сбоя, Keepalived переключит IP на новый узел благодаря скрипту.
|
||||||
|
|
||||||
|
Итог
|
||||||
|
Скрипт проверки: sudo не нужен, так как Keepalived запускает скрипт от root. Если будут проблемы с доступом, добавь --kubeconfig с путём к файлу конфигурации.
|
||||||
|
Приоритет узла для пода: Используй node affinity с "мягким" предпочтением (preferredDuringSchedulingIgnoredDuringExecution). Это позволит поду запускаться на нужном узле, но свободно перемещаться при сбоях без обязательного возвращения.
|
||||||
|
Твоя идея с приоритетами и "кочеванием" пода отлично реализуема, и связка Keepalived + affinity обеспечит гибкость и надёжность. Удачи с настройкой, если что — пиши! 😊
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Отлично. Ты мне отлично помог и 3x-ui заработал на VIP. Все сработало как ты сказал. Объясни пожалуйста про метки:
|
||||||
|
```
|
||||||
|
kubectl label nodes opi5plus-1 preferred=true
|
||||||
|
kubectl label nodes opi5plus-2 preferred=false
|
||||||
|
kubectl label nodes opi5plus-3 preferred=false
|
||||||
|
```
|
||||||
|
это означает, что все поды будут предпочитать opi5plus-1 или это касается только подов с nodeAffinity?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
3 / 3
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Completed
|
||||||
|
42s
|
||||||
|
Thinking
|
||||||
|
Исследую использование меток
|
||||||
|
|
||||||
|
|
||||||
|
Thinking
|
||||||
|
Исследую использование меток
|
||||||
|
Пользователь интересуется метками в K3s и их применением для привязки узлов. Они пометили один узел как "preferred=true", а другие как "preferred=false".
|
||||||
|
Метки в K3s — это пары ключ-значение, прикрепленные к объектам, таким как узлы, поды, сервисы. Они помогают организовать и выбрать подмножества объектов.
|
||||||
|
Привязка узлов позволяет указать, на каких узлах должен размещаться под. Есть два типа: обязательная и предпочтительная.
|
||||||
|
Метка "preferred=true" влияет только на поды с привязкой узлов, ищущими эту метку. Поды без таких правил размещаются без учета этой метки.
|
||||||
|
Все три узла имеют метку "preferred", но с разными значениями: opi5plus-1 — true, opi5plus-2 и opi5plus-3 — false.
|
||||||
|
Если под не имеет привязки узлов, планировщик размещает его на любом узле с нужными ресурсами, игнорируя метку "preferred".
|
||||||
|
Под без nodeSelector или nodeAffinity размещается на любом узле с необходимыми ресурсами. Метка "preferred" важна только для подов, ссылающихся на нее.
|
||||||
|
В K8s, метка "preferred=false" добавляет метку "preferred" со значением "false" на узел. Если метки нет, узел ее не имеет.
|
||||||
|
Под с предпочтительной привязкой сначала пытается разместиться на opi5plus-1, где preferred=true. Если не получится, может быть размещен на opi5plus-2 или opi5plus-3.
|
||||||
|
Метка "preferred=true" влияет только на поды с правилами, ищущими эту метку. Другие поды размещаются по другим критериям.
|
||||||
|
В стандартном K3s нет поведения, которое по умолчанию использует метки, такие как "preferred", для влияния на планирование. Метка важна только для подов, явно ссылающихся на нее в правилах привязки.
|
||||||
|
Другие поды, без ссылок на эту метку, будут размещены без учета "preferred", основываясь на доступности ресурсов.
|
||||||
|
Пример: под без привязки узлов размещается на любом узле, игнорируя метку "preferred". Это подтверждает, что метка влияет только на поды с соответствующими правилами.
|
||||||
|
Если под указывает nodeSelector с preferred="false", он размещается только на узлах, где preferred=false, например, opi5plus-2 или opi5plus-3.
|
||||||
|
Метка "preferred" влияет только на поды с правилами привязки или селектора, ссылающимися на эту метку. Поды без таких правил не затрагиваются.
|
||||||
|
Метка "preferred" важна только для подов, таких как 3x-ui, с установленной предпочтительной привязкой. Другие поды в кластере размещаются по своим спецификациям, если нет похожих правил.
|
||||||
|
Метки на узлах важны только тогда, когда правила планирования пода ссылаются на них. Например, под может исключать узлы, где preferred=true, если указано соответствующее правило.
|
||||||
|
Прямой ответ
|
||||||
|
Метки, такие как preferred=true или preferred=false, влияют только на поды с указанной аффинностью узлов, которые ссылаются на эту метку.
|
||||||
|
Другие поды, без аффинности, размещаются без учёта этих меток, основываясь на доступных ресурсах.
|
||||||
|
Влияние на поды
|
||||||
|
Метки на узлах, вроде preferred, не имеют значения для подов, если те не имеют правил аффинности, ссылающихся на эту метку. Например, под с аффинностью, предпочитающей preferred=true, будет стараться запускаться на opi5plus-1, но может переместиться на другие узлы, если opi5plus-1 недоступен.
|
||||||
|
|
||||||
|
Неожиданный нюанс
|
||||||
|
Интересно, что метки могут использоваться в других правилах, например, для исключения узлов (NotIn), но это требует явного указания в спецификации пода, что выходит за рамки стандартного поведения.
|
||||||
|
|
||||||
|
Отчёт
|
||||||
|
Введение
|
||||||
|
Метки (labels) в K3s — это ключевые пары, прикрепляемые к объектам, таким как узлы, поды и сервисы, для организации и выбора подмножества объектов. В контексте планирования подов метки на узлах используются для влияния на их размещение через механизмы, такие как аффинность узлов (node affinity). Пользователь задал вопрос о влиянии меток preferred=true и preferred=false, установленных на узлы, на все поды или только на те, у которых указана аффинность узлов.
|
||||||
|
|
||||||
|
Анализ влияния меток
|
||||||
|
Метки на узлах, такие как preferred=true или preferred=false, добавляются через команды вроде kubectl label nodes opi5plus-1 preferred=true. Эти метки сами по себе не имеют встроенного значения для планировщика K3s, если поды не ссылаются на них в своих правилах размещения.
|
||||||
|
|
||||||
|
В K3s, основанном на Kubernetes, планировщик подов (scheduler) размещает поды на узлах, учитывая доступные ресурсы (CPU, память) и любые ограничения, указанные в спецификации пода. Метки становятся значимыми только тогда, когда под использует их в правилах nodeSelector, nodeAffinity или nodeAntiAffinity.
|
||||||
|
|
||||||
|
Пользователь настроил метки следующим образом:
|
||||||
|
|
||||||
|
opi5plus-1: preferred=true
|
||||||
|
opi5plus-2: preferred=false
|
||||||
|
opi5plus-3: preferred=false
|
||||||
|
И в манифесте пода 3x-ui указал аффинность:
|
||||||
|
|
||||||
|
yaml
|
||||||
|
|
||||||
|
Collapse
|
||||||
|
|
||||||
|
Wrap
|
||||||
|
|
||||||
|
Copy
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- weight: 1
|
||||||
|
preference:
|
||||||
|
matchExpressions:
|
||||||
|
- key: preferred
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- "true"
|
||||||
|
Это означает, что под предпочтительно будет размещён на узле с меткой preferred=true, то есть на opi5plus-1. Если такой узел недоступен, под может быть размещён на opi5plus-2 или opi5plus-3, где preferred=false.
|
||||||
|
|
||||||
|
Влияние на поды без аффинности
|
||||||
|
Поды, у которых не указаны nodeSelector, nodeAffinity или nodeAntiAffinity, размещаются планировщиком K3s без учёта метки preferred. Планировщик выбирает узел на основе:
|
||||||
|
|
||||||
|
Доступных ресурсов.
|
||||||
|
Отсутствия конфликтов (например, taints).
|
||||||
|
Других факторов, таких как анти-аффинность подов или балансировка нагрузки.
|
||||||
|
Таким образом, метки preferred=true и preferred=false не влияют на поды, у которых нет правил, ссылающихся на эту метку. Например, поды Shadowsocks, если они не имеют аффинности, будут размещаться на любом узле без учёта preferred.
|
||||||
|
|
||||||
|
Пример с таблицей
|
||||||
|
Вот таблица, которая иллюстрирует влияние меток на поды:
|
||||||
|
|
||||||
|
Под Имеет аффинность к preferred? Узел предпочтения Возможные узлы при сбое
|
||||||
|
3x-ui Да, preferred=true opi5plus-1 opi5plus-2, opi5plus-3
|
||||||
|
Shadowsocks (без аффинности) Нет Любой доступный узел Любой доступный узел
|
||||||
|
Заключение
|
||||||
|
Метки preferred влияют только на поды с аффинностью, ссылающейся на эту метку, и не затрагивают остальные поды. Это позволяет гибко управлять размещением, сохраняя свободу для других приложений.
|
||||||
|
|
||||||
|
Ключевые цитаты
|
||||||
|
Kubernetes Node Affinity and Anti-Affinity https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity
|
||||||
|
K3s Documentation on Node Labels https://grok.com/chat/6a083b91-ea05-4246-9407-b9b25af4b48f#:~:text=K3s%20Documentation%20on%20Node%20Labels
|
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`. и в нем можно будет создавать и редактировать
|
||||||
|
схемы, например:
|
||||||
|
|
||||||
|

|
255
kubernetes/k3s-lets-encrypt-cert-manager.md
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
# Подключение менеджера сертификатов (cert-manager) Let's Encrypt к k3s
|
||||||
|
|
||||||
|
№ Установка cert-manager
|
||||||
|
|
||||||
|
Установим `cert-manager` из официального манифеста GitHub. Это автоматически добавит все необходимые компоненты,
|
||||||
|
включая CRD (Custom Resource Definitions) и контроллеры:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.3/cert-manager.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Это установит cert-manager версии 1.15.3 (предварительно надо проверь актуальную версию на GitHub cert-manager).
|
||||||
|
Компоненты развернутся в namespace cert-manager.
|
||||||
|
|
||||||
|
Проверим установку:
|
||||||
|
```bash
|
||||||
|
kubectl get pods --namespace cert-manager
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим, что все поды в статусе `Running`:
|
||||||
|
```text
|
||||||
|
NAME READY STATUS RESTARTS AGE
|
||||||
|
cert-manager-778cb8f45-gxtpf 1/1 Running 0 22m
|
||||||
|
cert-manager-cainjector-6d5854cdd8-w4d8x 1/1 Running 0 22m
|
||||||
|
cert-manager-webhook-78b57fb6bb-jljdx 1/1 Running 0 22m
|
||||||
|
```
|
||||||
|
|
||||||
|
Что это за поды:
|
||||||
|
- `cert-manager` — основной контроллер cert-manager, который управляет созданием и обновлением сертификатов.
|
||||||
|
- `cert-manager-cainjector` — контроллер, который автоматически добавляет CA-сертификаты в секреты и ресурсы Kubernetes.
|
||||||
|
- `cert-manager-webhook` — контроллер, который обрабатывает запросы на валидацию и мутацию ресурсов cert-manager.
|
||||||
|
|
||||||
|
Проверим, что Custom Resource Definitions (CRDs) для cert-manager созданы:
|
||||||
|
```bash
|
||||||
|
kubectl get crd | grep cert-manager
|
||||||
|
```
|
||||||
|
|
||||||
|
Должны быть `certificates.cert-manager.io`, `issuers.cert-manager.io`, `clusterissuers.cert-manager.io и другие:
|
||||||
|
```text
|
||||||
|
certificaterequests.cert-manager.io 2025-04-27T16:29:56Z
|
||||||
|
certificates.cert-manager.io 2025-04-27T16:29:56Z
|
||||||
|
challenges.acme.cert-manager.io 2025-04-27T16:29:56Z
|
||||||
|
clusterissuers.cert-manager.io 2025-04-27T16:29:56Z
|
||||||
|
issuers.cert-manager.io 2025-04-27T16:29:56Z
|
||||||
|
orders.acme.cert-manager.io 2025-04-27T16:29:57Z
|
||||||
|
```
|
||||||
|
|
||||||
|
# Создание ClusterIssuer (Эмитент сертификатов для всего кластера)
|
||||||
|
|
||||||
|
Создадим `ClusterIssuer` для Let's Encrypt. Это позволит cert-manager автоматически запрашивать и обновлять сертификаты.
|
||||||
|
Разместим его в отдельном файле `~/k3s/cert-manager/letsencrypt-clusterissuer.yaml`:
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/k3s/cert-manager
|
||||||
|
nano ~/k3s/cert-manager/letsencrypt-clusterissuer.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
И вставим в него следующий код (не забудьте заменить `<my@email.com>` на свой email):
|
||||||
|
```yaml
|
||||||
|
apiVersion: cert-manager.io/v1 # Версия API для cert-manager, v1 — текущая стабильная.
|
||||||
|
kind: ClusterIssuer # Тип ресурса. ClusterIssuer — глобальный объект для выдачи сертификатов
|
||||||
|
# всему кластеру (в отличие от Issuer, который ограничен namespace).
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt-prod # Имя `ClusterIssuer`, на него будем ссылаться в манифестах (например, в Certificate).
|
||||||
|
spec:
|
||||||
|
acme: # Используется ACME-протокол (Automated Certificate Management Environment) Let's Encrypt.
|
||||||
|
server: https://acme-v02.api.letsencrypt.org/directory # URL сервера Let's Encrypt (боевой).
|
||||||
|
email: <my@email.com> # Логин-клиент Let's Encrypt (раньше так же использовался для уведомлений).
|
||||||
|
privateKeySecretRef:
|
||||||
|
name: letsencrypt-prod # Имя секрета, где будет храниться приватные ключи для ACME-аккаунта.
|
||||||
|
solvers: # Список способов "решения" ACME-задач для подтверждения владения доменом.
|
||||||
|
- http01: # Используется HTTP-01 challenge (подтверждение через HTTP-запросы).
|
||||||
|
ingress:
|
||||||
|
class: traefik # Указывает, что ingress-контроллер Traefik будет обрабатывать HTTP-01 challenge
|
||||||
|
# (создает временные маршруты для `/.well-known/acme-challenge`) для подтверждения
|
||||||
|
# владения доменом.
|
||||||
|
```
|
||||||
|
|
||||||
|
Применим манифест:
|
||||||
|
```bash
|
||||||
|
kubectl apply -f ~/k3s/cert-manager/letsencrypt-clusterissuer.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверим, что `ClusterIssuer` создан:
|
||||||
|
```bash
|
||||||
|
kubectl get clusterissuer
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим что-то вроде:
|
||||||
|
```text
|
||||||
|
NAME READY AGE
|
||||||
|
letsencrypt-prod True 27s
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверим статус:
|
||||||
|
```bash
|
||||||
|
kubectl describe clusterissuer letsencrypt-prod
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим что-то вроде:
|
||||||
|
```text
|
||||||
|
...
|
||||||
|
...
|
||||||
|
Status:
|
||||||
|
Acme:
|
||||||
|
Last Private Key Hash: тут-хранится-хэш-приватного-ключа
|
||||||
|
Last Registered Email: <my@email.com>
|
||||||
|
Uri: https://acme-v02.api.letsencrypt.org/acme/acct/2365771147
|
||||||
|
Conditions:
|
||||||
|
Last Transition Time: 2025-04-27T17:00:56Z
|
||||||
|
Message: The ACME account was registered with the ACME server
|
||||||
|
Observed Generation: 1
|
||||||
|
Reason: ACMEAccountRegistered
|
||||||
|
Status: True
|
||||||
|
Type: Ready
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Должны быть `Status: True` и `Reason: ACMEAccountRegistered`, что означает, что `ClusterIssuer` успешно зарегистрирован
|
||||||
|
и готов к использованию.
|
||||||
|
|
||||||
|
# Создание сертификата
|
||||||
|
|
||||||
|
Эта общая часть, объясняющая как создавать манифесты для выпуска сертификатов.
|
||||||
|
|
||||||
|
В своем манифесте, например, в манифесте пода, создается блок (данный кусочек взят из манифеста пода
|
||||||
|
[Gitea](k3s-migrating-container-from-docker-to-kubernetes.md) и он находится в пространстве имен `gitea`):
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
# Манифест для получения сертификата от Let's Encrypt
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: gitea-tls # Имя сертификата `gitea-tls`
|
||||||
|
namespace: gitea # В пространстве имен `gitea`
|
||||||
|
spec:
|
||||||
|
secretName: gitea-tls # Имя секрета для сертификата
|
||||||
|
dnsNames: # Доменные имена для сертификата...
|
||||||
|
- git.cube2.ru
|
||||||
|
issuerRef: # Эмитент сертификата (Issuer)
|
||||||
|
name: letsencrypt-prod # Имя ClusterIssuer, который отвечает за получение сертификата: `letsencrypt-prod`
|
||||||
|
kind: ClusterIssuer # Тип эмитента
|
||||||
|
```
|
||||||
|
|
||||||
|
После применения манифеста, можно проверить статус сертификата:
|
||||||
|
```bash
|
||||||
|
kubectl get certificate -A
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим что-то вроде:
|
||||||
|
```text
|
||||||
|
NAMESPACE NAME READY SECRET AGE
|
||||||
|
ab-shelf audiobookshelf-tls False audiobookshelf-tls 90m
|
||||||
|
gitea gitea-tls True gitea-tls 66s
|
||||||
|
```
|
||||||
|
|
||||||
|
Тут можно заметить, что сертификат `audiobookshelf-tls` не готов (False), а `gitea-tls` готов (True).
|
||||||
|
|
||||||
|
Как проверить статус сертификата и причину его неготовности (на примере `audiobookshelf-tls`):
|
||||||
|
```bash
|
||||||
|
kubectl describe certificate audiobookshelf-tls -n ab-shelf
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим что-то вроде:
|
||||||
|
```text
|
||||||
|
Name: audiobookshelf-tls
|
||||||
|
Namespace: ab-shelf
|
||||||
|
...
|
||||||
|
...
|
||||||
|
...
|
||||||
|
Spec:
|
||||||
|
Dns Names:
|
||||||
|
<тут-будет-доменное-имя-для-которого-выдается-сертификат>
|
||||||
|
Issuer Ref:
|
||||||
|
Kind: ClusterIssuer
|
||||||
|
Name: letsencrypt-prod
|
||||||
|
Secret Name: audiobookshelf-tls
|
||||||
|
Status:
|
||||||
|
Conditions:
|
||||||
|
Last Transition Time: 2025-04-27T17:13:32Z
|
||||||
|
Message: The certificate request has failed to complete and will be retried: Failed to wait for order resource "audiobookshelf-tls-1-722111375" to become ready: order is in "errored" state: Failed to create Order: 429 urn:ietf:params:acme:error:rateLimited: too many certificates (5) already issued for this exact set of domains in the last 168h0m0s, retry after 2025-04-28 19:30:09 UTC: see https://letsencrypt.org/docs/rate-limits/#new-certificates-per-exact-set-of-hostnames
|
||||||
|
Observed Generation: 1
|
||||||
|
Reason: Failed
|
||||||
|
Status: False
|
||||||
|
Type: Issuing
|
||||||
|
Last Transition Time: 2025-04-27T17:13:29Z
|
||||||
|
Message: Issuing certificate as Secret does not exist
|
||||||
|
Observed Generation: 1
|
||||||
|
Reason: DoesNotExist
|
||||||
|
Status: False
|
||||||
|
Type: Ready
|
||||||
|
...
|
||||||
|
...
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Как видно, причина неготовности сертификата `audiobookshelf-tls` в том, что уже превышен лимит на количество сертификатов
|
||||||
|
(пять запросов) для данного доменного имени за последние 168 часов (семь дней). Указано время после которого можно
|
||||||
|
повторить запрос.
|
||||||
|
|
||||||
|
По идее запрос на повторный сертификат будет отправлен автоматически, но это может произойти спустя несколько часов
|
||||||
|
(три-четыре или более) после разрешенного времени. Кстати, время указывается в UTC, делайте поправку на свой часовой
|
||||||
|
пояс.
|
||||||
|
|
||||||
|
Чтобы ускорить процесс, можно удалить сертификат и создать его заново (на примере `audiobookshelf-tls`):
|
||||||
|
```bash
|
||||||
|
kubectl delete certificate audiobookshelf-tls -n ab-shelf
|
||||||
|
```
|
||||||
|
|
||||||
|
А затем повторно принять манифест, в котором у вас находится `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
|
||||||
|
```
|
||||||
|
|
1022
kubernetes/k3s-migrating-container-from-docker-to-kubernetes.md
Normal file
1251
kubernetes/k3s-postresql.md
Normal file
358
kubernetes/k3s-proxy.md
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
# Проксирование внешнего хоста через Traefik (Ingress-контроллер)
|
||||||
|
|
||||||
|
У меня в домашней сети есть хост с web-сервисом (audiobookshelf), а стандартные web-порты (80 для HTTP и 443 для HTTPS)
|
||||||
|
на домашнем роутере перенаправлены в кластер k3S (через keepalived). Таким образом, если прокинуть http-трафик этого
|
||||||
|
хоста через Traefik, то можно будет получить доступ к этому сервису через доменное имя и SSL-сертификат от
|
||||||
|
Let’s Encrypt.
|
||||||
|
|
||||||
|
Для удобства я поместил все манифесты в один файл (вы можете оформить из и как отдельные файлы). Так как хотелось бы
|
||||||
|
описать как делаются универсальные манифесты, которые можно использовать для проксирования любого сервиса, то
|
||||||
|
я заменил в нем конкретные значения на "заглушки. Можно взять этот манифест и просто заменить в нем значения на
|
||||||
|
свои:
|
||||||
|
|
||||||
|
* `<PROXIED-HOST>` -- IP-адрес хоста, где работает сервис, который надо проксировать.
|
||||||
|
* `<PROXIED-PORT>` -- порт, с которого отвечает сервис.
|
||||||
|
* `<YOU-DOMAIN-NAME>` -- доменное имя, на которое будет проксировать сервис.
|
||||||
|
* `<NAME-SPACE>` -- пространство имен кластера, в котором будет создан сервис, маршруты, секреты и все необходимое
|
||||||
|
для проксирования. Пространство имен -- это логическая группа ресурсов в кластере Kubernetes, которая позволяет
|
||||||
|
организовать и изолировать ресурсы.
|
||||||
|
* `<SERVICE-NAME>` -- имя сервиса, который будет проксироваться. Это имя, для простоты, будем использоваться
|
||||||
|
* и в маршрутах, и сертификатах, и в секрете...
|
||||||
|
|
||||||
|
## Пространство имен
|
||||||
|
|
||||||
|
Чтобы все было аккуратно и сервисы и поды не путались, создадим пространство имен для проксирования конкретного хоста.
|
||||||
|
Мо хост относится к <SERVICE-NAME>, поэтому назову пространство имен `<NAME-SPACE>`
|
||||||
|
Например, `<NAME-SPACE>`.
|
||||||
|
```bash
|
||||||
|
sudo ubectl create namespace <NAME-SPACE>
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверяем, что пространство создано:
|
||||||
|
```bash
|
||||||
|
sudo kubectl get namespace <NAME-SPACE>
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим, что пространство создано и активно:
|
||||||
|
```text
|
||||||
|
NAME STATUS AGE
|
||||||
|
<NAME-SPACE> Active 54s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Конфигурация всего
|
||||||
|
|
||||||
|
Для удобства я объединил манифесты в один файл (но можно и по отдельности). Создаем единый манифест:
|
||||||
|
```bash
|
||||||
|
sudo nano ~/k3s/<SERVICE-NAME>/<SERVICE-NAME>.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Он состоит из следующих частей:
|
||||||
|
* `Endpoints` -- указывает на конечную точку, в которую будет проксироваться трафик. В данном случае это
|
||||||
|
IP-адрес и порт, на который будет проксироваться запрос.
|
||||||
|
* `Service` -- создает сервис, который будет использоваться для проксирования запросов к `Endpoints`.
|
||||||
|
* `Certificate` -- создает сертификат для домена `<YOU-DOMAIN-NAME>`, который будет использоваться для
|
||||||
|
шифрования трафика. Сертификат запрашивается через `cert-manager`, который автоматически обновляет его по мере
|
||||||
|
необходимости.
|
||||||
|
* `Middleware` -- создает промежуточное ПО, которое будет использоваться для обработки запросов. В данном случае
|
||||||
|
это редирект с HTTP на HTTPS и исключение редиректа для ACME challenge (механизма внешней проверки владения доменом
|
||||||
|
со стороны Let’s Encrypt).
|
||||||
|
* `IngressRoute` -- создает маршрут, который будет использоваться для проксирования запросов к сервису.
|
||||||
|
В данном случае это маршруты для HTTP и HTTPS, которые будут обрабатывать запросы на домен `<YOU-DOMAIN-NAME>`.
|
||||||
|
Также создается маршрут для ACME challenge, который позволяет cert-manager пройти проверку через порт 80.
|
||||||
|
|
||||||
|
Вставляем в манифест следующее содержимое (не забудьте заменить `<PROXIED-HOST>`, `<PROXIED-PORT>`, `<YOU-DOMAIN-NAME>`,
|
||||||
|
`<NAME-SPACE>` и `<SERVICE-NAME>` на свои значения):
|
||||||
|
```yaml
|
||||||
|
# Endpoints для внешнего хоста <SERVICE-NAME>
|
||||||
|
# Задаёт IP и порт внешнего сервера, так как <SERVICE-NAME> внешний хост для k3s
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Endpoints
|
||||||
|
metadata:
|
||||||
|
name: <SERVICE-NAME>
|
||||||
|
namespace: <NAME-SPACE> # Namespace для <SERVICE-NAME>
|
||||||
|
subsets: # Прямо в корне, без spec
|
||||||
|
- addresses:
|
||||||
|
- ip: <PROXIED-HOST> # IP Synology, где работает <SERVICE-NAME>
|
||||||
|
ports:
|
||||||
|
- port: <PROXIED-PORT> # Порт Synology (HTTP)
|
||||||
|
protocol: TCP
|
||||||
|
|
||||||
|
---
|
||||||
|
# Service для маршрутизации трафика от Traefik к внешнему хосту
|
||||||
|
# Связывает IngressRoute с Endpoints
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: <SERVICE-NAME>
|
||||||
|
namespace: <NAME-SPACE>
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: <PROXIED-PORT> # Порт сервиса, на который Traefik отправляет трафик
|
||||||
|
targetPort: <PROXIED-PORT> # Порт на Synology
|
||||||
|
protocol: TCP
|
||||||
|
|
||||||
|
---
|
||||||
|
# Middleware для редиректа HTTP → HTTPS
|
||||||
|
# Применяется к HTTP-запросам для перенаправления на HTTPS
|
||||||
|
apiVersion: traefik.io/v1alpha1 # версия Traefik v34.2.1+up34.2.0 (Traefik v3.3.6)
|
||||||
|
# apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: https-redirect
|
||||||
|
namespace: <NAME-SPACE>
|
||||||
|
spec:
|
||||||
|
redirectScheme:
|
||||||
|
scheme: https # Перенаправлять на HTTPS
|
||||||
|
permanent: true # Код 301 (постоянный редирект)
|
||||||
|
|
||||||
|
---
|
||||||
|
# Middleware для исключения редиректа на ACME challenge
|
||||||
|
# Позволяет Let’s Encrypt проверять /.well-known/acme-challenge без HTTPS
|
||||||
|
apiVersion: traefik.io/v1alpha1 # версия Traefik v34.2.1+up34.2.0 (Traefik v3.3.6)
|
||||||
|
# apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: no-https-redirect
|
||||||
|
namespace: <NAME-SPACE>
|
||||||
|
spec:
|
||||||
|
stripPrefix:
|
||||||
|
prefixes:
|
||||||
|
- /.well-known/acme-challenge # Убрать префикс для ACME
|
||||||
|
|
||||||
|
---
|
||||||
|
# IngressRoute для HTTP (порт 80) с редиректом на HTTPS
|
||||||
|
# Обрабатывает HTTP-запросы и перенаправляет их
|
||||||
|
apiVersion: traefik.io/v1alpha1 # версия Traefik v34.2.1+up34.2.0 (Traefik v3.3.6)
|
||||||
|
# apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: <SERVICE-NAME>-http
|
||||||
|
namespace: <NAME-SPACE>
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- web # Порт 80 (стандартный HTTP)
|
||||||
|
routes:
|
||||||
|
- match: Host("<YOU-DOMAIN-NAME>") # Запросы к <YOU-DOMAIN-NAME>
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: <SERVICE-NAME> # Сервис <SERVICE-NAME>
|
||||||
|
port: <PROXIED-PORT> # Порт сервиса
|
||||||
|
middlewares:
|
||||||
|
- name: https-redirect # Редирект на HTTPS
|
||||||
|
|
||||||
|
---
|
||||||
|
# IngressRoute для HTTPS (порт 443)
|
||||||
|
# Обрабатывает HTTPS-запросы с TLS
|
||||||
|
apiVersion: traefik.io/v1alpha1 # версия Traefik v34.2.1+up34.2.0 (Traefik v3.3.6)
|
||||||
|
# apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: <SERVICE-NAME>-https
|
||||||
|
namespace: <NAME-SPACE>
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure # Порт 443 (HTTPS)
|
||||||
|
routes:
|
||||||
|
- match: Host("<YOU-DOMAIN-NAME>")
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: <SERVICE-NAME>
|
||||||
|
port: <PROXIED-PORT>
|
||||||
|
tls:
|
||||||
|
secretName: <SERVICE-NAME>-tls # Сертификат от cert-manager
|
||||||
|
|
||||||
|
---
|
||||||
|
# IngressRoute для HTTP-01 challenge (Let’s Encrypt)
|
||||||
|
# Позволяет cert-manager пройти проверку через порт 80
|
||||||
|
apiVersion: traefik.io/v1alpha1 # версия Traefik v34.2.1+up34.2.0 (Traefik v3.3.6)
|
||||||
|
# apiVersion: traefik.containo.us/v1alpha1 # старая версия Traefik
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: <SERVICE-NAME>-acme
|
||||||
|
namespace: <NAME-SPACE>
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- web # Порт 80
|
||||||
|
routes:
|
||||||
|
- match: Host("<YOU-DOMAIN-NAME>") && PathPrefix("/.well-known/acme-challenge")
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: <SERVICE-NAME>
|
||||||
|
port: <PROXIED-PORT>
|
||||||
|
middlewares:
|
||||||
|
- name: no-https-redirect # Не редиректить ACME
|
||||||
|
|
||||||
|
---
|
||||||
|
# Certificate для TLS-сертификата от Let’s Encrypt
|
||||||
|
# Запрашивает сертификат для <YOU-DOMAIN-NAME> через cert-manager
|
||||||
|
# ВАЖНО: cert-manager должен быть установлен в кластере
|
||||||
|
# ВАЖНО: если манифесты принимаются (apply) последовательно, то манифест с сертификатом должен быть последним для
|
||||||
|
# избежания исчерпания лимитов Let’s Encrypt (пять запросов в неделю)
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: <SERVICE-NAME>-tls
|
||||||
|
namespace: <NAME-SPACE>
|
||||||
|
spec:
|
||||||
|
secretName: <SERVICE-NAME>-tls # Имя секрета для сертификата
|
||||||
|
dnsNames:
|
||||||
|
- <YOU-DOMAIN-NAME> # Домен для сертификата
|
||||||
|
issuerRef:
|
||||||
|
name: letsencrypt-prod # ClusterIssuer для Let’s Encrypt
|
||||||
|
kind: ClusterIssuer
|
||||||
|
```
|
||||||
|
|
||||||
|
**ВАЖНО**: В манифесте используется letsencrypt-prod для получения сертификата от Let’s Encrypt. Это нестандартный
|
||||||
|
ClusterIssuer cert-manager, создание которого описано в [документации](https://cert-manager.io/docs/usage/ingress/#tls-termination)
|
||||||
|
и [отдельной инструкции](k3s-custom-container-deployment.md#создание-clusterissuer)
|
||||||
|
(возможно, вам нужно будет создать его отдельно). Если вы используете другой ClusterIssuer, то замените letsencrypt-prod
|
||||||
|
на имя вашего ClusterIssuer в секции `issuerRef` в манифесте.
|
||||||
|
|
||||||
|
## Применяем манифест
|
||||||
|
```bash
|
||||||
|
sudo kubectl apply -f ~/k3s/<SERVICE-NAME>/<SERVICE-NAME>.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверяем, что все ресурсы создались:
|
||||||
|
```bash
|
||||||
|
sudo kubectl get all -n <NAME-SPACE>
|
||||||
|
sudo kubectl get ingressroute -n <NAME-SPACE>
|
||||||
|
sudo kubectl get middleware -n <NAME-SPACE>
|
||||||
|
sudo kubectl get service -n <NAME-SPACE>
|
||||||
|
sudo kubectl get endpoints -n <NAME-SPACE>
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим что-то вроде:
|
||||||
|
```text
|
||||||
|
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||||
|
service/<SERVICE-NAME> ClusterIP 10.43.152.59 <none> <PROXIED-PORT>/TCP 3h
|
||||||
|
```
|
||||||
|
|
||||||
|
Для IngressRoute:
|
||||||
|
```text
|
||||||
|
NAME AGE
|
||||||
|
<SERVICE-NAME> 3h
|
||||||
|
<SERVICE-NAME>-acme 1h
|
||||||
|
<SERVICE-NAME>-http 1h
|
||||||
|
<SERVICE-NAME>-https 1h
|
||||||
|
```
|
||||||
|
|
||||||
|
Для Middleware:
|
||||||
|
```text
|
||||||
|
NAME AGE
|
||||||
|
https-redirect 1h
|
||||||
|
no-https-redirect 1h
|
||||||
|
```
|
||||||
|
|
||||||
|
Для Service:
|
||||||
|
```text
|
||||||
|
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||||
|
<SERVICE-NAME> ClusterIP 10.43.152.59 <none> <PROXIED-PORT>/TCP 3h
|
||||||
|
```
|
||||||
|
|
||||||
|
Для Endpoints:
|
||||||
|
```text
|
||||||
|
NAME ENDPOINTS AGE
|
||||||
|
<SERVICE-NAME> <PROXIED-HOST>:<PROXIED-PORT> 3h
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверяем, что сертификат создан:
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
|
||||||
|

|
@@ -23,8 +23,8 @@
|
|||||||
│ └── cclient-shadowsocks--izmir/ # Локация Измир
|
│ └── cclient-shadowsocks--izmir/ # Локация Измир
|
||||||
│ ├── config.yaml
|
│ ├── config.yaml
|
||||||
│ └── deployment.yaml
|
│ └── deployment.yaml
|
||||||
├── ...
|
├── …
|
||||||
└── ...
|
└── …
|
||||||
```
|
```
|
||||||
|
|
||||||
Создаем файл `config.yaml` для первого Shadowsocks-клиента (Москва):
|
Создаем файл `config.yaml` для первого Shadowsocks-клиента (Москва):
|
||||||
@@ -61,7 +61,7 @@ data:
|
|||||||
- `data:` — данные конфигурации.
|
- `data:` — данные конфигурации.
|
||||||
- `config.json:` — имя файла, в который будет записан конфиг.
|
- `config.json:` — имя файла, в который будет записан конфиг.
|
||||||
- `|` — говорит, что дальше будет многострочный текст.
|
- `|` — говорит, что дальше будет многострочный текст.
|
||||||
- `{...}` — Собственно JSON-конфигурация нашего Shadowsocks-клиента.
|
- `{…}` — Собственно JSON-конфигурация нашего Shadowsocks-клиента.
|
||||||
- `server` и `server_port` — адрес и порт нашего VPS.
|
- `server` и `server_port` — адрес и порт нашего VPS.
|
||||||
- `local_address` и `local_port` — где будет SOCKS5 внутри кластера.
|
- `local_address` и `local_port` — где будет SOCKS5 внутри кластера.
|
||||||
- `password` и `method` — пароль и метод шифрования. Метод шифрования `chacha20-ietf-poly1305` -- используется,
|
- `password` и `method` — пароль и метод шифрования. Метод шифрования `chacha20-ietf-poly1305` -- используется,
|
||||||
@@ -150,10 +150,10 @@ sudo k3s kubectl get pods -n kube-system
|
|||||||
Увидим что-то типа:
|
Увидим что-то типа:
|
||||||
```text
|
```text
|
||||||
NAME READY STATUS RESTARTS AGE
|
NAME READY STATUS RESTARTS AGE
|
||||||
...
|
…
|
||||||
...
|
…
|
||||||
shadowsocks-client-moscow-54d64bf5f4-trb6p 1/1 Running 0 24m
|
shadowsocks-client-moscow-54d64bf5f4-trb6p 1/1 Running 0 24m
|
||||||
...
|
…
|
||||||
```
|
```
|
||||||
|
|
||||||
Можно проверь логи:
|
Можно проверь логи:
|
||||||
@@ -232,10 +232,10 @@ sudo k3s ctr images ls | grep shadowsocks
|
|||||||
|
|
||||||
Увидим что-то типа:
|
Увидим что-то типа:
|
||||||
```text
|
```text
|
||||||
...
|
…
|
||||||
docker.io/library/shadowsocks-with-tools:latest application/vnd.oci.image.manifest.v1+json sha256:... 22.5 MiB linux/arm64 io.cri-containerd.image=managed
|
docker.io/library/shadowsocks-with-tools:latest application/vnd.oci.image.manifest.v1+json sha256:… 22.5 MiB linux/arm64 io.cri-containerd.image=managed
|
||||||
...
|
…
|
||||||
...
|
…
|
||||||
```
|
```
|
||||||
|
|
||||||
Теперь нам нужно передать образ контейнера на другие ноды кластера. Как это сделать есть заметка "[Развертывание
|
Теперь нам нужно передать образ контейнера на другие ноды кластера. Как это сделать есть заметка "[Развертывание
|
||||||
@@ -246,15 +246,15 @@ docker.io/library/shadowsocks-with-tools:latest application/vnd.oci.image.ma
|
|||||||
новый образ. Закомментируем строку `image: shadowsocks/shadowsocks-libev:latest` и вставим две строки после неё
|
новый образ. Закомментируем строку `image: shadowsocks/shadowsocks-libev:latest` и вставим две строки после неё
|
||||||
(обратите внимание на заметки):
|
(обратите внимание на заметки):
|
||||||
```yaml
|
```yaml
|
||||||
...
|
…
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: shadowsocks-client
|
- name: shadowsocks-client
|
||||||
# image: shadowsocks/shadowsocks-libev:latest
|
# image: shadowsocks/shadowsocks-libev:latest
|
||||||
image: shadowsocks-with-tools # Без :latest, чтобы k3s не "ходил" за контейнером в реестр (например, DockerHub)
|
image: shadowsocks-with-tools # Без :latest, чтобы k3s не "ходил" за контейнером в реестр (например, DockerHub)
|
||||||
imagePullPolicy: Never # Только локальный образ, не тянуть из реестра
|
imagePullPolicy: Never # Только локальный образ, не тянуть из реестра
|
||||||
...
|
…
|
||||||
...
|
…
|
||||||
```
|
```
|
||||||
|
|
||||||
Уберём старый под из deployment и удалим сам под из k3s:
|
Уберём старый под из deployment и удалим сам под из k3s:
|
||||||
@@ -276,9 +276,9 @@ sudo k3s kubectl get pods -n kube-system
|
|||||||
Увидим что-то типа:
|
Увидим что-то типа:
|
||||||
```text
|
```text
|
||||||
NAME READY STATUS RESTARTS AGE
|
NAME READY STATUS RESTARTS AGE
|
||||||
...
|
…
|
||||||
shadowsocks-client-moscow-6cf7b956b8-mtsg4 1/1 Running 0 9s
|
shadowsocks-client-moscow-6cf7b956b8-mtsg4 1/1 Running 0 9s
|
||||||
...
|
…
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Проверка работы Shadowsocks
|
#### Проверка работы Shadowsocks
|
||||||
@@ -344,12 +344,12 @@ sudo k3s kubectl logs -n kube-system shadowsocks-client-moscow-<hash>
|
|||||||
Для этого нужно изменить _local_address_ в конфиге shadowsocks-клиента `config.yaml`:
|
Для этого нужно изменить _local_address_ в конфиге shadowsocks-клиента `config.yaml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
...
|
…
|
||||||
"server_port": <ПОРТ>,
|
"server_port": <ПОРТ>,
|
||||||
# "local_address": "127.0.0.1",
|
# "local_address": "127.0.0.1",
|
||||||
"local_address": "0.0.0.0",
|
"local_address": "0.0.0.0",
|
||||||
"local_port": 1081,
|
"local_port": 1081,
|
||||||
...
|
…
|
||||||
```
|
```
|
||||||
|
|
||||||
Применим конфиг:
|
Применим конфиг:
|
||||||
@@ -437,9 +437,9 @@ sudo k3s kubectl get service -n kube-system
|
|||||||
Увидим что-то типа:
|
Увидим что-то типа:
|
||||||
```text
|
```text
|
||||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||||
...
|
…
|
||||||
ss-moscow-service ClusterIP 10.43.236.81 <none> 1081/TCP,1081/UDP 5m5s
|
ss-moscow-service ClusterIP 10.43.236.81 <none> 1081/TCP,1081/UDP 5m5s
|
||||||
...
|
…
|
||||||
```
|
```
|
||||||
|
|
||||||
Теперь другие поды могут обращаться к `ss-moscow-service.kube-system.svc.cluster.local:1081` как к SOCKS5-прокси.
|
Теперь другие поды могут обращаться к `ss-moscow-service.kube-system.svc.cluster.local:1081` как к SOCKS5-прокси.
|
||||||
|
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 из указанных стран при появлении их в логах.
|
||||||
|
И если честно, можно вообще все страны забанить, кроме тех, где бываю в отпуске. Нечего им делать на моем сервере. :)
|
||||||
|
|
@@ -20,7 +20,7 @@ IoT-устройства, edge-серверы и т.п.). Для кластер
|
|||||||
контейнеры всё равно запускаются runtime’ом (это containerd в k3s). И Docker все равно еще нужен для создания
|
контейнеры всё равно запускаются runtime’ом (это containerd в k3s). И Docker все равно еще нужен для создания
|
||||||
образов, и если при установке k3s не указать `--docker` то k3s будет использовать его как runtime._
|
образов, и если при установке k3s не указать `--docker` то k3s будет использовать его как runtime._
|
||||||
|
|
||||||
Но, есть у k3s и минус для конкретно моего случая -- распределенная база **etcd**, в которой хранится состояния
|
Но, есть у k3s и минус для конкретно моего случая — распределенная база **etcd**, в которой хранится состояния
|
||||||
кластера, нод и подов, в нем заменена SQLite. Это круто для маленьких компьютеров: экономно по памяти и другим ресурсам,
|
кластера, нод и подов, в нем заменена SQLite. Это круто для маленьких компьютеров: экономно по памяти и другим ресурсам,
|
||||||
и, что главное, никак не сказывается на производительности (пока узлов меньше 50-80), но означает, что в кластере k3s
|
и, что главное, никак не сказывается на производительности (пока узлов меньше 50-80), но означает, что в кластере k3s
|
||||||
может быть только одна мастер-нода. Если мастер-нода упадет, её некому будет заменить и весь кластер умрет.
|
может быть только одна мастер-нода. Если мастер-нода упадет, её некому будет заменить и весь кластер умрет.
|
||||||
@@ -29,7 +29,7 @@ IoT-устройства, edge-серверы и т.п.). Для кластер
|
|||||||
|
|
||||||
### Важное предупреждение
|
### Важное предупреждение
|
||||||
|
|
||||||
k3s -- это не упрощенная мини-версия Kubernetes, здесь все компоненты упакованы в один бинарник, а значит намного
|
k3s — это не упрощенная мини-версия Kubernetes, здесь все компоненты упакованы в один бинарник, а значит намного
|
||||||
проще не только добавлять узлы, но и удалять их. Так что если что-то пойдет не так с настройкой узла, просто удалите
|
проще не только добавлять узлы, но и удалять их. Так что если что-то пойдет не так с настройкой узла, просто удалите
|
||||||
и начните заново. Удаление k3s с узла:
|
и начните заново. Удаление k3s с узла:
|
||||||
```bash
|
```bash
|
||||||
@@ -41,7 +41,7 @@ sudo /usr/local/bin/k3s-agent-uninstall.sh # На воркере
|
|||||||
|
|
||||||
Некоторые требования к узлам:
|
Некоторые требования к узлам:
|
||||||
* На всех Orange Pi установлена одинаковая версия Ubuntu (например, 22.04 или 24.04).
|
* На всех Orange Pi установлена одинаковая версия Ubuntu (например, 22.04 или 24.04).
|
||||||
* Статические IP-адреса узлов (или зрезервированные под MAC-адреса IP в DHCP).
|
* Статические IP-адреса узлов (или зарезервированные под MAC-адреса IP в DHCP).
|
||||||
* На уздах открыты порты 6443 (для API), 2379-2380 (для etcd) и 10250 (для kubelet).
|
* На уздах открыты порты 6443 (для API), 2379-2380 (для etcd) и 10250 (для kubelet).
|
||||||
|
|
||||||
|
|
||||||
@@ -51,14 +51,14 @@ curl -sfL https://get.k3s.io | sh -s - server --cluster-init --tls-san=192.168.1
|
|||||||
```
|
```
|
||||||
|
|
||||||
Здесь:
|
Здесь:
|
||||||
* `server` -- значение по умолчанию, устанавливает узел k3s в режиме *мастер* (control-plane). В этом режиме узел
|
* `server` — значение по умолчанию, устанавливает узел k3s в режиме *мастер* (control-plane). В этом режиме узел
|
||||||
будет запускать все компоненты управления Kubernetes: API-сервер, контроллер-менеджер, планировщик (scheduler).
|
будет запускать все компоненты управления Kubernetes: API-сервер, контроллер-менеджер, планировщик (scheduler).
|
||||||
Такой узел отвечает за управление кластером и может также выполнять рабочие нагрузки (workloads), если
|
Такой узел отвечает за управление кластером и может также выполнять рабочие нагрузки (workloads), если
|
||||||
не настроены ограничения (taints). Если бы мы указали `agent` -- был бы установлен узел k3s в режиме *воркер*-узла.
|
не настроены ограничения (taints). Если бы мы указали `agent` — был бы установлен узел k3s в режиме *воркер*-узла.
|
||||||
* `--cluster-init` -- добавляет поддержку высокой доступности (HA -- High Availability) через встроенный `etcd`. Это
|
* `--cluster-init` — добавляет поддержку высокой доступности (HA — High Availability) через встроенный `etcd`. Это
|
||||||
значит, что узел инициализирует новый кластер и готов к тому, чтобы другие мастер-узлы могли к нему подключиться
|
значит, что узел инициализирует новый кластер и готов к тому, чтобы другие мастер-узлы могли к нему подключиться
|
||||||
(для создания HA-конфигурации).
|
(для создания HA-конфигурации).
|
||||||
* `--tls-san=192.168.1.27` -- добавляет IP 192.168.1.27 в сертификаты API-сервера, чтобы другие узлы и клиенты
|
* `--tls-san=192.168.1.27` — добавляет IP 192.168.1.27 в сертификаты API-сервера, чтобы другие узлы и клиенты
|
||||||
могли обращаться к нему по этому адресу.
|
могли обращаться к нему по этому адресу.
|
||||||
|
|
||||||
Проверим, что все k3s запущен:
|
Проверим, что все k3s запущен:
|
||||||
@@ -70,14 +70,64 @@ sudo service k3s status
|
|||||||
```text
|
```text
|
||||||
● k3s.service - Lightweight Kubernetes
|
● k3s.service - Lightweight Kubernetes
|
||||||
Loaded: loaded (/etc/systemd/system/k3s.service; enabled; vendor preset: enabled)
|
Loaded: loaded (/etc/systemd/system/k3s.service; enabled; vendor preset: enabled)
|
||||||
Active: active (running) since ...
|
Active: active (running) since …
|
||||||
...
|
…
|
||||||
...
|
…
|
||||||
```
|
```
|
||||||
|
|
||||||
Посмотрим сколько нод в кластере:
|
## Перемещение конфигурации kubectl
|
||||||
|
|
||||||
|
При выполнении команд `kubectl` используется файл конфигурации из переменной `KUBECONFIG`. При установке k3s создает
|
||||||
|
его в `/etc/rancher/k3s/k3s.yaml` и у него права `600` (только для root). Чтобы использовать `kubectl` от имени
|
||||||
|
пользователя хоста, нужно скопировать файл конфигурации в домашнюю директорию и изменить права доступа.
|
||||||
|
|
||||||
```bash
|
```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
|
```bash
|
||||||
sudo kubectl get pods -A
|
kubectl get pod -A
|
||||||
```
|
```
|
||||||
|
|
||||||
Целых семь подов (минималистичная установка k3s):
|
Целых семь подов (минималистичная установка k3s):
|
||||||
@@ -106,23 +156,31 @@ kube-system svclb-traefik-4f8c2580-jddgz 2/2 Running 0
|
|||||||
kube-system traefik-5d45fc8cc9-t5d58 1/1 Running 0 12m
|
kube-system traefik-5d45fc8cc9-t5d58 1/1 Running 0 12m
|
||||||
```
|
```
|
||||||
|
|
||||||
Тут статус X/Y в выводе kubectl get pods показывает:
|
* READY `X/Y` в выводе `kubectl get pod` показывает:
|
||||||
* Y — сколько контейнеров должно быть в поде (по спецификации).
|
* Y — сколько контейнеров должно быть в поде (по спецификации).
|
||||||
* X — сколько из них сейчас работает (running).
|
* X — сколько из них сейчас работает (running).
|
||||||
|
* STATUS показывает состояние пода. Основные статусы которые могут быть:
|
||||||
|
* `Running` — под работает и все контейнеры в нем запущены.
|
||||||
|
* `Completed` — под завершил свою работу (например, Job или CronJob).
|
||||||
|
* `Error` — под завершился с ошибкой.
|
||||||
|
* `ContainerCreating` — под в процессе создания, контейнеры в нем еще не запущены.
|
||||||
|
* `CrashLoopBackOff` — под перезапускается из-за ошибки.
|
||||||
|
* `ImagePullBackOff` — не удалось загрузить образ контейнера.
|
||||||
|
* `Pending` — под ожидает ресурсов (например, памяти или CPU).
|
||||||
|
|
||||||
Представлены следующие поды:
|
Представлены следующие поды:
|
||||||
1. `coredns` — это DNS-сервер для кластера. Он отвечает за разрешение имен внутри Kubernetes (например, чтобы поды
|
1. `coredns` — это DNS-сервер для кластера. Он отвечает за разрешение имен внутри Kubernetes (например, чтобы поды
|
||||||
могли обращаться друг к другу по именам сервисов вроде my-service.default.svc.cluster.local).
|
могли обращаться друг к другу по именам сервисов вроде my-service.default.svc.cluster.local).
|
||||||
2. `helm-install-traefik-crd` -- это временный под (Job), который устанавливает Custom Resource Definitions (CRD)
|
2. `helm-install-traefik-crd` — это временный под (Job), который устанавливает Custom Resource Definitions (CRD)
|
||||||
для *Traefik* — ingress-контроллера, встроенного в k3s. CRD нужны для управления ingress-ресурсами
|
для *Traefik* — ingress-контроллера, встроенного в k3s. CRD нужны для управления ingress-ресурсами
|
||||||
(маршрутизацией HTTP/HTTPS). Этот под — одноразовая задача (Job), а не постоянный сервис. Он запустился, выполнил
|
(маршрутизацией HTTP/HTTPS). Этот под — одноразовая задача (Job), а не постоянный сервис. Он запустился, выполнил
|
||||||
работу (установил CRD) и завершился. Статус "*Completed*" значит, что он больше не работает.
|
работу (установил CRD) и завершился. Статус "*Completed*" значит, что он больше не работает.
|
||||||
3. `helm-install-traefik` -- ещё один Job, который устанавливает сам Traefik через Helm-чарт. Этот под развернул
|
3. `helm-install-traefik` — ещё один Job, который устанавливает сам Traefik через Helm-чарт. Этот под развернул
|
||||||
основной Traefik-под и завершился.
|
основной Traefik-под и завершился.
|
||||||
4. `local-path-provisioner` -- компонент для автоматического создания локальных Persistent Volumes (PV) на узлах. Он
|
4. `local-path-provisioner` — компонент для автоматического создания локальных Persistent Volumes (PV) на узлах. Он
|
||||||
позволяет подам запрашивать хранилище (например, через PersistentVolumeClaim) без сложной настройки NFS или внешних
|
позволяет подам запрашивать хранилище (например, через PersistentVolumeClaim) без сложной настройки NFS или внешних
|
||||||
хранилищ. В k3s это встроено для простоты.
|
хранилищ. В k3s это встроено для простоты.
|
||||||
5. `metrics-server` -- собирает данные об использовании ресурсов (CPU, память) подов и узлов. Это нужно для команд
|
5. `metrics-server` — собирает данные об использовании ресурсов (CPU, память) подов и узлов. Это нужно для команд
|
||||||
вроде `kubectl top` или для Horizontal Pod Autoscaler (HPA). Установку метрик можно отключить при запуске k3s
|
вроде `kubectl top` или для Horizontal Pod Autoscaler (HPA). Установку метрик можно отключить при запуске k3s
|
||||||
флагом `--disable=metrics-server`.
|
флагом `--disable=metrics-server`.
|
||||||
6. `svclb-traefik` - это под для балансировки нагрузки (Service Load Balancer) для Traefik. В k3s нет встроенного
|
6. `svclb-traefik` - это под для балансировки нагрузки (Service Load Balancer) для Traefik. В k3s нет встроенного
|
||||||
@@ -131,19 +189,19 @@ kube-system traefik-5d45fc8cc9-t5d58 1/1 Running 0
|
|||||||
* один для самой логики балансировки;
|
* один для самой логики балансировки;
|
||||||
* другой для мониторинга или дополнительной функциональности (например, *keepalived* или аналога) и это зависит
|
* другой для мониторинга или дополнительной функциональности (например, *keepalived* или аналога) и это зависит
|
||||||
от реализации в k3s.
|
от реализации в k3s.
|
||||||
7. `traefik` -- сам Traefik, ingress-контроллер, который обрабатывает HTTP/HTTPS трафик кластера и маршрутизирует
|
7. `traefik` — сам Traefik, ingress-контроллер, который обрабатывает HTTP/HTTPS трафик кластера и маршрутизирует
|
||||||
его к соответствующим подам (с динамической конфигурацией нашим) и сервисам по правилам Ingress. Traefik в k3s
|
его к соответствующим подам (с динамической конфигурацией нашим) и сервисам по правилам Ingress. Traefik в k3s
|
||||||
установлен по умолчанию, но его можно отключить при запуске k3s флагом `--disable=traefik` (не будет ни *traefik*,
|
установлен по умолчанию, но его можно отключить при запуске k3s флагом `--disable=traefik` (не будет ни *traefik*,
|
||||||
ни *svclb*, ни связанных *Helm Jobs*).
|
ни *svclb*, ни связанных *Helm Jobs*).
|
||||||
|
|
||||||
Обратите внимание, что, например, под `coredns` получил имя `coredns-ccb96694c-tfjwj`. Имена подов (Pod Names)
|
Обратите внимание, что, например, под `coredns` получил имя `coredns-ccb96694c-tfjwj`. Имена подов (Pod Names)
|
||||||
в Kubernetes генерируются автоматически на основе правил, чтобы каждый под в кластере имел уникальное имя.
|
в Kubernetes генерируются автоматически на основе правил, чтобы каждый под в кластере имел уникальное имя.
|
||||||
Структура имени -- `<имя-приложения>-<хеш-ревизии>-<случайный-суффикс>`. Впрочем, `<хеш-ревизии>` может отсутствовать,
|
Структура имени — `<имя-приложения>-<хеш-ревизии>-<случайный-суффикс>`. Впрочем, `<хеш-ревизии>` может отсутствовать,
|
||||||
если под не имеет контроллера репликации (например, Job или CronJob).
|
если под не имеет контроллера репликации (например, Job или CronJob).
|
||||||
|
|
||||||
Можно проверить, что API нашего узла (кластера) отвечает:
|
Можно проверить, что API нашего узла (кластера) отвечает на порту `6443` (порт по умолчанию):
|
||||||
```bash
|
```bash
|
||||||
curl -k https://192.168.1.27
|
curl -k https://192.168.1.27:6443
|
||||||
```
|
```
|
||||||
|
|
||||||
Здесь ключ `-k` означает, что мы не проверяем сертификаты (нам важно только, что сервер отвечает). Должны получить
|
Здесь ключ `-k` означает, что мы не проверяем сертификаты (нам важно только, что сервер отвечает). Должны получить
|
||||||
@@ -160,15 +218,105 @@ 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
|
```bash
|
||||||
sudo cat /var/lib/rancher/k3s/server/node-token
|
sudo cat /var/lib/rancher/k3s/server/node-token
|
||||||
```
|
```
|
||||||
|
|
||||||
Вывод будет что-то вроде `K10...::server:longrandomstring`. Это и есть токен, который нужно будет использовать.
|
Вывод будет что-то вроде `K10…::server:longrandomstring`. Это и есть токен, который нужно будет использовать.
|
||||||
|
|
||||||
Теперь на втором Orange Pi (например, с IP 192.168.1.28) можно запустить второй мастер-узел (вставим токен
|
Теперь на втором Orange Pi (например, с IP 192.168.1.28) можно запустить второй мастер-узел (вставим токен
|
||||||
из предыдущего шага):
|
из предыдущего шага):
|
||||||
@@ -176,9 +324,9 @@ sudo cat /var/lib/rancher/k3s/server/node-token
|
|||||||
curl -sfL https://get.k3s.io | sh -s - server --server https://192.168.1.27:6443 --token <ТОКЕН> --tls-san=192.168.1.28
|
curl -sfL https://get.k3s.io | sh -s - server --server https://192.168.1.27:6443 --token <ТОКЕН> --tls-san=192.168.1.28
|
||||||
```
|
```
|
||||||
Здесь ключи:
|
Здесь ключи:
|
||||||
* `--server https://192.168.1.27:6443` -- указывает на API мастер-узла, чтобы наш новый узел мог подключиться к кластеру.
|
* `--server https://192.168.1.27:6443` — указывает на API мастер-узла, чтобы наш новый узел мог подключиться к кластеру.
|
||||||
* `--token` — токен аутентификации из предыдущего шага.
|
* `--token` — токен аутентификации из предыдущего шага.
|
||||||
* `--tls-san=192.168.1.28` -- добавляет IP нашего второго мастера в сертификаты (для будущих подключений).
|
* `--tls-san=192.168.1.28` — добавляет IP нашего второго мастера в сертификаты (для будущих подключений).
|
||||||
|
|
||||||
Проверим какие теперь ноды в кластере:
|
Проверим какие теперь ноды в кластере:
|
||||||
```bash
|
```bash
|
||||||
@@ -210,7 +358,7 @@ kube-system svclb-traefik-4f8c2580-xzt5d 2/2 Running 0
|
|||||||
kube-system traefik-5d45fc8cc9-t5d58 1/1 Running 0 2h 10.42.0.8 opi5plus-2 <none> <none>
|
kube-system traefik-5d45fc8cc9-t5d58 1/1 Running 0 2h 10.42.0.8 opi5plus-2 <none> <none>
|
||||||
```
|
```
|
||||||
|
|
||||||
Как видим, у нас появился еще один `svclb-traefik` на второй ноде. Это под -- Service Load Balancer (SLB) для Traefik.
|
Как видим, у нас появился еще один `svclb-traefik` на второй ноде. Это под — Service Load Balancer (SLB) для Traefik.
|
||||||
Он эмулирует облачный балансировщик нагрузки (типа AWS ELB), которого нет в локальном окружении вроде Orange Pi.
|
Он эмулирует облачный балансировщик нагрузки (типа AWS ELB), которого нет в локальном окружении вроде Orange Pi.
|
||||||
SLB перенаправляет внешний трафик (например, на порты 80/443) к сервисам типа LoadBalancer внутри кластера.
|
SLB перенаправляет внешний трафик (например, на порты 80/443) к сервисам типа LoadBalancer внутри кластера.
|
||||||
|
|
||||||
@@ -226,7 +374,7 @@ curl -sfL https://get.k3s.io | sh -s - agent --server https://192.168.1.10:6443
|
|||||||
```
|
```
|
||||||
|
|
||||||
Здесь ключ:
|
Здесь ключ:
|
||||||
* `agent` -- устанавливает узел в режиме воркера (worker). Это значит, что узел будет выполнять рабочие нагрузки
|
* `agent` — устанавливает узел в режиме воркера (worker). Это значит, что узел будет выполнять рабочие нагрузки
|
||||||
(поды), но не будет управлять кластером (без *control-plane*, *master* и на нем нет реплики *etcd*).
|
(поды), но не будет управлять кластером (без *control-plane*, *master* и на нем нет реплики *etcd*).
|
||||||
|
|
||||||
Посмотрим на ноды (команда выполняется на одном из мастер-узлов):
|
Посмотрим на ноды (команда выполняется на одном из мастер-узлов):
|
||||||
@@ -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 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 с тремя нодами: двумя мастерами и одним воркером. Но, как я уже говорил, это не
|
Что ж, теперь у нас есть кластер k3s с тремя нодами: двумя мастерами и одним воркером. Но, как я уже говорил, это не
|
||||||
идеальная конфигурация, так как у нас четное количество мастер-узлов.
|
идеальная конфигурация, так как у нас четное количество мастер-узлов.
|
||||||
@@ -301,7 +448,7 @@ traefik LoadBalancer 10.43.164.48 192.168.1.26,192.168.1.27,192.16
|
|||||||
им не нужен доступ к etcd в реальном времени.
|
им не нужен доступ к etcd в реальном времени.
|
||||||
|
|
||||||
В чем может быть смысл иметь два мастера? Это обеспечивает репликацию данных (второй хранит копию etcd), но не
|
В чем может быть смысл иметь два мастера? Это обеспечивает репликацию данных (второй хранит копию etcd), но не
|
||||||
даёт отказоустойчивости -- когда один мастер упал, кластер становится неуправляемым (нет управления через kubectl),
|
даёт отказоустойчивости — когда один мастер упал, кластер становится неуправляемым (нет управления через kubectl),
|
||||||
рабочие нагрузки (поды) могут продолжать работать, пока жив хотя бы один узел, но новые изменения (развертывание
|
рабочие нагрузки (поды) могут продолжать работать, пока жив хотя бы один узел, но новые изменения (развертывание
|
||||||
подов и обновления) невозможны.
|
подов и обновления) невозможны.
|
||||||
|
|
||||||
@@ -399,7 +546,7 @@ traefik-6c979cd89d-z6wwm 1/1 Running 0 2
|
|||||||
```
|
```
|
||||||
|
|
||||||
Хотя, в целом, кластер остается рабочим, и сам чинится при отключении и восстановлении узлов, но если отключается нода
|
Хотя, в целом, кластер остается рабочим, и сам чинится при отключении и восстановлении узлов, но если отключается нода
|
||||||
на которой исполняется под с `coredns` -- то временно будет затруднен перезапуска и создание новых подов, а значит
|
на которой исполняется под с `coredns` — то временно будет затруднен перезапуска и создание новых подов, а значит
|
||||||
и "переезд" подов с погашенного узла, до восстановления `coredns` тоже будет замедлен. Кроме того, если сценарий
|
и "переезд" подов с погашенного узла, до восстановления `coredns` тоже будет замедлен. Кроме того, если сценарий
|
||||||
приложения(ий) развернутых внутри k3s предполагает переподключение с использованием имен подов или обнаружение подов,
|
приложения(ий) развернутых внутри k3s предполагает переподключение с использованием имен подов или обнаружение подов,
|
||||||
то это тоже перестанет работать.
|
то это тоже перестанет работать.
|
||||||
@@ -410,12 +557,12 @@ sudo k3s kubectl edit deployment coredns -n kube-system
|
|||||||
```
|
```
|
||||||
|
|
||||||
Здесь:
|
Здесь:
|
||||||
* `kubectl edit` -- Открывает редактор (по умолчанию *vim*) для изменения ресурса Kubernetes напрямую в кластере.
|
* `kubectl edit` — Открывает редактор (по умолчанию *vim*) для изменения ресурса Kubernetes напрямую в кластере.
|
||||||
Вместо создания локального YAML-файла и применения его через `kubectl apply`, мы сразу редактируем "живой" конфиг.
|
Вместо создания локального YAML-файла и применения его через `kubectl apply`, мы сразу редактируем "живой" конфиг.
|
||||||
* `deployment coredns` -- Указывает, что редактируем объект типа *deployment* с именем `coredns`. Deployment — это
|
* `deployment coredns` — Указывает, что редактируем объект типа *deployment* с именем `coredns`. Deployment — это
|
||||||
контроллер, который управляет набором подов (в данном случае coredns), обеспечивая их количество (реплики),
|
контроллер, который управляет набором подов (в данном случае coredns), обеспечивая их количество (реплики),
|
||||||
перезапуск и обновления.
|
перезапуск и обновления.
|
||||||
* `-n kube-system` -- Указывает пространство имён (namespace), где находится *coredns8. В k3s системные компоненты,
|
* `-n kube-system` — Указывает пространство имён (namespace), где находится *coredns8. В k3s системные компоненты,
|
||||||
к которым относится *coredns(, обычно живут в kube-system.
|
к которым относится *coredns(, обычно живут в kube-system.
|
||||||
|
|
||||||
В открывшемся окне найдем строку `replicas: 1` и заменим её на `replicas: 2`.
|
В открывшемся окне найдем строку `replicas: 1` и заменим её на `replicas: 2`.
|
||||||
@@ -431,7 +578,7 @@ spec:
|
|||||||
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
|
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
|
||||||
coredns-ccb96694c-n4qsp 0/1 ContainerCreating 0 5s <none> opi5plus-1 <none> <none>
|
coredns-ccb96694c-n4qsp 0/1 ContainerCreating 0 5s <none> opi5plus-1 <none> <none>
|
||||||
coredns-ccb96694c-wzh96 1/1 Running 0 3h10m 10.42.1.8 opi5plus-3 <none> <none>
|
coredns-ccb96694c-wzh96 1/1 Running 0 3h10m 10.42.1.8 opi5plus-3 <none> <none>
|
||||||
...
|
…
|
||||||
```
|
```
|
||||||
|
|
||||||
А затем:
|
А затем:
|
||||||
@@ -439,7 +586,7 @@ coredns-ccb96694c-wzh96 1/1 Running 0
|
|||||||
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
|
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
|
||||||
coredns-ccb96694c-n4qsp 1/1 Running 0 15s 10.42.2.6 opi5plus-1 <none> <none>
|
coredns-ccb96694c-n4qsp 1/1 Running 0 15s 10.42.2.6 opi5plus-1 <none> <none>
|
||||||
coredns-ccb96694c-wzh96 1/1 Running 0 3h10m 10.42.1.8 opi5plus-3 <none> <none>
|
coredns-ccb96694c-wzh96 1/1 Running 0 3h10m 10.42.1.8 opi5plus-3 <none> <none>
|
||||||
...
|
…
|
||||||
```
|
```
|
||||||
|
|
||||||
**Как это будет работать?** Обе реплики `coredns` привязаны к сервису `kube-dns` в пространстве имён `kube-system`.
|
**Как это будет работать?** Обе реплики `coredns` привязаны к сервису `kube-dns` в пространстве имён `kube-system`.
|
||||||
@@ -455,10 +602,10 @@ sudo k3s kubectl get endpoints kube-dns -n kube-system
|
|||||||
гасили при экспериментах с устойчивостью кластера:
|
гасили при экспериментах с устойчивостью кластера:
|
||||||
```text
|
```text
|
||||||
NAME ENDPOINTS AGE
|
NAME ENDPOINTS AGE
|
||||||
kube-dns 10.42.1.8:53,10.42.2.6:53,10.42.1.8:53 + 3 more... 5d23h
|
kube-dns 10.42.1.8:53,10.42.2.6:53,10.42.1.8:53 + 3 more… 5d23h
|
||||||
```
|
```
|
||||||
|
|
||||||
Каждый под `coredns` -- самостоятельный DNS-сервер. Они не взаимодействуют друг с другом и не обмениваются данными. Это
|
Каждый под `coredns` — самостоятельный DNS-сервер. Они не взаимодействуют друг с другом и не обмениваются данными. Это
|
||||||
просто экземпляры одного и того же сервиса, работающие параллельно. Они независимы, получают данные из API Kubernetes
|
просто экземпляры одного и того же сервиса, работающие параллельно. Они независимы, получают данные из API Kubernetes
|
||||||
и отвечают на запросы параллельно. В каждом поде кластера в качестве DNS настроен `kube-dns` (задаётся в файле
|
и отвечают на запросы параллельно. В каждом поде кластера в качестве DNS настроен `kube-dns` (задаётся в файле
|
||||||
`/etc/resolv.conf` внутри пода). Когда под отправляет DNS-запрос, его получит `kube-dns` и перенаправит запрос
|
`/etc/resolv.conf` внутри пода). Когда под отправляет DNS-запрос, его получит `kube-dns` и перенаправит запрос
|
||||||
@@ -488,6 +635,10 @@ rpi3b Ready <none> 27s v1.31.6+k3s1
|
|||||||
официальной поддержки Kubernetes/k3s для узлов на macOS (можно использовать обходные пути с использованием виртуальныех
|
официальной поддержки Kubernetes/k3s для узлов на macOS (можно использовать обходные пути с использованием виртуальныех
|
||||||
машин).
|
машин).
|
||||||
|
|
||||||
|
> **На всякий случай:**
|
||||||
|
>
|
||||||
|
> Если для вашего Kubernetes-кластера требуется блочное хранилище `longhorn` (для обеспечения репликации файлов между узлами кластера и высокой доступности данных), то понадобится модуль `iSCSI` (_Internet Small Computer System Interface_) на уровне системы. В составе Ubuntu 22.04 для Orange Pi 5 этого модуля нет. Потребуется [компиляция ядра](opi5plus-rebuilding-linux-kernel-for-iscsi.md).
|
||||||
|
|
||||||
### Добавление узлов во "внешнем" интернете
|
### Добавление узлов во "внешнем" интернете
|
||||||
|
|
||||||
В моем проекте (специализированном поисковике) будет нужно парсить и интернет сайты, включая заблокированные сайты.
|
В моем проекте (специализированном поисковике) будет нужно парсить и интернет сайты, включая заблокированные сайты.
|
||||||
@@ -495,6 +646,8 @@ rpi3b Ready <none> 27s v1.31.6+k3s1
|
|||||||
не сработает. Выходом может стать использование воркер-узлов во внешнем интернете. Идея в том, что если какой-нибудь
|
не сработает. Выходом может стать использование воркер-узлов во внешнем интернете. Идея в том, что если какой-нибудь
|
||||||
URL не получится обработать на поде одного узла, то можно попробовать обработать его на другом узле, в другой локации.
|
URL не получится обработать на поде одного узла, то можно попробовать обработать его на другом узле, в другой локации.
|
||||||
|
|
||||||
|
#### Настройка Keepalived
|
||||||
|
|
||||||
Так как узлы k3s взаимодействуют через API на 6443-порте, то для доступа к кластеру из внешнего интернета нужно будет
|
Так как узлы k3s взаимодействуют через API на 6443-порте, то для доступа к кластеру из внешнего интернета нужно будет
|
||||||
обеспечить проброс трафика через роутер сети на один из мастер-узлов. НО у нас три мастер-узла, а значит если упадет
|
обеспечить проброс трафика через роутер сети на один из мастер-узлов. НО у нас три мастер-узла, а значит если упадет
|
||||||
узел на который происходит проброс, то удаленный воркер-узел "отвелится" и потеряет доступ к кластеру. Объединить
|
узел на который происходит проброс, то удаленный воркер-узел "отвелится" и потеряет доступ к кластеру. Объединить
|
||||||
@@ -514,8 +667,8 @@ sudo apt install keepalived
|
|||||||
sudo nano /etc/keepalived/keepalived.conf
|
sudo nano /etc/keepalived/keepalived.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
На первом мастер-узле (хост -- `opi5plus-1`, IP -- `192.168.1.26`):
|
На первом мастер-узле (хост — `opi5plus-1`, IP — `192.168.1.26`):
|
||||||
```text
|
```pycon
|
||||||
vrrp_instance VI_1 {
|
vrrp_instance VI_1 {
|
||||||
state MASTER # ЭТО ГЛАВНЫЙ ХОСТ. ПО УМОЛЧАНИЮ ТРАФИК С VIP БУДЕТ ПЕРЕНАПРАВЛЯТЬСЯ НА ЭТОТ ХОСТ
|
state MASTER # ЭТО ГЛАВНЫЙ ХОСТ. ПО УМОЛЧАНИЮ ТРАФИК С VIP БУДЕТ ПЕРЕНАПРАВЛЯТЬСЯ НА ЭТОТ ХОСТ
|
||||||
interface enP4p65s0 # У Orange Pi 5 plus два интерфейса, и хост подключен по интерфейсу enP4p65s0
|
interface enP4p65s0 # У Orange Pi 5 plus два интерфейса, и хост подключен по интерфейсу enP4p65s0
|
||||||
@@ -533,8 +686,8 @@ vrrp_instance VI_1 {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
На втором мастер-узле (хост -- `opi5plus-2`, IP -- `192.168.1.27`):
|
На втором мастер-узле (хост — `opi5plus-2`, IP — `192.168.1.27`):
|
||||||
```text
|
```pycon
|
||||||
vrrp_instance VI_1 {
|
vrrp_instance VI_1 {
|
||||||
state BACKUP # ЭТО ВТОРОЙ ХОСТ. ОН БУДЕТ ПОЛУЧАТЬ ТРАФИК С VIP, ЕСЛИ ГЛАВНЫЙ ХОСТ УПАДЕТ
|
state BACKUP # ЭТО ВТОРОЙ ХОСТ. ОН БУДЕТ ПОЛУЧАТЬ ТРАФИК С VIP, ЕСЛИ ГЛАВНЫЙ ХОСТ УПАДЕТ
|
||||||
interface enP4p65s0 # У Orange Pi 5 plus два интерфейса, и хост подключен по интерфейсу enP4p65s0
|
interface enP4p65s0 # У Orange Pi 5 plus два интерфейса, и хост подключен по интерфейсу enP4p65s0
|
||||||
@@ -552,8 +705,8 @@ vrrp_instance VI_1 {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
И, наконец, на третьем мастер-узле (хост -- `opi5plus-3`, IP -- `192.168.1.28`):
|
И, наконец, на третьем мастер-узле (хост — `opi5plus-3`, IP — `192.168.1.28`):
|
||||||
```text
|
```pycon
|
||||||
vrrp_instance VI_1 {
|
vrrp_instance VI_1 {
|
||||||
state BACKUP # ЭТО ТРЕТИЙ ХОСТ. ОН БУДЕТ ПОЛУЧАТЬ ТРАФИК С VIP, ЕСЛИ ГЛАВНЫЙ- И БЭКАП-ХОСТ УПАДЕТ
|
state BACKUP # ЭТО ТРЕТИЙ ХОСТ. ОН БУДЕТ ПОЛУЧАТЬ ТРАФИК С VIP, ЕСЛИ ГЛАВНЫЙ- И БЭКАП-ХОСТ УПАДЕТ
|
||||||
interface enP4p65s0 # У Orange Pi 5 plus два интерфейса, и этот узел подключен по enP4p65s0
|
interface enP4p65s0 # У Orange Pi 5 plus два интерфейса, и этот узел подключен по enP4p65s0
|
||||||
@@ -584,15 +737,15 @@ ip addr show
|
|||||||
|
|
||||||
то увидим:
|
то увидим:
|
||||||
```text
|
```text
|
||||||
...
|
…
|
||||||
...
|
…
|
||||||
2: enP4p65s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
|
2: enP4p65s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
|
||||||
link/ether c0:74:2b:fd:42:3c brd ff:ff:ff:ff:ff:ff
|
link/ether c0:74:2b:fd:42:3c brd ff:ff:ff:ff:ff:ff
|
||||||
inet 192.168.1.26/24 brd 192.168.1.255 scope global dynamic noprefixroute enP4p65s0
|
inet 192.168.1.26/24 brd 192.168.1.255 scope global dynamic noprefixroute enP4p65s0
|
||||||
valid_lft 68779sec preferred_lft 68779sec
|
valid_lft 68779sec preferred_lft 68779sec
|
||||||
inet 192.168.1.200/32 scope global enP4p65s0
|
inet 192.168.1.200/32 scope global enP4p65s0
|
||||||
valid_lft forever preferred_lft forever
|
valid_lft forever preferred_lft forever
|
||||||
...
|
…
|
||||||
```
|
```
|
||||||
Обратите внимание на виртуальный IP-адрес `192.168.1.200` находится в другой подсети (CIDR) и имеет маску `/32` (то
|
Обратите внимание на виртуальный IP-адрес `192.168.1.200` находится в другой подсети (CIDR) и имеет маску `/32` (то
|
||||||
есть с маской подсети `255.255.255.255`). Это "точечная" подсеть, содержащая только один адрес, не привязан к основной
|
есть с маской подсети `255.255.255.255`). Это "точечная" подсеть, содержащая только один адрес, не привязан к основной
|
||||||
@@ -648,9 +801,9 @@ vps-sw-eye Ready <none> 35m v1.31.6+k3s1
|
|||||||
|
|
||||||
Таким образом, для управления удаленным узлом нужно чтобы он имел локальный IP-адрес в домашней сети, а не внешний.
|
Таким образом, для управления удаленным узлом нужно чтобы он имел локальный IP-адрес в домашней сети, а не внешний.
|
||||||
SSH-тоннель с помощью `autossh` и упаковкой UDP-трафика в TCP через `socat` не сработает (а я надеялся). Таким образом
|
SSH-тоннель с помощью `autossh` и упаковкой UDP-трафика в TCP через `socat` не сработает (а я надеялся). Таким образом
|
||||||
"пробросить" Flannel для полноценного подключения удаленного k3s-узла -- VPN-туннель между каждой мастер-нодой на
|
"пробросить" Flannel для полноценного подключения удаленного k3s-узла — VPN-туннель между каждой мастер-нодой на
|
||||||
удаленный узел. Это вполне рабочия вариант, если удаленные узлы -- полноценные и произвольные хосты. Но в моём
|
удаленный узел. Это вполне рабочия вариант, если удаленные узлы — полноценные и произвольные хосты. Но в моём
|
||||||
случае удаленный узел -- хост на 1 ядро и 1 ГБ ОЗУ. К тому же он на платформе x86_64, а не ARM, а значит ради одного
|
случае удаленный узел — хост на 1 ядро и 1 ГБ ОЗУ. К тому же он на платформе x86_64, а не ARM, а значит ради одного
|
||||||
узла не стоит заморачиваться с VPN.
|
узла не стоит заморачиваться с VPN.
|
||||||
|
|
||||||
Другим вариантом является подключение внутри самих подов на удаленном узле к необходимым сервисам напрямую. Но таким
|
Другим вариантом является подключение внутри самих подов на удаленном узле к необходимым сервисам напрямую. Но таким
|
||||||
@@ -658,5 +811,30 @@ SSH-тоннель с помощью `autossh` и упаковкой UDP-тра
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-----
|
||||||
|
Cертификаты
|
||||||
|
sudo kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.yaml
|
||||||
|
|
||||||
|
PVC Longhorn
|
||||||
|
sudo kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.5.3/deploy/longhorn.yaml
|
||||||
|
|
||||||
|
Установить Helm (менеджер пакетов для Kubernetes):
|
||||||
|
|
||||||
|
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
||||||
|
|
||||||
|
Добавить репозиторий Traefik в Helm:
|
||||||
|
helm repo add traefik https://helm.traefik.io/traefik
|
||||||
|
|
||||||
|
Обновить репозитории:
|
||||||
|
helm repo update
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Установить Traefik:
|
||||||
|
```bash
|
||||||
|
helm install traefik traefik/traefik -n kube-system --set service.type=LoadBalancer
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@@ -9,14 +9,14 @@ SSD NVMe, и для eMMC. Но, как и всякая магия, она мож
|
|||||||
|
|
||||||
## Установка накопителей
|
## Установка накопителей
|
||||||
|
|
||||||
Выключим Orange Pi 5 Plus и установим в него eMMC-носитель...
|
Выключим Orange Pi 5 Plus и установим в него eMMC-носитель…
|
||||||
|
|
||||||
| Фото до и после установки eMMC. Внимание, устанавливайте до щелчка с обоих сторон! |
|
| Фото до и после установки eMMC. Внимание, устанавливайте до щелчка с обоих сторон! |
|
||||||
|:-------------------------------------------------------------------------------------|
|
|:-------------------------------------------------------------------------------------|
|
||||||
|  |
|
|  |
|
||||||
|  |
|
|  |
|
||||||
|
|
||||||
...и/или SSD-накопитель NVMe
|
…и/или SSD-накопитель NVMe
|
||||||
|
|
||||||
| Фото до и после установки NVMe. Более просто в установке. Для надёжности закрепить винтиком. |
|
| Фото до и после установки NVMe. Более просто в установке. Для надёжности закрепить винтиком. |
|
||||||
|:---------------------------------------------------------------------------------------------------|
|
|:---------------------------------------------------------------------------------------------------|
|
||||||
|
@@ -0,0 +1,399 @@
|
|||||||
|
# Сборка ядра Linux для Orange Pi 5 Plus (и других устройств на Rockchip RK3588) с поддержкой iSCSI
|
||||||
|
|
||||||
|
Для моего проекта (k3s на Orange Pi 5 Plus) потребовалась поддержка система управления блочным хранилищем 'longhorn'
|
||||||
|
(longhorn.io). _Longhorn_ позволяет создавать надёжные тома для контейнеров, автоматически реплицируя их между узлами
|
||||||
|
и обеспечения высокую доступности приложений (если один узел выходит из строя, данные остаются доступны на репликах
|
||||||
|
на других узлах).
|
||||||
|
|
||||||
|
Для _longhorn_ нужен `iSCSI` (**Internet Small Computer System Interface**) — сетевой протокол, который по TCP/IP
|
||||||
|
подсоединяет удалённые диски так, будто они подключены физически. Longhorn через iSCSI обеспечивает передачу
|
||||||
|
данных между узлами по сети.
|
||||||
|
|
||||||
|
В ядре Linux поддержка _iSCSI_ реализована через модули, такие как `iscsi_tcp` и `scsi_transport_iscsi`. Однако,
|
||||||
|
в большинстве дистрибутивов для одноплатных компьютеров, включая и Orange Pi 5 Plus, эти модули отключены. Это делают
|
||||||
|
для экономии ресурсов, да и в целом, это нетипичный сценарий использования Orange Pi. В версии Ubuntu 22.04
|
||||||
|
для Orange Pi 5 Plus (в ядре от Xunlong), поддержка iSCSI тоже отключена, и это делает невозможным запуск Longhorn
|
||||||
|
из коробки.
|
||||||
|
|
||||||
|
**А значит нужно компиляция ядра (сбока)**
|
||||||
|
|
||||||
|
Мы вручную включим поддержку _iSCSI_ (модули `iscsi_tcp` и `scsi_transport_iscsi`), и cможем настроить
|
||||||
|
ядро под свои задачи. Однако компиляция и его замена требует осторожности и внимательности, ошибка в процессе может
|
||||||
|
привести к тому, что система не загрузится (но мы сделаем резервную копию текущего ядра, загрузчика, чтобы можно
|
||||||
|
было восстановить систему).
|
||||||
|
|
||||||
|
Обновления системы (например, через `apt`) с новым ядром будет проходить, как обычно: пакеты, утилиты и приложения
|
||||||
|
обновятся без проблем. Однако пересобранное ядро больше не будет автоматически обновляться через стандартные средства
|
||||||
|
дистрибутива. Если выйдет новая версия ядра, то придётся вручную повторить процесс компиляции. К счастью,
|
||||||
|
поставщик (это Xunlong) обновляет ядро не часто.
|
||||||
|
|
||||||
|
| ✎ Заметка |
|
||||||
|
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| Orange Pi 5 Plus использует процессор **Rockchip RK3588** (как и многие другие устройства под брендом Orange). Процесс компиляции ядра для всех RK35xx почти идентичен, и базируется на одном и том же дистрибутиве. Однако, разные периферийные устройства: например, GPIO, USB или Ethernet-контроллеры могут отличаться. Мы будем использовать `.config` от текущего, рабочего ядра, так что настройки будут актуальны. Но следует иметь в виду, что возможно, в будущем могут появиться изменения в конфигурации, дистрибутива и т.п. Следует учитывать, это и **никто ничего не гарантирует**. |
|
||||||
|
|
||||||
|
## План сборки ядра с iSCSI
|
||||||
|
|
||||||
|
* Резервное копирование текущего ядра, загрузчика, дерева устройств, /boot и т.п.
|
||||||
|
* Установить инструментарий для настройки и компиляции ядра
|
||||||
|
* Скачать исходники.
|
||||||
|
* Настроить конфиг для включения модулей iSCSI (и мониторинга NVMe).
|
||||||
|
* Скомпилировать ядро и модули.
|
||||||
|
* Установить новое ядро.
|
||||||
|
* Перезагрузиться в новое ядро.
|
||||||
|
* Проверка.
|
||||||
|
|
||||||
|
## Резервное копирование текущего ядра и /boot
|
||||||
|
|
||||||
|
Архивируем том `/boot` на нашем Orange Pi 5 Plus во временный каталог `/tmp`:
|
||||||
|
```bash
|
||||||
|
sudo tar -czf /tmp/boot-backup.tar.gz /boot
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверим, что архив создался:
|
||||||
|
```bash
|
||||||
|
ls -al /tmp/boot-backup.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим что архив создался:
|
||||||
|
```text
|
||||||
|
-rw-r--r-- 1 root root 62116591 мар 23 20:33 /tmp/boot-backup.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
Теперь нам нужно скопировать его на какой-нибудь внешний хост (или носитель, но я буду копировать на хост другого
|
||||||
|
компьютера). Если что-то пойдёт не так (например, после обновления ядра система не загрузится) можно будет восстановить
|
||||||
|
файлы из этого архива. Мой Orange Pi 5 Plus, на котором я буду компилировать ядро имеет хост — `opi5plus-3` (замени
|
||||||
|
на свой хост), а имя пользователя от которого я работаю — `opi` (замени на свой). На рабочем компьютере, с которого
|
||||||
|
я захожу по SSH на Orange, у меня есть папка `~/backup/` (`mkdir -p ~/backup`). Скачиваю в неё архив:
|
||||||
|
```bash
|
||||||
|
scp opi@opi5plus-3.local:/tmp/boot-backup.tar.gz ~/backup/opi5plus-3-boot-backup.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
### Что в архиве?
|
||||||
|
|
||||||
|
Если что-то пойдет не так, нам надо знать какие файлы в `/boot` важны и для чего предназначены. Вот список файлов
|
||||||
|
('ls -al /boot'):
|
||||||
|
```text
|
||||||
|
drwxr-xr-x 4 root root 4096 янв 1 1970 ./
|
||||||
|
drwxr-xr-x 19 root root 4096 фев 16 19:59 ../
|
||||||
|
-rwxr-xr-x 1 root root 230456 ноя 21 09:56 boot.bmp*
|
||||||
|
-rwxr-xr-x 1 root root 3341 ноя 21 09:55 boot.cmd*
|
||||||
|
-rwxr-xr-x 1 root root 3413 ноя 21 10:04 boot.scr*
|
||||||
|
-rwxr-xr-x 1 root root 230393 ноя 21 07:08 config-6.1.43-rockchip-rk3588*
|
||||||
|
drwxr-xr-x 3 root root 4096 ноя 21 09:55 dtb/
|
||||||
|
drwxr-xr-x 3 root root 4096 ноя 21 09:55 dtb-6.1.43-rockchip-rk3588/
|
||||||
|
-rwxr-xr-x 1 root root 40579584 ноя 21 07:08 Image*
|
||||||
|
-rwxr-xr-x 1 root root 15462140 мар 21 16:36 initrd.img-6.1.43-rockchip-rk3588*
|
||||||
|
-rwxr-xr-x 1 root root 1152056 ноя 21 09:56 logo.bmp*
|
||||||
|
-rwxr-xr-x 1 root root 0 ноя 21 09:55 .next*
|
||||||
|
-rwxr-xr-x 1 root root 178 фев 16 19:59 orangepiEnv.txt*
|
||||||
|
-rwxr-xr-x 1 root root 1542 ноя 21 09:56 orangepi_first_run.txt.template*
|
||||||
|
-rwxr-xr-x 1 root root 6209932 ноя 21 07:08 System.map-6.1.43-rockchip-rk3588*
|
||||||
|
-rwxr-xr-x 1 root root 15462204 мар 21 16:36 uInitrd*
|
||||||
|
-rwxr-xr-x 1 root root 40579584 ноя 21 07:08 vmlinuz-6.1.43-rockchip-rk3588*
|
||||||
|
```
|
||||||
|
|
||||||
|
Ключевые файлы:
|
||||||
|
* `Image` — собственно ядро Linux (в данном случае версия 6.1.43 для Rockchip RK3588). При старте компьютера загрузчик
|
||||||
|
U-Boot загрузит его в память и передаст ему управление. Без этого файла система не запустится.
|
||||||
|
* `vmlinuz-6.1.43-rockchip-rk3588` — копия ядра (в системе уже есть резервная копия).
|
||||||
|
* `dtb/` — Каталог файлами **Device Tree Blob** (`DTB`). Это бинарные файлы, описывающие аппаратное обеспечение устройства.
|
||||||
|
Для Orange Pi 5 Plus используется файл вроде `rk3588-orangepi-5-plus.dtb` (находится в подкаталоге `dtb/rockchip/`).
|
||||||
|
DTB передаётся ядру, чтобы оно знало, как работать с процессором (количество ядер, частоты), памятью (RAM, её объём
|
||||||
|
и адреса), периферией (UART, I2C, SPI, Ethernet, USB, GPIO, прерывания и тому подобное). На ARM-устройствах
|
||||||
|
(в отличие от x86, где есть ACPI) нет стандартного способа обнаружения оборудования. DTB решает эту проблему,
|
||||||
|
предоставляя ядру "карту" железа. U-Boot загружает DTB из `/boot/dtb/ `и передаёт его ядру при старте. Кстати,
|
||||||
|
в подкаталоге dtb/rockchip/ есть `overlay/` — это дополнительные файлы, которые могут использоваться для добавления
|
||||||
|
и/или изменения функциональности устройства. Например, можно добавить поддержку новых периферийных устройств (камеру,
|
||||||
|
дисплей и т.п.).
|
||||||
|
* `uInitrd` — Начальный RAM-диск (initrd), адаптированный для U-Boot. Содержит модули и скрипты, необходимые для
|
||||||
|
пред-загрузки (выбор накопителя, монтирование корневой файловой системы и т.п.). Если он повреждён или несовместим
|
||||||
|
с ядром, загрузка может упасть на этапе инициализации.
|
||||||
|
* `orangepiEnv.txt` — Конфигурационный файл для U-Boot. Здесь задаются параметры загрузки, такие как путь расположение
|
||||||
|
дерева DTB, UUID корневой файловой системы, тип файловой системы… Без правильных настроек в этом файле U-Boot не
|
||||||
|
найдёт нужные для загрузки файлы.
|
||||||
|
* `boot.scr` — Скрипт загрузки для U-Boot. Содержит команды для загрузки ядра, initrd и DTB. U-Boot выполняет его
|
||||||
|
при старте системы. Этот файл создаётся из `boot.cmd` с помощью утилиты `mkimage`. Если он повреждён или отсутствует,
|
||||||
|
U-Boot не сможет загрузить систему.
|
||||||
|
* `dtb-6.1.43-rockchip-rk3588/` — Копия каталога `dtb/`, обычно появляется, когда ядро устанавливается или обновляется
|
||||||
|
через пакетный менеджер (например, `apt`). Она привязана к конкретной версии ядра — в данном случае
|
||||||
|
`6.1.43-rockchip-rk3588`, для того, чтобы: Хранить DTB-файлы, соответствующие этой версии ядра и избегать конфликты
|
||||||
|
между DTB от разных версий ядра (если используется несколько ядер на одной системе).
|
||||||
|
|
||||||
|
Менее критичные, но полезные файлы:
|
||||||
|
* `config-6.1.43-rockchip-rk3588` — Конфигурация ядра, использованная при его сборке (он нам понадобится, чтобы
|
||||||
|
пересобрать ядро с iSCSI).
|
||||||
|
* `System.map-6.1.43-rockchip-rk3588` — Карта меток (символов) ядра, полезна для отладки.
|
||||||
|
* `initrd.img-6.1.43-rockchip-rk3588` — Обычный initrd, из которого генерируется uInitrd.
|
||||||
|
* `boot.bmp` и `logo.bmp` — Изображения для экрана загрузки (не влияют на работу системы).
|
||||||
|
|
||||||
|
## Устанавливать инструменты для сборки ядра
|
||||||
|
|
||||||
|
Обновим репозитории и установим минимальный инструментарий для сборки ядра:
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y build-essential bc flex bison libssl-dev libncurses-dev git
|
||||||
|
```
|
||||||
|
|
||||||
|
Нам понадобятся следующие пакеты:
|
||||||
|
|
||||||
|
* `build-essential` — Включает `gcc` (для ARM64, make и другие базовые инструменты компиляции.
|
||||||
|
* `bc` — Утилита для точных математических вычислений в командной строке. Используется для вычислений в скриптах сборки ядра.
|
||||||
|
* `flex` и `bison` — генератор лексических анализаторов и парсер. Нужны для обработки конфигурационных файлов ядра.
|
||||||
|
* `libssl-dev` — Для поддержки криптографии в ядре и `OpenSSL`.
|
||||||
|
* `libncurses-dev` — Библиотека для создания текстовых интерфейсов в терминале. Необходимо для работы интерфейса
|
||||||
|
`menuconfig` при настройке параметров ядра.
|
||||||
|
* `git` — Для клонирования исходного кода ядра из репозитория Xunlong.
|
||||||
|
|
||||||
|
> **Примечание:** Если вы хотите собрать ядро на x86-системе, установите кросс-компилятор `gcc-aarch64-linux-gnu` (`sudo apt install gcc-aarch64-linux-gnu`) и используйте его вместо обычного gcc в командах сборки. Он позволит собрать ядро для ARM64-архитектуры на x86-системе.
|
||||||
|
|
||||||
|
|
||||||
|
## Скачивание и подготавливаем исходники ядра
|
||||||
|
|
||||||
|
Клонируем репозиторий Orange Pi для ядра 6.1 из репозитория Xunlong. Мы будем брать ветку (branch)
|
||||||
|
`orange-pi-6.1-rk35xx`. Это ветка с исходниками для всех устройств на базе Rockchip RK35xx (включая Orange Pi 5 Plus):
|
||||||
|
```bash
|
||||||
|
git clone --depth 1 https://github.com/orangepi-xunlong/linux-orangepi -b orange-pi-6.1-rk35xx ~/linux-orangepi
|
||||||
|
```
|
||||||
|
|
||||||
|
После клонирования репозитория, переходим в каталог с ним:
|
||||||
|
```bash
|
||||||
|
cd ~/linux-orangepi
|
||||||
|
```
|
||||||
|
|
||||||
|
И первым делом в `Makefile` установим `EXTRAVERSION` (модификатор версии ядра). Узнаем текущую версию ядра:
|
||||||
|
```bash
|
||||||
|
uname -r
|
||||||
|
```
|
||||||
|
|
||||||
|
Я на своем Orange Pi 5 Plus вижу (у вас может быть другая версия):
|
||||||
|
```text
|
||||||
|
6.1.43-rockchip-rk3588
|
||||||
|
```
|
||||||
|
|
||||||
|
Открываем `Makefile` в текстовом редакторе:
|
||||||
|
```bash
|
||||||
|
nano Makefile
|
||||||
|
```
|
||||||
|
|
||||||
|
И устанавливаем модификатор версии ядра, строку `XTRAVERSION =` (у меня это пятая строчка):
|
||||||
|
```text
|
||||||
|
EXTRAVERSION = -rockchip-rk3588
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Затем копируем конфиг текущего ядра в конфиг для сборки.
|
||||||
|
```bash
|
||||||
|
zcat /proc/config.gz > .config
|
||||||
|
```
|
||||||
|
|
||||||
|
`.config` содержит все текущие настройки ядра. Это очень удобно, так как нужно добавить только поддержку нужных модулей
|
||||||
|
(в нашем случае `iSCSI`), а не настраивать всё с нуля.
|
||||||
|
|
||||||
|
## Настройка конфига с iSCSI
|
||||||
|
|
||||||
|
Теперь нужно отредактировать, `.config` чтобы включить `iSCSI`. Заходим в интерфейс конфигуратора ядра `menuconfig`:
|
||||||
|
```bash
|
||||||
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
Используя стрелки на клавиатуре, чтобы двигаться по пунктам меню.
|
||||||
|
1. Перейди в `Device Drivers` → `SCSI device support` → `SCSI Transports`.
|
||||||
|
2. Найти внутри `iSCSI Transport Attributes`, с помощью стрелок выбрать его.
|
||||||
|
3. Нажимает `M` (для модуля) — появится `<M>` рядом с пунктом. Это означает, что модуль будет собираться как модуль,
|
||||||
|
а не встраиваться в ядро. Модуль (`iscsi_tcp.ko`) проще протестировать без перезагрузки.
|
||||||
|
4. В нижнем меню выбрать `<Save>` и подтвердим сохранение в `.config`.
|
||||||
|
6. Вернуться в меню выше `SCSI device support` (через нижнее меню `<Exit>`).
|
||||||
|
7. Зайти в раздел `SCSI low-level drivers`.
|
||||||
|
8. Выбрать `iSCSI Initiator over TCP/IP` и нажать `M` (для модуля).
|
||||||
|
9. Снова сохраняем конфиг в `.config` (через нижнее меню `<Save>`).
|
||||||
|
10. Выйти из `menuconfig` (через нижнее меню `<Exit>`, три раза).
|
||||||
|
|
||||||
|
| ✎ Может быть полезно: |
|
||||||
|
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| На моём Orange Pi 5 Plus загрузка идет с NVMe диска ([см. как настроить](opi5plus-move-system-to-nvme-or-emmc.md)), и мне было полезно так же включить ещё и систему мониторинга NVMe: `Device Drivers` → `NVME Support`, найти пункт `SNVMe hardware monitoring` и включить его. В нижнем меню выбрать `<Save>` и подтвердим сохранение в `.config`. Выйти из `menuconfig` (через нижнее меню `<Exit>`, три раза). Теперь у меня будет возможность отслеживать состояние NVMe-диска (температуру, износ и т.д.) через инструменты вроде `nvme-cli`. |
|
||||||
|
|
||||||
|
Проверим, что iSCSI включён:
|
||||||
|
```bash
|
||||||
|
grep -E "ISCSI_TCP|SCSI_ISCSI_ATTRS|ISCSI_BOOT" .config
|
||||||
|
```
|
||||||
|
|
||||||
|
Должны увидеть что-то типа:
|
||||||
|
```text
|
||||||
|
CONFIG_SCSI_ISCSI_ATTRS=m
|
||||||
|
CONFIG_ISCSI_TCP=m
|
||||||
|
# CONFIG_ISCSI_BOOT_SYSFS is not set
|
||||||
|
```
|
||||||
|
|
||||||
|
| ✎ Проверка, что включен мониторинг NVMe (если включали): |
|
||||||
|
|---------------------------------------------------------------|
|
||||||
|
| `grep -E "NVME_HWMON" .config` и увидим `CONFIG_NVME_HWMON=y` |
|
||||||
|
|
||||||
|
## Компиляция ядра и модулей
|
||||||
|
|
||||||
|
Запускаем компиляцию ядра и модулей:
|
||||||
|
```bash
|
||||||
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j8
|
||||||
|
```
|
||||||
|
|
||||||
|
`-j8` — это количество потоков, используем столько, сколько у нас ядер. На Orange Pi 5 Plus восемь ядер. Но, например,
|
||||||
|
если у вас четыре ядра, то `-j4`.
|
||||||
|
|
||||||
|
Компиляция займёт 20-40 минут (Orange Pi 5 Plus).
|
||||||
|
|
||||||
|
Установим модули ядра:
|
||||||
|
```bash
|
||||||
|
sudo make modules_install
|
||||||
|
```
|
||||||
|
|
||||||
|
После завершения ядро будет находиться в каталоге `~/linux-orangepi/arch/arm64/boot/` (файл `Image`), а дерево описания
|
||||||
|
устройства в `~/linux-orangepi/arch/arm64/boot/dts/rockchip/` (наш файл для Orange Pi 5 Plus --
|
||||||
|
`rk3588-orangepi-5-plus.dtb`, дле версий 5, 5 Max, AI, Ultra соответствующие файлы там же).
|
||||||
|
|
||||||
|
Проверим, что ядро скомпилировалось:
|
||||||
|
```bash
|
||||||
|
ls -l arch/arm64/boot/Image
|
||||||
|
```
|
||||||
|
|
||||||
|
Ожидаемый размер: около 40 МБ:
|
||||||
|
```text
|
||||||
|
-rw-rw-r-- 1 opi opi 40579584 Mar 22 19:09 arch/arm64/boot/Image
|
||||||
|
```
|
||||||
|
|
||||||
|
Размер должен почти совпасть (или полностью совпадать) с размером текущего ядра:
|
||||||
|
```bash
|
||||||
|
ls -l /boot/Image
|
||||||
|
```
|
||||||
|
```text
|
||||||
|
-rwxr-xr-x 1 root root 40579584 Nov 21 07:08 /boot/Image
|
||||||
|
```
|
||||||
|
|
||||||
|
И это не ошибка. Мы добавили только `CONFIG_ISCSI_TCP=m` и `CONFIG_SCSI_ISCSI_ATTRS=m`. Это подключит модули (`iscsi_tcp.ko`),
|
||||||
|
но само ядро (`Image`) почти не увеличивается, ведь `.ko`-модуль iSCSI не встраивается в ядро (мы выбрали `<M>`,
|
||||||
|
а не `<*>`). Кроме того, Linux выравнивает размеры файлов по блокам (например, 4 Кб), и добавление
|
||||||
|
пары небольших модулей могут не повлиять на итоговый размер.
|
||||||
|
|
||||||
|
## Устанавливаем новое ядро рядом со старым
|
||||||
|
|
||||||
|
Мы должны скопировать два файла в `/boot`. Первый — это `Image`. Это само ядро, и оно скомпилировалось в каталог
|
||||||
|
`~/linux-orangepi/arch/arm64/boot/`. Второй файл — это `rk3588-orangepi-5-plus.dtb`. Он находится
|
||||||
|
в `~/linux-orangepi/arch/arm64/boot/dts/rockchip/`. Как было сказано выше, это дерево описания оборудования
|
||||||
|
(Device Tree Blob), и нужно при загрузке ядра. Кстати, при компиляции мы получили `.dtb` для всех моделей Orange Pi 5
|
||||||
|
(5, 5 Plus, 5 Max, AI, Ultra). Для моей модели (Orange Pi 5 Plus) нужен `rk3588-orangepi-5-plus.dtb`, а вы можете
|
||||||
|
взять другой, для своей модели.
|
||||||
|
|
||||||
|
Копируем новые файлы в `/boot` с уникальными именами (с суффиксом `-iscsi`):
|
||||||
|
```bash
|
||||||
|
sudo cp ~/linux-orangepi/arch/arm64/boot/Image /boot/Image-iscsi
|
||||||
|
sudo cp ~/linux-orangepi/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-plus.dtb /boot/dtb/rockchip/rk3588-orangepi-5-plus.dtb-iscsi
|
||||||
|
```
|
||||||
|
|
||||||
|
Старое ядро и дерево описания переименуем (это будет резервная копия, с суффиксом `-original`):
|
||||||
|
```bash
|
||||||
|
sudo mv /boot/Image /boot/Image-original
|
||||||
|
sudo mv /boot/dtb/rockchip/rk3588-orangepi-5-plus.dtb /boot/dtb/rockchip/rk3588-orangepi-5-plus.dtb-original
|
||||||
|
```
|
||||||
|
|
||||||
|
Устанавливаем новые ядро и дерево:
|
||||||
|
```bash
|
||||||
|
sudo cp /boot/Image-iscsi /boot/Image
|
||||||
|
sudo cp /boot/dtb/rockchip/rk3588-orangepi-5-plus.dtb-iscsi /boot/dtb/rockchip/rk3588-orangepi-5-plus.dtb
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Важно**: Не обновляйте `uInitrd`, если текущий работает, — его замена может сломать загрузку системы.
|
||||||
|
|
||||||
|
Все готово.
|
||||||
|
|
||||||
|
## Перезагрузка в новое ядро
|
||||||
|
|
||||||
|
Тут все просто:
|
||||||
|
```bash
|
||||||
|
sudo reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Если что-то пошло не так и система не загрузилась
|
||||||
|
|
||||||
|
Если вы все делали правильно, то такого быть не должно. Но, тем не менее, если загрузка не произошла, то это может
|
||||||
|
выглядеть двумя способами:
|
||||||
|
* Система зависает на этапе загрузки: синенький огонек на Orange Pi не загорается и Ethernet тоже не мигает —
|
||||||
|
_вы неправильно собрали ядро или испортили загрузчик.
|
||||||
|
* Система, вроде как, грузится, но все никак… огоньки весело мигают, но не получается подключиться ни по SSH, ни
|
||||||
|
другим способом, пинги на IP-адрес не проходят — вы забыли подключить модули, накосячили с конфигом или с `.dtb`_.
|
||||||
|
|
||||||
|
Чтобы починить, загружайтесь с MicroSD-карты (не забудьте отключить питание перед тем как вставить MicroSD-карту).
|
||||||
|
Затем смонтируйте, в зависимости где у вас система, eMMC:
|
||||||
|
```bash
|
||||||
|
mkdir -p /mnt/emmc
|
||||||
|
mount /dev/mmcblk2p1 /mnt/emmc
|
||||||
|
cd /mnt/emmc
|
||||||
|
```
|
||||||
|
|
||||||
|
…или NVMe:
|
||||||
|
```bash
|
||||||
|
mkdir -p /mnt/nvme
|
||||||
|
mount /dev/nvme0n1p1 /mnt/nvme
|
||||||
|
cd /mnt/nvme
|
||||||
|
```
|
||||||
|
|
||||||
|
И восстанавливаем старое ядро и дерево описания:
|
||||||
|
```bash
|
||||||
|
sudo cp Image-original Image
|
||||||
|
sudo cp dtb/rockchip/rk3588-orangepi-5-plus.dtb-original dtb/rockchip/rk3588-orangepi-5-plus.dtb
|
||||||
|
```
|
||||||
|
|
||||||
|
Выключаем систему:
|
||||||
|
```bash
|
||||||
|
sudo poweroff
|
||||||
|
```
|
||||||
|
|
||||||
|
Вынимаем MicroSD-карту, включаем, и загрузиться старая система.
|
||||||
|
|
||||||
|
## Проверка нового ядра
|
||||||
|
|
||||||
|
Если же иситма поле обновления ядра загрузилась, проверим текущую версию ядра:
|
||||||
|
```bash
|
||||||
|
uname -r
|
||||||
|
```
|
||||||
|
|
||||||
|
Она должна остаться той же:
|
||||||
|
```text
|
||||||
|
6.1.43-rockchip-rk3588
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверим, работает ли iSCSI:
|
||||||
|
```bash
|
||||||
|
sudo systemctl status iscsid
|
||||||
|
```
|
||||||
|
|
||||||
|
Увидим что-то вроде:
|
||||||
|
```text
|
||||||
|
● iscsid.service - iSCSI initiator daemon (iscsid)
|
||||||
|
Loaded: loaded (/lib/systemd/system/iscsid.service; enabled; vendor preset: enabled)
|
||||||
|
Active: active (running) since Mon 2025-03-24 17:51:59 MSK; 28min ago
|
||||||
|
TriggeredBy: ● iscsid.socket
|
||||||
|
Docs: man:iscsid(8)
|
||||||
|
Main PID: 1070 (iscsid)
|
||||||
|
Tasks: 2 (limit: 18977)
|
||||||
|
Memory: 4.6M
|
||||||
|
CPU: 94ms
|
||||||
|
CGroup: /system.slice/iscsid.service
|
||||||
|
├─1069 /sbin/iscsid
|
||||||
|
└─1070 /sbin/iscsid
|
||||||
|
|
||||||
|
мар 24 17:51:58 opi5plus-3 systemd[1]: Starting iSCSI initiator daemon (iscsid)...
|
||||||
|
мар 24 17:51:59 opi5plus-3 iscsid[1061]: iSCSI logger with pid=1069 started!
|
||||||
|
мар 24 17:51:59 opi5plus-3 iscsid[1069]: iSCSI daemon with pid=1070 started!
|
||||||
|
мар 24 17:51:59 opi5plus-3 systemd[1]: Started iSCSI initiator daemon (iscsid).
|
||||||
|
```
|
||||||
|
|
||||||
|
Как видим, `active (running)`, значит iSCSI работает.
|
||||||
|
|
||||||
|
| ✎ Заметка |
|
||||||
|
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| Если вы включили мониторинг NVMe, то проверим и его. Установим nvme-cli: `sudo apt install nvme-cli` и проверим состояние нашего накопителя `sudo nvme smart-log /dev/nvme0n1`. |
|
||||||
|
|
||||||
|
**ВСË!**
|
||||||
|
|
||||||
|
А, да, не забудьте удалить репозиторий с исходниками ядра, если он вам больше не нужен:
|
||||||
|
```bash
|
||||||
|
rm -rf ~/linux-orangepi
|
||||||
|
```
|