15 KiB
Перенос 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).
Три скрипта, один алгоритм:
- Читает
request.xmlиrequest.xml.sigиз примонтированной директории. - Отправляет SOAP-запрос (
sendRequest), получает$request_code. - Опрашивает сервис каждые 3 минуты (
getResult/getResultSocResources) до готовности. - Записывает 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
apiVersion: v1
kind: Namespace
metadata:
name: rkn-poller
Secret с файлами запроса
Создаётся вручную на manager-ноде (не хранится в Git):
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
При смене сертификата оператора:
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
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
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: rkn-data
namespace: rkn-poller
spec:
storageClassName: longhorn
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
CronJob: rkn-black-list
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
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) |
План миграции
- Создать deploy token в GitLab (
docker/rkn-list-poller→ Settings → Repository → Deploy tokens, scoperead_registry) - Применить манифесты через Flux или
kubectl apply:- Namespace → PVC → CronJob × 2
- Создать секреты вручную на manager-ноде:
rkn-registry-pull(registry credentials)rkn-request-files(request.xml + request.xml.sig из текущего Swarm-тома)
- Запустить джобы вручную для проверки:
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 - Убедиться, что
dump_black.zipобновился в PVC: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 - Остановить Swarm-сервис
rknпосле подтверждения работы.