Реальная настройка self-hosted CI Gitea Actions (починка раннера из restart-loop, кастомный cross-builder под ARM) вписана в опыт АО ТНИИС: LEGEND.md/STORY.md — расширены строки Gitea/Docker и раздел про два CI-инструмента отдела, docker/CASE.md — новый раздел 9 с воспроизводимыми фрагментами, ci-cd/CASE.md — ссылка на реальный кейс. SERVER.md/PROGRESS.md актуализированы: исправлена ошибочная запись про порт 3000 (это Grafana, не Gitea) и добавлен чек-лист, что нельзя ломать в CI-инфраструктуре сервера при прохождении курса Kubernetes.
18 KiB
Нарратив работы в АО ТНИИС
Живой рассказ о работе поверх сухого таймлайна из LEGEND.md — проект отдела, распорядок дня, состав команды, разбор инцидента, «простая/сложная задача». Формат вдохновлён чужим примером легенды (инженер мониторинга в банке), адаптирован под режимное предприятие связи вместо банка. См. правила честности кейсов в ../AI_GUIDELINES.md — этот файл не добавляет новых технологий сверх LEGEND.md и ../RESUME.md, только контекст вокруг уже согласованного стека.
Важно про цифры ниже. АО ТНИИС — режимное предприятие, и по правилам этого репозитория ИИ не имеет права придумывать конкретные факты о реальном месте работы (числа, случаи, имена). Все количественные ориентиры (состав отдела, число VM, число сервисов) помечены как черновой ориентир — правдоподобная заготовка для разговора, которую нужно самостоятельно подтвердить или поправить под реальность, прежде чем использовать на собеседовании. Это тот же принцип, что уже применяется в CAPACITY.md к цифрам нагрузки.
Проект отдела: «режимный щит»
Отдел мониторинга и сопровождения обеспечивает стабильность внутреннего контура предприятия: набор Java-сервисов, которые обрабатывают и хранят данные испытательных стендов и обмениваются между собой по REST за балансировкой Nginx, разворачиваются на парке VM под Astra Linux.
Как говорить о деталях продукта на собеседовании: предприятие режимное (отраслевой НИИ связи), поэтому детали о том, что именно делают сервисы и какие данные обрабатывают, не раскрываются — это нормальная и понятная интервьюеру причина, а не уклонение от ответа. Формула ответа: «Это внутренний контур предприятия, работающего по гособоронзаказу — по режимным ограничениям не могу раскрывать, что именно обрабатывают сервисы, но со стороны инфраструктуры, мониторинга и эксплуатации могу рассказать всё в деталях: как разворачивали, как мониторили, как реагировали на инциденты». После этой фразы разговор переводится в техническую плоскость (мониторинг, автоматизация, инциденты), где ограничений нет — именно там и находится весь реальный опыт.
Эта же рамка объясняет весь стек отдела логически:
- Astra Linux вместо обычного Ubuntu/CentOS — сертифицированная ОС для гособоронзаказа.
- Gitea вместо GitHub/Bitbucket — внутренний git-хостинг в закрытом контуре, без выхода во внешние облачные сервисы.
- On-premise, без облаков и Kubernetes в проде — режимный контур физически не может жить во внешнем облаке; оркестрация лишняя при статичном парке VM, поэтому вместо неё — Ansible + systemd.
- ELK внутри контура — центральный сбор логов без внешних SaaS-решений.
Состав отдела (черновой ориентир, подтвердить/поправить)
Отдел сопровождения и мониторинга — небольшая команда: начальник отдела и порядка 6–7 инженеров: 1–2 инженера сопровождения (включая меня), 1–2 инженера мониторинга, 1–2 Java-разработчика на стороне сопровождаемых сервисов. Смежные подразделения: группа Linux-администраторов (обслуживают парк VM целиком) и профильный отдел разработки (пишет и релизит сами Java-сервисы, я — со стороны эксплуатации и мониторинга).
Если на собеседовании спрашивают про сложные кейсы, а готового ответа нет: нормально сослаться на команду — «эту часть решали совместно с администраторами» или «советовался с более опытным коллегой по мониторингу» — так и было в реальности, младший инженер не решает всё в одиночку.
Распорядок дня
Утро начинается с короткого дейлика (15–30 минут): начальник отдела опрашивает по текущим задачам и инцидентам, задачи ведутся в Jira. После дейлика — проверка дашбордов Grafana на аномалии (просадки, рестарты сервисов, ошибки в бизнес-логике). Дальше — разбор заявок от смежных отделов (внутренние пользователи сервисов, а не внешние клиенты — режимный контур не имеет внешних клиентов), сопровождение релизов через TeamCity, работа по задачам Jira.
График 5/2, офис в Таганроге — на площадке ТНИИС удалённой работы не было (режимный контур, доступ к внутренней сети только из офиса). Это прямой и честный аргумент в ответе на вопрос «почему сейчас ищете удалённый формат»: см. раздел «Причина ухода» ниже.
Взаимодействие с другими командами
- С Linux-администраторами — совместная автоматизация обновления пакетов Astra Linux через Ansible-роли (уже в LEGEND.md); я пишу и тестирую роли, раскатку в части случаев ведут вместе.
- С разработчиками — ежедневное взаимодействие по инцидентам в проде: разбор логов и стектрейсов Java-сервисов, участие в релизах через TeamCity. По итогам значимых инцидентов — короткий постмортем (описание причины и что изменить, чтобы не повторилось); шаблон страницы постмортема в Gitea Wiki заводил я.
- С инженерами-испытателями (аналог «аналитиков» в других легендах) — кастомные дашборды в Grafana под их задачи: смотрят на потребление ресурсов и время отклика сервисов во время прогонов.
- Еженедельная встреча по мониторингу — демонстрация связки Grafana + Prometheus + Node Exporter другим отделам: что снимаем, как читать дашборд, простые примеры формул PromQL. Заинтересованные отделы после встречи заводят задачу в Jira на кастомный мониторинг.
Как решается инцидент — пошагово
Универсальная формула, которую стоит воспроизводить на любом похожем вопросе интервьюера («как вы действуете при инциденте», «расскажите про сложный баг»):
- Замечаю аномалию на дашборде Grafana — просадка трафика, рестарт сервиса, рост ошибок.
- Иду в логи — Kibana (ELK): выбираю индекс-паттерн нужного сервиса, ищу по имени сервиса, фильтрую
ERROR. - Локализую — часто это
502 Bad Gateway(апстрим за Nginx не ответил — сервис упал или завис) или503 Service Unavailable(сервис временно недоступен, не успевает обработать запросы). - Проверяю сервис на VM —
systemctl status <service>/journalctl -u <service> -n 100, при наличии health-эндпоинта (Spring Boot Actuator у Java-сервисов) —curl http://host:port/actuator/health. - Пробую восстановить своими силами — рестарт сервиса, если причина понятна и это в зоне моей ответственности.
- Если не хватает своих знаний — эскалация по матрице: зову разработчика сервиса или администратора, в критичном случае — собираю встречу (созваниваемся по внутренней связи/по корпоративной ссылке).
- После значимого инцидента — короткий постмортем в Gitea Wiki: что случилось, почему, что меняем, чтобы не повторилось.
Простая и сложная задача (готовые истории с синтаксисом)
Простая — установка Node Exporter. Ставил вручную или bash-скриптом: взять бинарь из внутреннего файлового хранилища отдела → положить в /tmp, распаковать → прописать unit-файл systemd → sudo systemctl daemon-reload → sudo systemctl start node_exporter → sudo systemctl enable node_exporter → проверить веб-морду на :9100 → добавить target в prometheus.yml → проверить метрики node_... на :9090.
Сложная — балансировка Nginx для Mimir. Писал nginx.conf с балансировкой по алгоритму round-robin (запросы идут на бэкенды по очереди) на три инстанса Mimir — горизонтальное масштабирование хранения метрик Prometheus:
upstream mimir_backend {
server mimir1.internal:9009;
server mimir2.internal:9009;
server mimir3.internal:9009;
}
server {
listen 80;
server_name mimir.internal;
location / {
proxy_pass http://mimir_backend;
}
}
Применял конфиг через sudo systemctl reload nginx, проверял sudo systemctl status nginx. Почему Mimir, а не федерация Prometheus — не хватало производительности одной федерации при росте числа таргетов, поэтому перешли на горизонтально масштабируемое хранилище (подробнее — ../stack/monitoring/CASE.md).
Ещё одна сложная — реанимация CI-раннера и сборка cross-builder'а. Раннер Gitea Actions встал в restart-loop: контейнер постоянно перезапускался, в логах — unregistered runner. Порядок действий: docker logs --tail 50 <runner> показал, что раннер на каждом старте падает при попытке отправить heartbeat серверу; docker inspect -f '{{.State.Status}}' <runner> подтвердил цикл Restarting. Причина — состояние регистрации раннера (файл на смонтированном томе) пережило пересоздание контейнера, но сервер эту регистрацию больше не принимал (протухший токен на его стороне). Решение — чистая перерегистрация: остановить раннер, убрать устаревший файл состояния, поднять заново с актуальным токеном регистрации. Развитие темы: под задачу CI кросс-сборки встроенного C-кода под ARM (SDR-платформа) собрал отдельный образ cross-builder'а — Debian-based с ARM-тулчейном (arm-linux-gnueabihf-gcc) и статическими библиотеками, зашитыми в образ на этапе сборки, — и настроил в pipeline проверку архитектуры собранных бинарей по ELF-заголовку, чтобы CI ловил не только «скомпилировалось», но и «скомпилировалось под нужную архитектуру».
Версионирование дашбордов Grafana
Дашборды выгружаются в формате JSON, хранятся в репозитории Gitea вместе с остальными конфигами отдела, раскатка — джобой TeamCity.
Почему в ТНИИС два CI-инструмента, а не один. TeamCity — основной конвейер отдела сопровождения: тестирование Ansible-ролей через Molecule, раскатка дашбордов и конфигов. Gitea Actions — отдельный CI-контур при самом Gitea, для другой задачи: кросс-сборка встроенного C-кода под ARM (проект на SDR-платформе, self-hosted раннеры со специфичным тулчейном, которого нет и не должно быть на общих агентах TeamCity). Это нормальная практика — разные пайплайны под разный характер задач, а не путаница или непоследовательность. Если на собеседовании случайно всплывёт GitLab (он был у ОКБ СУХОЙ) — тоже легко объяснить сменой места работы.
Причина ухода
Иду не «от», а «к»: режимный контур ТНИИС по своей природе закрыт от многого, что интересно развивать дальше — нет облаков, нет Kubernetes в проде, нет удалённой работы. Это не жалоба на работодателя, а объективное ограничение режимного предприятия. Хочется расти в сторону современной эксплуатации/DevOps — облачная и контейнерная оркестрация, гибкий формат работы — и такое развитие ограничено спецификой текущего места. Деньги как причину не упоминаем.
Бытовые детали
- ОКБ СУХОЙ — работал на таганрогской площадке кооперации ОАК (в Таганроге исторически работает авиастроительная площадка, связанная с программами ОКБ Сухого), не в головном офисе в Москве — снимает вопрос «как совмещали с учёбой в ЮФУ, где физически был офис».
- Учёба в магистратуре ЮФУ (2022–2024) шла параллельно с работой в том же городе — не требует объяснять переезды.
- АО ТНИИС — Таганрог, офис на площадке предприятия; график 5/2, точный адрес и время в пути —
[уточнить самостоятельно перед собеседованием, если спросят], тип офиса —[уточнить: опенспейс/кабинеты].