Skip to main content

Deployment Guide


Module: Lens Version: 1.0.0-RELEASE Last Updated: October 25, 2025


Overview

This document provides comprehensive deployment instructions for the AWS Lens module:

  • Docker containerization
  • Kubernetes deployment
  • Infrastructure requirements
  • CI/CD pipelines
  • Monitoring and observability
  • Scaling strategies
  • Troubleshooting procedures

1. Infrastructure Requirements

1.1 Minimum Requirements (Development)

ComponentSpecification
CPU2 vCPUs
Memory4 GB RAM
Storage20 GB
JavaOpenJDK 17+

1.2 Production Requirements

ComponentSpecification
CPU4-8 vCPUs
Memory8-16 GB RAM
Storage50 GB SSD
JavaOpenJDK 17+
OSUbuntu 20.04 LTS or Amazon Linux 2

1.3 External Dependencies

ServiceVersionPurpose
SnowflakeLatestAnalytics database
MongoDB5.0+Document storage
MySQL8.0+Transactional data
Redis6.2+Caching layer
RabbitMQ3.9+Message queue
Config Server3.1+Configuration management

2. Docker Containerization

2.1 Dockerfile

File: Dockerfile

# Multi-stage build for optimized image size

# Stage 1: Build
FROM gradle:7.6-jdk17-alpine AS builder

WORKDIR /app

# Copy Gradle files
COPY build.gradle settings.gradle ./
COPY gradle ./gradle

# Download dependencies (cached layer)
RUN gradle dependencies --no-daemon

# Copy source code
COPY src ./src

# Build application
RUN gradle clean build -x test --no-daemon

# Stage 2: Runtime
FROM eclipse-temurin:17-jre-alpine

WORKDIR /app

# Create non-root user
RUN addgroup -S spring && adduser -S spring -G spring

# Copy built artifact from builder stage
COPY --from=builder /app/build/libs/lens-1.0.0-RELEASE.jar ./lens.jar

# Copy SQL query files
COPY --from=builder /app/src/main/resources/queries ./queries

# Set ownership
RUN chown -R spring:spring /app

# Switch to non-root user
USER spring:spring

# Expose port
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:8080/lens/actuator/health || exit 1

# JVM options
ENV JAVA_OPTS="-Xms512m -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=4 -XX:ConcGCThreads=2 -Djava.security.egd=file:/dev/./urandom"

# Run application
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar lens.jar"]

2.2 Docker Compose (Development)

File: docker-compose.yml

version: '3.8'

services:
lens:
build:
context: .
dockerfile: Dockerfile
container_name: lens
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=dev
- SNOWFLAKE_URL=jdbc:snowflake://dev-account.snowflakecomputing.com
- SNOWFLAKE_USER=${SNOWFLAKE_USER}
- SNOWFLAKE_PASSWORD=${SNOWFLAKE_PASSWORD}
- MONGODB_HOST=mongodb
- MYSQL_HOST=mysql
- REDIS_HOST=redis
- RABBITMQ_HOST=rabbitmq
depends_on:
- mongodb
- mysql
- redis
- rabbitmq
networks:
- lens-network
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/lens/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s

mongodb:
image: mongo:5.0
container_name: lens-mongodb
ports:
- "27017:27017"
environment:
- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_ROOT_PASSWORD=password
- MONGO_INITDB_DATABASE=lens
volumes:
- mongodb-data:/data/db
networks:
- lens-network

mysql:
image: mysql:8.0
container_name: lens-mysql
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=rootpassword
- MYSQL_DATABASE=lens
- MYSQL_USER=lensuser
- MYSQL_PASSWORD=lenspassword
volumes:
- mysql-data:/var/lib/mysql
networks:
- lens-network
command: --default-authentication-plugin=mysql_native_password

redis:
image: redis:6.2-alpine
container_name: lens-redis
ports:
- "6379:6379"
command: redis-server --appendonly yes
volumes:
- redis-data:/data
networks:
- lens-network

rabbitmq:
image: rabbitmq:3.9-management-alpine
container_name: lens-rabbitmq
ports:
- "5672:5672"
- "15672:15672" # Management UI
environment:
- RABBITMQ_DEFAULT_USER=admin
- RABBITMQ_DEFAULT_PASS=password
volumes:
- rabbitmq-data:/var/lib/rabbitmq
networks:
- lens-network

networks:
lens-network:
driver: bridge

volumes:
mongodb-data:
mysql-data:
redis-data:
rabbitmq-data:

2.3 Build and Run Commands

# Build Docker image
docker build -t lens:1.0.0 .

# Run with Docker Compose
docker-compose up -d

# View logs
docker-compose logs -f lens

# Stop services
docker-compose down

# Stop and remove volumes
docker-compose down -v

3. Kubernetes Deployment

3.1 Namespace

File: k8s/namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
name: lens
labels:
name: lens
environment: production

3.2 ConfigMap

File: k8s/configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
name: lens-config
namespace: lens
data:
application.yml: |
spring:
application:
name: lens
profiles:
active: prod
server:
port: 8080
logging:
level:
root: INFO
com.ttn.ck.lens: INFO

3.3 Secret

File: k8s/secret.yaml

apiVersion: v1
kind: Secret
metadata:
name: lens-secrets
namespace: lens
type: Opaque
data:
# Base64 encoded values
snowflake-user: <base64-encoded-user>
snowflake-password: <base64-encoded-password>
mongodb-uri: <base64-encoded-uri>
mysql-password: <base64-encoded-password>
redis-password: <base64-encoded-password>
rabbitmq-password: <base64-encoded-password>
jwt-secret: <base64-encoded-secret>

Create secrets:

# Create from literals
kubectl create secret generic lens-secrets \
--from-literal=snowflake-user=myuser \
--from-literal=snowflake-password=mypassword \
--namespace=lens

# Or create from file
kubectl create secret generic lens-secrets \
--from-env-file=secrets.env \
--namespace=lens

3.4 Deployment

File: k8s/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: lens
namespace: lens
labels:
app: lens
version: 1.0.0
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: lens
template:
metadata:
labels:
app: lens
version: 1.0.0
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/lens/actuator/prometheus"
spec:
serviceAccountName: lens-sa
containers:
- name: lens
image: your-registry.com/lens:1.0.0
imagePullPolicy: Always
ports:
- containerPort: 8080
name: http
protocol: TCP
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
- name: JAVA_OPTS
value: "-Xms1g -Xmx2g -XX:+UseG1GC"
- name: SNOWFLAKE_USER
valueFrom:
secretKeyRef:
name: lens-secrets
key: snowflake-user
- name: SNOWFLAKE_PASSWORD
valueFrom:
secretKeyRef:
name: lens-secrets
key: snowflake-password
- name: MONGODB_URI
valueFrom:
secretKeyRef:
name: lens-secrets
key: mongodb-uri
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: lens-secrets
key: mysql-password
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: lens-secrets
key: redis-password
- name: RABBITMQ_PASSWORD
valueFrom:
secretKeyRef:
name: lens-secrets
key: rabbitmq-password
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: lens-secrets
key: jwt-secret

resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 2000m
memory: 4Gi

livenessProbe:
httpGet:
path: /lens/actuator/health/liveness
port: 8080
initialDelaySeconds: 90
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3

readinessProbe:
httpGet:
path: /lens/actuator/health/readiness
port: 8080
initialDelaySeconds: 60
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3

volumeMounts:
- name: config
mountPath: /app/config
readOnly: true
- name: queries
mountPath: /app/queries
readOnly: true

volumes:
- name: config
configMap:
name: lens-config
- name: queries
configMap:
name: lens-queries

affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- lens
topologyKey: kubernetes.io/hostname

3.5 Service

File: k8s/service.yaml

apiVersion: v1
kind: Service
metadata:
name: lens-service
namespace: lens
labels:
app: lens
spec:
type: ClusterIP
selector:
app: lens
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
sessionAffinity: None

3.6 Ingress

File: k8s/ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: lens-ingress
namespace: lens
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/rate-limit: "100"
spec:
tls:
- hosts:
- lens.cloudkeeper.com
secretName: lens-tls
rules:
- host: lens.cloudkeeper.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: lens-service
port:
number: 80

3.7 HorizontalPodAutoscaler

File: k8s/hpa.yaml

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: lens-hpa
namespace: lens
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: lens
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 2
periodSeconds: 30
selectPolicy: Max

3.8 Deploy to Kubernetes

# Apply all manifests
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/ingress.yaml
kubectl apply -f k8s/hpa.yaml

# Verify deployment
kubectl get pods -n lens
kubectl get svc -n lens
kubectl get ingress -n lens

# View logs
kubectl logs -f deployment/lens -n lens

# Port forward for local access
kubectl port-forward svc/lens-service 8080:80 -n lens

4. CI/CD Pipeline

4.1 GitLab CI/CD

File: .gitlab-ci.yml

stages:
- test
- build
- deploy

variables:
DOCKER_REGISTRY: registry.cloudkeeper.com
IMAGE_NAME: lens
KUBERNETES_NAMESPACE: lens

# Test stage
test:
stage: test
image: gradle:7.6-jdk17
script:
- gradle clean test --no-daemon
artifacts:
reports:
junit: build/test-results/test/TEST-*.xml
paths:
- build/reports/tests/
only:
- branches

# Build Docker image
build:
stage: build
image: docker:20.10
services:
- docker:20.10-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $DOCKER_REGISTRY
script:
- docker build -t $DOCKER_REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA .
- docker tag $DOCKER_REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA $DOCKER_REGISTRY/$IMAGE_NAME:latest
- docker push $DOCKER_REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA
- docker push $DOCKER_REGISTRY/$IMAGE_NAME:latest
only:
- main
- develop

# Deploy to development
deploy:dev:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl config use-context dev-cluster
- kubectl set image deployment/lens lens=$DOCKER_REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA -n $KUBERNETES_NAMESPACE
- kubectl rollout status deployment/lens -n $KUBERNETES_NAMESPACE
environment:
name: development
url: https://lens-dev.cloudkeeper.com
only:
- develop

# Deploy to production
deploy:prod:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl config use-context prod-cluster
- kubectl set image deployment/lens lens=$DOCKER_REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA -n $KUBERNETES_NAMESPACE
- kubectl rollout status deployment/lens -n $KUBERNETES_NAMESPACE
environment:
name: production
url: https://lens.cloudkeeper.com
when: manual
only:
- main

4.2 GitHub Actions

File: .github/workflows/deploy.yml

name: Build and Deploy

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

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Run tests
run: ./gradlew test
- name: Publish test results
uses: dorny/test-reporter@v1
if: always()
with:
name: Test Results
path: build/test-results/test/*.xml
reporter: java-junit

build:
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v3
- name: Log in to Container Registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest

deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to Kubernetes
uses: azure/k8s-deploy@v4
with:
manifests: |
k8s/deployment.yaml
k8s/service.yaml
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
namespace: lens

5. Blue-Green Deployment

5.1 Blue Deployment (Current Production)

apiVersion: apps/v1
kind: Deployment
metadata:
name: lens-blue
namespace: lens
spec:
replicas: 3
selector:
matchLabels:
app: lens
version: blue
template:
metadata:
labels:
app: lens
version: blue
spec:
containers:
- name: lens
image: lens:1.0.0
# ... other config

5.2 Green Deployment (New Version)

apiVersion: apps/v1
kind: Deployment
metadata:
name: lens-green
namespace: lens
spec:
replicas: 3
selector:
matchLabels:
app: lens
version: green
template:
metadata:
labels:
app: lens
version: green
spec:
containers:
- name: lens
image: lens:1.1.0 # New version
# ... other config

5.3 Service Selector Switch

apiVersion: v1
kind: Service
metadata:
name: lens-service
namespace: lens
spec:
selector:
app: lens
version: blue # Switch to 'green' to activate new version
ports:
- port: 80
targetPort: 8080

5.4 Deployment Process

# 1. Deploy green version
kubectl apply -f k8s/deployment-green.yaml

# 2. Wait for green to be ready
kubectl rollout status deployment/lens-green -n lens

# 3. Test green version internally
kubectl port-forward deployment/lens-green 9090:8080 -n lens

# 4. Switch traffic to green
kubectl patch service lens-service -n lens -p '{"spec":{"selector":{"version":"green"}}}'

# 5. Monitor for issues
kubectl logs -f deployment/lens-green -n lens

# 6. If issues, rollback to blue
kubectl patch service lens-service -n lens -p '{"spec":{"selector":{"version":"blue"}}}'

# 7. If successful, delete blue deployment
kubectl delete deployment lens-blue -n lens

6. Monitoring and Observability

6.1 Prometheus ServiceMonitor

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: lens-metrics
namespace: lens
spec:
selector:
matchLabels:
app: lens
endpoints:
- port: http
path: /lens/actuator/prometheus
interval: 30s

6.2 Grafana Dashboard

Key Metrics to Monitor:

  • Request rate (req/sec)
  • Response time (p50, p95, p99)
  • Error rate (%)
  • JVM heap usage
  • Database connection pool usage
  • Cache hit ratio
  • Pod CPU/Memory usage

6.3 Logging with ELK Stack

Logstash Configuration:

input {
beats {
port => 5044
}
}

filter {
if [kubernetes][labels][app] == "lens" {
json {
source => "message"
}
date {
match => ["timestamp", "ISO8601"]
}
}
}

output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "lens-logs-%{+YYYY.MM.dd}"
}
}

7. Rollback Procedures

7.1 Kubernetes Rollback

# View rollout history
kubectl rollout history deployment/lens -n lens

# Rollback to previous version
kubectl rollout undo deployment/lens -n lens

# Rollback to specific revision
kubectl rollout undo deployment/lens --to-revision=3 -n lens

# Verify rollback
kubectl rollout status deployment/lens -n lens

7.2 Docker Rollback

# Stop current container
docker stop lens

# Start previous version
docker run -d --name lens \
-p 8080:8080 \
lens:1.0.0-previous

8. Troubleshooting

8.1 Common Issues

Issue 1: Pods Not Starting

# Check pod status
kubectl get pods -n lens

# View pod events
kubectl describe pod <pod-name> -n lens

# Check logs
kubectl logs <pod-name> -n lens

# Common causes:
# - Image pull errors → Check registry credentials
# - Resource limits → Increase memory/CPU limits
# - Config errors → Validate ConfigMap/Secrets

Issue 2: High Memory Usage

# Check memory usage
kubectl top pods -n lens

# Increase memory limits in deployment.yaml
resources:
limits:
memory: 4Gi

# Adjust JVM heap size
env:
- name: JAVA_OPTS
value: "-Xms2g -Xmx3g"

Issue 3: Database Connection Issues

# Test connectivity from pod
kubectl exec -it <pod-name> -n lens -- sh
wget -qO- http://localhost:8080/lens/actuator/health

# Check Snowflake connectivity
# Review connection pool settings in ConfigMap

Summary

Deployment Options:

  • Docker (development)
  • Kubernetes (production)
  • Blue-Green deployment (zero-downtime)

Key Files:

  • Dockerfile (containerization)
  • docker-compose.yml (local development)
  • k8s/*.yaml (Kubernetes manifests)
  • .gitlab-ci.yml / .github/workflows/*.yml (CI/CD)

Best Practices:

  • Use multi-stage Docker builds
  • Implement health checks (liveness + readiness)
  • Enable horizontal pod autoscaling
  • Configure resource limits
  • Use rolling updates with zero downtime
  • Monitor with Prometheus + Grafana
  • Centralize logs with ELK stack
  • Implement blue-green deployments for critical releases

Document Version: 1.0 Last Updated: October 25, 2025