Skip to content

CI/CD автоматизация (Jenkins) ​

1. Концепция непрерывной интеграции и доставки ​

В производственном контуре платформы «UrFU HeatBalance» развёрнута автоматизированная система непрерывной интеграции и непрерывной доставки (CI/CD) на базе сервера автоматизации Jenkins.

Главные цели внедрения конвейера автоматизации:

  • Исключение влияния человеческого фактора: автоматизация всех рутинных операций компиляции, сборки контейнеров и обновления серверов.
  • Непрерывный контроль качества (Quality Gates): обязательный прогон набора модульных тестов математических моделей перед допуском изменений к контейнеризации.
  • Иммутабельность артефактов: контейнерные образы собираются один раз с уникальным версионным идентификатором ${BUILD_NUMBER} и развёртываются без промежуточных ручных модификаций.
  • Минимизация времени простоя (Downtime): оркестрация обновления микросервисов средствами Docker Compose с сохранением состояния постоянных томов баз данных и брокера сообщений.

2. Архитектура и разбор пайплайна (Jenkinsfile) ​

Сценарий автоматизации зафиксирован в корне репозитория в файле Jenkinsfile. Ниже приведён построчный анализ ключевых блоков декларативного конвейера.

2.1. Инициализация и переменные окружения ​

groovy
pipeline {
    agent any

    triggers {
        githubPush()
    }

    environment {
        // Теги для Docker образов на основе монотонно возрастающего номера сборки
        DOCKER_TAG = "${env.BUILD_NUMBER}"
    }
  • agent any: выполнение этапов конвейера на любом доступном исполнителе Jenkins (по умолчанию локальный рабочий узел хоста).
  • triggers { githubPush() }: автоматический запуск сборки при поступлении входящего вебхука от сервиса GitHub о появлении новых коммитов.
  • DOCKER_TAG = "${env.BUILD_NUMBER}": генерация уникального строкового тега для формируемых контейнеров. Это предотвращает коллизии версий и обеспечивает возможность точного аудита запущенного кода.

2.2. Стадия 1: Клонирование исходного кода (Checkout) ​

groovy
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

На этой стадии Jenkins обращается к системе управления версиями Git, загружает дерево каталогов репозитория и переключает рабочее пространство на целевой коммит.


2.3. Стадия 2: Сборка и тестирование бэкенда (Build & Test Backend) ​

groovy
        stage('Build & Test Backend') {
            steps {
                dir('backend/HeatBalanceSystem2') {
                    echo 'Restoring backend dependencies...'
                    sh 'dotnet restore HeatBalanceSystem.sln'
                    
                    echo 'Building C# backend solution...'
                    sh 'dotnet build HeatBalanceSystem.sln --configuration Release --no-restore'
                    
                    echo 'Running backend unit tests...'
                    sh 'dotnet test HeatBalance.Math.Tests/HeatBalance.Math.Tests.csproj --configuration Release --no-build'
                }
            }
        }

Назначение операций стадии: ​

  1. dotnet restore: загрузка всех внешних NuGet-пакетов (OpenIddict, YARP, MassTransit, QuestPDF, Npgsql, xUnit) в локальный кэш сборщика.
  2. dotnet build --configuration Release --no-restore: компиляция всех микросервисов и разделяемых библиотек платформы с оптимизацией компилятора Roslyn. Флаг --no-restore ускоряет процедуру за счёт повторного использования кэшированных зависимостей.
  3. dotnet test ... --no-build: запуск набора модульных тестов теплотехнического ядра HeatBalance.Math.Tests.

IMPORTANT

Модульные тесты HeatBalance.Math.Tests проверяют корректность термодинамических формул для камерных, конвейерных, электрических печей и сушильных барабанов. В случае падения хотя бы одного теста (например, превышение невязки баланса свыше 0.01% или некорректный расчёт энтальпии) сборка аварийно прерывается, и некорректный код ни при каких обстоятельствах не попадёт в продакшен.


2.4. Стадия 3: Сборка клиентского интерфейса (Install & Build Frontend) ​

groovy
        stage('Install & Build Frontend') {
            steps {
                dir('frontend') {
                    echo 'Installing frontend dependencies...'
                    sh 'npm ci --silent'
                    
                    echo 'Running linter...'
                    sh 'npm run lint || true'
                    
                    echo 'Building React production bundle...'
                    sh 'npm run build'
                }
            }
        }
  • npm ci --silent: чистая детерминированная установка клиентских npm-зависимостей строго по файлу package-lock.json с подавлением избыточного вывода в лог.
  • npm run lint || true: проверка соответствия стилистическим стандартам ESLint. Конструкция || true предотвращает падение пайплайна на минорных предупреждениях форматирования.
  • npm run build: транспиляция исходных файлов JSX/ES6, минификация JavaScript, упаковка CSS-стилей и генерация финального дистрибутива в директории frontend/dist/.

2.5. Стадия 4: Сборка Docker-образов (Docker Build Services) ​

Сборка образов выполняется только при слиянии изменений в основную производственную ветку main:

groovy
        stage('Docker Build Services') {
            when {
                expression { env.GIT_BRANCH?.contains('main') || env.BRANCH_NAME == 'main' }
            }
            steps {
                echo 'Building container images for all services...'
                
                // Сборка веб-интерфейса (Nginx со статическим бандлом)
                sh "docker build -t heatbalance-frontend:${DOCKER_TAG} -t heatbalance-frontend:latest ./frontend"
                
                // Сборка портала документации
                sh "docker build -t heatbalance-docs:${DOCKER_TAG} -t heatbalance-docs:latest ./docs"
                
                // Сборка бэкенд-микросервисов с контекстом корневой директории бэкенда
                dir('backend/HeatBalanceSystem2') {
                    sh "docker build -t heatbalance-auth:${DOCKER_TAG} -t heatbalance-auth:latest -f src/Auth/HeatBalance.Auth/Dockerfile ."
                    sh "docker build -t heatbalance-gateway:${DOCKER_TAG} -t heatbalance-gateway:latest -f src/Gateway/HeatBalance.Gateway/Dockerfile ."
                    sh "docker build -t heatbalance-dataset:${DOCKER_TAG} -t heatbalance-dataset:latest -f src/Services/HeatBalance.DatasetService/Dockerfile ."
                    sh "docker build -t heatbalance-conveyor:${DOCKER_TAG} -t heatbalance-conveyor:latest -f src/Services/HeatBalance.ConveyorKilnService/Dockerfile ."
                    sh "docker build -t heatbalance-chamber:${DOCKER_TAG} -t heatbalance-chamber:latest -f src/Services/HeatBalance.ChamberKilnService/Dockerfile ."
                    sh "docker build -t heatbalance-electric:${DOCKER_TAG} -t heatbalance-electric:latest -f src/Services/HeatBalance.ElectricKilnService/Dockerfile ."
                    sh "docker build -t heatbalance-drum:${DOCKER_TAG} -t heatbalance-drum:latest -f src/Services/HeatBalance.DrumDryerService/Dockerfile ."
                    sh "docker build -t heatbalance-run:${DOCKER_TAG} -t heatbalance-run:latest -f src/Services/HeatBalance.RunService/Dockerfile ."
                    sh "docker build -t heatbalance-report:${DOCKER_TAG} -t heatbalance-report:latest -f src/Services/HeatBalance.ReportService/Dockerfile ."
                    sh "docker build -t heatbalance-iot:${DOCKER_TAG} -t heatbalance-iot:latest -f src/Services/HeatBalance.IoTService/Dockerfile ."
                    sh "docker build -t heatbalance-analytics:${DOCKER_TAG} -t heatbalance-analytics:latest -f src/Services/HeatBalance.AnalyticsService/Dockerfile ."
                }
            }
        }

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

  1. heatbalance-<service>:${DOCKER_TAG}: постоянный версионный тег, соответствующий порядковому номеру билда в Jenkins.
  2. heatbalance-<service>:latest: указатель на последнюю актуальную стабильную версию, используемую оркестратором Docker Compose при запуске.

2.6. Стадия 5: Развёртывание стека (Deploy) ​

groovy
        stage('Deploy') {
            when {
                expression { env.GIT_BRANCH?.contains('main') || env.BRANCH_NAME == 'main' }
            }
            steps {
                echo 'Copying Nginx configuration to permanent location...'
                sh 'mkdir -p /var/lib/jenkins/app-config/nginx'
                sh 'cp -rf nginx/nginx.conf /var/lib/jenkins/app-config/nginx/nginx.conf'
                
                echo 'Ensuring environment configuration (.env)...'
                sh '''
                    mkdir -p /var/lib/jenkins/app-config
                    if [ -f /var/lib/jenkins/app-config/.env ]; then
                        echo "Applying persistent environment from /var/lib/jenkins/app-config/.env..."
                        cp /var/lib/jenkins/app-config/.env .env
                    elif [ -f .env ]; then
                        echo "Using existing workspace .env..."
                    else
                        echo "WARNING: /var/lib/jenkins/app-config/.env not found! Creating default .env template..."
                        cp .env.example /var/lib/jenkins/app-config/.env
                        sed -i 's/generate_a_secure_db_password_here/hb_secure_pass_2026/g' /var/lib/jenkins/app-config/.env
                        sed -i 's/Generate_A_Strong_Admin_Password_123!/Admin123$/g' /var/lib/jenkins/app-config/.env
                        sed -i 's/generate_random_m2m_secret_token_here/run-service-secret-dev/g' /var/lib/jenkins/app-config/.env
                        sed -i 's/put_your_secure_smtp_app_password_here//g' /var/lib/jenkins/app-config/.env
                        cp /var/lib/jenkins/app-config/.env .env
                    fi
                '''

                echo 'Deploying application stack via Docker Compose...'
                withEnv([
                    "NGINX_CONF_PATH=/var/lib/jenkins/app-config/nginx/nginx.conf",
                    "NGINX_SSL_PATH=/var/lib/jenkins/app-config/nginx/ssl"
                ]) {
                    sh "docker compose down --remove-orphans || true"
                    sh "docker compose up -d"
                    echo '=== POST-DEPLOY DEBUG ==='
                    sh "docker ps -a || true"
                }
            }
        }

Порядок выполнения деплоя: ​

  1. Синхронизация конфигурации Nginx: обновлённый файл nginx/nginx.conf копируется в персистентную системную директорию /var/lib/jenkins/app-config/nginx/nginx.conf.
  2. Монтирование постоянных переменных окружения (.env): подтягивание файла секретов из постоянной хостовой директории /var/lib/jenkins/app-config/.env в сборочное пространство.
  3. Передача системных путей (withEnv): определение переменных NGINX_CONF_PATH и NGINX_SSL_PATH, указывающих на постоянные места хранения конфигурации и сертификатов SSL на хосте.
  4. Остановка и очистка (docker compose down --remove-orphans): остановка предыдущих версий контейнеров с автоматическим удалением сервисов, исключённых из конфигурации.
  5. Запуск обновлённого стека (docker compose up -d): фоновый запуск всех 14 контейнеров с подтягиванием свежесобранных локальных образов :latest.
  6. Диагностический листинг (docker ps -a): фиксация в консоли сборки Jenkins текущего статуса всех запущенных сервисов.

2.7. Секция Post-Actions: Очистка ресурсов и логирование ​

groovy
    post {
        success {
            echo 'HeatBalance pipeline completed successfully! All microservices built and deployed.'
        }
        failure {
            echo 'Pipeline failed.'
        }
        always {
            echo 'Cleaning up workspace and unused Docker images...'
            sh 'docker image prune -f || true'
            cleanWs()
        }
    }
  • docker image prune -f: автоматическое удаление промежуточных слоёв и безымянных образов («dangling images»), образующихся в процессе сборки multi-stage Dockerfile. Это предотвращает переполнение дискового пространства хоста.
  • cleanWs(): удаление временных файлов сборки из рабочей директории Jenkins после завершения работы конвейера.

3. Настройка сервера Jenkins и интеграция с GitHub ​

3.1. Системные зависимости узла Jenkins ​

Для корректного выполнения конвейера на хост-сервере должны быть установлены следующие пакеты:

bash
# Проверка наличия .NET 10 SDK
dotnet --version

# Проверка наличия Node.js и npm
node -v
npm -v

# Проверка доступа демона Jenkins к сокету Docker
sudo usermod -aG docker jenkins
sudo systemctl restart jenkins

3.2. Настройка Webhook в репозитории GitHub ​

  1. Перейдите в репозиторий GitHub: Settings -> Webhooks -> Add webhook.
  2. Укажите параметры входящего хука:
    • Payload URL: https://ci.heatbalance.online/github-webhook/
    • Content type: application/json
    • Secret: секретная строка для проверки подлинности запроса (настраивается в Jenkins).
    • Which events would you like to trigger this webhook?: Just the push event.
  3. Нажмите кнопку Add webhook.

4. Регламент аварийного отката версий (Rollback Strategy) ​

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

bash
# 1. Просмотр доступных версий локальных образов
docker images | grep heatbalance

# 2. Выбор целевого стабильного номера сборки (например, 42)
TARGET_TAG=42

# 3. Перетегирование стабильных образов как :latest
docker tag heatbalance-frontend:${TARGET_TAG} heatbalance-frontend:latest
docker tag heatbalance-auth:${TARGET_TAG} heatbalance-auth:latest
docker tag heatbalance-gateway:${TARGET_TAG} heatbalance-gateway:latest
docker tag heatbalance-dataset:${TARGET_TAG} heatbalance-dataset:latest
docker tag heatbalance-conveyor:${TARGET_TAG} heatbalance-conveyor:latest
docker tag heatbalance-chamber:${TARGET_TAG} heatbalance-chamber:latest
docker tag heatbalance-electric:${TARGET_TAG} heatbalance-electric:latest
docker tag heatbalance-drum:${TARGET_TAG} heatbalance-drum:latest
docker tag heatbalance-run:${TARGET_TAG} heatbalance-run:latest
docker tag heatbalance-report:${TARGET_TAG} heatbalance-report:latest
docker tag heatbalance-iot:${TARGET_TAG} heatbalance-iot:latest
docker tag heatbalance-analytics:${TARGET_TAG} heatbalance-analytics:latest

# 4. Перезапуск контейнеров с откаченными версиями
cd /opt/UrFU-HeatBalance-Microservices
docker compose down --remove-orphans
docker compose up -d

Процедура отката занимает менее 30 секунд, не требует повторной сборки и восстанавливает работоспособность системы с сохранением всех пользовательских данных в PostgreSQL.

Разработано в рамках проекта моделирования тепловых балансов и промышленного мониторинга