362 lines
15 KiB
Markdown
362 lines
15 KiB
Markdown
# Перенос 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` после подтверждения работы.
|