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.