diff --git a/.docs/storage-audit-instruction.md b/.docs/storage-audit-instruction.md new file mode 100644 index 0000000..157f507 --- /dev/null +++ b/.docs/storage-audit-instruction.md @@ -0,0 +1,191 @@ +# Инструкция: Анализ хранилища Kubernetes и настройка NFS + +## Цель +Проанализировать текущую конфигурацию хранилища Kubernetes и подготовить план внедрения NFS StorageClass для сохранения данных при удалении namespace. + +## 1. Собрать информацию о кластере + +### 1.1. Версия Kubernetes и тип дистрибутива +```bash +kubectl version --short +# или +kubectl version +``` + +Определить, используется ли k3s, k8s, microk8s и т.д.: +```bash +# Проверить наличие k3s +which k3s +# Проверить процесс +ps aux | grep -E 'kube|k3s' +``` + +### 1.2. StorageClass +```bash +kubectl get storageclass -o wide +``` + +Запомнить: +- `PROVISIONER` — какой драйвер используется +- `RECLAIMPOLICY` — Delete или Retain +- Какой StorageClass помечен как `(default)` + +### 1.3. Существующие PV и PVC +```bash +kubectl get pv -o wide +kubectl get pvc --all-namespaces +``` + +Посмотреть, какие PVC привязаны к каким PV, и какой reclaimPolicy у PV. + +### 1.4. Нода и диски +```bash +# Список нод +kubectl get nodes -o wide + +# На каждой ноде (через ssh или локально): +lsblk +df -h +cat /etc/fstab +``` + +Определить: +- Есть ли отдельный раздел/диск для данных +- Куда смонтированы разделы +- Сколько свободного места +- Есть ли монтирование NTFS-разделов (как `/media/forust/Programs`) + +### 1.5. Где local-path хранит данные (для k3s) +```bash +ls -la /var/lib/rancher/k3s/storage/ 2>/dev/null +# или для microk8s +ls -la /var/snap/microk8s/common/ 2>/dev/null +``` + +## 2. Анализ: сохраняются ли данные при удалении namespace? + +| Сценарий | Результат | +|---|---| +| `kubectl delete ns ` | Все PVC в namespace удаляются | +| PVC → PV c `reclaimPolicy: Delete` | PV и данные удалены | +| PVC → PV c `reclaimPolicy: Retain` | PV остаётся (статус Released), данные целы | + +**Вывод:** Если reclaimPolicy в StorageClass = `Delete`, то данные **пропадут**. Если `Retain` — сохранятся. + +## 3. План внедрения NFS + +### 3.1. Проверить, установлен ли NFS +```bash +which nfsstat exportfs mount.nfs +systemctl status nfs-server 2>/dev/null || systemctl status nfs-kernel-server 2>/dev/null +``` + +### 3.2. Выбрать директорию для NFS-экспорта + +Варианты (выбрать подходящий): +- `/var/lib/k8s-nfs/` — на корневом разделе +- `<путь к отдельному разделу>/k8s-nfs/` — если есть отдельный диск/раздел +- Не рекомендуется использовать NTFS-раздел (проблемы с правами и производительностью) + +Требования: +- Файловая система: ext4 или xfs (не ntfs!) +- Достаточно свободного места +- Права: `755`, владелец root + +### 3.3. Установить NFS-сервер + +```bash +# Debian/Ubuntu +apt update && apt install -y nfs-kernel-server + +# RHEL/Fedora +dnf install -y nfs-utils +``` + +### 3.4. Настроить экспорт + +Создать директорию: +```bash +mkdir -p /var/lib/k8s-nfs +chmod 755 /var/lib/k8s-nfs +``` + +Добавить в `/etc/exports`: +``` +/var/lib/k8s-nfs *(rw,sync,no_subtree_check,no_root_squash) +``` + +Применить: +```bash +exportfs -rav +``` + +Проверить: +```bash +showmount -e localhost +``` + +### 3.5. Выбрать способ интеграции с Kubernetes + +#### Вариант A: nfs-subdir-external-provisioner (проще) +```bash +helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/ +helm install nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \ + --namespace kube-system \ + --set nfs.server=127.0.0.1 \ + --set nfs.path=/var/lib/k8s-nfs \ + --set storageClass.name=nfs \ + --set storageClass.defaultClass=false \ + --set storageClass.reclaimPolicy=Retain +``` + +#### Вариант B: NFS CSI Driver +```bash +helm repo add csi-driver-nfs https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts +helm install csi-driver-nfs csi-driver-nfs/csi-driver-nfs --namespace kube-system +``` + +После установки CSI драйвера создать StorageClass: +```yaml +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: nfs +provisioner: nfs.csi.k8s.io +parameters: + server: 127.0.0.1 + share: /var/lib/k8s-nfs +reclaimPolicy: Retain +volumeBindingMode: Immediate +``` + +### 3.6. Проверить результат +```bash +kubectl get storageclass +kubectl get pods -n kube-system | grep -E 'nfs|provisioner' +``` + +## 4. Итоговая конфигурация + +После внедрения в кластере будет два StorageClass: + +| Имя | Provisioner | ReclaimPolicy | Назначение | +|---|---|---|---| +| `local-path` (default) | rancher.io/local-path | Delete | Временные данные, stateless | +| `nfs` | nfs-subdir-external-provisioner или nfs.csi.k8s.io | Retain | Данные, которые нужно сохранять | + +**Главное преимущество:** PVC c `storageClassName: nfs` при удалении namespace сохраняют данные на диске, так как NFS-провизор использует `reclaimPolicy: Retain` или файлы физически остаются в NFS-экспорте. + +## 5. Ответы на частые вопросы + +**В:** Не упадёт ли local-path при установке NFS? +**О:** Нет, они независимы. local-path продолжает работать как обычно. + +**В:** Данные NFS и local-path будут на одном диске? +**О:** Да, можно настроить оба на одном разделе, в разных каталогах. + +**В:** Что если у меня несколько нод? +**О:** NFS сервер нужно поднять на одной ноде, а с других нод должна быть доступна шари. Для multi-node лучше использовать отдельный сервер или distributed storage (Longhorn, Rook/Ceph). + +**В:** Можно ли использовать существующий NTFS-раздел для NFS? +**О:** Не рекомендуется — NTFS не поддерживает права Linux (no_root_squash не сработает корректно), возможны проблемы с блокировками и производительностью. diff --git a/.gitea/workflows/validate.yml b/.gitea/workflows/validate.yml new file mode 100644 index 0000000..db5b67f --- /dev/null +++ b/.gitea/workflows/validate.yml @@ -0,0 +1,52 @@ +name: validate + +on: + push: + branches: + - "**" + pull_request: + workflow_dispatch: + +jobs: + yaml: + runs-on: [self-hosted, linux, arch, homelab] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install yamllint + run: python -m pip install --disable-pip-version-check --no-cache-dir yamllint + + - name: Lint YAML syntax + shell: bash + run: | + git ls-files -z '*.yml' '*.yaml' \ + | xargs -0 -r yamllint -c .yamllint + + k8s: + runs-on: [self-hosted, linux, arch, homelab] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Validate Kubernetes manifests + shell: bash + run: | + mapfile -t manifests < <( + git ls-files '*k8s/*.yaml' '*k8s/*.yml' \ + | grep -Ev '(^|/)(kustomization\.ya?ml|.*\.example\.ya?ml|.*values\.ya?ml|patch-.*\.ya?ml)$' + ) + + if [ "${#manifests[@]}" -eq 0 ]; then + echo "No Kubernetes manifests found." + exit 0 + fi + + docker run --rm \ + -v "$PWD:/work" \ + -w /work \ + ghcr.io/yannh/kubeconform:latest \ + -strict \ + -ignore-missing-schemas \ + -summary \ + "${manifests[@]}" diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..db5b67f --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,52 @@ +name: validate + +on: + push: + branches: + - "**" + pull_request: + workflow_dispatch: + +jobs: + yaml: + runs-on: [self-hosted, linux, arch, homelab] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install yamllint + run: python -m pip install --disable-pip-version-check --no-cache-dir yamllint + + - name: Lint YAML syntax + shell: bash + run: | + git ls-files -z '*.yml' '*.yaml' \ + | xargs -0 -r yamllint -c .yamllint + + k8s: + runs-on: [self-hosted, linux, arch, homelab] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Validate Kubernetes manifests + shell: bash + run: | + mapfile -t manifests < <( + git ls-files '*k8s/*.yaml' '*k8s/*.yml' \ + | grep -Ev '(^|/)(kustomization\.ya?ml|.*\.example\.ya?ml|.*values\.ya?ml|patch-.*\.ya?ml)$' + ) + + if [ "${#manifests[@]}" -eq 0 ]; then + echo "No Kubernetes manifests found." + exit 0 + fi + + docker run --rm \ + -v "$PWD:/work" \ + -w /work \ + ghcr.io/yannh/kubeconform:latest \ + -strict \ + -ignore-missing-schemas \ + -summary \ + "${manifests[@]}" diff --git a/.zed/settings.json b/.zed/settings.json index 042dac8..2c80716 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -9,31 +9,31 @@ "hard_tabs": false, "format_on_save": "on", "formatter": { - "language_server": { "name": "yaml-language-server" } - } + "language_server": { "name": "yaml-language-server" }, + }, }, "Python": { "tab_size": 4, "format_on_save": "on", "formatter": { - "language_server": { "name": "ruff" } - } - } + "language_server": { "name": "ruff" }, + }, + }, }, "lsp": { "yaml-language-server": { "settings": { "yaml": { "schemas": { - "kubernetes": ["**/k8s/*.yaml", "**/k8s/*.yml"] + "kubernetes": ["**/k8s/*.yaml", "**/k8s/*.yml"], }, "validate": true, "completion": true, "format": { - "enable": true - } - } - } - } - } + "enable": true, + }, + }, + }, + }, + }, } diff --git a/adguardhome/k8s/ingress.yaml b/adguardhome/k8s/ingress.yaml index ab065a3..128c962 100644 --- a/adguardhome/k8s/ingress.yaml +++ b/adguardhome/k8s/ingress.yaml @@ -14,6 +14,13 @@ spec: services: - name: adguard-service port: 3000 + - match: (Host(`adguard.forust.xyz`) || Host(`dns.forust.xyz`)) && PathPrefix(`/dns-query`) + kind: Rule + middlewares: + - name: "crowdsec-crowdsec-bouncer@kubernetescrd" + services: + - name: adguard-service + port: 3000 tls: certResolver: letsencrypt --- @@ -31,22 +38,4 @@ spec: services: - name: adguard-service port: 3000 ---- -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: adguard-doh - namespace: adguard -spec: - entryPoints: - - websecure - routes: - - match: (Host(`adguard.forust.xyz`) || Host(`dns.forust.xyz`)) && PathPrefix(`/dns-query`) - kind: Rule - middlewares: - - name: "crowdsec-crowdsec-bouncer@kubernetescrd" - services: - - name: adguard-service - port: 3000 - tls: - certResolver: letsencrypt + - match: Host((`adguard.workstation.internal`) || Host(`dns.workstation.internal`) || Host(`adguard.gigaforust.internal`) || Host(`dns.gigaforust.internal`)) && PathPrefix(`/dns-query`) diff --git a/gitea/k8s/ingress.yaml b/gitea/k8s/ingress.yaml index a00da16..4b2cb85 100644 --- a/gitea/k8s/ingress.yaml +++ b/gitea/k8s/ingress.yaml @@ -14,6 +14,13 @@ spec: services: - name: gitea-service port: 3000 + - match: Host(`gcr.forust.xyz`) && PathPrefix(`/v2`) + kind: Rule + middlewares: + - name: "crowdsec-crowdsec-bouncer@kubernetescrd" + services: + - name: gitea-service + port: 3000 tls: certResolver: letsencrypt --- @@ -31,23 +38,11 @@ spec: services: - name: gitea-service port: 3000 ---- -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: gitea-registry - namespace: gitea -spec: - entryPoints: - - websecure - routes: - - match: Host(`gcr.forust.xyz`) && PathPrefix(`/v2`) + - match: Host((`gcr.workstation.internal`) || Host(`gcr.gigaforust.internal)) && PathPrefix(`/v2`) kind: Rule services: - name: gitea-service port: 3000 - tls: - certResolver: letsencrypt --- apiVersion: traefik.io/v1alpha1 kind: IngressRouteTCP