CI/CD-Mastery Serie | Teil 11 von 12

Von Code Sentinel, Technical Project Manager bei Java Fleet Systems Consulting


🗺️ Wo du in der Serie stehst

ModulTeileStatus
Modul 1: FoundationsTeil 1-4✅ Abgeschlossen
Modul 2: Docker & ContainerTeil 5-7✅ Abgeschlossen
Modul 3: Deployment-StrategienTeil 8-10✅ Abgeschlossen
Modul 4: Enterprise & AdvancedTeil 11-12🔄 Du bist hier!

Level: 🟡 PROFESSIONALS | Lesezeit: ~15 Minuten | Voraussetzungen: Teil 1-10


📚 Was du bisher gelernt hast

Dein CI/CD-Stack bis jetzt:

  • ✅ GitHub Actions Pipelines (Teil 1)
  • ✅ Security & Quality Gates (Teil 2-4)
  • ✅ Container-Workflows (Teil 5-7)
  • ✅ Deployment-Strategien (Teil 8-9)
  • ✅ GitOps mit ArgoCD (Teil 10)

Heute: Jenkins für Enterprise-Szenarien – wenn du 50+ Projekte mit konsistenten Pipelines managen musst.


⚡ 30-Sekunden-Überblick

Was du heute lernst:

  • 🌱 Einsteiger: Warum Jenkins in Enterprises noch relevant ist
  • 🌿 Erfahrene: Shared Libraries schreiben und nutzen
  • 🌳 Profis: Pipeline-Templates für große Teams

Nach diesem Teil kannst du:

  • ✅ Den Unterschied zwischen GitHub Actions und Jenkins Enterprise verstehen
  • ✅ Eine Shared Library von Grund auf erstellen
  • ✅ Pipeline-Templates für Teams designen
  • ✅ Jenkins Configuration as Code (JCasC) einsetzen

👋 Code Sentinel: „Die Enterprise-Realität“

Hey! 👋

Code Sentinel hier.

Ich weiß was du denkst: „Jenkins? Ernsthaft? Wir haben doch GitHub Actions gelernt!“

Fair point. Für Greenfield-Projekte ist GitHub Actions oft die bessere Wahl. Einfacher, integrierter, weniger Maintenance.

Aber hier ist die Enterprise-Realität:


Oliver aus Frankfurt (Platform Engineer bei einem Finanzdienstleister) schrieb mir:

„Code, wir haben 180 Java-Microservices. Alle brauchen die gleiche Pipeline: Build, Test, Security-Scan, Container, Deploy. Mit GitHub Actions copy-paste ich 180 Workflow-Files. Bei jeder Änderung muss ich 180 PRs machen. Das skaliert nicht.“

Das Problem:

GitHub Actions vs. Jenkins Shared Libraries

Abbildung 1: Der Maintenance-Unterschied bei 180 Services

Jenkins mit Shared Libraries:

Das ist der Grund, warum Jenkins in Enterprises nicht wegzudenken ist.

Lass uns das bauen! 🚀


🟢 GRUNDLAGEN

Wann Jenkins, wann GitHub Actions?

Jenkins

Entscheidungsmatrix:

SzenarioEmpfehlung
Kleines Team, < 10 ReposGitHub Actions
Open Source ProjektGitHub Actions
Greenfield, Cloud-nativeGitHub Actions
50+ Repos, gleiche PipelineJenkins + Shared Libraries
On-Premise RequirementJenkins
Komplexe GenehmigungsprozesseJenkins
Bestehende Jenkins-InfrastrukturJenkins optimieren

💡 Neu bei Jenkins? Jenkins ist ein Open-Source Automation Server. Pipelines werden in Groovy geschrieben (Jenkinsfile). Shared Libraries sind wiederverwendbare Groovy-Module.


Jenkins Pipeline Basics (Auffrischer)

Einfaches Jenkinsfile:

pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Deploy') {
            steps {
                sh 'kubectl apply -f k8s/'
            }
        }
    }
    
    post {
        always {
            junit '**/target/surefire-reports/*.xml'
        }
    }
}

Das Problem bei 180 Services:

Jeder Service hat fast das gleiche Jenkinsfile. Aber „fast“ ist das Problem:

  • Service A braucht Java 17
  • Service B braucht Java 21
  • Service C hat zusätzliche Integration Tests
  • Service D deployed nach AWS statt Azure

Copy-Paste führt zu Drift. Nach 6 Monaten hat jedes Jenkinsfile kleine Unterschiede. Maintenance-Hölle.


🟡 PROFESSIONALS

Shared Library Struktur

Jenkins Shared Library Architektur

Abbildung 2: Die Architektur einer Jenkins Shared Library

Repository-Aufbau:

jenkins-shared-library/
├── vars/                    # Global verfügbare Funktionen
│   ├── javaPipeline.groovy  # Haupt-Pipeline
│   ├── buildJavaApp.groovy  # Build-Step
│   ├── runSecurityScan.groovy
│   └── deployToKubernetes.groovy
├── src/                     # Groovy Klassen
│   └── com/
│       └── javafleet/
│           └── PipelineConfig.groovy
├── resources/               # Nicht-Groovy Dateien
│   └── scripts/
│       └── deploy.sh
└── README.md

Wichtig:

  • vars/ = Globale Funktionen, direkt aufrufbar
  • src/ = Klassen für komplexere Logik
  • resources/ = Shell-Scripts, Config-Files

Erste Shared Library schreiben

vars/javaPipeline.groovy:

#!/usr/bin/env groovy

def call(Map config = [:]) {
    // Defaults setzen
    def javaVersion = config.javaVersion ?: '17'
    def deployEnv = config.deployEnv ?: 'dev'
    def skipTests = config.skipTests ?: false
    def enableSecurityScan = config.enableSecurityScan ?: true
    
    pipeline {
        agent {
            kubernetes {
                yaml """
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: maven
    image: maven:3.9-eclipse-temurin-${javaVersion}
    command: ['cat']
    tty: true
  - name: docker
    image: docker:24-dind
    securityContext:
      privileged: true
"""
            }
        }
        
        environment {
            MAVEN_OPTS = '-Xmx1024m'
            DOCKER_REGISTRY = credentials('docker-registry')
        }
        
        stages {
            stage('Checkout') {
                steps {
                    checkout scm
                }
            }
            
            stage('Build') {
                steps {
                    container('maven') {
                        sh 'mvn clean package -DskipTests'
                    }
                }
            }
            
            stage('Test') {
                when {
                    expression { !skipTests }
                }
                steps {
                    container('maven') {
                        sh 'mvn test'
                    }
                }
                post {
                    always {
                        junit '**/target/surefire-reports/*.xml'
                    }
                }
            }
            
            stage('Security Scan') {
                when {
                    expression { enableSecurityScan }
                }
                steps {
                    container('maven') {
                        sh 'mvn org.owasp:dependency-check-maven:check'
                    }
                }
                post {
                    always {
                        dependencyCheckPublisher pattern: '**/dependency-check-report.xml'
                    }
                }
            }
            
            stage('Build Container') {
                steps {
                    container('docker') {
                        script {
                            def image = docker.build("${env.DOCKER_REGISTRY}/${env.JOB_NAME}:${env.BUILD_NUMBER}")
                            image.push()
                            image.push('latest')
                        }
                    }
                }
            }
            
            stage('Deploy') {
                steps {
                    deployToKubernetes(
                        environment: deployEnv,
                        image: "${env.DOCKER_REGISTRY}/${env.JOB_NAME}:${env.BUILD_NUMBER}"
                    )
                }
            }
        }
        
        post {
            success {
                slackSend(
                    color: 'good',
                    message: "✅ ${env.JOB_NAME} #${env.BUILD_NUMBER} deployed to ${deployEnv}"
                )
            }
            failure {
                slackSend(
                    color: 'danger',
                    message: "❌ ${env.JOB_NAME} #${env.BUILD_NUMBER} failed"
                )
            }
        }
    }
}

Modulare Helper-Funktionen

vars/deployToKubernetes.groovy:

#!/usr/bin/env groovy

def call(Map config) {
    def environment = config.environment
    def image = config.image
    def namespace = config.namespace ?: environment
    def timeout = config.timeout ?: 300
    
    echo "Deploying ${image} to ${environment}"
    
    // Kubeconfig basierend auf Environment
    withCredentials([file(credentialsId: "kubeconfig-${environment}", variable: 'KUBECONFIG')]) {
        
        // Image in Deployment updaten
        sh """
            kubectl set image deployment/\${JOB_NAME} \
                app=${image} \
                -n ${namespace}
        """
        
        // Warten auf Rollout
        sh """
            kubectl rollout status deployment/\${JOB_NAME} \
                -n ${namespace} \
                --timeout=${timeout}s
        """
    }
    
    echo "Deployment to ${environment} successful"
}

vars/runSecurityScan.groovy:

#!/usr/bin/env groovy

def call(Map config = [:]) {
    def failOnCritical = config.failOnCritical ?: true
    def cvssThreshold = config.cvssThreshold ?: 7.0
    
    stage('OWASP Dependency Check') {
        sh """
            mvn org.owasp:dependency-check-maven:check \
                -DfailBuildOnCVSS=${cvssThreshold}
        """
        
        dependencyCheckPublisher pattern: '**/dependency-check-report.xml'
    }
    
    stage('Trivy Container Scan') {
        def trivyArgs = failOnCritical ? '--exit-code 1 --severity CRITICAL,HIGH' : ''
        
        sh """
            trivy image ${trivyArgs} \
                --format json \
                --output trivy-report.json \
                \${DOCKER_REGISTRY}/\${JOB_NAME}:\${BUILD_NUMBER}
        """
        
        archiveArtifacts artifacts: 'trivy-report.json'
    }
}

Library in Jenkins einbinden

Methode 1: Global in Jenkins konfigurieren

  1. Jenkins verwalten → System konfigurieren
  2. Global Pipeline Libraries
  3. Name: java-fleet-library
  4. Default Version: main
  5. Retrieval Method: Modern SCM → Git
  6. Repository URL: https://github.com/your-org/jenkins-shared-library.git

Methode 2: Im Jenkinsfile deklarieren

@Library('java-fleet-library@main') _

javaPipeline(
    javaVersion: '21',
    deployEnv: 'staging'
)

Verwendung in Projekten

Minimales Jenkinsfile (3 Zeilen!):

@Library('java-fleet-library') _

javaPipeline()

Mit Konfiguration:

@Library('java-fleet-library') _

javaPipeline(
    javaVersion: '21',
    deployEnv: 'production',
    enableSecurityScan: true,
    skipTests: false
)

Mit Custom Stages:

@Library('java-fleet-library') _

javaPipeline(
    javaVersion: '17',
    deployEnv: 'dev',
    additionalStages: {
        stage('Integration Tests') {
            steps {
                sh 'mvn verify -Pintegration'
            }
        }
    }
)

🔵 BONUS

Configuration as Code (JCasC)

Das Problem: Jenkins-Konfiguration nur über UI? Nicht reproduzierbar, kein Audit-Trail.

JCasC löst das:

jenkins.yaml:

jenkins:
  systemMessage: "Java Fleet CI/CD Server"
  numExecutors: 0  # Controller führt keine Builds aus
  
  securityRealm:
    ldap:
      configurations:
        - server: "ldaps://ldap.company.com"
          rootDN: "dc=company,dc=com"
          userSearchBase: "ou=users"
          userSearch: "uid={0}"
          
  authorizationStrategy:
    roleBased:
      roles:
        global:
          - name: "admin"
            permissions:
              - "Overall/Administer"
            entries:
              - group: "jenkins-admins"
          - name: "developer"
            permissions:
              - "Overall/Read"
              - "Job/Build"
              - "Job/Read"
            entries:
              - group: "developers"
              
  clouds:
    - kubernetes:
        name: "kubernetes"
        serverUrl: "https://kubernetes.default.svc"
        namespace: "jenkins"
        jenkinsUrl: "http://jenkins.jenkins.svc:8080"
        podLabels:
          - key: "jenkins"
            value: "agent"

unclassified:
  globalLibraries:
    libraries:
      - name: "java-fleet-library"
        defaultVersion: "main"
        retriever:
          modernSCM:
            scm:
              git:
                remote: "https://github.com/your-org/jenkins-shared-library.git"
                credentialsId: "github-token"
                
  slackNotifier:
    teamDomain: "javafleet"
    tokenCredentialId: "slack-token"
    room: "#ci-notifications"

credentials:
  system:
    domainCredentials:
      - credentials:
          - usernamePassword:
              scope: GLOBAL
              id: "docker-registry"
              username: "${DOCKER_USER}"
              password: "${DOCKER_PASSWORD}"
          - file:
              scope: GLOBAL
              id: "kubeconfig-production"
              fileName: "kubeconfig"
              secretBytes: "${KUBECONFIG_PROD_BASE64}"

Jenkins Helm Chart mit JCasC:

# values.yaml
controller:
  JCasC:
    configScripts:
      welcome-message: |
        jenkins:
          systemMessage: "Java Fleet CI/CD"
      
  installPlugins:
    - kubernetes:latest
    - workflow-aggregator:latest
    - git:latest
    - configuration-as-code:latest
    - job-dsl:latest

Distributed Builds für große Teams

Problem: 180 Services, alle wollen gleichzeitig bauen.

Lösung: Kubernetes-basierte Agents

// In der Shared Library
def call(Map config = [:]) {
    def podTemplate = """
apiVersion: v1
kind: Pod
metadata:
  labels:
    jenkins: agent
spec:
  serviceAccountName: jenkins-agent
  containers:
  - name: maven
    image: maven:3.9-eclipse-temurin-${config.javaVersion ?: '17'}
    command: ['cat']
    tty: true
    resources:
      requests:
        memory: "1Gi"
        cpu: "500m"
      limits:
        memory: "2Gi"
        cpu: "2000m"
    volumeMounts:
    - name: maven-cache
      mountPath: /root/.m2
  - name: docker
    image: docker:24-dind
    securityContext:
      privileged: true
    volumeMounts:
    - name: docker-storage
      mountPath: /var/lib/docker
  volumes:
  - name: maven-cache
    persistentVolumeClaim:
      claimName: maven-cache
  - name: docker-storage
    emptyDir: {}
"""
    
    pipeline {
        agent {
            kubernetes {
                yaml podTemplate
                defaultContainer 'maven'
            }
        }
        // ... stages
    }
}

Skalierung:

  • Kubernetes skaliert Pods automatisch
  • Maven-Cache als PVC = schnellere Builds
  • Jeder Build bekommt eigenen Pod = Isolation

Pipeline-Templates mit Job DSL

Für 180 Services automatisch Jobs erstellen:

seed-job.groovy:

// Liste aller Services aus Git oder Config
def services = [
    [name: 'user-service', javaVersion: '21', team: 'platform'],
    [name: 'order-service', javaVersion: '17', team: 'commerce'],
    [name: 'payment-service', javaVersion: '21', team: 'finance'],
    // ... 177 weitere
]

// Für jeden Service einen Job erstellen
services.each { service ->
    multibranchPipelineJob("${service.team}/${service.name}") {
        displayName(service.name)
        
        branchSources {
            git {
                remote("https://github.com/your-org/${service.name}.git")
                credentialsId('github-token')
            }
        }
        
        orphanedItemStrategy {
            discardOldItems {
                numToKeep(10)
            }
        }
        
        factory {
            workflowBranchProjectFactory {
                scriptPath('Jenkinsfile')
            }
        }
        
        configure { node ->
            def traits = node / sources / data / 'jenkins.branch.BranchSource' / source / traits
            traits << 'jenkins.plugins.git.traits.BranchDiscoveryTrait' {}
        }
    }
}

Ergebnis: Ein Seed-Job erstellt/aktualisiert alle 180 Service-Jobs automatisch.


✅ Checkpoint

Hast du heute gelernt:

  • [ ] Wann Jenkins sinnvoller ist als GitHub Actions?
  • [ ] Wie Shared Libraries strukturiert werden?
  • [ ] Wie du eine Pipeline-Funktion in vars/ schreibst?
  • [ ] Wie JCasC Jenkins-Konfiguration versionierbar macht?
  • [ ] Wie Job DSL Jobs automatisch erstellt?

Quick-Test:

  1. Wo liegen globale Funktionen in einer Shared Library?
  2. Was macht @Library('name') _ im Jenkinsfile?
  3. Warum sollte der Jenkins Controller keine Builds ausführen?

🎨 Challenge für diese Woche

Deine Mission:

  1. Erstelle eine Shared Library mit einer javaPipeline-Funktion
  2. Die Pipeline soll Build, Test und Security-Scan enthalten
  3. Mache Java-Version und Deploy-Environment konfigurierbar
  4. Teste mit mindestens 2 verschiedenen Projekten

Bonus: Füge JCasC hinzu, um die Library automatisch zu konfigurieren.


❓ FAQ

Frage 1: Kann ich GitHub Actions Workflows auch wiederverwenden?

Ja, mit Composite Actions oder Reusable Workflows. Aber: Du brauchst trotzdem ein Workflow-File pro Repo. Bei 180 Repos ist das mehr Overhead als ein 3-Zeilen-Jenkinsfile.

Frage 2: Wie teste ich Shared Libraries lokal?

Mit dem Jenkins Pipeline Unit Framework:

@Test
void testJavaPipeline() {
    def script = loadScript('vars/javaPipeline.groovy')
    script.call(javaVersion: '17')
    // Assertions...
}

Frage 3: Was wenn ein Service eine komplett andere Pipeline braucht?

Dann schreib ein eigenes Jenkinsfile. Die Shared Library ist ein Angebot, kein Zwang. Für 90% Standard-Pipeline, für 10% Custom.

Frage 4: Wie migriere ich von GitHub Actions zu Jenkins?

Nicht komplett migrieren. Nutze Jenkins für die großen Projekte mit Shared Libraries. Lass kleinere Projekte bei GitHub Actions. Hybrid ist okay.

Frage 5: Wie update ich die Shared Library ohne alle Builds zu brechen?

Semantic Versioning. Projekte referenzieren @Library('lib@v1'). Breaking Changes gehen in v2. Projekte migrieren nach und nach.

Frage 6: Brauche ich Jenkins, wenn ich schon ArgoCD habe?

Ja, unterschiedliche Aufgaben. Jenkins baut und testet. ArgoCD deployed. Jenkins pusht Images → ArgoCD synct Kubernetes.

Frage 7: Wie geht ihr bei Java Fleet mit Legacy-Jenkins-Installationen um?

seufzt Das ist eher was für private logs. Aber kurz: Franz-Martin hat einmal eine Jenkins-Instanz von 2016 gefunden. Mit Plugins, die es nicht mehr gibt. Das Upgrade hat 3 Wochen gedauert. Kofi nennt es immer noch „Das Jenkins-Massaker von Q2“. Seitdem: JCasC. Immer. 🔧


📖 CI/CD-Mastery Serie – Alle Teile

TeilThemaStatus
1Erste Pipeline
2Security Gates (OWASP & Trivy)
3Coverage Gates (JaCoCo)
4Quality Gates (SonarQube)
5Multi-Stage Docker Builds
6Container Security (SBOM)
7Registry Integration
8Blue-Green Deployments
9Canary & Kubernetes
10GitOps & Environments
→ 11Jenkins Enterprise📍 Du bist hier
12Multi-Platform & Finale📅 Nächste Woche

📦 Downloads

RessourceBeschreibung
shared-library-starter.zipKomplette Shared Library Struktur
jcasc-example.zipJenkins Configuration as Code Templates
job-dsl-seed.zipSeed Job für automatische Job-Erstellung

Quick Start:

# 1. ZIP entpacken
unzip shared-library-starter.zip
cd shared-library-starter

# 2. Als Git Repo initialisieren
git init
git add .
git commit -m "Initial shared library"

# 3. In Jenkins konfigurieren (siehe README)

🔗 Externe Links – Teil 11: Jenkins Enterprise

Für Einsteiger 🌱

RessourceBeschreibung
Jenkins Pipeline SyntaxGrundlagen der Pipeline-Syntax
Shared Libraries IntroEinstieg in Shared Libraries
JCasC Getting StartedConfiguration as Code Überblick

Offizielle Dokumentation 📚

RessourceBeschreibung
Jenkins Shared LibrariesKomplette Shared Library Referenz
Configuration as Code PluginJCasC Plugin-Dokumentation
Job DSL PluginJob DSL Plugin-Dokumentation
Job DSL API ViewerInteraktive API-Referenz
Jenkins on KubernetesKubernetes-Installation

Beispiele & Demos 🎯

RessourceBeschreibung
JCasC DemosOffizielle JCasC-Beispiele
Pipeline ExamplesJenkins Pipeline-Beispiele
Shared Library ExamplesShared Library Patterns

Helm & Kubernetes 🐳

RessourceBeschreibung
Jenkins Helm ChartOffizielles Helm Chart
Artifact Hub – JenkinsHelm Chart auf Artifact Hub
Kubernetes PluginDynamische K8s-Agents

Fortgeschritten 🌳

RessourceBeschreibung
Pipeline Unit TestingShared Libraries testen
Jenkins OperatorJenkins als Kubernetes Operator
Jenkins XCloud-Native Jenkins

🔮 Nächste Woche: Multi-Platform & Finale

Teil 12: GitLab CI, Azure DevOps & Platform-agnostisches Design

Du lernst:

  • GitLab CI Pipeline Syntax
  • Azure DevOps Pipelines
  • Wie du Pipelines platform-agnostisch designst
  • Migration zwischen Plattformen

Das große Finale der CI/CD-Mastery Serie! 🎉


👋 Bis nächste Woche!

Shared Libraries sind ein Game-Changer für große Teams. Einmal schreiben, überall nutzen. Änderungen zentral, sofort wirksam.

Aber vergiss nicht: Das Tool ist egal, die Prinzipien zählen. Ob Jenkins, GitHub Actions oder GitLab CI – die Konzepte (Gates, Security, Automation) bleiben gleich.

Fragen? Schreib mir: code.sentinel@java-developer.online

Keep automating, keep scaling! 🛡️


Bernd’s Corner:

„Shared Libraries? Wir hatten damals EIN Shell-Script. build.sh. Hat alles gemacht. Okay, 2000 Zeilen, keine Tests, niemand wusste genau was es macht. Aber es hat funktioniert! Meistens. Wenn Mercury nicht retrograd war.“

— Bernd, der sein build.sh von 2003 immer noch als „elegant“ bezeichnet


Tags: #Jenkins #SharedLibraries #DevOps #CI/CD #Enterprise

© 2025 Java Fleet Systems Consulting | java-developer.online

Autor

  • Code Sentinel

    32 Jahre alt, Technical Project Manager und Security-Experte bei Java Fleet Systems Consulting. Code ist ein erfahrener Entwickler, der in die Projektleitung aufgestiegen ist, aber immer noch tief in der Technik verwurzelt bleibt. Seine Mission: Sicherstellen, dass Projekte termingerecht, sicher und wartbar geliefert werden.