k8s/research/apps/rkn-list-poller.md
2026-07-15 11:14:21 +03:00

362 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Перенос rkn-list-poller в Kubernetes
**Репозиторий:** `docker/rkn-list-poller` на `gitlab.gigacoms.info`
**Текущая платформа:** Docker Swarm
**Целевая платформа:** Kubernetes (кластер `k8s_infrastructure`)
---
## Что делает приложение
`rkn-list-poller` — пакетный PHP-воркер для выгрузки реестров заблокированных ресурсов через SOAP-сервис Роскомнадзора (`vigruzki.rkn.gov.ru`).
**Три скрипта, один алгоритм:**
1. Читает `request.xml` и `request.xml.sig` из примонтированной директории.
2. Отправляет SOAP-запрос (`sendRequest`), получает `$request_code`.
3. Опрашивает сервис каждые 3 минуты (`getResult` / `getResultSocResources`) до готовности.
4. Записывает ZIP-архив дампа в ту же директорию.
| Скрипт | SOAP-метод | Выходной файл | Расписание cron |
|---|---|---|---|
| `rkn_get_black_list.php` | `getResult` | `dump_black.zip` | `0 * * * *` (каждый час в :00) |
| `rkn_get_soc_list.php` | `getResultSocResources` | `dump_soc.zip` | `30 * * * *` (каждый час в :30) |
| `rkn_get_list.php` | `getResult` | `dump.zip` | не в активном cron |
---
## Текущая архитектура (Docker Swarm)
```
Docker Swarm (manager node)
└── Service: rkn-poller (replicas: 1)
├── Image: reg.gitlab.gigacoms.info/docker/rkn-list-poller:main-latest
├── Entrypoint: cron -f (PID 1, foreground)
│ ├── 0 */1 * * * → php /app/rkn_get_black_list.php
│ └── 30 */1 * * * → php /app/rkn_get_soc_list.php
└── Volume: /mnt/swarm_quorum/rkn-poller → /data/rezult
├── request.xml (генерируется rknutils.jar, обновляется вручную)
├── request.xml.sig (GOST-подпись, обновляется вручную)
├── dump_black.zip (выход)
├── dump_soc.zip (выход)
└── rkn.log (журнал)
```
**Особенности образа:**
- База: `rnix/openssl-gost:latest` (Debian Stretch, EOL) — нужен для GOST-криптографии при TLS с `vigruzki.rkn.gov.ru`
- PHP 7.0, php-soap, php-xml
- Российские корневые сертификаты Минцифры (5 штук в `certs/`) добавлены в хранилище системы и в `php.ini`
- Лимит памяти PHP: `-1` (без ограничений) — дамп может занять сотни МБ
---
## Ключевые наблюдения для миграции
### 1. Приложение — пакетный джоб, не сервис
Контейнер сейчас держит внутри себя `cron` и работает постоянно. В Kubernetes это антипаттерн.
**PHP-скрипты — это одиночные запуски с выходом**: `php /app/rkn_get_black_list.php` запускается, ждёт ответа РКН, сохраняет файл, завершается.
Это идеально соответствует `CronJob`. Нужды в Deployment нет.
### 2. Время выполнения непредсказуемо
Скрипт опрашивает РКН каждые 3 минуты в цикле. РКН может отвечать от 5 минут до нескольких часов. Нужно устанавливать `activeDeadlineSeconds` с большим запасом (6 часов).
### 3. Конкурентный запуск опасен
Если предыдущий джоб ещё работает (РКН медленно отвечает), новый запуск перезапишет тот же `dump_black.zip`. Нужен `concurrencyPolicy: Forbid`.
### 4. Персистентное хранилище — единственная точка состояния
Из Swarm-тома в директорию `/data/rezult` нужно перенести:
- `request.xml` и `request.xml.sig`**входные файлы, которые нельзя потерять**. Обновляются вручную при смене сертификата оператора. Логично хранить как Secret.
- Выходные ZIP-архивы — в PVC.
### 5. Образ менять не нужно
Образ `rnix/openssl-gost` специфичен для GOST-криптографии и уже работает. CI/CD уже публикует его в GitLab Registry. **Менять образ не нужно** — только изменить способ запуска (убрать cron, запускать скрипт напрямую).
> **Технический долг:** Debian Stretch достиг EOL в 2022. `rnix/openssl-gost` — заброшенный образ. Рекомендуется проверить наличие более свежих альтернатив (например, `openssl-gost` на базе Debian Bookworm), но это отдельная задача, не блокирующая миграцию.
---
## Целевая архитектура в Kubernetes
```
Namespace: rkn-poller
├── Secret: rkn-request-files
│ ├── request.xml (base64)
│ └── request.xml.sig (base64)
├── PersistentVolumeClaim: rkn-data (Longhorn, RWO, 5Gi)
│ └── /data/rezult/
│ ├── dump_black.zip
│ ├── dump_soc.zip
│ └── rkn.log
├── CronJob: rkn-black-list
│ ├── schedule: "0 * * * *"
│ ├── concurrencyPolicy: Forbid
│ ├── activeDeadlineSeconds: 21600
│ ├── command: php /app/rkn_get_black_list.php
│ └── volumes: Secret → /data/rezult (request файлы), PVC → /data/rezult (выход)
└── CronJob: rkn-soc-list
├── schedule: "30 * * * *"
├── concurrencyPolicy: Forbid
├── activeDeadlineSeconds: 21600
├── command: php /app/rkn_get_soc_list.php
└── volumes: то же самое
```
**Почему два тома в одну директорию?**
Оба пути монтирования (`/data/rezult`) можно разрешить через `subPath` в PVC — тогда Secret с файлами запроса проецируется поверх PVC не заменяя его содержимое. Либо использовать initContainer для копирования файлов из Secret в PVC перед запуском основного контейнера (более явный вариант).
---
## Манифесты
### Namespace
```yaml
apiVersion: v1
kind: Namespace
metadata:
name: rkn-poller
```
### Secret с файлами запроса
Создаётся **вручную** на manager-ноде (не хранится в Git):
```bash
kubectl create secret generic rkn-request-files \
--from-file=request.xml=/path/to/request.xml \
--from-file=request.xml.sig=/path/to/request.xml.sig \
-n rkn-poller
```
При смене сертификата оператора:
```bash
kubectl create secret generic rkn-request-files \
--from-file=request.xml=/path/to/new/request.xml \
--from-file=request.xml.sig=/path/to/new/request.xml.sig \
-n rkn-poller \
--dry-run=client -o yaml | kubectl apply -f -
```
### Secret для pull из GitLab Registry
```bash
kubectl create secret docker-registry rkn-registry-pull \
--docker-server=reg.gitlab.gigacoms.info \
--docker-username=<gitlab_deploy_token_user> \
--docker-password=<gitlab_deploy_token_password> \
-n rkn-poller
```
### PersistentVolumeClaim
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: rkn-data
namespace: rkn-poller
spec:
storageClassName: longhorn
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
```
### CronJob: rkn-black-list
```yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: rkn-black-list
namespace: rkn-poller
spec:
schedule: "0 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
activeDeadlineSeconds: 21600 # 6 часов: РКН может отвечать долго
template:
spec:
restartPolicy: OnFailure
imagePullSecrets:
- name: rkn-registry-pull
initContainers:
- name: copy-request-files
image: busybox:1.36
command:
- sh
- -c
- cp /secrets/request.xml /data/rezult/request.xml &&
cp /secrets/request.xml.sig /data/rezult/request.xml.sig
volumeMounts:
- name: request-secret
mountPath: /secrets
readOnly: true
- name: rkn-data
mountPath: /data/rezult
containers:
- name: poller
image: reg.gitlab.gigacoms.info/docker/rkn-list-poller:main-latest
command: ["php", "/app/rkn_get_black_list.php"]
env:
- name: RKN_DATA_DIR
value: /data/rezult
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 2Gi
cpu: 500m
volumeMounts:
- name: rkn-data
mountPath: /data/rezult
volumes:
- name: request-secret
secret:
secretName: rkn-request-files
- name: rkn-data
persistentVolumeClaim:
claimName: rkn-data
```
### CronJob: rkn-soc-list
```yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: rkn-soc-list
namespace: rkn-poller
spec:
schedule: "30 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
activeDeadlineSeconds: 21600
template:
spec:
restartPolicy: OnFailure
imagePullSecrets:
- name: rkn-registry-pull
initContainers:
- name: copy-request-files
image: busybox:1.36
command:
- sh
- -c
- cp /secrets/request.xml /data/rezult/request.xml &&
cp /secrets/request.xml.sig /data/rezult/request.xml.sig
volumeMounts:
- name: request-secret
mountPath: /secrets
readOnly: true
- name: rkn-data
mountPath: /data/rezult
containers:
- name: poller
image: reg.gitlab.gigacoms.info/docker/rkn-list-poller:main-latest
command: ["php", "/app/rkn_get_soc_list.php"]
env:
- name: RKN_DATA_DIR
value: /data/rezult
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 2Gi
cpu: 500m
volumeMounts:
- name: rkn-data
mountPath: /data/rezult
volumes:
- name: request-secret
secret:
secretName: rkn-request-files
- name: rkn-data
persistentVolumeClaim:
claimName: rkn-data
```
---
## Интеграция с Flux
Манифесты размещаются в fleet-репозитории `k8s/k8s-fleet` (путь: `clusters/production/rkn-poller/`):
```
clusters/production/rkn-poller/
├── namespace.yaml
├── pvc.yaml
├── cronjob-black-list.yaml
└── cronjob-soc-list.yaml
```
Secret-ресурсы (`rkn-request-files`, `rkn-registry-pull`) **не хранятся в Git** — создаются вручную один раз.
Flux автоматически применит PVC и CronJob после push в fleet-репозиторий.
---
## Замечания по firewall и сети
- `vigruzki.rkn.gov.ru` — внешний HTTPS-хост. Pods в кластере имеют выход в интернет через worker-ноды — дополнительных правил не нужно.
- NetworkPolicy не требуется: поды не принимают входящих соединений.
- Для ГОСТ-TLS нужны российские корневые сертификаты — они уже вшиты в образ, ничего дополнительно настраивать не нужно.
---
## Риски и ограничения
| Риск | Оценка | Митигация |
|---|---|---|
| `rnix/openssl-gost` на Debian Stretch EOL | средний | Образ работает, обновление — отдельная задача. Изолирован в контейнере. |
| Долгий старт пода (pull большого образа) | низкий | Образ уже в локальном Registry, время pull минимально |
| РКН возвращает ошибку — pod упадёт, cron не перезапустит до следующего часа | средний | `restartPolicy: OnFailure` сделает retry. Можно добавить `backoffLimit: 3` в Job |
| PVC `ReadWriteOnce` — только одна нода | принимается | Оба CronJob работают последовательно и на одной ноде (single-worker кластер) |
| `request.xml.sig` истёк (сертификат оператора) — нет алертинга | средний | Добавить мониторинг по коду завершения Job (future work) |
---
## План миграции
1. **Создать deploy token** в GitLab (`docker/rkn-list-poller` → Settings → Repository → Deploy tokens, scope `read_registry`)
2. **Применить манифесты** через Flux или `kubectl apply`:
- Namespace → PVC → CronJob × 2
3. **Создать секреты вручную** на manager-ноде:
- `rkn-registry-pull` (registry credentials)
- `rkn-request-files` (request.xml + request.xml.sig из текущего Swarm-тома)
4. **Запустить джобы вручную** для проверки:
```bash
kubectl create job rkn-black-list-test \
--from=cronjob/rkn-black-list -n rkn-poller
kubectl logs -f -n rkn-poller -l job-name=rkn-black-list-test
```
5. **Убедиться, что `dump_black.zip` обновился** в PVC:
```bash
kubectl run check --rm -it --image=busybox \
--overrides='{"spec":{"volumes":[{"name":"d","persistentVolumeClaim":{"claimName":"rkn-data"}}],"containers":[{"name":"c","image":"busybox","command":["ls","-lh","/data"],"volumeMounts":[{"name":"d","mountPath":"/data"}]}]}}' \
-n rkn-poller
```
6. **Остановить Swarm-сервис** `rkn` после подтверждения работы.