Skip to main content

DevOps

Learn how to deploy, monitor, and maintain JAQ Stack applications using modern DevOps practices.

Overview​

DevOps practices for JAQ Stack focus on automation, monitoring, and continuous delivery to ensure reliable and scalable applications.

CI/CD Pipeline​

GitHub Actions Workflow​

# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

services:
mongodb:
image: mongo:latest
ports:
- 27017:27017
redis:
image: redis:latest
ports:
- 6379:6379

steps:
- uses: actions/checkout@v3

- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

- name: Cache Maven dependencies
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}

- name: Install dependencies
run: |
cd backend && mvn clean install -DskipTests
cd ../frontend && npm ci

- name: Run backend tests
run: |
cd backend && mvn test

- name: Run frontend tests
run: |
cd frontend && npm test -- --coverage --watchAll=false

- name: Build application
run: |
cd backend && mvn package -DskipTests
cd ../frontend && npm run build

- name: Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
path: |
backend/target/surefire-reports/
frontend/coverage/

deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'

steps:
- uses: actions/checkout@v3

- name: Deploy to staging
run: |
echo "Deploying to staging environment..."
# Add your deployment commands here

- name: Run smoke tests
run: |
echo "Running smoke tests..."
# Add smoke test commands here

- name: Deploy to production
run: |
echo "Deploying to production environment..."
# Add production deployment commands here

Containerization​

Docker Configuration​

Backend Dockerfile​

# backend/Dockerfile
FROM openjdk:17-jdk-slim

WORKDIR /app

# Copy Maven files
COPY pom.xml .
COPY src ./src

# Install Maven
RUN apt-get update && apt-get install -y maven

# Build the application
RUN mvn clean package -DskipTests

# Expose port
EXPOSE 8080

# Run the application
CMD ["java", "-jar", "target/jaqstack-backend.jar"]

Frontend Dockerfile​

# frontend/Dockerfile
FROM node:18-alpine AS builder

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy source code
COPY . .

# Build the application
RUN npm run build

# Production stage
FROM nginx:alpine

# Copy built files
COPY --from=builder /app/dist /usr/share/nginx/html

# Copy nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf

# Expose port
EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Docker Compose​

# docker-compose.yml
version: '3.8'

services:
mongodb:
image: mongo:latest
container_name: jaqstack-mongodb
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: password
volumes:
- mongodb_data:/data/db
networks:
- jaqstack-network

redis:
image: redis:latest
container_name: jaqstack-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- jaqstack-network

backend:
build: ./backend
container_name: jaqstack-backend
ports:
- "8080:8080"
environment:
SPRING_DATA_MONGODB_HOST: mongodb
SPRING_DATA_MONGODB_PORT: 27017
SPRING_REDIS_HOST: redis
SPRING_REDIS_PORT: 6379
depends_on:
- mongodb
- redis
networks:
- jaqstack-network

frontend:
build: ./frontend
container_name: jaqstack-frontend
ports:
- "80:80"
depends_on:
- backend
networks:
- jaqstack-network

volumes:
mongodb_data:
redis_data:

networks:
jaqstack-network:
driver: bridge

Kubernetes Deployment​

Namespace​

# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: jaqstack

ConfigMap​

# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: jaqstack-config
namespace: jaqstack
data:
MONGODB_HOST: mongodb-service
MONGODB_PORT: "27017"
REDIS_HOST: redis-service
REDIS_PORT: "6379"
API_URL: "http://backend-service:8080"

Secrets​

# k8s/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: jaqstack-secrets
namespace: jaqstack
type: Opaque
data:
MONGODB_USERNAME: YWRtaW4= # base64 encoded
MONGODB_PASSWORD: cGFzc3dvcmQ= # base64 encoded
JWT_SECRET: eW91ci1qd3Qtc2VjcmV0 # base64 encoded

MongoDB Deployment​

# k8s/mongodb.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongodb
namespace: jaqstack
spec:
replicas: 1
selector:
matchLabels:
app: mongodb
template:
metadata:
labels:
app: mongodb
spec:
containers:
- name: mongodb
image: mongo:latest
ports:
- containerPort: 27017
env:
- name: MONGO_INITDB_ROOT_USERNAME
valueFrom:
secretKeyRef:
name: jaqstack-secrets
key: MONGODB_USERNAME
- name: MONGO_INITDB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: jaqstack-secrets
key: MONGODB_PASSWORD
volumeMounts:
- name: mongodb-storage
mountPath: /data/db
volumes:
- name: mongodb-storage
persistentVolumeClaim:
claimName: mongodb-pvc
---
apiVersion: v1
kind: Service
metadata:
name: mongodb-service
namespace: jaqstack
spec:
selector:
app: mongodb
ports:
- port: 27017
targetPort: 27017
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodb-pvc
namespace: jaqstack
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi

Backend Deployment​

# k8s/backend.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: jaqstack
spec:
replicas: 3
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: jaqstack/backend:latest
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: jaqstack-config
- secretRef:
name: jaqstack-secrets
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: backend-service
namespace: jaqstack
spec:
selector:
app: backend
ports:
- port: 8080
targetPort: 8080
type: ClusterIP

Frontend Deployment​

# k8s/frontend.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: jaqstack
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: jaqstack/frontend:latest
ports:
- containerPort: 80
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
name: frontend-service
namespace: jaqstack
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 80
type: LoadBalancer

Ingress​

# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jaqstack-ingress
namespace: jaqstack
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- jaqstack.example.com
secretName: jaqstack-tls
rules:
- host: jaqstack.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: backend-service
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80

Monitoring and Observability​

Prometheus Configuration​

# monitoring/prometheus.yml
global:
scrape_interval: 15s

scrape_configs:
- job_name: 'jaqstack-backend'
static_configs:
- targets: ['backend-service:8080']
metrics_path: '/actuator/prometheus'
scrape_interval: 5s

- job_name: 'jaqstack-mongodb'
static_configs:
- targets: ['mongodb-exporter:9216']

- job_name: 'jaqstack-redis'
static_configs:
- targets: ['redis-exporter:9121']

Grafana Dashboard​

{
"dashboard": {
"title": "JAQ Stack Monitoring",
"panels": [
{
"title": "Application Response Time",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m]))",
"legendFormat": "95th percentile"
}
]
},
{
"title": "Database Connections",
"type": "graph",
"targets": [
{
"expr": "mongodb_connections_current",
"legendFormat": "MongoDB Connections"
}
]
}
]
}
}

Application Monitoring​

// Backend monitoring configuration
@Configuration
@EnableMetrics
public class MonitoringConfig {

@Bean
public MeterRegistry meterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}

@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}

// Custom metrics
@Component
public class ApplicationMetrics {

private final Counter requestCounter;
private final Timer requestTimer;

public ApplicationMetrics(MeterRegistry meterRegistry) {
this.requestCounter = Counter.builder("jaqstack.requests.total")
.description("Total number of requests")
.register(meterRegistry);

this.requestTimer = Timer.builder("jaqstack.requests.duration")
.description("Request duration")
.register(meterRegistry);
}

public void incrementRequestCount() {
requestCounter.increment();
}

public void recordRequestDuration(Duration duration) {
requestTimer.record(duration);
}
}

Logging​

Centralized Logging​

# logging/fluentd-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-config
namespace: jaqstack
data:
fluent.conf: |
<source>
@type tail
path /var/log/containers/*jaqstack*.log
pos_file /var/log/fluentd-containers.log.pos
tag kubernetes.*
format json
time_key time
time_format %Y-%m-%dT%H:%M:%S.%NZ
</source>

<match kubernetes.**>
@type elasticsearch
host elasticsearch-service
port 9200
index_name jaqstack-logs
type_name _doc
</match>

Application Logging​

// Logging configuration
@Configuration
public class LoggingConfig {

@Bean
public Logger jaqstackLogger() {
return LoggerFactory.getLogger("jaqstack");
}
}

// Structured logging
@Service
public class UserService {

private static final Logger logger = LoggerFactory.getLogger(UserService.class);

public User createUser(User user) {
logger.info("Creating user",
kv("userId", user.getId()),
kv("email", user.getEmail()),
kv("timestamp", Instant.now()));

try {
User createdUser = userRepository.save(user);
logger.info("User created successfully",
kv("userId", createdUser.getId()));
return createdUser;
} catch (Exception e) {
logger.error("Failed to create user",
kv("userId", user.getId()),
kv("error", e.getMessage()));
throw e;
}
}
}

Security​

Security Scanning​

# security/trivy-scan.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: trivy-scan
namespace: jaqstack
spec:
schedule: "0 2 * * *" # Daily at 2 AM
jobTemplate:
spec:
template:
spec:
containers:
- name: trivy
image: aquasec/trivy:latest
command:
- trivy
- image
- --format
- json
- --output
- /tmp/scan-results.json
- jaqstack/backend:latest
volumeMounts:
- name: scan-results
mountPath: /tmp
volumes:
- name: scan-results
emptyDir: {}
restartPolicy: OnFailure

Network Policies​

# security/network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: jaqstack-network-policy
namespace: jaqstack
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: mongodb
ports:
- protocol: TCP
port: 27017
- to:
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379

Backup and Disaster Recovery​

Database Backup​

#!/bin/bash
# scripts/backup.sh

# MongoDB backup
mongodump --host mongodb-service:27017 \
--db jaqstack \
--out /backup/mongodb/$(date +%Y%m%d_%H%M%S)

# Redis backup
redis-cli -h redis-service -p 6379 BGSAVE

# Upload to cloud storage
aws s3 sync /backup s3://jaqstack-backups/$(date +%Y%m%d)/

Disaster Recovery Plan​

  1. RTO (Recovery Time Objective): 4 hours
  2. RPO (Recovery Point Objective): 1 hour
  3. Backup Frequency: Daily full backup, hourly incremental
  4. Testing: Monthly disaster recovery drills

Getting Help​

Next Steps​