Очередной пайплайн сборки для вашего приложения

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

В этой статье описан «универсальный» пайплайн для сборки golang приложений. Универсальный в кавычках, потому что для сборки десятков разных приложений при помощи одного пайплайна нужно придерживаться  общих стандартов разработки. Которые как бывает не всегда соблюдаются даже в рамках одного проекта.

Шаги сборки 

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

  • Получение кода;

  • Запуск тестов;

  • Статический анализ кода;

  • Сборка кода;

  • Сборка докер образа:

  • Деплой деплой образа в registry;

  • Деплой приложения на тестовую среду;

  • Запуск интеграционных тестов;

  • Запуск нагрузочного тестирования;

  • Запуск любых дополнительных проверок которые вы посчитаете нужными;

  • Деплой на прод. 

Инструменты 

Для реализации пайплайна будут использоваться следующие инструменты:

В статье не описано развертывание jenkins, harbor, SonarQube.

Пайплайн будет написан при помощи jenkins declarative pipeline.

Сборка будет производится на примере node_exporter

Требования к jenkins нодам

На нодах jenkins должны быть установлены:

  • git;

  • docker;

  • java.

Получение кода.

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

  • Генерируем ключи;

  • Заходим в Настроить jenkins > Manage credentials;

  • Выбираем где будет хранится токен. У нас только 1 вариант. System > Global credentials (unrestricted)

91549e89b05f4bd454d608bfcb56aaef.png14b875677a03bbfe64e6f6f72371d10e.png

Вводим ключ  id и описание. В id лучше вводить что то осмысленное;

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

  • sonarProject = код проекта в sonarqube;

  • sonarProjectName = название проекта в sonarqube;

  • gitCommitHash — короткий хэш коммита для использования в качестве версии докер образа;

  • GIT_REPO_BRANCH и GIT_REPO_URL определяются в параметрах запуска.

  • credentialsId: 'jenkins-jenkins' наш ssh ключ созданный выше.

    stages {
        stage('Get sources') {  //название этапа пайплайна
            steps {
                cleanWs() // очистка каталога сборки
                checkout([$class: 'GitSCM', branches: [[name: params.GIT_REPO_BRANCH]], extensions: [], userRemoteConfigs: [[credentialsId: 'jenkins-jenkins', url: params.GIT_REPO_URL]]])  // клонирование репозитория
                script { 
                    // получение информации из git для дальнейшего использования при сборке и в sonarqube
                    gitRepoPath = params.GIT_REPO_URL.substring(params.GIT_REPO_URL.lastIndexOf(":") + 1)
                    sonarProject = gitRepoPath.minus(".git").minus("-").minus("_").minus("/") 
                    sonarProjectName = gitRepoPath.minus(".git")
                    gitCommitHash = sh (script: "git log -n 1 --pretty=format:'%h'", returnStdout: true)
                    
                    currentBuild.displayName = sonarProjectName + " " + params.GIT_REPO_BRANCH 
                    currentBuild.description = sonarProjectName + " " + params.GIT_REPO_BRANCH + " " + gitCommitHash
                }
            }
        }

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

На данном этапе запускаются юнит тесты.

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

  • beforeAgent true проверка условия запуска перед загрузкой образа агента;

  • image params.BUILDER_IMAGE — контейнер в котором будут запускатся тесты, задается в параметрах;

  • agent запуск этапа в докер контейнере;

  • args  '-v /home/jenkins/.cache:/home/jenkins/.cache ' монтируется каталог с библиотеками go, для того чтобы не скачивать их при повторном запуске;

  • reuseNode true — запуск на той же ноде, на которой запускались предыдущие шаги.

        stage ('Run tests') {
            when {  
                environment name: 'RUN_TESTS',
                value: 'true'
                beforeAgent true
            }
            agent {  
                docker {
                    image params.BUILDER_IMAGE
                    args  '-v /home/jenkins/.cache:/home/jenkins/.cache '
                    registryCredentialsId 'docker-registry'
                    registryUrl "https://harbor.iops-test.com"
                    reuseNode true
                }
            }
            steps {
                sh """
                make test
                """
            }
        }

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

Статический анализ кода.

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

После сканирования кода обработка результатов на стороне сервера sonarqube занимает некоторое время. Обработка может длится довольно долго при большом объеме кода. Так же webhook от сервера sonarqube с результатами  может провалится. Поэтому нужно всегда выставить таймаут.

Настройка SonarQube quality gate

  • Заходим в SonarQube;

  • В верхней панели навигации переходим на вкладку quality gate;

fa6c65a9d5cd090183c16a5df0a46208.png

  • Нажимаем Create;

  • Вводим название нашего qg и нажимаем save;

  • В открывшемся окне нажимаем add condition и вводим интересующие нас критерии. При несоответствии этим критериям qg будет считаться не пройденным и наш пайплайн завершится с ошибкой.

af573f02884d6afe86ae91aab2603919.png68b8a62ada900b25dce38afd5813a05d.png6423a727ccf271ddcd14ad9ffbe42b5f.png

Настройка SonarQube webhook

Для того чтобы jenkins мог получать результаты проверки quality gate нужно настроить webhook.

На стороне jenkins:

  • Добавляем токен в credentials c типом secret text, как это было описано выше. токен вводим произвольный.

  • Заходим в Настроить jenkins > Конфигурация системы;

  • В разделе SonarQube servers наш токен в Webhook Secret;

  • Остальные параметры также должны быть заполнены. Server authentication token — берется из настроек пользователя SonarQube;

1bbe1fccfa6178fe94c464668583208d.png

На стороне SonarQube:

  • Заходим в administration > configurations > webhooks

  • Нажимаем Create;

  • Вводим название, адрес jenkins, токен который мы указывали как Webhook Secret выше. адрес в формате  /sonarqube-webhook/

28368933aba141d46fed844d33a62625.png

   stage ('Run sonar checks') {
            when {
                environment name: 'SONAR_CHECKS',
                value: 'true'
                beforeAgent true
            }
            agent {
                docker {
                    image 'sonarsource/sonar-scanner-cli'
                    args  '--rm  '
                    reuseNode true
                }
            }
            steps{
                script{
                    withSonarQubeEnv('sonar-server') { 
                        sh """
                        curl -u ${SONAR_AUTH_TOKEN}:  ${SONAR_HOST_URL}/api/projects/create -d 'project=${sonarProject}&name=${sonarProjectName}' # Создаем проект, если его еще нет
                        /opt/sonar-scanner/bin/sonar-scanner \ # запуск сканера
                        -Dsonar.projectKey=${sonarProject} \
                        -Dsonar.sources=./
                        """
                    }
                    timeout(time: 5, unit: 'MINUTES') { // таймаут на ожидание результатов
                        def qg = waitForQualityGate() // Reuse taskId previously collected by withSonarQubeEnv
                        if (qg.status != 'OK') { // завершение пайплайна при непрохождении qualitygate
                            error "Pipeline aborted due to quality gate failure: ${qg.status}"
                        }
                    }
                }
            }
        }

Сборка кода.

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

stage ('build service') {
    when {
        environment name: 'BUILD_SERVICE',
        value: 'true'
        beforeAgent true
    }
    agent {
        docker {
            image params.BUILDER_IMAGE
            args  '-v /home/jenkins/.cache:/home/jenkins/.cache '
            registryCredentialsId 'docker-registry'
            registryUrl "https://harbor.iops-test.com"
            reuseNode true
        }
    }
    steps {
        sh """
        make build
        """
    }
}

Сборка докер образа.

Это последний этап сборки docker образа и его деплоя в harbor.

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

stage ('build docker image') {
    when { 
        environment name: 'BUILD_IMAGE',
        value: 'true'
        beforeAgent true
    }
    steps {
        script {
            docker.withRegistry('https://harbor.iops-test.com/', 'docker-registry') {
               sh """#!/bin/bash
               docker build -t harbor.iops-test.com/internal/sgapp:${gitCommitHash} . 
               docker push harbor.iops-test.com/internal/sgapp:${gitCommitHash}
               docker rmi harbor.iops-test.com/internal/sgapp:${gitCommitHash}
               """
            }
        }  
    }
}

Как результат мы имеем собранный образ в нашем registry. Там же его можно проверить на уязвимости.

71e5b72ffd1803ef3855c569a23bae5b.png

Для примера взят другой образ.

Создание пайплайна

fe978277f4154793d777b8cbd1bbceb8.pnge95265890f68cb6c26495e44ed73b391.png2ec92d719f8334c0852021d5d1c0e6f3.png

сохраняем.

Запуск пайплайна

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

При втором запуске появляются параметры выполнения. Заполняем их.

4c71bab46ac524b39fec1f5c6e78e3e5.pngc70b3a3014626ae46b4c7d06b560259d.png

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

слева на экране мы видимо историю сборок.

4001ed613d3dfad96b0e3a993ebce9bf.png

щелкнув по значку SonarQube  перейдем в проект  и увидим результаты проверки.

d64ace75ad1a95072cbe1c54c67f8aa0.png

Но все таки давайте проверим что наш пайплайн работает полностью. Для этого напишем простое приложение и попробуем его собрать. Так же, вероятно вам не захочется каждый раз запускать руками сборку. Добавим автосборку по пушу. Для этого будем использовать generic webhook plugin.

Для создания вебхука который будет запускать наш пайплайн зайдем в настройки репозитория с нашим приложением — https://github.com/kilin-s/sgapp/.

  • Заходим в настройки settings > webhook > add webhook;

  • Добавляем ссылку на jenkins в формате /generic-webhook-trigger/invoke? token=abc123

37d2712cdb4c64dddbaca022064a0d1d.png

Кроме доступа к jenkins По токену будет определятся пайплайн для запуска.

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

triggers {
    GenericTrigger(
        genericVariables: [
            [key: 'GIT_REPO_URL', value: '$.repository.ssh_url'],
            [key: 'GIT_REPO_BRANCH', value: '$.ref']
        ],
        causeString: 'Triggered on $ref',
        tokenCredentialId: 'go-build-common-hook',
        printContributedVariables: true,
        printPostContent: false,
        silentResponse: false,
        shouldNotFlattern: false
    )
}

  • tokenCredentialId — токен используемый дя запуска, берется из credentials;

  • genericVariables — запись значений полей из json запроса в параметры запуска.

Для того чтобы webhook начал работать пайплайн нужно запустить еще раз.

Сделаем еще 1 коммит в наш репозиторий для проверки запуска и сборки.

554874ff46932202183e6152c151446a.png

Пайплайн завершился успешно.

Заключение.

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

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

ссылки:

sonarqube jenkins extension

 репозиторий собираемого приложения

Репозиторий с пайплайном

jenkins pipeline syntax

node exporter

harbor

© Habrahabr.ru