Jenkins X for Kubernetes CI/CD in Java

Comprehensive Jenkins X Implementation Guide

1. Jenkins X Configuration Files

jenkins-x.yml - Main Pipeline Configuration

# jenkins-x.yml
buildPack: maven-java11
projectType: libraries
dockerRegistry: ghcr.io
pipelineConfig:
pipelines:
pullRequest:
pipeline:
agent:
image: maven:3.8.6-openjdk-11-slim
stages:
- name: setup
steps:
- name: setup-environment
command: |
echo "Setting up Java CI environment"
java -version
mvn -version
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
- name: tests
steps:
- name: unit-tests
command: |
mvn clean test
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 2000m
memory: 4Gi
- name: integration-tests
command: |
mvn verify -Pintegration-tests
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 2000m
memory: 4Gi
- name: analysis
steps:
- name: code-quality
command: |
mvn sonar:sonar -Dsonar.projectKey=my-java-app
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 2000m
memory: 4Gi
- name: security-scan
command: |
mvn org.owasp:dependency-check-maven:check
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 2000m
memory: 4Gi
release:
pipeline:
agent:
image: maven:3.8.6-openjdk-11-slim
environment:
- name: DOCKER_REGISTRY
value: ghcr.io
- name: VERSION
value: ${VERSION}
- name: MAVEN_OPTS
value: -Xmx2g
stages:
- name: build-and-package
steps:
- name: build
command: |
mvn clean package -DskipTests -Pprod
resources:
requests:
cpu: 2000m
memory: 4Gi
limits:
cpu: 4000m
memory: 8Gi
- 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: quality-gate
steps:
- name: code-coverage
command: |
mvn jacoco:check
- name: vulnerability-scan
command: |
trivy image ${DOCKER_REGISTRY}/${ORG}/${APP_NAME}:${VERSION}
- name: deploy-to-staging
steps:
- name: deploy
command: |
jx step helm apply --name ${APP_NAME}
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 1000m
memory: 2Gi
- name: validation
steps:
- name: smoke-tests
command: |
mvn verify -Psmoke-tests
- name: api-tests
command: |
mvn verify -Papi-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
- name: GRADLE_OPTS
value: -Xmx1024m
# Preview environments configuration
previewEnvironment:
ingress:
domain: preview.example.com
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi

2. Kubernetes Deployment Manifests

Helm Chart Structure

charts/
└── my-java-app/
├── Chart.yaml
├── values.yaml
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   └── hpa.yaml

charts/my-java-app/Chart.yaml

apiVersion: v2
name: my-java-app
description: A Spring Boot Java application
type: application
version: 0.1.0
appVersion: "1.0.0"
dependencies:
- name: postgresql
version: 12.1.0
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled

charts/my-java-app/values.yaml

# Default values for my-java-app
replicaCount: 2
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
className: "nginx"
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rewrite-target: /
hosts:
- host: my-app.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: my-java-app-tls
hosts:
- my-app.example.com
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 1000m
memory: 2Gi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 85
probes:
liveness:
path: /actuator/health/liveness
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readiness:
path: /actuator/health/readiness
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
startup:
path: /actuator/health/readiness
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 10
config:
spring:
profiles:
active: "kubernetes"
logging:
level:
com.example: INFO
org.springframework: INFO
postgresql:
enabled: true
auth:
postgresPassword: "changeme"
database: "mydatabase"
username: "myuser"
password: "mypassword"
# Jenkins X specific values
jenkins-x:
preview:
enabled: true
domain: preview.example.com

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 }}
app.kubernetes.io/component: api
app.kubernetes.io/part-of: {{ .Chart.Name }}
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 | quote }}
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/actuator/prometheus"
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: {{ .Values.probes.liveness.path }}
port: http
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
readinessProbe:
httpGet:
path: {{ .Values.probes.readiness.path }}
port: http
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
startupProbe:
httpGet:
path: {{ .Values.probes.startup.path }}
port: http
initialDelaySeconds: {{ .Values.probes.startup.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.startup.periodSeconds }}
timeoutSeconds: {{ .Values.probes.startup.timeoutSeconds }}
failureThreshold: {{ .Values.probes.startup.failureThreshold }}
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 "Gi" "g" | replace "Mi" "m" }} -XX:+UseContainerSupport -XX:+UseG1GC"
- name: MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE
value: "health,info,prometheus,metrics"
envFrom:
- configMapRef:
name: {{ include "my-java-app.fullname" . }}-config
- secretRef:
name: {{ include "my-java-app.fullname" . }}-secrets
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
imagePullSecrets:
- name: ghcr-secret
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000

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

{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "my-java-app.fullname" . }}
labels:
{{- include "my-java-app.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "my-java-app.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 1
periodSeconds: 60
- type: Percent
value: 50
periodSeconds: 60
selectPolicy: Min
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 2
periodSeconds: 60
- type: Percent
value: 50
periodSeconds: 60
selectPolicy: Max
{{- end }}

3. Java Application with Kubernetes Support

Spring Boot Application Configuration

// Application.java
package com.example.myapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties(ApplicationProperties.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Kubernetes Health Indicators

// KubernetesHealthIndicator.java
package com.example.myapp.health;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import org.springframework.jdbc.core.JdbcTemplate;
@Component
public class KubernetesHealthIndicator implements HealthIndicator {
private final JdbcTemplate jdbcTemplate;
private final ApplicationProperties properties;
public KubernetesHealthIndicator(JdbcTemplate jdbcTemplate, 
ApplicationProperties properties) {
this.jdbcTemplate = jdbcTemplate;
this.properties = properties;
}
@Override
public Health health() {
try {
// Check database connectivity
jdbcTemplate.execute("SELECT 1");
// Check application-specific health
if (!isApplicationHealthy()) {
return Health.down()
.withDetail("application", "Application health check failed")
.build();
}
return Health.up()
.withDetail("database", "Connected")
.withDetail("application", "Healthy")
.withDetail("version", properties.getVersion())
.build();
} catch (Exception e) {
return Health.down()
.withDetail("database", "Connection failed: " + e.getMessage())
.build();
}
}
private boolean isApplicationHealthy() {
// Add application-specific health checks
return true;
}
}
// LivenessHealthIndicator.java
package com.example.myapp.health;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class LivenessHealthIndicator implements HealthIndicator {
@Override
public Health health() {
// Simple liveness check - application is running
return Health.up().build();
}
}
// ReadinessHealthIndicator.java
package com.example.myapp.health;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import org.springframework.jdbc.core.JdbcTemplate;
@Component
public class ReadinessHealthIndicator implements HealthIndicator {
private final JdbcTemplate jdbcTemplate;
public ReadinessHealthIndicator(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Health health() {
try {
// Check if application is ready to serve traffic
jdbcTemplate.execute("SELECT 1");
return Health.up()
.withDetail("database", "Ready")
.withDetail("service", "Ready to serve traffic")
.build();
} catch (Exception e) {
return Health.down()
.withDetail("database", "Not ready: " + e.getMessage())
.build();
}
}
}

Application Properties Configuration

// ApplicationProperties.java
package com.example.myapp.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app")
public class ApplicationProperties {
private String version = "1.0.0";
private String environment = "development";
private Database database = new Database();
private Security security = new Security();
private Kubernetes kubernetes = new Kubernetes();
// Getters and setters
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public String getEnvironment() { return environment; }
public void setEnvironment(String environment) { this.environment = environment; }
public Database getDatabase() { return database; }
public void setDatabase(Database database) { this.database = database; }
public Security getSecurity() { return security; }
public void setSecurity(Security security) { this.security = security; }
public Kubernetes getKubernetes() { return kubernetes; }
public void setKubernetes(Kubernetes kubernetes) { this.kubernetes = kubernetes; }
public static class Database {
private int connectionTimeout = 30000;
private int maxPoolSize = 10;
public int getConnectionTimeout() { return connectionTimeout; }
public void setConnectionTimeout(int connectionTimeout) { this.connectionTimeout = connectionTimeout; }
public int getMaxPoolSize() { return maxPoolSize; }
public void setMaxPoolSize(int maxPoolSize) { this.maxPoolSize = maxPoolSize; }
}
public static class Security {
private boolean enabled = true;
private String jwtSecret;
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public String getJwtSecret() { return jwtSecret; }
public void setJwtSecret(String jwtSecret) { this.jwtSecret = jwtSecret; }
}
public static class Kubernetes {
private boolean enabled = false;
private String namespace = "default";
private String clusterName;
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public String getNamespace() { return namespace; }
public void setNamespace(String namespace) { this.namespace = namespace; }
public String getClusterName() { return clusterName; }
public void setClusterName(String clusterName) { this.clusterName = clusterName; }
}
}

4. Dockerfile for Java Application

# Multi-stage build for Java application
FROM maven:3.8.6-openjdk-11 AS builder
# Install additional tools
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy pom.xml and download dependencies
COPY pom.xml .
RUN mvn dependency:go-offline -B
# Copy source code and build application
COPY src ./src
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 \
wait-for-it \
&& rm -rf /var/lib/apt/lists/*
# Create non-root 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
# Copy entrypoint script
COPY entrypoint.sh .
RUN chmod +x entrypoint.sh
# Create necessary directories and set permissions
RUN mkdir -p /app/logs && \
chown -R appuser:appuser /app
# Switch to non-root user
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health/liveness || exit 1
# JVM options for container
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC -XX:+ExitOnOutOfMemoryError"
# Expose port
EXPOSE 8080
# Use entrypoint script
ENTRYPOINT ["/app/entrypoint.sh"]

Entrypoint Script

#!/bin/bash
# entrypoint.sh
set -e
echo "Starting Java application..."
# Wait for dependencies if needed
if [ -n "$WAIT_FOR_HOSTS" ]; then
IFS=',' read -ra HOSTS <<< "$WAIT_FOR_HOSTS"
for host in "${HOSTS[@]}"; do
echo "Waiting for $host..."
/usr/bin/wait-for-it "$host" --timeout=60
done
fi
# Set default JVM options if not provided
if [ -z "$JAVA_OPTS" ]; then
JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
fi
# Start the application with exec to replace the shell process
exec java $JAVA_OPTS -jar app.jar "$@"

5. Maven Configuration with Jenkins X Profiles

pom.xml

<?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>
<name>My Java Application</name>
<description>Spring Boot application for Kubernetes</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/>
</parent>
<properties>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Test dependencies -->
<junit.version>5.8.2</junit.version>
<testcontainers.version>1.17.3</testcontainers.version>
<!-- Quality tools -->
<jacoco.version>0.8.8</jacoco.version>
<sonar-maven-plugin.version>3.9.1.2184</sonar-maven-plugin.version>
<owasp-dependency-check.version>7.1.1</owasp-dependency-check.version>
<!-- Performance testing -->
<gatling.version>3.9.0</gatling.version>
</properties>
<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Kubernetes -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-client-all</artifactId>
<version>2.1.3</version>
</dependency>
<!-- Monitoring -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<!-- Performance Testing -->
<dependency>
<groupId>io.gatling.highcharts</groupId>
<artifactId>gatling-charts-highcharts</artifactId>
<version>${gatling.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<!-- Production profile -->
<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>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<!-- Integration Tests 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>
<!-- Smoke Tests Profile -->
<profile>
<id>smoke-tests</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
<configuration>
<includes>
<include>**/*SmokeTest.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<!-- Performance Tests Profile -->
<profile>
<id>performance-tests</id>
<build>
<plugins>
<plugin>
<groupId>io.gatling</groupId>
<artifactId>gatling-maven-plugin</artifactId>
<version>${gatling.version}</version>
<configuration>
<simulationsFolder>src/test/gatling/simulations</simulationsFolder>
<resourcesFolder>src/test/gatling/resources</resourcesFolder>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<plugins>
<!-- JaCoCo Code Coverage -->
<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>
<execution>
<id>check</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<!-- SonarQube Analysis -->
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>${sonar-maven-plugin.version}</version>
</plugin>
<!-- OWASP Dependency Check -->
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>${owasp-dependency-check.version}</version>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<configuration>
<failBuildOnAnyVulnerability>true</failBuildOnAnyVulnerability>
<suppressionFile>dependency-check-suppressions.xml</suppressionFile>
</configuration>
</plugin>
<!-- Git commit info -->
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>4.9.10</version>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<injectAllReactorProjects>true</injectAllReactorProjects>
<failOnNoGitDirectory>false</failOnNoGitDirectory>
</configuration>
</plugin>
</plugins>
</build>
</project>

6. Jenkins X Quickstart Location Configuration

jx-requirements.yml

cluster:
clusterName: my-java-cluster
environmentGitOwner: my-org
project: my-java-project
provider: gke
zone: us-central1-a
cluster:
project: my-gcp-project
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
webhook: lighthouse

jx-edit.yml

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

7. Custom Pipeline Extensions

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 -Pperformance-tests
- name: container-scan
command: |
trivy image ${DOCKER_REGISTRY}/${ORG}/${APP_NAME}:${VERSION}
- name: database-migration
command: |
mvn liquibase:update -Pprod
- name: api-docs
command: |
mvn springdoc:generate-docs

8. Application Configuration Files

application-kubernetes.yml

# Kubernetes-specific configuration
spring:
config:
activate:
on-profile: "kubernetes"
datasource:
url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5432}/${POSTGRES_DB:mydatabase}
username: ${POSTGRES_USER:myuser}
password: ${POSTGRES_PASSWORD:mypassword}
hikari:
maximum-pool-size: 10
connection-timeout: 30000
jpa:
hibernate:
ddl-auto: validate
show-sql: false
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
jdbc:
lob:
non_contextual_creation: true
cloud:
kubernetes:
reload:
enabled: true
mode: event
management:
endpoints:
web:
exposure:
include: health,info,prometheus,metrics
endpoint:
health:
show-details: always
probes:
enabled: true
prometheus:
enabled: true
health:
livenessstate:
enabled: true
readinessstate:
enabled: true
app:
environment: kubernetes
kubernetes:
enabled: true
namespace: ${KUBERNETES_NAMESPACE:default}

application-prod.yml

# Production configuration
spring:
config:
activate:
on-profile: "prod"
jackson:
default-property-inclusion: non_null
logging:
level:
com.example: INFO
org.springframework: WARN
org.hibernate: WARN
pattern:
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
app:
environment: production
security:
enabled: true

9. Integration Tests with Testcontainers

// BaseIntegrationTest.java
package com.example.myapp.integration;
import org.junit.jupiter.api.BeforeAll;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
@ActiveProfiles("integration-test")
public abstract class BaseIntegrationTest {
@Container
static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:13")
.withDatabaseName("testdb")
.withUsername("testuser")
.withPassword("testpass");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
}
@BeforeAll
static void beforeAll() {
postgreSQLContainer.start();
}
}
// UserControllerIntegrationTest.java
package com.example.myapp.integration;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class UserControllerIntegrationTest extends BaseIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void shouldReturnHealthStatus() {
ResponseEntity<String> response = restTemplate.getForEntity("/actuator/health", String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
}
@Test
void shouldCreateAndRetrieveUser() {
// Integration test implementation
// This would test the full flow from controller to database
}
}

This comprehensive Jenkins X setup provides:

  • Complete CI/CD pipeline with multiple stages
  • Kubernetes-optimized Java application
  • Multi-environment deployment (dev, staging, production)
  • Preview environments for each PR
  • Comprehensive testing strategy
  • Security scanning and quality gates
  • Performance testing integration
  • Health checks and monitoring
  • Auto-scaling configuration
  • GitOps approach for environment management

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