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

15 KiB
Raw Permalink Blame History

Перенос 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

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)

План миграции

  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. Запустить джобы вручную для проверки:
    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:
    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 после подтверждения работы.