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)
| Component | Specification |
|---|---|
| CPU | 2 vCPUs |
| Memory | 4 GB RAM |
| Storage | 20 GB |
| Java | OpenJDK 17+ |
1.2 Production Requirements
| Component | Specification |
|---|---|
| CPU | 4-8 vCPUs |
| Memory | 8-16 GB RAM |
| Storage | 50 GB SSD |
| Java | OpenJDK 17+ |
| OS | Ubuntu 20.04 LTS or Amazon Linux 2 |
1.3 External Dependencies
| Service | Version | Purpose |
|---|---|---|
| Snowflake | Latest | Analytics database |
| MongoDB | 5.0+ | Document storage |
| MySQL | 8.0+ | Transactional data |
| Redis | 6.2+ | Caching layer |
| RabbitMQ | 3.9+ | Message queue |
| Config Server | 3.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 /app/build/libs/lens-1.0.0-RELEASE.jar ./lens.jar
# Copy SQL query files
COPY /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 \
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