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.