Привет, Хабр! Меня зовут Илья Олексив, я лидер компетенции направления DevOps в Цифровом СИБУРе. Вместе с моим коллегой Мишей Фуфаевым aka @Redemax, техлидом, в ноябре мы выступали на Industrial++ и обещали выложить наше выступление со слайдами сюда.

Собственно, рассказываем, как нам удалось сократить количество откатов релизов в три раза, при этом увеличив частоту этих релизов в два и уменьшив среднее время установки на 30%. И всё это — в географически распределённой инфраструктуре со множеством команд эксплуатации.

В чём сложность с релизами в СИБУРе?

Чтобы понять, почему тема релизов проблемная, для начала давайте разберёмся, что из себя представляет компания СИБУР. Мы крупная, географически распределённая компания, в состав которой входит 26 заводов и работает более 40 тыс. высококвалифицированных сотрудников.

В СИБУРе Цифровом мы занимаемся цифровизацией процессов материнской компании, у нас более 2000 сотрудников и 200 IT-систем в развитии и поддержке.

Основные задачи СИБУРа Цифрового:

  • Повышение эффективности производственных процессов.

  • Разработка новых продуктов и услуг.

  • Обеспечение безопасности и устойчивого развития.

  • Повышение конкурентоспособности СИБУРа.

Типы проектов

С точки зрения инфраструктуры проекты, которые находятся у нас в разработке и на поддержке, можно разделить на две категории: Infrastructure hard и Infrastructure easy.

В Infrastructure hard мы можем отнести:

  • Лабораторию

  • Видеоаналитику

  • IOT

  • Допуски

В категорию Infrastructure easy мы включаем:

  • Соцсеть

  • Личный кабинет

  • IDP

И давайте на примере видеоаналитики и IDP объясним, почему первый проект мы относим в одну категорию, а второй — в другую. Начнём с Infrastructure easy.

Проект easy: IDP

Что такое IDP? Это не Identity Provider, как вы могли подумать, а Internal Developer Portal — веб-платформа, предоставляющая разработчикам компании централизованный доступ к ресурсам, инструментам и документации, необходимым для создания и поддержки программного обеспечения.

По сути, это платформа, которая занимается улучшением процессов, связанных с разработкой ПО. К примеру, бэкендеры у нас живут в микросервисных реалиях уже достаточно давно, и чтобы сделать это максимально быстро и без стресса, они используют IDP, в которой просто заполняют несколько полей и создают микросервис за несколько секунд, который готов к раскатке как на dev, так и на test и prod.

И теперь давайте рассмотрим его с точки зрения архитектуры и инфраструктуры. У нас есть dev часть. Это, по сути, часть разработки, которая разделяется глобально на три слоя. 

Первый слой — indexer — вытягивает данные из различных систем компаний и складывает в графовую базу данных. Дальше слои — service и ui — по сути, ui запрашивает у service данные из базы, всё достаточно просто.

Нам нужно развернуть это в приватном облаке московского ЦОДа, разворачиваем в Kubernetes.

И здесь команда Dev (разработчиков) и Ops (эксплуатации), по сути, объединены — у них всё просто, минимум бюрократии, они пьют смузи и разворачиваются в кубере. Поэтому мы их называем Infrastructure easy.

Проект hard: видеоаналитика

Что это такое и зачем нужно? Это проект видеоаналитики, его основные задачи:

  • Повышение эффективности использования систем видеонаблюдения на производстве.

  • Снижение информационной нагрузки на оператора на производстве, сокращение времени реакции на какие-то нештатные ситуации, которые могут произойти на производстве.

Чтобы было проще понять, давайте посмотрим на примеры.

Это кадр не с нашего производства, нам по политикам ИБ запрещено такие вещи постить, поэтому выше сгенерированная картинка, смысл по ней понять можно. Специалист работает без страховки, алгоритм анализирует видеопоток, выявляет нарушения техники безопасности и подсвечивает работника красным квадратом, а потом инициирует отправку сообщения руководителю.

Ещё один кейс на сгенерированном джипеге. Алгоритмы анализируют, насколько эффективно и безопасно рабочие отработали смену, и выводят инфу на дашборд.

Теперь рассмотрим особенности этого проекта.

Начнём с разработческой части. Она глобально делится на четыре слоя:

  1. Detector. Основная задача детектора — преобразовывать картинку, которая получается из видеопотока, в какое-то событие.

  2. Inspector. Задача инспектора — дать характеристику событию: плохое (красный квадрат) или хорошее (зелёный квадрат).

  3. Logic. Задача лоджика — дать этому оценку: критично, некритично, суперкритично либо это вообще очень хорошо.

  4. Aggregator. Это уже опционально, если на производстве для каких-то бизнес-задач нужно агрегировать данные, к примеру, для формирования дашборда из второго кейса.

С точки зрения инфраструктуры этот проект можно разворачивать по-разному. Например, у нас есть завод номер 1, для него мы разворачиваем detector, inspector, aggregator. На втором разворачиваем только detector и logic. А на третьем разворачиваем сразу всё, потому что там высокая степень цифровизации и есть соответствующий запрос. Было бы здорово, если можно было развернуть всё в облаке вот так.

Но такой сценарий не работает, потому что СИБУР — географически распределённая компания, а проект чувствителен к пропускной способности канала связи. Поэтому нужно разворачивать все компоненты видеоаналитики локально.

К тому же каждый завод, по сути, — отдельное юрлицо, и качество поставок ПО должно отслеживаться сотрудниками этого юрлица. И как бы нам ни хотелось сформировать единую команду эксплуатации, которая будет отвечать за сохранность и работоспособность сервисов на заводских мощностях, находясь где-нибудь в Москве, мы так сделать не можем. Поэтому мы неизбежно приходим к структуре, когда у нас общая команда разработки, которая создаёт и развивает компоненты, затем поставляет их на заводы, на которых свои команды эксплуатации.

Команде разработчиков нужно договариваться с каждой командой эксплуатации. Им нужно учитывать особенности инфраструктуры на каждом заводе, потому что на одном заводе может быть одно, на втором — вообще противоположное.

Поэтому мы считаем этот проект Infrastructure hard.

В итоге у нас есть проект — это IDP, в котором ребята пьют смузи и разворачиваются в кубере. Команда разработки и эксплуатации у них единая. И есть команда видеоаналитики, которым нужно договариваться с каждым заводом, учитывать специфику и проблемы, которые могут возникнуть при развёртывании на заводских мощностях.

И теперь основной вопрос всей этой статьи. Каким образом мы можем получить универсальный процесс доставки приложения, который подойдёт как проектам Infrastructure hard, так и проектам Infrastructure easy?

Чтобы ответить на этот вопрос, давайте немного погрузимся в историю вопроса: как вообще выглядят взаимоотношения девов и опсов с точки зрения DevOps топологии.

Взаимоотношения Dev и Ops

Начиналось всё с того, что между разработкой и эксплуатацией была «стена» — команды жили отдельно друг от друга, и их задачи никак не пересекались.

И это всё приводило к тому, что у разработчиков недостаточно информации и контекста о том, что происходит в эксплуатации. А эксплуатации плевать на то, что происходит в разработке. Это проблема.

Попытка всё наладить

Стандартное решение проблемы — это завести команду опсов в разработке, которая будет фокусироваться на проблемах разработки и пытаться отладить процессы там.

Это шаг вперёд. Но это решение не убирает главную проблему, «стена» не исчезает, команды всё ещё живут отдельно.

Решение #1

Ещё вариант решения — это сотрудничество команд разработки и эксплуатации. Каждая команда живёт отдельно по своим личным вопросам, но когда возникает общая проблема, они садятся и решают её вместе.

Но как мы показали на кейсе проекта Infrastructure hard, такое невозможно из-за наших организационных ограничений сейчас. И для этого нужна высокая степень компетенции как со стороны разработки в сторону эксплуатации, так и со стороны эксплуатации в сторону разработки. Поэтому эта схема нам не очень подходит.

Решение #2

Мы можем полностью размыть границы. И такая схема работает на проекте IDP, о котором говорили выше.

В целом всё прекрасно, там отсутствует бюрократия, но проекты Infrastructure hard не позволяют нам использовать такую схему везде.

Решение #3 — наш выбор

В итоге то, что нам подходит, — схема DevOps as a Service (DaaS). Концепция заключается в том, что эксплуатация должна обеспечить гибкую инфраструктуру для развёртывания и запуска приложения.

Это единая методология для всех проектов на базе набора утилит, о которых мы расскажем дальше. При этом граница между командами сохраняется — в этом минус. «Стену» мы не разрушили, но придумали «автоматизированные ворота», которые могут открываться и закрываться по алгоритмам DaaS.

На примере проекта видеоаналитики это будет выглядеть как «кошачья лапка».

У нас есть общая команда разработки и более трёх заводов, на которых отдельные команды эксплуатации. И есть какое-то ПО, какая-то методология, которая связывает эти части.

Теперь посмотрим DaaS проекта IDP.

Тут границы размыты, но и здесь DaaS полезен, потому что это, по сути, ПО, которое связывает разработку и эксплуатацию. Но даже при размытых границах у нас есть ролевая модель и разграничения зон ответственности внутри команды, поэтому это ПО тоже будет полезно для них.

Как построить DaaS?

Рассмотрим на конкретном примере. Допустим, у нас есть завод, на нём есть дата-центр, и нам нужно на него как-то выкатить то, что выпустил разработчик.

Разработчик хранит свои исходные коды на GitLab. Соответственно, нам нужен Gitlab runner. Но одного Gitlab runner здесь недостаточно, нужно ещё что-то, что поможет нам преобразовать код в инфраструктуру и получить надёжное, понятное развёртывание. В этом случае нам нужен и IaC (Infrastructure as Code) подход. Но легко сказать «используйте IaC» — его нужно как-то организовать.

Способы организации IaC

Способ один

Первый способ — использовать монорепозиторий. В этом случае каждый из сервисов, который мы разработали, будет храниться в отдельной папке. Также в этом репо будут находиться CI/DC-скрипты.

Плюсы подхода в том, что легко отслеживать изменения в репозитории. Однако здесь возникает сложность в управлении CI-скриптами и их оптимизации. Поэтому нам такой вариант не подошёл.

Способ два

Ещё вариант — отдельный репозиторий для каждого сервиса. Есть, например, сервис detector — у него свой репозиторий, в нём CI/CD-скрипты.

С этим подходом всё классно, пока не появится продукт с десятками сервисов, когда для каждого сервиса нужен будет отдельный процесс выкатки — каждый сервис нужно будет протестировать и убедиться, что новая версия попала в прод.

Соответственно, этот подход генерирует хаос. Мы пробовали его в работе и знаем, чем это чревато. Можно тратить многие часы на созвоны и совещания в попытках понять, что, куда и зачем выкатывать. А если учесть множество инсталляций на разных заводах, то для каждой получается свой отдельный релизный хаос.

Нам это не подходит.

Способ три

И так мы пришли к следующему варианту — общий репозиторий, в котором лежат все конфигурации развёртывания. В нашем случае это Ansible playbook. А все CI/CD-скрипты, исходный код также оставили в независимых репозиториях.

Чем хорош этот подход? По сути, мы отвязали разработку от управления развёртыванием. То есть разработчик живёт в своём мире, выпускает новую версию, проверяет, что та функциональность, которую ему нужно было добавить, внесена, и говорит, что всё готово. После этого нужно обновить скрипты развёртывания и можно выкатываться.

И здесь вроде бы всё хорошо. Но давайте посмотрим, как это можно реализовать.

Как организовать?

В нашем случае у нас, как мы уже говорили, есть два типа продуктов: Infrastructure hard и Infrastructure easy. В случае с Infrastructure hard мы разворачиваем продукт на завод, там есть сервера, и нам нужен Ansible, чтобы это что-то развернуть. В случае с Infrastructure easy мы разворачиваемся в Kubernetes, и здесь мы выбрали Helmfile.

Ansible

Начнём с Ansible. В этом случае всё достаточно просто. За полный цикл развёртывания отвечает Ansible playbook, и с помощью inventory файлов мы управляем теми серверами, куда нам нужно это развернуть.

Соответственно, всё развёртывание превращается в одну команду ansible-playbook — и поехали.

Helmfile

Что такое Helmfile? Это надстройка над helm-чартами, которая необходима для того, чтобы организовать их взаимодействие. Если очень сильно упрощать, то это аналог docker-compose для helm-чартов. Он позволяет из разных сервисов сделать единый модуль, который можно выкатывать.

Когда мы выкатываем релиз в Kubernetes, у нас всё достаточно просто. Есть репозиторий, где описан helmfile, из него мы запускаем CI, и всё это деплоится в кубер.

Плюсы подхода:

  • Сложность доставки релиза уменьшается и становится константной, даже если количество инсталляций растёт.

  • Управляемость релиза выше.

  • Соответствует принципам подхода IaC (всё должно быть в коде).

Стратегические проблемы подхода:

  1. Как накопить версии компонентов и обновлять их автоматически? Необходимо обновлять скрипты в каждом репозитории — можно вручную, но это не самая приятная работа.

  2. Как гарантировать, что мы протестировали нужную версию? Нужно проверять, что мы выкатили ту же версию, что и протестировали.

  3. Как выдать доступ разным поддержкам в один репозиторий? Нужно контролировать уровни доступа для разных команд — если выдать всем доступ к единому репозиторию, могут возникнуть проблемы.

Решение стратегических проблем

Рассмотрим решение каждой проблемы по-отдельности. Начнём с проблемы накопления версий, компонентов и автоматического обновления скриптов.

Для этого сформируем наш DoD (Definition of Done). Что мы вообще хотим? Мы хотим в нашем playbook обновить detector с версии 24.0.2 по версию 24.2.0. Уже знакомый нам inspector хотим обновить с версии 21.10.0 по версию 21.11.0 и так далее.

Теперь давайте сформируем ТЗ. Что мы вообще хотим и что мы вообще ищем? Назовём это тактическими проблемами, которые нам нужно решить при выборе инструмента:

  • Автоматическое обновление версий.

  • Бесплатное и подходящее для enterprise решение.

  • Возможность накопления обновлений версий.

  • Возможность контролируемого обновления.

И всё это мы решили с помощью Renovate — это бот для управления зависимостями.

Renovate

Первая и основная функциональность Renovate — управление зависимостями.

На примере наших проектов он сделал merge request в Gitlab, по которым обновил зависимости gradle на версию 8.10.2, playwright — на версию 1.47.0, ну и так далее.

Теперь детальнее рассмотрим, что он делает в этом merge request.

  1. Прикладывает информацию в первой части реквеста о том, чего вообще касается обновление.

  2. Говорит нам о характере этого обновления. В данном случае это патчи обновления.

  3. И дальше говорится, с какой по какую версию обновились. В данном случае мы обновились с версии три 13 по версию три 14.

  4. И что очень приятно, в нижней части он прикладывает детальную информацию о том, а что же вообще произошло между этими версиями.

Вторая его основная функциональность — отслеживание уязвимостей в зависимостях.

Renovate интегрирован с OSV (Open Source Vulnerabilities) database. И в случае если он видит нашу зависимость, он мапит её с данными из OSV, если она была найдена, то он помечает, что она небезопасна. И если доступен фикс на эту уязвимость, он сам сделает merge request на обновление этой зависимости. И ещё прикладывает детальную информацию о том, какая уязвимость в нём выявлена и при каких обстоятельствах она, собственно, проявляется.

Третья основная функциональность Renovate — формирование dependency dashboard. Чисто технически это issue в GitLab, в рамках которой мы можем посмотреть:

  • по каким зависимостям открыты merge request;

  • по каким зависимостям, по каким причинам открыть merge request не удалось;

  • какие уязвимости выявлены в репозитории с точки зрения их зависимостей;

  • какие зависимости смог найти Renovate в репозитории.

Чтобы это работало, нам нужно сделать конфиг, обычно он называется renovate.json, кладут его в корень, и он в 90% случаев делится на две части.

{
    "extends": [
        "config:recommended"
    ],
    "packageRules": [
        {
            "matchDatasources": [
                "docker"
            ],
            "registryUrls": [
                "http://repo.dev002.local/docker"
            ]
        }
    ]
}

Первая часть — наследование какой-то глобальной конфигурации либо корпоративной конфигурации, либо в данном случае в инструкции extends мы наследуемся от конфигурации, рекомендуемой самими разработчиками Renovate.

Во второй части мы даём инструкцию packageRules, в рамках которой рассказываем Renovate, каким образом мапить зависимости. В примере выше мы говорим, что все зависимости, которые каким-либо образом относятся к docker, к примеру, выявлены в docker-файле, нужно искать в реестре по адресу https://repo.dev002.local/docker.

Хорошо, в целом проблему автоматического обновления версий мы решили. При этом Renovate — open source решение, которое подходит для корпоративного сектора, потому что у него огромное сообщество, около 18 000 звёзд на GitHub, и это сообщество очень активно. Бывают дни, когда они выпускают по пять релизов.

Как Renovate решает проблемы

Давайте теперь сконцентрируемся на проблемах, описанных выше, и на том, как мы их решили с помощью Renovate.

Возможность накопления обновлений версий

Может ли Renovate это? Может.

С помощью инструкции groupName мы можем сказать, что все зависимости, которые относятся к docker, нужно искать в repo.dev002 и группировать их в merge request с названием release. Давайте посмотрим результат.

Видим уже знакомые нам detector, inspector, logic и aggregator. Видим всю информацию о том, с какой по какую версию произошло обновление. И что приятно, он не только накопил нам обновление версий, но и обновил release notes — по сути, детальную информацию в одном месте по всем сервисам, которые в этот merge request попали.

Хорошо, накопление версий работает, но релиз — это не всегда выкат последней версии. А если мы хотим выкатить предпоследнюю версию, можем ли мы это сделать через Renovate? Это мы тоже можем.

С помощью инструкции allowedVersions можем указать, что в зависимости, относящейся к docker, которая называется detector, нужно обновить версию не больше чем на версию 24.2.0. И в результате получаем такой merge request.

И видим, что detector обновился не на 24.3.0, а на версию 24.2.0.

Получается, все тактические проблемы мы решили. И на основании этого я делаю вывод, что первая стратегическая проблема у нас решена.

Гарантия правильной выкатки

Вторая стратегическая задача заключается в том, что нам нужно гарантировать, что мы протестировали нужную версию и её же отправили в прод. Чтобы разобраться в этом, рассмотрим процесс обновления версий при помощи Renovate.

Допустим, мы выпустили новую версию detector и запаблишили её в артефакты. После этого мы запускаем дочерний пайп в нашем деплой репо, и в нём, соответственно, происходит два этапа.

На первом этапе мы запускаем Renovate, он находит новую версию. А вторым этапом Renovate, соответственно, выкатывает новый конфиг с этой версией на dev. Выглядит это следующим образом: Renovate нашёл -> обновил ветку -> конфиг выкатился. После этого нам нужно выкатить уже полноценный релиз на тест. Давайте посмотрим, как это происходит.

Опять же, у нас есть деплой репозиторий. В какой-то момент тестировщик говорит, что всё классно, фичу разработали, давайте теперь выпустим её на тест и проверим, что она работает.

Соответственно, он приходит, запускает пайп. Этот пайп убеждается, что с самими файлами релиза всё ок, и после этого запускает Renovate. Renovate запускает и создаёт merge request с конфигами теста и прода. После чего уже тестировщик запускает развёртывание тест ветки с полной конфигурацией на тест среду. В тест среде проходит тест, запускаются автотесты, ручные тесты. Мы убеждаемся в том, что наш продукт работает.

И после этого те же самые конфигурации, которые мы только что протестировали, отправляются уже с помощью merge request в прод ветку. То есть мы фактически говорим команде эксплуатации, что «мы продукт проверили, в этой версии всё работает так, как мы с вами договорились, можете устанавливать на прод, когда вам удобно».

И здесь важно, что команды эксплуатации на местах могут раскатывать релиз в любой момент. Например, сотрудники эксплуатации живут в Томске, merge request создали вечером в Москве, они пришли утром в Томске и развернули. То есть процесс может прерываться по естественным причинам, но при этом мы детерминируем ответственность каждого участника процесса.

Таким образом мы гарантируем, что мы протестировали и выпустили в прод именно то, что нам нужно.

Разграничение уровней доступа

И третья стратегическая задача. Когда у нас есть единый репозиторий и много команд эксплуатации, как разграничить доступы?

Но и здесь мы должны признаться, что пошли другим путём. Репозиторий у нас не один. Благодаря тому, что Renovate очень легко управлять, мы сделали много репозиториев. По сути, каждый завод имеет свой репозиторий со своей инсталляцией. Давайте посмотрим, как выглядит процесс на схеме.

Разработчик выпускает новую версию detector. Renovate её подхватывает, обновляет репозиторий один, репозиторий два, репозиторий три. После чего мы тестируем эти инсталляции и выкатываем на завод уже с помощью специалистов эксплуатации.

Таким образом мы решили третью стратегическую задачу. И можно сказать, что Renovate фактически является ядром DaaS-подхода.

И давайте посмотрим, как это выглядит в наших проектах.

Вернёмся к видеоаналитике из категории Infrastructure hard. Помните эту «кошачью лапку», да? Она реализована следующим образом: есть разработка, есть Renovate, который заполняет почти весь этот фиолетовый кружочек, и есть более трёх команд эксплуатации.

Для более простого проекта с точки зрения инфраструктуры всё проще.

Renovate просто обновляет репозиторий. Это всё находится в ведении одной команды.

Им даже особо не нужно друг с другом как-то договариваться. Они и так этим полноценно владеют, соответственно, отвечают за это вместе.

Итоги

Ну и давайте подытожим, чего мы добились при таком подходе.

  1. В тех командах, которые внедрили наш подход, время на установку релиза сократилось на 30% — это общая цифра, для инфраструктурно простых проектов оно сейчас занимает не более 15 минут, притом что большую часть этого времени занимают тесты.

  2. Частота релизов увеличилась в два раза. Почему это важно? Потому что на самом деле командам стало намного проще выпускать релизы. Раньше, чтобы договориться с командой на релиз, нужно было бороться: команда пыталась оттянуть релиз, накапливалось больше изменений, и мы получали монструозный релиз, который сложно тестировать и который почти всегда приводил к проблемам. Сейчас мы ставим несколько релизов, и это, по сути, конвейер. Несмотря на то что в процессе есть разрывы и согласования.

  3. Количество откатов уменьшилось в три раза.

И вот базовые рекомендации, которые мы можем дать с учётом нашего опыта.

  1. Ищите open source решения. Когда мы впервые столкнулись с этой задачей, первое, о чём подумали, — это что-то купить. Второе, о чём подумали, — заняться велосипедостроением. Как оказалось, проблему можно решить, используя решения с открытым исходным кодом. Ansible, Helm, Renovate — это всё открытые решения.

  2. Внедряйте Renovate. Эта штука работает великолепно. Издержки с точки зрения эксплуатации просто мизерные. Если говорить о kubernetes, это cronjob, которая приносит колоссальное количество пользы. Во-первых, она держит вашу кодовую базу с точки зрения зависимостей в постоянной актуальности. Во-вторых, сейчас модно говорить у тестировщиков про shift-left модель, по сути, она смещает влево SecOps, потому что ты можешь спать, проснуться, а у тебя уже висит merge request на фикс уязвимости в твоих зависимостях. Ну и в-третьих, Renovate неплохо подошёл для управления релизным процессом.

  3. Не бойтесь менять методологию под ваши реалии. Не всё из описанного здесь подойдёт под ваши реалии, что-то придётся поправить, чтобы вам и вашим командам было удобно.

На этом всё. Если есть какие-то вопросы/дополнения/предложения — обсудим в комментариях.

Комментарии (2)


  1. cmyser
    20.12.2024 16:38

    Классная статья, так и хочется попробовать все это самому и автоматизировать все на свете)


  1. cmyser
    20.12.2024 16:38

    Но название вообще не релевантное