21 KiB
workflows-engine через k3s (планирование Job'ов) — Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Добавить в контур workflows-engine как сервис docker-compose, который через kubeconfig-контекст создаёт Job'ы в k3s; Job-поды резолвят postgres/minio из compose-сети через k8s DNS-мост.
Architecture: engine — обычный compose-контейнер (до workflow_db ходит по compose-DNS), в k3s ходит out-of-cluster (KUBE_CONFIG+KUBE_ADDR). В namespace processing k3s заводятся Service+Endpoints postgres/minio на статические IP compose-контейнеров, чтобы Job-поды достукивались до БД и S3. Поднимаем только k3s-server.
Tech Stack: Docker Compose, k3s (rancher/k3s v1.36), kubectl, Ansible (роль sarex_stack), Go-сервис workflows-engine (конфиг через env, cleanenv).
Спека: docs/superpowers/specs/2026-07-30-processing-engine-k3s-scheduling-design.md
Global Constraints
- Целевой хост — RedOS 8 (SELinux → volume-флаги
:z/:ro,z), деплой из WSL через ansible рольsarex_stack. - Все образы параметризуются
${VAR:-default}; секреты — через.env, генерятся ролью. - Только
k3s-server(безk3s-worker). DownstreamENABLE_*engine выключены, кромеENABLE_S3_STORAGE. - Статические IP в compose:
postgres=172.28.0.10,minio=172.28.0.11, subnet172.28.0.0/16. - Имена в k8s = compose-имена:
postgres,minio, namespaceprocessing. - Образ engine:
${SAREX_WORKFLOWS_ENGINE_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/workflows-endigne_prod:075fc0}. - Проверки инфраструктуры — не pytest: валидация
docker compose config -q,kubectl --dry-run, коннект-чек по логам. Каждая задача завершается коммитом.
File Structure
iac/docker-compose.yaml(Modify) — top-levelnetworks.defaultIPAM; static IP уpostgres/minio; сервисыengine,processing-k8s-init.iac/.env.example(Modify) —SAREX_WORKFLOWS_ENGINE_IMAGE.iac/k3s/manifests/processing/namespace.yaml(Create) — namespaceprocessing.iac/k3s/manifests/processing/bridge-postgres.yaml(Create) — Service+Endpointspostgres.iac/k3s/manifests/processing/bridge-minio.yaml(Create) — Service+Endpointsminio.iac/backend/yc-s3-service-account.json→ нет; S3 SA кладём вiac/engine/yc-s3-service-account.json(Create, плейсхолдер наhttp://minio:9000).iac/aero/roles/sarex_stack/tasks/main.yml(Modify) — копированиеk3s/manifestsиengine/.iac/aero/roles/sarex_stack/defaults/main.yml(Modify) —sarex_services+=k3s-server,processing-k8s-init,engine.iac/aero/pyproject.toml(Modify) — poe-таск проверки k8s-моста (опц.).
Task 1: Параметр образа engine + S3 SA-плейсхолдер
Files:
- Modify:
iac/.env.example - Create:
iac/engine/yc-s3-service-account.json
Interfaces:
-
Produces: env-переменная
SAREX_WORKFLOWS_ENGINE_IMAGE; файл SAiac/engine/yc-s3-service-account.json, монтируемый в engine на/etc/sarex/yc-s3/yc-s3-service-account.json. -
Step 1: Добавить переменную образа в
.env.example
В секцию образов iac/.env.example добавить строку:
# workflows-engine (processing) — оркестратор Job'ов в k3s
SAREX_WORKFLOWS_ENGINE_IMAGE=cr.yandex/crp3ccidau046kdj8g9q/workflows-endigne_prod:075fc0
- Step 2: Создать плейсхолдер S3 service-account JSON
iac/engine/yc-s3-service-account.json (endpoint на прямой MinIO; app-креды подставит рантайм/ansible — тут плейсхолдер для планирования):
{
"host": "http://minio:9000",
"bucket": "sarex-media-storage",
"access_key_id": "sarex-app",
"secret_access_key": "sarex-app-secret",
"region": "us-east-1",
"verify": false
}
- Step 3: Проверка — файл валидный JSON
Run: python -c "import json;json.load(open('iac/engine/yc-s3-service-account.json'));print('ok')"
Expected: ok
- Step 4: Commit
git add iac/.env.example iac/engine/yc-s3-service-account.json
git commit -m "feat(engine): образ workflows-engine + S3 SA-плейсхолдер"
Task 2: Compose-сеть со статическими IP для postgres/minio
Files:
- Modify:
iac/docker-compose.yaml
Interfaces:
-
Produces:
postgresдоступен по172.28.0.10,minioпо172.28.0.11в сетиdefault(subnet172.28.0.0/16). На эти IP ссылаются k8s-Endpoints (Task 4). -
Step 1: Добавить top-level
networksc IPAM
В конец iac/docker-compose.yaml (рядом с volumes:) добавить:
networks:
default:
ipam:
config:
- subnet: 172.28.0.0/16
- Step 2: Пин статического IP у
postgres
В сервис postgres добавить блок networks:
networks:
default:
ipv4_address: 172.28.0.10
- Step 3: Пин статического IP у
minio
В сервис minio добавить:
networks:
default:
ipv4_address: 172.28.0.11
- Step 4: Проверка — конфиг валиден и IP на месте
Run (в iac/): docker compose --env-file .env.example config | grep -A3 -E "ipv4_address|subnet"
Expected: видно subnet: 172.28.0.0/16, ipv4_address: 172.28.0.10 и 172.28.0.11.
(Если docker недоступен локально — выполнить на сервере в deploy_dir после копирования.)
- Step 5: Commit
git add iac/docker-compose.yaml
git commit -m "feat(net): статические IP postgres/minio для k8s-моста"
Task 3: k8s-манифесты DNS-моста (namespace + Service/Endpoints)
Files:
- Create:
iac/k3s/manifests/processing/namespace.yaml - Create:
iac/k3s/manifests/processing/bridge-postgres.yaml - Create:
iac/k3s/manifests/processing/bridge-minio.yaml
Interfaces:
-
Consumes: статические IP из Task 2 (
172.28.0.10,172.28.0.11). -
Produces: в namespace
processingрезолвятся именаpostgres:5432иminio:9000(→ compose-контейнеры). -
Step 1: namespace
iac/k3s/manifests/processing/namespace.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: processing
- Step 2: мост postgres (Service без селектора + Endpoints)
iac/k3s/manifests/processing/bridge-postgres.yaml:
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: processing
spec:
ports:
- name: pg
port: 5432
targetPort: 5432
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: postgres
namespace: processing
subsets:
- addresses:
- ip: 172.28.0.10
ports:
- name: pg
port: 5432
protocol: TCP
- Step 3: мост minio
iac/k3s/manifests/processing/bridge-minio.yaml:
apiVersion: v1
kind: Service
metadata:
name: minio
namespace: processing
spec:
ports:
- name: s3
port: 9000
targetPort: 9000
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: minio
namespace: processing
subsets:
- addresses:
- ip: 172.28.0.11
ports:
- name: s3
port: 9000
protocol: TCP
- Step 4: Проверка — YAML валиден (клиентский dry-run)
Run (при наличии kubectl; иначе отложить на сервер):
kubectl apply --dry-run=client -f iac/k3s/manifests/processing/
Expected: namespace/processing created (dry run), service/postgres ..., endpoints/postgres ..., service/minio ..., endpoints/minio ....
Fallback без kubectl: python -c "import glob,yaml; [list(yaml.safe_load_all(open(f))) for f in glob.glob('iac/k3s/manifests/processing/*.yaml')]; print('ok')" → ok.
- Step 5: Commit
git add iac/k3s/manifests/processing/
git commit -m "feat(k3s): DNS-мост postgres/minio в namespace processing"
Task 4: Сервис processing-k8s-init (применение манифестов)
Files:
- Modify:
iac/docker-compose.yaml
Interfaces:
-
Consumes: манифесты
./k3s/manifests/processing/(Task 3), kubeconfig./k3s/kubeconfig.yaml(пишетсяk3s-server). -
Produces: applied namespace + мост в k3s; сервис завершается успешно (barrier для engine).
-
Step 1: Добавить сервис-хелпер (переиспользуем образ k3s — в нём есть kubectl)
В iac/docker-compose.yaml (после k3s-worker/до gitea или рядом с processing-сервисами) добавить:
# Одноразовый провижининг k3s: namespace processing + DNS-мост postgres/minio.
# Образ k3s содержит kubectl; server/tls перекрываем на k3s-server:6443.
processing-k8s-init:
image: ${K3S_IMAGE:-rancher/k3s:v1.36.2-k3s1}
container_name: processing-k8s-init
restart: "no"
depends_on:
k3s-server:
condition: service_healthy
entrypoint: ["/bin/sh", "-ec"]
command:
- |
kubectl --kubeconfig /kube/config \
--server https://k3s-server:6443 --insecure-skip-tls-verify \
apply -f /manifests/
volumes:
- ./k3s/kubeconfig.yaml:/kube/config:ro,z
- ./k3s/manifests/processing:/manifests:ro,z
- Step 2: Проверка — конфиг валиден
Run (в iac/): docker compose --env-file .env.example config --services | grep processing-k8s-init
Expected: processing-k8s-init.
- Step 3: Commit
git add iac/docker-compose.yaml
git commit -m "feat(k3s): processing-k8s-init — применение DNS-моста через kubectl"
Task 5: Сервис engine в docker-compose
Files:
- Modify:
iac/docker-compose.yaml
Interfaces:
-
Consumes:
k3s-server(kube-API),processing-k8s-init(мост applied),postgres/postgres-init(workflow_db), kubeconfig./k3s/kubeconfig.yaml, S3 SA./engine/yc-s3-service-account.json(Task 1). -
Produces: работающий оркестратор, создающий Job'ы в namespace
processing. -
Step 1: Добавить сервис
engine
В iac/docker-compose.yaml рядом с processing-сервисами:
# workflows-engine — оркестратор: читает workflow_db, создаёт Job'ы в k3s.
# Ходит в k3s out-of-cluster (KUBE_CONFIG+KUBE_ADDR). K8s-исполнитель; AMQP выкл.
engine:
image: ${SAREX_WORKFLOWS_ENGINE_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/workflows-endigne_prod:075fc0}
container_name: engine
restart: unless-stopped
entrypoint: ["/engine"]
environment:
APP_NAME: workflows-engine
LOG_LEVEL: info
ENVIRONMENT: contour
# исполнители
ENABLE_KUBERNETES_EXECUTOR: "1"
ENABLE_AMQP_EXECUTOR: "0"
COUNT_RUNNING_WORKERS: "1"
COUNT_CANCELING_WORKERS: "1"
COUNT_HANDLE_JOB_WORKERS: "1"
WORKFLOW_PRIORITY: low
MAX_WORKFLOWS_LIMIT: "5"
# доступ в k3s (out-of-cluster)
KUBE_CONFIG: /kube/config
KUBE_CONTEXT: default
KUBE_ADDR: https://k3s-server:6443
JOBS_NAMESPACE: processing
# свой доступ к workflow_db (compose-DNS)
POSTGRES_ADDRESS: postgres
POSTGRES_PORT: "5432"
POSTGRES_DB: ${SAREX_PROCESSING_DB:-workflow_db}
POSTGRES_USER: ${SAREX_PROCESSING_DB_USER:-processing}
POSTGRES_PASSWORD: ${SAREX_PROCESSING_DB_PASSWORD:-processing-secret}
POSTGRES_POOL_SIZE: "20"
POSTGRES_SSL_USE: "0"
# хранилища: только S3, напрямую в minio
ENABLE_S3_STORAGE: "1"
S3_SERVICE_ACCOUNT: /etc/sarex/yc-s3/yc-s3-service-account.json
# дефолты планирования подов
DEFAULT_IMAGE_PULL_POLICY: IfNotPresent
DEFAULT_CPU_REQUESTS: 100m
DEFAULT_MEMORY_REQUESTS: 64Mi
volumes:
- ./k3s/kubeconfig.yaml:/kube/config:ro,z
- ./engine/yc-s3-service-account.json:/etc/sarex/yc-s3/yc-s3-service-account.json:ro,z
depends_on:
postgres:
condition: service_healthy
postgres-init:
condition: service_completed_successfully
k3s-server:
condition: service_healthy
processing-k8s-init:
condition: service_completed_successfully
- Step 2: Проверка — конфиг рендерится, образ и зависимости на месте
Run (в iac/): docker compose --env-file .env.example config | grep -A2 "container_name: engine"
Expected: сервис engine с образом workflows-endigne_prod.
Run: docker compose --env-file .env.example config | grep -E "KUBE_ADDR|JOBS_NAMESPACE"
Expected: KUBE_ADDR: https://k3s-server:6443, JOBS_NAMESPACE: processing.
- Step 3: Commit
git add iac/docker-compose.yaml
git commit -m "feat(engine): сервис workflows-engine (k8s-исполнитель через kubeconfig)"
Task 6: Интеграция в Ansible-роль
Files:
- Modify:
iac/aero/roles/sarex_stack/defaults/main.yml - Modify:
iac/aero/roles/sarex_stack/tasks/main.yml
Interfaces:
-
Consumes: файлы Task 1-5 в репозитории
iac/. -
Produces:
poe stack/installкопирует манифесты иengine/, поднимаетk3s-server,processing-k8s-init,engine. -
Step 1: Добавить сервисы в
sarex_services
В iac/aero/roles/sarex_stack/defaults/main.yml, список sarex_services, добавить (порядок не критичен — depends_on рулит; но добавим осмысленно):
- k3s-server
- processing-k8s-init
и после celery (или рядом с processing) добавить:
- engine
- Step 2: Копировать каталог
k3s/manifestsна хост
В iac/aero/roles/sarex_stack/tasks/main.yml, рядом с копированием nginx/backend, добавить задачу:
- name: Скопировать k3s-манифесты (DNS-мост processing)
ansible.builtin.copy:
src: "{{ deploy_src_root }}/k3s/manifests/"
dest: "{{ deploy_dir }}/k3s/manifests/"
mode: "0644"
- Step 3: Копировать каталог
engine/(S3 SA) на хост
Там же добавить:
- name: Скопировать конфиги engine (S3 service-account)
ansible.builtin.copy:
src: "{{ deploy_src_root }}/engine/"
dest: "{{ deploy_dir }}/engine/"
mode: "0644"
- Step 4: Проверка — YAML роли валиден и сервисы в списке
Run (в iac/aero/): uv run python -c "import yaml;d=yaml.safe_load(open('roles/sarex_stack/defaults/main.yml'));assert {'k3s-server','processing-k8s-init','engine'} <= set(d['sarex_services']);print('services ok')"
Expected: services ok.
Run: uv run python -c "import yaml;list(yaml.safe_load_all(open('roles/sarex_stack/tasks/main.yml')));print('tasks ok')"
Expected: tasks ok.
- Step 5: Commit
git add iac/aero/roles/sarex_stack/defaults/main.yml iac/aero/roles/sarex_stack/tasks/main.yml
git commit -m "feat(ansible): деплой engine + k3s DNS-моста в роли sarex_stack"
Task 7: Развёртывание и проверка планирования на сервере
Files: (нет изменений кода — деплой и верификация)
Interfaces:
-
Consumes: всё выше, ветка
aero, доступ к хосту через ansible из WSL. -
Step 1: Задеплоить файлы и поднять стек
Run (в iac/aero/, WSL): uv run poe stack
(копирует compose/манифесты/.env, docker compose up -d включая k3s-server, processing-k8s-init, engine.)
Expected: playbook завершается без ошибок; changed.
- Step 2: Проверить, что k3s-server поднялся
Run: uv run poe ps (или ad-hoc docker compose ps)
Expected: k3s-server в статусе Up (healthy); processing-k8s-init — Exited (0); engine — Up.
- Step 3: Проверить DNS-мост в k3s
Run (ad-hoc на хосте): docker exec k3s-server kubectl -n processing get svc,endpoints
Expected: Service postgres, minio и Endpoints с адресами 172.28.0.10:5432, 172.28.0.11:9000.
- Step 4: Проверить коннект engine к workflow_db и kube-API
Run: uv run poe logs engine --tail 120
Expected: в логах — успешное подключение к Postgres и к kube-API, нет паник (failed to parse, connection refused, panic).
Если engine падает на старте — сверить с риском №2 спеки (downstream ENABLE_*) и №1 (KUBE_ADDR vs правка server: в kubeconfig).
- Step 5: Проверить резолв имён из тестового пода в k3s
Run:
docker exec k3s-server kubectl -n processing run netcheck --rm -it --restart=Never \
--image=busybox:1.36 -- sh -c "nslookup postgres; nc -z -w3 postgres 5432 && echo PG_OK; nc -z -w3 minio 9000 && echo S3_OK"
Expected: postgres резолвится в ClusterIP, PG_OK, S3_OK (под достучался до compose-postgres/minio через мост).
- Step 6: Проверить планирование Job'а
Инициировать workflow (через processing-api/фронт или тестовую запись в workflow_db), затем:
Run: docker exec k3s-server kubectl -n processing get jobs,pods
Expected: появляется Job-объект, под запланирован на ноду (статус Running/ContainerCreating/ImagePullBackOff — любой из них подтверждает планирование; успешный прогон конвертации — вне рамок).
- Step 7: Зафиксировать результат
Никаких изменений кода; при необходимости — записать в память проекта факт готовности планирования и оставшиеся follow-up (валидный S3 SA, downstream-сервисы, образы задач).
Self-Review
1. Spec coverage:
- Компонент A (engine compose) → Task 1, 5. ✓
- Компонент B (только k3s-server) → Task 6 (
sarex_services), Task 7 (проверка). ✓ - Компонент C (DNS-мост postgres/minio) → Task 3, 4. ✓
- Сеть/статические IP → Task 2. ✓
- Ansible-интеграция → Task 6. ✓
- Порядок провижининга →
depends_on(Task 4, 5) + Task 7. ✓ - Риски (KUBE_ADDR, стартовые проверки, S3 SA, образ задачи, cgroup/SELinux) → Task 7 Step 4/6 отсылают к рискам спеки. ✓
- Проверка результата (svc/endpoints, логи, jobs) → Task 7. ✓
2. Placeholder scan: S3 SA — намеренный плейсхолдер (задокументирован в спеке как приемлемый для планирования), не «TODO в требовании». Остальное — конкретный контент. ✓
3. Type consistency: имена согласованы сквозняком — 172.28.0.10/172.28.0.11, namespace processing, Service/Endpoints postgres/minio, env KUBE_ADDR=https://k3s-server:6443, JOBS_NAMESPACE=processing, образ workflows-endigne_prod:075fc0. ✓
Открытый нюанс для исполнителя: если engine НЕ принимает KUBE_ADDR как override (риск №1), fallback — в processing-k8s-init/отдельном шаге sed-правкой заменить server: https://127.0.0.1:6443 на https://k3s-server:6443 в ./k3s/kubeconfig.yaml и снять KUBE_ADDR, оставив KUBE_CONFIG+KUBE_CONTEXT.