Jenkins X for Kubernetes CI/CD in Java

Comprehensive Jenkins X Implementation for Java Applications

1. Jenkins X Configuration Files

jenkins-x.yml - Main Configuration

buildPack: maven-java11 projectType: libraries dockerRegistry: ghcr.io pipelineConfig: pipelines: pullRequest: pipeline: agent: image: maven:3.8.6-openjdk-11 stages: - name: pr-checks steps: - name: test command: | mvn clean test resources: requests: cpu: 400m memory: 512Mi limits: cpu: 1000m memory: 1024Mi - name: static-analysis command: | mvn sonar:sonar -Dsonar.projectKey=my-java-app - name: security-scan command: | mvn org.owasp:dependency-check-maven:check release: pipeline: agent: image: maven:3.8.6-openjdk-11 environment: - name: DOCKER_REGISTRY value: ghcr.io - name: VERSION value: ${VERSION} stages: - name: build-and-package steps: - name: build command: | mvn clean package -DskipTests -Pprod resources: requests: cpu: 1000m memory: 2Gi limits: cpu: 2000m memory: 4Gi - name: docker-build command: | docker build -t ${DOCKER_REGISTRY}/${ORG}/${APP_NAME}:${VERSION} . - name: docker-push command: | docker push ${DOCKER_REGISTRY}/${ORG}/${APP_NAME}:${VERSION} - name: deploy-to-staging steps: - name: deploy command: | jx step helm apply --name ${APP_NAME} - name: integration-tests steps: - name: api-tests command: | mvn verify -Pintegration-tests - name: performance-tests command: | mvn gatling:test -Pperformance-tests - name: promote-to-production steps: - name: promote command: | jx promote --app ${APP_NAME} --version ${VERSION} --env production when: branch: main containerOptions: resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi env: - name: MAVEN_OPTS value: -Xmx1024m

2. Kubernetes Deployment Manifests

charts/my-java-app/values.yaml

# Default values for my-java-app. image: repository: ghcr.io/my-org/my-java-app tag: latest pullPolicy: IfNotPresent nameOverride: "" fullnameOverride: "" service: type: ClusterIP port: 8080 targetPort: 8080 ingress: enabled: true annotations: kubernetes.io/ingress.class: nginx cert-manager.io/cluster-issuer: letsencrypt-prod hosts: - host: my-app.example.com paths: ["/"] tls: - secretName: my-java-app-tls hosts: - my-app.example.com resources: requests: cpu: 200m memory: 512Mi limits: cpu: 1000m memory: 1024Mi autoscaling: enabled: true minReplicas: 2 maxReplicas: 10 targetCPUUtilizationPercentage: 80 livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 5 periodSeconds: 5 config: spring: profiles: active: "kubernetes" logging: level: com.example: INFO

charts/my-java-app/templates/deployment.yaml

apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "my-java-app.fullname" . }} labels: {{- include "my-java-app.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: {{- include "my-java-app.selectorLabels" . | nindent 6 }} template: metadata: labels: {{- include "my-java-app.selectorLabels" . | nindent 8 }} version: {{ .Chart.AppVersion }} annotations: checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 8080 protocol: TCP livenessProbe: {{- toYaml .Values.livenessProbe | nindent 12 }} readinessProbe: {{- toYaml .Values.readinessProbe | nindent 12 }} resources: {{- toYaml .Values.resources | nindent 12 }} env: - name: SPRING_PROFILES_ACTIVE value: {{ .Values.config.spring.profiles.active | quote }} - name: JAVA_OPTS value: "-Xmx{{ .Values.resources.limits.memory | replace "Mi" "m" | replace "Gi" "g" }} -XX:+UseG1GC" envFrom: - configMapRef: name: {{ include "my-java-app.fullname" . }}-config imagePullSecrets: - name: ghcr-secret

3. Java Application with Kubernetes Support

Spring Boot Application with Health Checks

@SpringBootApplication public class MyJavaApplication { public static void main(String[] args) { SpringApplication.run(MyJavaApplication.class, args); } @Bean public KubernetesHealthIndicator kubernetesHealthIndicator() { return new KubernetesHealthIndicator(); } } @Component public class KubernetesHealthIndicator implements HealthIndicator { private final ApplicationContext applicationContext; private final DataSource dataSource; public KubernetesHealthIndicator(ApplicationContext applicationContext, DataSource dataSource) { this.applicationContext = applicationContext; this.dataSource = dataSource; } @Override public Health health() { // Check database connectivity try (Connection connection = dataSource.getConnection()) { if (!connection.isValid(5)) { return Health.down().withDetail("database", "Not connected").build(); } } catch (SQLException e) { return Health.down().withDetail("database", e.getMessage()).build(); } // Check application context if (!applicationContext.isActive()) { return Health.down().withDetail("application", "Context not active").build(); } return Health.up() .withDetail("application", "Running") .withDetail("timestamp", Instant.now()) .build(); } }

Configuration for Kubernetes

@Configuration @Profile("kubernetes") public class KubernetesConfig { @Bean @ConfigurationProperties(prefix = "app.kubernetes") public KubernetesProperties kubernetesProperties() { return new KubernetesProperties(); } @Bean public LoggingFilter loggingFilter() { return new LoggingFilter(); } } @ConfigurationProperties(prefix = "app.kubernetes") public class KubernetesProperties { private String clusterName; private String namespace; private boolean metricsEnabled = true; private int gracefulShutdownTimeout = 30; // Getters and setters public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public boolean isMetricsEnabled() { return metricsEnabled; } public void setMetricsEnabled(boolean metricsEnabled) { this.metricsEnabled = metricsEnabled; } public int getGracefulShutdownTimeout() { return gracefulShutdownTimeout; } public void setGracefulShutdownTimeout(int gracefulShutdownTimeout) { this.gracefulShutdownTimeout = gracefulShutdownTimeout; } }

4. Dockerfile for Java Application

# Multi-stage build FROM maven:3.8.6-openjdk-11 AS builder WORKDIR /app COPY pom.xml . COPY src ./src # Build the application RUN mvn clean package -DskipTests -Pprod # Runtime stage FROM openjdk:11-jre-slim # Install required packages RUN apt-get update && apt-get install -y \ curl \ && rm -rf /var/lib/apt/lists/* # Create app user RUN groupadd -r appuser && useradd -r -g appuser appuser WORKDIR /app # Copy built artifact from builder stage COPY --from=builder /app/target/*.jar app.jar # Create non-root user and switch to it RUN chown -R appuser:appuser /app USER appuser # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1 # JVM options ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC" # Run the application ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

5. Jenkins X Pipeline Extensions

Custom Pipeline Steps

// jenkins-x-custom-steps.yaml apiVersion: jenkins.io/v1 kind: PipelineActivity spec: pipeline: custom-java-pipeline steps: - name: security-scan command: mvn org.owasp:dependency-check-maven:check - name: performance-test command: mvn gatling:test - name: container-scan command: | docker scan ${DOCKER_REGISTRY}/${ORG}/${APP_NAME}:${VERSION}

Java-specific Pipeline Library

// vars/javaPipeline.groovy def call(body) { def config = [:] body.resolveStrategy = Closure.DELEGATE_FIRST body.delegate = config body() pipeline { agent { kubernetes { yaml """ apiVersion: v1 kind: Pod spec: containers: - name: maven image: maven:3.8.6-openjdk-11 command: ['cat'] tty: true resources: requests: cpu: 400m memory: 1Gi limits: cpu: 1 memory: 2Gi volumeMounts: - name: maven-cache mountPath: /root/.m2 volumes: - name: maven-cache persistentVolumeClaim: claimName: maven-cache-pvc """ } } stages { stage('Build') { steps { container('maven') { sh 'mvn clean compile' } } } stage('Test') { steps { container('maven') { sh 'mvn test' } } post { always { junit 'target/surefire-reports/*.xml' } } } stage('Sonar Analysis') { steps { container('maven') { withSonarQubeEnv('sonar-server') { sh 'mvn sonar:sonar' } } } } stage('Build Image') { steps { container('maven') { script { docker.build("${env.DOCKER_REGISTRY}/${env.ORG}/${env.APP_NAME}:${env.VERSION}") } } } } } } }

6. Maven Configuration for Jenkins X

pom.xml with Jenkins X Profiles

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>my-java-app</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <properties> <java.version>11</java.version> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <spring-boot.version>2.7.0</spring-boot.version> <jacoco.version>0.8.8</jacoco.version> <sonar-maven-plugin.version>3.9.1.2184</sonar-maven-plugin.version> </properties> <profiles> <profile> <id>prod</id> <properties> <spring.profiles.active>prod</spring.profiles.active> </properties> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <executable>true</executable> </configuration> </plugin> </plugins> </build> </profile> <profile> <id>integration-tests</id> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>3.0.0-M7</version> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </profile> <profile> <id>performance-tests</id> <dependencies> <dependency> <groupId>io.gatling.highcharts</groupId> <artifactId>gatling-charts-highcharts</artifactId> <version>3.9.0</version> <scope>test</scope> </dependency> </dependencies> </profile> </profiles> <build> <plugins> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>${jacoco.version}</version> <executions> <execution> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <phase>test</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.sonarsource.scanner.maven</groupId> <artifactId>sonar-maven-plugin</artifactId> <version>${sonar-maven-plugin.version}</version> </plugin> <plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> <version>7.1.1</version> <executions> <execution> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>

7. Kubernetes-aware Java Application

Configuration Reloader

@Component public class KubernetesConfigReloader { private static final Logger logger = LoggerFactory.getLogger(KubernetesConfigReloader.class); private final ConfigurableApplicationContext applicationContext; private final KubernetesProperties kubernetesProperties; public KubernetesConfigReloader(ConfigurableApplicationContext applicationContext, KubernetesProperties kubernetesProperties) { this.applicationContext = applicationContext; this.kubernetesProperties = kubernetesProperties; } @EventListener public void onConfigMapChange(ConfigMapChangeEvent event) { logger.info("ConfigMap changed, reloading configuration: {}", event.getSource()); // Refresh Spring configuration applicationContext.publishEvent(new EnvironmentChangeEvent(event.getKeys())); // Log the configuration reload logger.info("Configuration reloaded successfully"); } @EventListener public void onSecretChange(SecretChangeEvent event) { logger.info("Secret changed, reloading sensitive configuration: {}", event.getSource()); // Handle secret rotation applicationContext.publishEvent(new EnvironmentChangeEvent(event.getKeys())); logger.info("Secret configuration reloaded successfully"); } }

Metrics Exporter for Kubernetes

@Component public class KubernetesMetricsExporter { private final MeterRegistry meterRegistry; private final ApplicationContext applicationContext; public KubernetesMetricsExporter(MeterRegistry meterRegistry, ApplicationContext applicationContext) { this.meterRegistry = meterRegistry; this.applicationContext = applicationContext; } @PostConstruct public void exportMetrics() { // JVM metrics new JvmMemoryMetrics().bindTo(meterRegistry); new JvmGcMetrics().bindTo(meterRegistry); new ProcessorMetrics().bindTo(meterRegistry); new JvmThreadMetrics().bindTo(meterRegistry); // Custom application metrics Gauge.builder("app.uptime") .description("Application uptime in seconds") .register(meterRegistry, this, exporter -> { return ManagementFactory.getRuntimeMXBean().getUptime() / 1000; }); Gauge.builder("app.bean.count") .description("Number of Spring beans") .register(meterRegistry, this, exporter -> { return applicationContext.getBeanDefinitionCount(); }); } }

8. Jenkins X Preview Environments

Preview Environment Configuration

# charts/my-java-app/preview/values.yaml preview: enabled: true image: repository: ghcr.io/my-org/my-java-app tag: latest ingress: enabled: true annotations: kubernetes.io/ingress.class: nginx hosts: - host: ${APP_NAME}-preview.${DOMAIN} paths: ["/"] resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 512Mi

9. GitOps Configuration

jx-requirements.yml

cluster: clusterName: my-java-cluster environmentGitOwner: my-org project: my-java-project provider: gke zone: us-central1-a environments: - key: dev - key: staging - key: production ingress: domain: example.com externalDNS: true tls: email: [email protected] enabled: true production: true storage: logs: enabled: true url: gs://my-java-app-logs reports: enabled: true url: gs://my-java-app-reports repository: enabled: true url: gs://my-java-app-repository versionStream: url: https://github.com/jenkins-x/jenkins-x-versions ref: v1.0.0

10. Jenkins X Quickstart Location

jx-edit.yml

buildPack: maven-java11 dockerRegistry: ghcr.io quickstartLocations: - owner: jenkins-x includes: - name: spring-boot-* - name: java-* excludes: - name: *-nodejs - name: *-python

This comprehensive Jenkins X setup provides:

  • Multi-stage CI/CD pipelines with PR and release flows
  • Kubernetes-optimized Java application with health checks
  • Security scanning and quality gates
  • Preview environments for each PR
  • GitOps approach for environment management
  • Performance testing integration
  • Custom metrics for monitoring
  • Configuration management for different environments

The setup ensures your Java applications are built, tested, and deployed efficiently on Kubernetes using Jenkins X best practices.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper