# Перенос 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= \ --docker-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` после подтверждения работы.