iac/docs/superpowers/plans/2026-07-30-processing-engine-k3s-scheduling.md

21 KiB
Raw Blame History

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). Downstream ENABLE_* engine выключены, кроме ENABLE_S3_STORAGE.
  • Статические IP в compose: postgres = 172.28.0.10, minio = 172.28.0.11, subnet 172.28.0.0/16.
  • Имена в k8s = compose-имена: postgres, minio, namespace processing.
  • Образ 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-level networks.default IPAM; static IP у postgres/minio; сервисы engine, processing-k8s-init.
  • iac/.env.example (Modify) — SAREX_WORKFLOWS_ENGINE_IMAGE.
  • iac/k3s/manifests/processing/namespace.yaml (Create) — namespace processing.
  • iac/k3s/manifests/processing/bridge-postgres.yaml (Create) — Service+Endpoints postgres.
  • iac/k3s/manifests/processing/bridge-minio.yaml (Create) — Service+Endpoints minio.
  • 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; файл SA iac/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 (subnet 172.28.0.0/16). На эти IP ссылаются k8s-Endpoints (Task 4).

  • Step 1: Добавить top-level networks c 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-initExited (0); engineUp.

  • 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.