DevSecOps - Kubernetes DevOps & Security
DevOps Pipeline
Demo Kubernetes Deployment
In this tutorial, we’ll walk through a complete CI/CD workflow that builds a Spring Boot application, runs tests, publishes a Docker image, and deploys it to a Kubernetes cluster using a Jenkins Pipeline. Our Git repo contains a Kubernetes manifest (k8s_deployment_service.yaml
) defining both a Deployment and a Service.
Table of Contents
- Jenkins Pipeline (Jenkinsfile)
- Kubernetes Deployment & Service Manifest
- Deploying the Node.js Service
- Numeric Spring Boot Application
- Verifying the Deployment
- Links & References
Jenkins Pipeline (Jenkinsfile)
Note
This pipeline leverages Maven for build and test, Docker for image creation and pushing, and the Kubernetes CLI (kubectl
) for deployment.
pipeline {
agent any
stages {
stage('Build Artifact - Maven') {
steps {
sh 'mvn clean package -DskipTests=true'
archiveArtifacts 'target/*.jar'
}
}
stage('Unit Tests - JUnit & JaCoCo') {
steps {
sh 'mvn test'
}
post {
always {
junit 'target/surefire-reports/*.xml'
jacoco execPattern: 'target/jacoco.exec'
}
}
}
stage('Docker Build & Push') {
steps {
withDockerRegistry([credentialsId: 'docker-hub', url: '']) {
sh 'docker build -t siddharth67/numeric-app:${GIT_COMMIT} .'
sh 'docker push siddharth67/numeric-app:${GIT_COMMIT}'
}
}
}
stage('Kubernetes Deployment - DEV') {
steps {
withKubeConfig([credentialsId: 'kubeconfig']) {
sh 'sed -i "s#replace#siddharth67/numeric-app:${GIT_COMMIT}#g" k8s_deployment_service.yaml'
sh 'kubectl apply -f k8s_deployment_service.yaml'
}
}
}
}
}
Kubernetes Deployment & Service Manifest
The k8s_deployment_service.yaml
file defines:
- A Deployment named
devsecops
with 2 replicas. - A Service of type
NodePort
exposing port 8080.
apiVersion: apps/v1
kind: Deployment
metadata:
name: devsecops
labels:
app: devsecops
spec:
replicas: 2
selector:
matchLabels:
app: devsecops
template:
metadata:
labels:
app: devsecops
spec:
containers:
- name: devsecops-container
image: replace
---
apiVersion: v1
kind: Service
metadata:
name: devsecops-svc
labels:
app: devsecops
spec:
type: NodePort
selector:
app: devsecops
ports:
- port: 8080
targetPort: 8080
protocol: TCP
Resource Type | Purpose | Example Command |
---|---|---|
Deployment | Manages replicated Pods | kubectl apply -f k8s_deployment_service.yaml |
Service | Exposes Pods internally/externally | kubectl expose ... |
Warning
Ensure you replace the placeholder replace
with your Docker image tag (siddharth67/numeric-app:${GIT_COMMIT}
) before applying the manifest.
Deploying the Node.js Service
We need a backend service that our Spring Boot app will call. Deploy a pre-built Node.js service (siddharth67/node-service:v1
) in the default
namespace:
# Create the Deployment
kubectl -n default create deployment node-app --image=siddharth67/node-service:v1
# Expose as ClusterIP on port 5000
kubectl -n default expose deployment node-app \
--name=node-service \
--port=5000 \
--target-port=5000
# Verify
kubectl -n default get all
Expected output:
NAME READY STATUS RESTARTS AGE
pod/node-app-6b8496465-tsrkf 1/1 Running 0 24s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/node-service ClusterIP 10.96.198.94 <none> 5000/TCP 4s
The Node.js service is now reachable at http://node-service:5000
.
Numeric Spring Boot Application
Update your controller to call the Kubernetes service instead of localhost
. Example:
package com.devsecops;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
@RestController
public class NumericController {
private final Logger logger = LoggerFactory.getLogger(NumericController.class);
private static final String BASE_URL = "http://node-service:5000/plussone";
private final RestTemplate restTemplate;
public NumericController(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
@GetMapping("/")
public String welcome() {
return "Kubernetes DevSecOps";
}
@GetMapping("/increment/{value}")
public String increment(@PathVariable int value) {
ResponseEntity<String> response =
restTemplate.getForEntity(BASE_URL + "/" + value, String.class);
return response.getBody();
}
@GetMapping("/compare/{value}")
public String compareToFifty(@PathVariable int value) {
return (value < 50) ? "Less than 50" : "Greater than 50";
}
}
Commit all changes to your main
branch—Jenkins will automatically trigger the pipeline.
Verifying the Deployment
After the pipeline completes, verify both applications:
kubectl -n default get all
Sample output:
NAME READY STATUS RESTARTS AGE
pod/devsecops-xxxxx-xxxxx 1/1 Running 0 2m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/devsecops-svc NodePort 10.96.xxx.xxx <none> 8080:31933/TCP 2m
service/node-service ClusterIP 10.96.xxx.xxx <none> 5000/TCP 5m
Access the Numeric application via NodePort:
http://<NODE_IP>:31933/
→Kubernetes DevSecOps
http://<NODE_IP>:31933/increment/77
→78
http://<NODE_IP>:31933/compare/44
→Less than 50
http://<NODE_IP>:31933/compare/95
→Greater than 50
Both services communicate seamlessly within the Kubernetes cluster.
Links & References
Watch Video
Watch video content
Practice Lab
Practice lab