Привет! Я Владислав Раев, DevOps & DevTools Engineer в команде Wildberries & Russ. Сегодня погружу вас в увлекательную историю о том, как мы наводили порядок в Nexus OSS и экспериментировали с Terraform и Ansible. Спойлер: контекст оказался важнее технологий.
Но сначала три случайных факта об авторе: 1) я пришёл в IT из строительной сферы четыре года назад, 2) люблю покатушки на эндуро, 3) обожаю животных — у меня сразу пять питомцев! А теперь к делу.
Сначала был Nexus OSS
Nexus OSS — это хранилище артефактов, то есть любых файлов, которые существуют в репозитории и используются для деплоя или распространения приложений.
Когда я пришёл в компанию, мне почти сразу вручили Nexus OSS, развёрнутый в одном-единственном инстансе. Тимлид сказал: «Володос, надо этот Nexus разделить на два: Proxy и Hosted. Первый — для публичных репозиториев, второй — для артефактов, которые производит Wildberries». Я кивнул и взялся за дело.
Фиксируем наше «До»:
две заявки,
ручное создание ресурсов,
пересылка паролей, ролей, юзеров и репозиториев через слабозащищённые каналы;
в заявках нет аппруверов.
Единая точка входа: Keycloak
Первым делом я поднял два инстанса — это была самая простая задача.
Чтобы выдать доступ по лицам в Proxy, нужен только анонимный доступ на просмотр и чтение. А вот с Hosted возникли трудности. Keycloak, нашу единую точку входа во все сервисы, нужно было интегрировать с Nexus OSS, который не имеет нативной интеграции с Keycloak.
Что делать? Первый вариант: написать плагин на Java. Честно говоря, не хотелось этим заниматься. Второй вариант: подобрать готовое решение.
Я отправился на поиски и наткнулся на плагин Community для Keycloak. Адаптация и пересборка под требования Wildberries сотворили чудо: мы получили единую точку входа в Nexus Hosted для всей компании, настроили автоматический маппинг групп из Keycloak в роли Nexus OSS и централизованное управление доступами. Плагин на GitHub доступен по ссылке. Без-воз-мезд-но, то есть даром!
Мы создали репозитории, настроили роли и стали подключать пользователей. Всё шло отлично, но пришли безопасники и сказали: «Где разделение на Prod- и Dev-артефакты? Где разделение на проекты? Почему одна команда может видеть артефакты и скачивания другой команды? Кто вообще согласует доступ в эти репозитории?» Короче, всё не так, давайте по-новой. И мы взялись за исправления.
Стандартизация прежде всего
В результате долгих дискуссий мы создали такую схему:

Так выглядит согласованная структура:

Для разграничения Prod- и Dev-артефактов, а также проектов репозитории должны называться <subproject>. <format> — это любой формат, существующий в Nexus OSS, только для Hosted-репозитория. Остаётся окружение <env>: dev и prd для соответствующих артефактов и public — если одна из команд решит опубликовать свою библиотеку или артефакт для всей компании.
Пояснения по разделу «Роли» вы можете увидеть на картинке. Далее — две сервисные учётки для работы через CI/CD. Эти данные необходимо хранить в Vault и забирать только из CI/CD. Пользователи не должны иметь доступ на пуш, удаление и редактирование файлов в Prod-репозитории.
Возвращаемся к Keycloak. Мы подключили его и согласовали три типа заявок:
Создание репозиториев, ролей, юзеров и секретов в Vault.
Создание в Keycloak подгрупп для выдачи прав в репозиториях Nexus OSS. Здесь же указываем, кто будет согласовывать доступ к конкретному репозиторию — тимлид или продакт-оунер.
Добавление пользователей в подгруппы.
Global Inventory — наш выбор!
Отлично, мы определили, как будем именовать репозитории и выдавать доступ и где будем хранить учётные записи. Осталось решить два вопроса:
Где запускать автоматизацию?
На чём написать автоматизацию?
Наш основной подход к IaC — это Global Inventory (единый репозиторий с переменными для Ansible), поэтому было принято решение использовать именно его для автоматизации. Вот пример структуры Global Inventory:

В проекте devops возможно большое количество подпроектов. Именно для них — yaml-team и yaml-developer — требуется создавать сущности в Nexus OSS и выдавать роли. Подпроекты находятся в проекте, и конфигурация для них должна находиться в общих переменных для этого проекта. В нашем случае это commonservices и файл nexus-vars.yml.
На чём писать автоматизацию? Есть API для работы с Nexus OSS из коробки, но необходимо выбрать подход: писать свой сервис или искать готовый. Я выбрал второй вариант и остановился на Terraform, у которого есть всё для создания автоматизаций и сущностей.
Что работает для 10, то ломается для 100
Я выбрал Terraform по нескольким причинам:
есть готовый провайдер,
используется IaС-подход,
можно стать первопроходцем и внедрить Terraform в Global Inventory,
личный интерес к изучению Terraform.
Я разработал модуль, который выполняет необходимое действие, и начал тестирование. На десяти проектах он работал без нареканий, но стоило увеличить количество тикетов — начинались проблемы.
Проблема масштаба. Terraform State рос, увеличивал количество времени на выполнение пайплайна и повышал риск самоповреждения. Так можно и данные потерять!
Terraform на малом масштабе: 10 проектов = State 2 Мб = план 60 секунд.
Terraform на реальном масштабе: 500 проектов = State 100+ Мб = план 10+ минут.
Проблема воркфлоу Global Inventory. Когда мы заводим тикет, в репозитории из мастер-ветки создаётся новая ветка, куда вносятся изменения и где создаётся мердж-реквест. Дежурный инженер сравнивает заявку и мердж-реквест, если всё в норме — пропускает. После успешной прокатки MR должен вливаться в мастер-ветку, но часто задерживается — иногда на час, иногда на сутки. И так с каждым тикетом.
Если помните, вся конфигурация для подпроектов хранится в commonservices и nexus-vars.yml — получается единый Terraform State. Поэтому при создании нескольких тикетов создаётся от пяти до десяти параллельных MR с разными данными.
Вдобавок играет роль человеческий фактор. Должно быть последовательно: merge → pull → apply. Забыл pull = удалил репозитории!

Представим некий проект под названием Mobile, с подпроектами фронтенда и бэкенда. Приходят фронтендеры и говорят: «Нам нужен NPM-репозиторий». Задача уходит в работу, ветка по воркфлоу Global Inventory создаётся и после проверок успешно запускается.
Автоматизация работает на ура, но MR ещё не влился в мастер-ветку, и фактически изменений нет.

Дальше приходят бэкендеры и говорят: «Нам нужен Go-репозиторий». Новый тикет, новая ветка из мастер-ветки, успешный запуск. Однако есть нюанс: всё хранится в едином файле, и свежие изменения Terraform стирают более ранние.

Можно подтянуть первый тикет в мастер-ветку, спулить во вторую ветку и потом прокатить, но где гарантии, что инженер не забудет этого сделать и мы не потеряем репозиторий?
Меньше состояние — меньше проблем. Выбираем Ansible
Я принял решение отказаться от Terraform и переписать всё на Ansible. Ему важны только конфигурация и переменные, которые мы скармливаем. Количество репозиториев, тикетов и веток не играет роли. Человеческий фактор исключён, от конфликтов спасает идемпотентность — возможность многократного вызова с гарантией того, что состояние системы изменится лишь единожды.

В конфигурационном файле Terraform нельзя явно увидеть, что хранится в конкретном Terraform State. Конфигурации Ansible прозрачны. Каждый MR независим.
Логика простая:
трогаем ТОЛЬКО то, что находится в vars.yml,
проверяем существование,
создаём только отсутствующее,
НЕ ТРОГАЕМ другие подпроекты.

Какой воркфлоу мы получили?
Напомню наше «До»: две заявки, мануальное управление ресурсами, небезопасная передачами данных и отсутствие аппруверов.
А вот наше «После»:
три заявки: создание репозиториев, создание подгруппы в Keycloak для управления доступами, добавление пользователей,
три репозитория: <subproject> — <format> — <env>,
четыре роли: юзер, Dev, Prod и публичная анонимная роль,
хранение секретов в Vault, единая точка доступа через Keycloak.
Нам удалось исключить человеческий фактор и достичь полной автоматизации Nexus OSS, настроить безопасную параллельную работу, которая не вызывает конфликтов, и обеспечить совместимость с воркфлоу Global Inventory. Процессы стали прозрачными, появилась чёткая система согласования.
Главный урок, который я вынес, — не все инструменты универсальны. Приоритезируйте контекст, а не технологию. Terraform хорош, но не для нашего воркфлоу. Под конкретную задачу и архитектуру нужно искать свой инструмент, и для нас это Ansible.
Вам доводилось работать с Terraform и Ansible? Поделитесь выводами в комментариях, буду рад обсудить!