Skip to content

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 05 Oct 13:42
· 128 commits to main since this release
5c830f3

Шлюз под сбои и под масштаб, безопасность, симулятор и фасад на Rust (Python в репозитории больше нет), условная
логика прошивки в симуляторе. Формат сообщений Kafka, ключи и топики не менялись; новое для монитора —
docs/MONITOR_INTEGRATION.md («Что нового для монитора в шлюзе v0.3»), эксплуатация — docs/OPERATIONS.md.

Исправлено

  • Недоставленная телеметрия не теряется молча. После обрыва брокера значения, которые фильтр
    «по исключению» уже считал отправленными, не доходили до монитора до полной отправки. Теперь, когда
    доставка восстановилась, все теги отправляются заново (scada_kafka_resyncs_total); предупреждения о
    недоставленных пишутся раз в 30 с (раньше — по строке на сообщение, тысячи при обрыве).
  • Полная отправка без «залпа». Первая полная отправка каждого тега смещена на свою долю периода
    (filter::phase): на 54 800 тегах вместо одной секунды с десятками тысяч сообщений раз в 30 с —
    ровный поток.
  • Падение задачи не остаётся незамеченным. Опрос контроллеров, приём команд и фоновые задачи
    перезапускаются надзирателем (scada_task_restarts_total, событие SYSTEM/ERROR); отравленный
    мьютекс больше не каскадирует паники.
  • События в Kafka не зависят от БД. Запись в БД вынесена в отдельную задачу: при недоступной БД
    смена роли пары и обрывы связи уходят в Kafka сразу, а не после таймаута пула. Потерянные из-за БД
    строки считаются (scada_db_write_errors_total, scada_db_rows_lost_total).
  • /actuator/health показывает компонент kafka.
  • OPC UA-клиент шлюза пробует все адреса имени. Клиент async-opcua берёт первый адрес из DNS и
    не пробует остальные: localhost → ::1 первым, а сервер на 0.0.0.0 слушает только IPv4 —
    шлюз на хосте не подключался (Could not connect to host [::1]:4840), хотя 127.0.0.1 отвечал.
    Теперь имя с несколькими адресами проверяется TCP-подключением, и если первый недоступен,
    используется тот, что ответил (в журнале — строка «недоступен — подключаюсь к …»).

Проверка конфигурации

  • Конфигурация контроллеров проверяется целиком при старте: повторяющееся имя тега (ключ Kafka и адрес команды),
    повторяющееся имя контроллера, пустое имя тега и неизвестный протокол в endpoint останавливают запуск с
    перечнем проблем (раньше контроллер с неизвестным протоколом молча не опрашивался, а два тега с одним именем
    делали телеметрию и команды неоднозначными). Выключенные контроллеры и теги в расчёт не идут.

Безопасность OPC UA

  • Защищённое подключение к ПЛК. security: (Basic256Sha256, Aes128_Sha256_RsaOaep, Aes256_Sha256_RsaPss,
    Basic256, Basic128Rsa15; режим _Sign / _SignAndEncrypt) и username:/password: в controllers.yaml
    теперь работают — раньше разбирались и молча игнорировались (канал оставался открытым и анонимным). Сертификат сервера
    проверяется по trusted/ (GATEWAY_OPCUA_PKI_DIR, том /app/pki в образе);
    GATEWAY_OPCUA_TRUST_SERVER_CERTS — только для стенда. Ошибка в security, логин без пароля и защита у
    Modbus/PAC останавливают запуск с понятным сообщением; пароль не попадает в журнал и Debug.
  • Симулятор: SIM_OPCUA_USER / SIM_OPCUA_PASSWORD включают защищённые конечные точки (Basic256Sha256) — на них
    проверяются политика, режимы, неверный пароль, анонимный вход, цепочка доверия к сертификату сервера.

Эксплуатация

  • docs/OPERATIONS.md — настройки, здоровье, метрики и оповещения, что шлюз делает при сбоях.
  • scripts/stack_smoke.sh — дымовой тест стенда целиком с отказами Kafka, БД и ПЛК.
  • scripts/load_check.sh (job load в CI) — нагрузочная проверка на 27 400 тегах: опрос не отстаёт, нет ошибок доставки и перезапусков задач, память не растёт.

Симулятор повторяет условную логику прошивки

  • Снято с прошивки мойки (все 1816 записываемых каналов станции) и воспроизведено в симуляторе: часть полей прошивка
    принимает (код 0), но не меняет (обратная связь, датчики, регистры команд, состояния техобъектов — 107), часть
    действует только в ручном режиме (LINE#V0 — главные клапаны линий, виртуальные выходы, сторожевые таймеры — 18; при
    возврате в автоматику значение возвращается к программе), часть хранится целой (16). В replay_config.yaml — ключи
    write: ignore, write_requires: {field: M, equals: 1}, write_int, write_state. Протокол PAC отвечает как
    прошивка: неизвестное поле известного прибора — код 0, не число — код 1.
  • simulator conformance [host] [port] [config] проверяет соответствие по driver-master у любого PAC (CI — на симуляторе;
    на прошивке мойки: 81 тег соответствует, 45 пропущено, расхождений 0). Описание — docs/FIRMWARE_WRITE_BEHAVIOR.md.

Изменено

  • PLC-симулятор переписан с Python на Rust (simulator/): OPC UA, Modbus TCP и PAC
    driver-master в одном процессе, тот же replay_config.yaml, тот же реплей архива BN1_MCA1
    (формат архива — data/archive_replay.bin.gz вместо pickle). Образ — бинарник без интерпретатора,
    узлы OPC UA получают только изменившиеся значения, запись оператора применяется сразу.
    Структура снимка PAC и раскладка регистров Modbus совпадают с прежним симулятором
    (сверено на 63 приборах PAC и 500 регистрах). Новое: поле-массив PAC пишется командой
    set_cmd('RT_PAR_F', 12, v) как у настоящего ptusa; simulator probe-pac вместо pac_probe.py.
    Убраны: синтетические генераторы сигнала (в конфигурации только replay), режим «сырых записей
    приборов» (sim_records.py, нужен был Java-шлюзу), tools/*.py (build_archive.py
    строил архив из SQL-дампа, которого в репозитории нет).

  • OPC UA-фасад ptusa-opcua/ переписан с Python на Rust (async-opcua + mlua): те же узлы
    ns=2;s=<прибор>.<поле>, те же настройки окружения и порт, запись — set_cmd с исходом прошивки
    в статусе записи. Проверен на настоящей прошивке ptusa: 1834 канала GOOD, команда APPLIED за
    27 мс, переключение резерва шлюза. Песочница Lua — как в шлюзе (без os/io/load/байткода,
    память 64 МБ, 1 с на снимок), потолок распаковки zlib 8 МБ; вместе с этим закрыт эффект алерта
    GHSA-69v7-xpr6-6gjm (lupa в репозитории больше нет). Python в репозитории не осталось.
    Ограничение: конечные точки GetEndpoints строятся из OPCUA_BIND (async-opcua не разделяет
    адрес привязки и анонса); сторонним клиентам OPC UA, которые не пробуют второй адрес localhost, вне Docker нужен OPCUA_BIND=[::]:4840 (шлюз с этим справляется сам, см. «Исправлено»).

Добавлено

  • Безопасность Kafka: TLS и SASL (PLAIN, SCRAM) — KAFKA_SECURITY_PROTOCOL,
    KAFKA_SASL_*, KAFKA_SSL_*, любое свойство librdkafka через KAFKA_CLIENT_*, Spring-имена
    SPRING_KAFKA_PROPERTIES_* (включая JAAS); OpenSSL вшит в бинарник. Секреты не попадают в журнал.
    Проверено на брокере с SASL_PLAINTEXT: неверный пароль — отказ в аутентификации, верный — данные идут.
  • Безопасность REST: GATEWAY_API_TOKEN (или GATEWAY_API_TOKEN_FILE) закрывает /api/*
    токеном Bearer; /actuator/health и /actuator/prometheus открыты. GATEWAY_HTTP_BIND.
  • Резервирование по здоровью: активный экземпляр без связи со всеми контроллерами
    (GATEWAY_HA_YIELD_AFTER_MS, 30 с) передаёт лидерство партнёру, который в группе, и не отбирает
    его обратно; без партнёра остаётся активным.

Образ: docker pull ghcr.io/euzireael/scada_rust:v0.3.0