Ошибки сервера взаимодействия 1С: как найти причину по журналу

Всем привет, с вами Низамов Илья. Ошибка системы взаимодействия 1С почти всегда выглядит одинаково: у пользователя «Нет соединения с сервером системы взаимодействия», у администратора служба, которая молча остановилась. Причин за этим симптомом с десяток, и почти каждая оставляет в журнале сервера взаимодействия 1С свою строку. Ниже собраны ошибки, которые я сам поймал, когда ставил сервер взаимодействия 30.0.42 руками на Windows, а потом упаковывал его в Docker. Каждая проверена перезапуском: текст ошибки, причина, команда исправления.

Если вам нужно не только починить штатный сервер, но и понять, как он устроен изнутри, а заодно написать свой WebSocket-сервер для уведомлений из 1С, это курс «Сервер взаимодействия 1С на Python».

Если сервер ещё не установлен, начните с обзора что такое сервер взаимодействия и из чего он состоит или с установки в Docker. Здесь речь только о поломках.

Почему сервер взаимодействия ломается молча?

Сервер взаимодействия — это не одна программа, а небольшой кластер из четырёх частей:

Часть Что делает Порт
сам сервер (1c-cs-server-small) WebSocket для 1С:Предприятия, REST обслуживания, точка подключения интеграций 9999, 8087, 8443
PostgreSQL абоненты, приложения, беседы, сообщения 5432
Elasticsearch 5.6 поиск по переписке 9200, 9300
Hazelcast 3.9 доставка сообщений, счётчики непрочитанного, «собеседник печатает» 5701

Сообщения не лежат в информационной базе. 1С:Предприятие подключается к серверу по WebSocket, и вся переписка живёт там. Поэтому любая из четырёх частей, упавшая или недоступная, для пользователя выглядит одинаково: обсуждения не работают.

Вторая причина «молчания»: сервер при старте сам накатывает схему базы (миграциями Flyway), сам подключается к поиску и к кэшу. Если на любом из этих шагов что-то не так, процесс останавливается, а в окне служб Windows остаётся просто «Остановлена». Причина при этом записана, но в файле, а не на экране.

Ещё одна практическая деталь: доставка нового сообщения клиентам идёт через Hazelcast, а не из базы. Сообщение, вставленное в PostgreSQL в обход сервера, не увидит никто, поэтому «починить переписку запросом в базу» не получится.

В каком порядке искать причину?

В моей инструкции к ручной установке есть фраза, которая сэкономила мне больше всего времени:

Порядок проверки: сначала статус службы, потом логи, и только потом curl.
curl сам по себе службу не роняет, он лишь показывает, что она уже не работает.

Соблазн обычно обратный: дёрнуть /rs/health, не получить ответа и начать гадать. Правильный маршрут такой:

  1. Служба или контейнер. Работает ли процесс, не перезапускается ли он по кругу. В Docker это docker compose ps и признак (healthy), а не Restarting (1) 5 seconds ago с растущим счётчиком. В Windows это службы 1ce-*.
  2. Журнал. Последняя строка перед падением.
  3. /rs/health. Только когда процесс жив.
  4. Что сервер принял. Журнал показывает, что вы серверу отправили, а ring ... list-params показывает, что он применил. При разборе поломки спрашивать надо второе.
  5. Сторона базы 1С. Регистрация, адрес, версия, сертификат.

Пройдите этот маршрут по шагам, отвечая на вопросы:

Команда из шага 4 в контейнере выглядит так:

docker compose exec cs bash -c 'R=$(echo /opt/1C/1CE/components/1c-enterprise-ring-*/ring); \
  "$R" cs --instance cs_instance websocket list-params; \
  "$R" cs --instance cs_instance maintenance list-params; \
  "$R" cs --instance cs_instance elasticsearch list-params'
Response{hostname='0.0.0.0', port=19999, ... wss=false, ...}
Response{address=/0.0.0.0, port=8087}
Response{addresses='elasticsearch:9300', clusterName='1ce-cs', ...}

Осторожно с пулом JDBC: ring cs --instance cs_instance jdbc pools --name common list-params печатает пароль к базе открытым текстом. Прежде чем вставлять этот вывод в чат или в задачу, пароль нужно замазать.

Где лежат журналы сервера взаимодействия?

У каждого компонента свой каталог экземпляра (instance), в нём папка logs. У сервера там три файла, которые нужны при разборе:

  • server.log — главный журнал с причинами падений. Ищите строки level=ERROR;
  • launcher.log — вывод пусковика: как поднималась JVM;
  • websocket.log — события канала до 1С:Предприятия. Пуст, пока ни одна база не подключена.

Файлы с суффиксами .1, .2 остаются от предыдущих запусков. Это полезно: причина вчерашнего падения никуда не делась.

На Windows каталог экземпляра тот, который вы указали в ring cs instance create --dir, например C:\cs\cs_instance\logs\. В Docker журнал пусковика идёт в docker compose logs, а файлы лежат на томе:

docker compose logs cs --tail 40
docker compose exec -T cs ls -la /var/lib/1ce/cs/logs/
docker compose exec -T cs grep "level=ERROR" /var/lib/1ce/cs/logs/server.log

Ошибки при запуске сервера

Если у вас на руках строка из журнала, вставьте её в поиск. В нём собраны все ошибки из статьи, плюс безвредные предупреждения, которые часто принимают за причину:

function uuid_generate_v4() does not exist

Самая известная ловушка. Сервер при первом старте накатывает схему, миграции нужна функция uuid_generate_v4() из расширения uuid-ossp, а расширения в базе нет. Сервер останавливается, и выглядит это как «не запускается без причины».

Коварство в том, что расширение обычно создали, но не там. Расширение PostgreSQL создаётся внутри конкретной базы: создали в postgres — в cs_db его нет. В моей инструкции предупреждение написано капслоком не случайно:

Для базы данных cs_db следует подключить расширения uuid-ossp.
ВАЖНО: расширение должно быть создано именно в базе cs_db, а не в базе postgres.

Исправление и проверка:

\c cs_db
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
SELECT extname, extversion FROM pg_extension;

В Docker ошибиться нельзя, если положить скрипт в docker-entrypoint-initdb.d: штатный образ PostgreSQL выполняет его уже подключённым к базе из POSTGRES_DB. Скрипт заодно проверяет, что функция отвечает, и падает сразу, а не через минуту в чужом журнале:

psql -v ON_ERROR_STOP=1 --username "${POSTGRES_USER}" --dbname "${POSTGRES_DB}" <<'SQL'
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
SQL

psql -v ON_ERROR_STOP=1 --username "${POSTGRES_USER}" --dbname "${POSTGRES_DB}" \
     -At -c "SELECT uuid_generate_v4()" > /dev/null

Проверьте заодно пулы JDBC. Их два, common (рабочий) и privileged (для миграций), и оба должны смотреть в одну базу. Если они разъедутся, сервер поднимется, а схему не обновит.

BindException: Cannot assign requested address

SocketIO server start failed at port: 19999!
BindException: Cannot assign requested address

Параметр websocket.hostname — это адрес привязки сокета, а не имя, которое сервер сообщает клиенту. На Windows, где сервер работает на самой машине, там стоит адрес машины, и это работает. Я перенёс то же значение в контейнер и получил бесконечный перезапуск: такого адреса внутри контейнера нет. Разбирался три перезапуска.

ring cs --instance cs_instance websocket set-params --hostname 0.0.0.0

Адрес, по которому идёт клиент, задаётся публикацией порта, а не этим параметром.

С портом WebSocket есть и обратная ловушка: номер попадает в строку подключения, которую человек вводит при регистрации базы. Если опубликовать порт наружу под другим номером, чем внутри, однажды получите «сервер недоступен» без единой ошибки в журнале. Поэтому в compose у меня порт одинаковый с обеих сторон:

ports:
  - "${AIP_CS_WS_PORT:-19999}:${AIP_CS_WS_PORT:-19999}"

PID file exists and the process with PID referenced there is running

Ошибка контейнерная, но показательная. Пусковик 1С хранит номер процесса в daemon.pid внутри каталога экземпляра, то есть на томе. Контейнер упал, файл остался. При следующем старте пусковик видит файл, проверяет процесс с этим номером и находит его живым: в контейнере номера начинаются с единицы, и вчерашний PID сегодня принадлежит самому скрипту запуска. Итог: отказ в цикле.

Лечится удалением файла перед стартом. Раз мы стартуем, нашего процесса в этом контейнере ещё нет, проверять нечего:

clear_stale_pid() {
    local dir="$1"
    if [ -f "${dir}/daemon.pid" ]; then
        log "снимаю daemon.pid от прошлого запуска ($(cat "${dir}/daemon.pid" 2>/dev/null))"
        rm -f "${dir}/daemon.pid"
    fi
}

/rs/health не открывается с другой машины

REST обслуживания (/rs/health, /admin/**) по умолчанию слушает 127.0.0.1. Локально всё отвечает, снаружи соединение отклонено.

На версии 25.0.22 штатная команда ring cs ... maintenance set-params --address падала с ошибкой «невозможно найти соответствующее значение для поля java.net.InetAddress». Обход — записать адрес в файл config/maintenance.yml напрямую:

server:
  address: 0.0.0.0
  port: 8087

В 30.0.42 команда уже работает, я проверил: ring вернул 0 и записал тот же YAML. Файлом я адрес всё равно пишу: значение здесь не настройка, а свойство установки, и ради него не стоит поднимать ещё одну JVM. Каждый вызов ring занимает пару секунд. Не забудьте открыть порт 8087 в брандмауэре, если проверяете сервер с другой машины.

Ошибки Elasticsearch и Hazelcast

Если сервер и узлы поиска с кэшем живут на одной машине и слушают локальную петлю, этих ошибок вы не увидите. Они появляются, как только компоненты разнесены по машинам или контейнерам.

Боевой режим Elasticsearch: четыре отказа подряд

Сервер взаимодействия ходит в Elasticsearch не по HTTP (9200), а транспортным протоколом на порт 9300. Значит, узлу поиска нельзя слушать только петлю. А Elasticsearch 5.6, как только слушает не петлю, считает себя боевым и включает стартовые проверки. Каждая проверка — отдельный отказ запуска:

Строка в журнале Что нужно
max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] параметр ядра хоста ≥ 262144
initial heap size [...] not equal to maximum heap size [...] -Xms равен -Xmx
max file descriptors [4096] for elasticsearch process is too low дескрипторов ≥ 65536, потоков ≥ 2048
can not run elasticsearch as root запуск от отдельного пользователя

vm.max_map_count — самое неудобное требование: параметр ядра общий на всю машину, контейнер его видит, но не меняет. Это самая частая поломка при переносе на новый Linux-сервер:

cat /proc/sys/vm/max_map_count          # надо >= 262144
sudo sysctl -w vm.max_map_count=262144  # разово
echo 'vm.max_map_count=262144' | sudo tee /etc/sysctl.d/99-elasticsearch.conf

Размер кучи задаётся явно, двумя равными числами. Без этого JVM посчитает умолчания от памяти всей машины, и они разойдутся. Остальные два требования — пределы процесса:

elasticsearch:
  ulimits:
    nofile:
      soft: 65536
      hard: 65536
    nproc: 4096

Отдельно про elasticsearchOk: false при живом узле: либо узел ещё поднимается (сервер делает повторные попытки), либо в параметрах сервера указан порт 9200 вместо 9300, либо не совпало имя кластера (cluster.name у узла и --cluster-name у сервера).

Hazelcast: Member [127.0.0.1]:5701

В hazelcast.xml из поставки адрес узла — 127.0.0.1. Это не только список для поиска соседей: по нему Hazelcast выбирает адрес, на котором слушает сам. Узел объявляет себя Member [127.0.0.1]:5701, порт вроде бы открыт, а сервер с другой машины или из соседнего контейнера до него не доходит. В /rs/health это видно как "hazelcast":{"available":false}.

Нужно подставить реальный адрес узла. Именно адрес, а не 0.0.0.0: Hazelcast сообщает свой адрес клиенту, и клиент идёт по нему следующим соединением. У меня это делает скрипт запуска:

ip="$(hostname -i | awk '{print $1}')"
sed -E -i \
    -e "s#<interface>[0-9.]+</interface>#<interface>${ip}</interface>#" \
    -e "s#<member>[0-9.]+</member>#<member>${ip}</member>#" \
    "${dir}/config/hazelcast.xml"

Проверка на живом узле: grep -n "<member>" hazelcast.xml не должен показывать 127.0.0.1.

Как читать ответ /rs/health?

У исправного сервера ответ выглядит так:

{"status":"UP","mainDbOk":true,"allShardsOk":true,
 "hazelcast":{"available":true,"members":["172.29.0.2:5701"]},
 "elasticsearchOk":true,"mediaClusterOk":false,"mediaServers":{},"pushOk":false}

Да, два false у исправного сервера. mediaClusterOk и pushOk остаются ложными, пока не настроены MinIO для файлов и служба push-уведомлений. Я сверил это с ручной установкой на Windows: там ровно то же. Чинить их не нужно. Отсюда практический вывод: автоматическая проверка здоровья должна ждать только "status":"UP", иначе она будет вечно красной.

Переключите поля и посмотрите, что означает каждое:

Чаще всего после установки встречается allShardsOk: false. Сервер поднялся, но список серверов хранения разговоров пуст, и складывать переписку некуда. В инструкции производителя это отдельный ручной шаг после старта, и его легко пропустить:

curl -Sf -X POST -H "Content-Type: application/json" \
  -u admin:<пароль> \
  -d '{"url":"jdbc:postgresql://<хост>:5432/cs_db","username":"cs_user","password":"<пароль>","enabled":true}' \
  http://localhost:8087/admin/bucket_server

Если в ответ пришёл 401, неверна учётка администратора. Прочитать текущий список можно тем же адресом через GET: одна запись со ссылкой на базу и даёт allShardsOk: true.

И про учётку: в поставке это admin/admin, а под ней лежит весь /admin/**, то есть регистрация баз, хранилище и настройки сервера. Пароль меняется командой ring cs ... http_security set-params --users "admin::<пароль>::ADMIN". Формат имя::пароль::роль через запятую, поэтому двоеточий и запятых в пароле быть не должно.

Ошибка протокола системы взаимодействия: не та версия

Самая обидная ошибка, потому что выглядит как поломка сети. База на платформе 8.3.27 регистрировалась на сервере 25.0.22. WebSocket-соединение устанавливалось, рукопожатие проходило, а в server.log сыпалось:

level=ERROR ... UnknownEventException: Unknown event: setOptions
level=ERROR ... UnknownEventException: Unknown event: createOrUpdateApplication

Платформа шлёт события, которых сервер старой линейки не знает. Версия сервера взаимодействия привязана к версии платформы 1С: для 8.3.25 ставится линейка 25.0.x, для 8.3.27 — 27.0.x и новее. Лечится только заменой сервера на подходящую линейку. Выбирать дистрибутив нужно под платформу баз, а не «какой посвежее».

Проверить со стороны базы можно встроенным языком:

Вызов Что должно вернуться
СистемаВзаимодействия.ИнформационнаяБазаЗарегистрирована() Истина
СистемаВзаимодействия.ИспользованиеДоступно() Истина
СистемаВзаимодействия.АдресСервера() ws://<хост>:<порт>
СистемаВзаимодействия.ВерсияСервера() 30.0.42 (ваша версия)

Если база подключена не к своему серверу, а к облачному серверу 1С, журналов сервера у вас нет. Из этой статьи тогда применимы только проверки на стороне клиента: версия платформы, сеть, сертификаты.

При обновлении сервера порядок такой: сначала останавливается сам сервер, потом Hazelcast и Elasticsearch, поднимаются они в обратном порядке. Отключать базы обработкой перед обновлением не нужно, производитель отдельно об этом предупреждает: клиенты на время покажут значок с восклицательным знаком и переподключатся сами. Отключение обработкой означало бы повторную регистрацию. Перед обновлением обязательна копия базы сервера:

pg_dump -U cs_user cs_db | gzip > cs_db-$(date +%Y%m%d).sql.gz

Нет соединения при регистрации: сертификаты и wss

Регистрация базы делается обработкой CollaborationSystemRegister.epf из поставки или через «Администрирование → Органайзер → Обсуждения». Нужна почта администратора и код из письма, адрес сервера — ws://<хост>:<порт>. Пока канал без шифрования, проблем здесь обычно нет. С wss на самоподписанном сертификате у меня было три поломки подряд.

Первая:

Client requested protocol TLSv1 is not enabled or supported in server context.
Ошибка при регистрации. Невозможно установить соединение с сервером системы
взаимодействия tlsv1 alert protocol version

Клиент попросил TLSv1, а Java на сервере его запретила. На моей установке помогло убрать TLSv1 из jdk.tls.disabledAlgorithms в файле conf\security\java.security каталога LibericaJDK и перезапустить службу. Это ослабляет шифрование, так что держите такой обход временным.

Вторая, сразу после первой:

Ошибка при регистрации. certificate verify failed

Сертификат сервера не в доверенных на машине, с которой регистрируют базу. Лечение: скопировать .cer и установить его в «Доверенные корневые центры сертификации» локального компьютера. Хранилище ключей и файл сертификата делаются утилитой из JDK:

keytool -genkey -alias websocket-keystore -keyalg RSA -keystore websocket-keystore.jks
keytool -export -alias websocket-keystore -file websocket-keystore.cer \
        -keystore websocket-keystore.jks

Третья проявляется позже. Сертификат ставится на каждую машину, где запускается 1С с подключением к серверу. Десять рабочих мест — десять установок, и ещё раз при каждой смене сертификата. Самоподписанный сертификат годится, чтобы проверить механизм, но не как решение. В работе нужен сертификат домена, либо TLS снимает nginx перед сервером (proxy_pass на порт WebSocket с заголовком Upgrade).

Не путайте этот TLS с TLS точки подключения интеграций (Webhook, Telegram, VK и прочие внешние системы). Там требование другое: публичный адрес обязан начинаться с https, это константа в коде сервера, выключателя нет. Сам endpoint при этом слушает обычный http на 8443, так что сертификат нужен не серверу, а nginx перед ним:

Интернет ──https──► nginx ──http──► сервер взаимодействия :8443

Адрес обратного вызова сервер собирает как <publicUrl>/integration/webhook/<токен>/callback, поэтому publicUrl задаётся без пути, только схема и домен. Быстрая проверка, что точка на месте: GET на путь обратного вызова должен вернуть 405 Method Not Allowed. Путь существует, просто ждёт POST.

Какие предупреждения можно не чинить?

Эти строки появляются в журнале при каждом старте и регулярно попадают в поиск причины. Причиной они не являются:

  • WARNING: An illegal reflective access operation has occurred — библиотека Hazelcast внутри сервера собрана под Java 8, а работает на 11. Четыре строки при каждом старте, ни на что не влияют.
  • Flyway upgrade recommended: ... 17.9 is newer than this version of Flyway and support has not been tested — Flyway 7.7.3 из поставки не знает PostgreSQL 17. Уровень WARN, всё работает, я проверил на двух установках с 17-й базой. Консервативный выбор — PostgreSQL 13, но если миграции поведут себя странно, вспомните об этом предупреждении первым.
  • awk: function gensub never defined — пусковой скрипт ring зовёт awk с функцией gensub, которой нет у mawk в урезанных Debian. ring при этом работает. Строка исчезнет после apt-get install gawk.
  • mediaClusterOk: false, pushOk: false — разобраны выше, у исправного сервера так же.

Как не ловить эти ошибки второй раз

Почти все ошибки выше — следствие ручной настройки: команду забыли, выполнили в другой базе, перенесли значение с одной машины на другую. Ручную установку по инструкции повторяешь каждый раз немного иначе. Поэтому в Docker-версии я сделал так, что настройка раскладывается при каждом старте контейнера из переменных окружения, а не один раз при установке. Том с настроенным экземпляром можно удалить целиком, и стек поднимется таким же.

Что это даёт на практике:

  • uuid-ossp создаётся скриптом инициализации в нужной базе и сразу проверяется;
  • адрес WebSocket, адрес REST и адрес узла Hazelcast выставляются под контейнер, а не копируются с машины;
  • daemon.pid снимается перед каждым стартом;
  • сервер хранения разговоров регистрируется фоном после /rs/health, повторный запуск ничего не портит: сначала читается текущий список;
  • сервер не стартует, пока база, кэш и поиск не ответили «здоров» (depends_on с condition: service_healthy);
  • без пароля администратора стек отказывается запускаться, умолчания admin/admin нет.

Проверка здоровья самого сервера ждёт только статус, по причине из раздела про /rs/health:

healthcheck:
  test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8087/rs/health | grep -q '\"status\":\"UP\"'"]
  interval: 10s
  timeout: 10s
  retries: 30
  start_period: 90s

Весь стек, четыре контейнера, поднимается до полного здоровья за 33 секунды. Цикл docker compose down / up проходит без единого ручного шага. Только не путайте down с down -v: второй удаляет тома, то есть всю переписку, индексы и настройки экземпляров.

Чего такая установка сама не закроет: копии базы cs_db по расписанию, внешнюю проверку /rs/health (метрики сервер уже отдаёт на /actuator/prometheus) и сертификат для wss. Первые две стоит сделать до первого живого клиента, иначе о падении узнает клиент, а не вы.

И отдельно про лицензию. Сервер взаимодействия производитель описывает как внутриорганизационный, бесплатное право на свой экземпляр получают организации с лицензиями уровня КОРП. Технически сервер лицензию не проверяет вовсе, так что «оно же запустилось» доводом не является. Если собираетесь подключать базы чужих юрлиц, это вопрос к партнёру 1С, а не к настройке.

Что дальше

Если не хотите разбираться с этим сами, настройку сервера взаимодействия можно заказать у нас.

А если хотите понимать систему взаимодействия изнутри — события, бота, свой WebSocket-сервер на Python для уведомлений и прогресса операций без облака 1С, — приходите на курс «Сервер взаимодействия 1С на Python». Сервер 1С:Предприятия, кластер и PostgreSQL на Linux разбираем на курсе «Сервер 1С:Предприятие».

Частые вопросы