Как вы разделяли элементы CI и CD в Jenkins?

«Как вы разделяли элементы CI и CD в Jenkins?» — вопрос из категории CI/CD, который задают на 23% собеседований Devops Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Jenkins мы использовали Declarative Pipeline для четкого разделения этапов CI и CD. Вот как это выглядело на проекте с микросервисами на Java, которые деплоились в Kubernetes.

Конвейер (Jenkinsfile) был структурирован следующим образом:

pipeline {
    agent any
    environment {
        DOCKER_REGISTRY = 'my-registry.example.com'
        K8S_NAMESPACE = 'staging'
    }
    stages {
        // --- ФАЗА CONTINUOUS INTEGRATION (CI) ---
        stage('Checkout') {
            steps { checkout scm }
        }
        stage('Build & Unit Tests') {
            steps {
                sh 'mvn clean package'
                junit 'target/surefire-reports/*.xml'
            }
        }
        stage('Static Code Analysis') {
            steps {
                sh 'mvn sonar:sonar'
            }
        }
        stage('Build Docker Image') {
            steps {
                script {
                    dockerImage = docker.build("${DOCKER_REGISTRY}/app:${env.BUILD_NUMBER}")
                }
            }
        }
        stage('Push Image') {
            steps {
                script {
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-credentials-id') {
                        dockerImage.push()
                    }
                }
            }
        }

        // --- ФАЗА CONTINUOUS DELIVERY/DEPLOYMENT (CD) ---
        stage('Deploy to Staging') {
            steps {
                sh """
                # Обновляем манифест Kubernetes новой версией образа
                sed -i 's|IMAGE_TAG|${env.BUILD_NUMBER}|g' k8s/deployment.yaml
                kubectl apply -f k8s/ -n ${K8S_NAMESPACE}
                """
            }
        }
        stage('Integration Tests') {
            steps {
                sh 'mvn verify -Pintegration-tests'
            }
        }
        stage('Approval for Production') {
            steps {
                timeout(time: 1, unit: 'HOURS') {
                    input message: 'Деплой в production?', ok: 'Deploy'
                }
            }
        }
        stage('Deploy to Production') {
            environment {
                K8S_NAMESPACE = 'production'
            }
            steps {
                sh "kubectl apply -f k8s/ -n ${K8S_NAMESPACE}"
            }
        }
    }
    post {
        always {
            cleanWs() // Очистка workspace
        }
        failure {
            slackSend(color: 'danger', message: "Build ${env.JOB_NAME} #${env.BUILD_NUMBER} failed")
        }
        success {
            slackSend(color: 'good', message: "Build ${env.JOB_NAME} #${env.BUILD_NUMBER} succeeded and deployed to production")
        }
    }
}

Ключевые принципы разделения:

  1. CI-этапы (автоматические, быстрые): Все, что происходит до создания артефакта (Docker-образа). Их цель — гарантировать качество кода. Сбой на любом из этих этапов немедленно прерывает конвейер.
  2. CD-этапы (управляемые, последовательные): Все, что связано с развертыванием артефакта в среды. Мы внедряли "воротца" (gates), такие как ручное подтверждение (input) перед production, и обязательные интеграционные тесты в staging-среде.
  3. Разделение агентов: Иногда для этапов сборки и деплоя использовались разные Jenkins agents (например, сборка на агенте с Maven, а деплой — на агенте с настроенным kubectl и доступом к кластеру).