This repository is a DevOps coursework project that demonstrates modern cloud-native development practices through a fully functional microservices data pipeline.
What this project showcases:
- 🔄 Microservices Architecture — A data processing pipeline with 4 interconnected services (Maker → Hasher → Trimmer → Store) communicating via Redis pub/sub and queues
- 🚀 GitLab CI/CD — Change-based pipeline automation that builds, tests, and deploys only the modified components
- ☸️ Kubernetes Deployment — Helm charts for each service with Helmfile for orchestrated full-stack deployment
- 📊 Full Observability — Distributed tracing (Jaeger), metrics collection (Prometheus), and dashboards (Grafana)
The pipeline processes data through a series of transformations: random data generation → SHA256 hashing → hash trimming → PostgreSQL persistence, with complete end-to-end tracing and metrics at each step.
- Architecture Overview
- CI/CD Pipeline Architecture
- Components
- Observability
- Quick Start
- Documentation
The system implements a data processing pipeline with the following flow:
graph LR
A[Maker CronJob] -->|POST /hash| B[Hasher Service]
B -->|Pub/Sub: hasher_output| C[Trimmer Service]
C -->|Queue: trimmer_output| D[Store Service]
D -->|Persist| E[(PostgreSQL)]
B -.->|Uses| F[(Redis)]
C -.->|Uses| F
D -.->|Uses| F
Components:
- Maker - CronJob that generates random data and sends it to Hasher
- Hasher - Calculates SHA256 hashes and publishes to Redis Pub/Sub
- Trimmer - Subscribes to hash messages, trims data, and pushes to Redis queue
- Store - Consumes from Redis queue and persists to PostgreSQL
- Redis - Message broker for Pub/Sub and queue operations
- PostgreSQL - Persistent storage for processed data
The CI/CD pipeline uses GitLab CI/CD with a change-based trigger system that enables individual component deployment and testing. Only the components that have changed are built, tested, and deployed.
The root .gitlab-ci.yml defines triggers for each component:
stages:
- triggers
trigger-hasher:
stage: triggers
trigger:
include: /services/hasher/.gitlab-ci.yml
rules:
- changes:
- services/hasher/*
- charts/hasher/*Change Detection: Each trigger only activates when files in the specific component directory change.
Each microservice follows a 5-stage pipeline:
graph LR
A[Preparation] --> B[Test]
B --> C[Build]
C --> D[Deliver]
D --> E[Deploy]
- Generates unique
BUILD_IDbased on timestamp - Creates image naming conventions
- Exports variables for subsequent stages via artifacts
Artifacts: context.env file with build metadata
Two parallel test jobs:
Unit Tests:
- Runs in Docker-in-Docker environment
- Executes component-specific unit tests
- Uses test Dockerfile for isolated testing
Functional Tests:
- Validates component functionality
- Runs integration scenarios
- Uses Kaniko for secure, rootless image building
- Builds Docker image from component Dockerfile
- Pushes to GitLab Container Registry
- Naming:
<image-name>-<environment>-<commit-sha>:<build-id>
- Pulls built image from GitLab Registry
- Re-tags and pushes to ttl.sh (temporary registry)
- TTL: Images expire after 2 hours
- Enables deployment without long-term registry storage
- Uses Helm to deploy to Kubernetes
- Resource Lock:
production-lockensures sequential deployments - Strategy: Each service is deployed as its own Helm release
- Updates the specific service chart with new image tag
Deployment Command:
helm upgrade --install <service-name> ./charts/<service-name> \
--namespace default \
--set image.repository="<image-repo>" \
--set image.tag="<image-tag>"Each component can be tested independently:
Template: services/.gitlab-ci-template.yml
Configuration Example:
include:
- local: 'services/.gitlab-ci-template.yml'
inputs:
image-name: 'hasher'
environment: 'prod'
directory-name: 'services/hasher'Individual Stages:
- Unit tests via Docker container
- Functional tests
- Image build and registry push
- Deployment to Kubernetes cluster
Functional Tests Overview:
Each service has a dedicated functional test suite that runs in a Docker environment using testcontainers. These tests verify the service's integration with its dependencies (Redis, PostgreSQL, other services).
-
Maker Service:
- Goal: Verify message generation and delivery.
- Flow: Starts Hasher and Redis containers -> Starts Maker -> Maker generates a message -> Sends POST to Hasher -> Hasher publishes to Redis.
- Verification: The test subscribes to Redis to ensure the message was successfully received and processed by the Hasher.
-
Hasher Service:
- Goal: Verify hashing logic and pub/sub communication.
- Flow: Starts Redis container -> Starts Hasher -> Test sends a POST request with data -> Hasher calculates SHA256 hash -> Publishes to
hasher_outputchannel. - Verification: The test subscribes to the Redis channel and asserts that the received message contains the correct hash and metadata.
-
Trimmer Service:
- Goal: Verify hash trimming and queue operations.
- Flow: Starts Redis container -> Starts Trimmer -> Test publishes a message to
hasher_outputchannel (simulating Hasher) -> Trimmer receives, trims hash to 10 chars, and pushes totrimmer_outputqueue. - Verification: The test pops from the Redis queue and asserts that the hash is correctly trimmed.
-
Store Service:
- Goal: Verify data persistence.
- Flow: Starts Redis and PostgreSQL containers -> Starts Store -> Test pushes a message to
trimmer_outputqueue (simulating Trimmer) -> Store consumes and inserts into Postgres. - Verification: The test queries the PostgreSQL database to confirm the record exists with the correct data.
Template: charts/.gitlab-ci-template.yml
Configuration Example:
include:
- local: 'charts/.gitlab-ci-template.yml'
inputs:
chart-name: 'redis'
directory-name: 'charts/redis'Workflow:
- Detects chart changes (values.yaml, templates, etc.)
- Deploys updated chart to Kubernetes
- Uses Helm upgrade with pipeline chart
What happens:
- Developer updates
services/hasher/src/index.ts - Pipeline detects change in
services/hasher/* - Triggers
trigger-hasherjob - Runs: Preparation → Tests → Build → Deliver → Deploy
- Deploys with:
helm upgrade --install hasher ./charts/hasher - Only the hasher release is updated in Kubernetes
Other services: Remain unchanged
What happens:
- Developer updates
charts/hasher/values.yaml - Pipeline detects change in
charts/hasher/* - Triggers
trigger-hasherjob - Runs full pipeline including image rebuild
- Deploys with:
helm upgrade --install hasher ./charts/hasher - Updates the hasher release with new configuration
Image: Rebuilt and deployed with updated chart configuration
What happens:
- Developer updates
charts/redis/values.yaml - Pipeline detects change in
charts/redis/* - Triggers
trigger-redisjob - Deploys with:
helm upgrade --install redis ./charts/redis - Updates the Redis release with new configuration
Services: Unaffected
What happens:
- Developer updates both hasher and trimmer services
- Both
trigger-hasherandtrigger-trimmeractivate - Pipelines run in parallel
- Deployment stage uses
resource_group: production-lock - Deployments execute sequentially to avoid conflicts
Only components with file changes trigger their pipelines
Unit and functional tests run concurrently
Each component builds independently using Kaniko
- Resource locks prevent race conditions
- Each service deployed as independent release
- Helm rollback capability per service
ttl.sh provides temporary image storage without registry bloat
- Hasher - SHA256 hash calculation service
- Maker - Data generation CronJob
- Trimmer - Hash trimming service
- Store - Data persistence service
- Redis - Message broker and cache
- PostgreSQL - Relational database
- Charts Directory - Individual Helm charts for each service and infrastructure component
The pipeline is fully instrumented with distributed tracing and metrics collection using industry-standard tools.
- Technology: OpenTelemetry SDK with Jaeger exporter
- Protocol: Jaeger Thrift HTTP (port 14268)
- Trace Propagation: W3C Trace Context via message payloads
Each message carries a traceContext field enabling end-to-end tracing from maker → hasher → trimmer → store. See each service README for detailed message formats including the traceContext structure.
graph LR
A[Maker] -->|trace-id| B[Hasher]
B -->|trace-id via Redis| C[Trimmer]
C -->|trace-id via Redis| D[Store]
B & C & D -->|spans| E[Jaeger]
Each service exposes a /metrics endpoint with:
| Metric | Description |
|---|---|
*_processing_time_seconds |
Processing time per service |
*_messages_processed_total |
Successful message count |
*_messages_errors_total |
Error count |
pipeline_end_to_end_time_seconds |
Full pipeline latency |
- Tiempo promedio de procesamiento end-to-end - Pipeline latency from maker to store
- Delay promedio en cada cola de Redis - Redis pub/sub and queue latencies
- Cantidad de mensajes procesados - Success/error counts per service
# Grafana (metrics visualization)
kubectl port-forward svc/kube-prometheus-stack-grafana -n monitoring 3000:80
# Prometheus (query interface)
kubectl port-forward svc/kube-prometheus-stack-prometheus -n monitoring 9090:9090
# Jaeger (distributed traces)
kubectl port-forward svc/jaeger-query -n monitoring 16686:16686For detailed PromQL queries and configuration, see Observability Guide.
The easiest way to deploy the entire stack including the observability suite (Prometheus, Grafana, Jaeger) is using helmfile:
# Install helmfile if not already installed
# https://github.com/helmfile/helmfile#installation
# Deploy all components + observability
helmfile syncThis single command deploys:
- Observability:
kube-prometheus-stack(Prometheus + Grafana) +jaeger - Infrastructure:
redis+postgres - Services:
maker,hasher,trimmer,store
See helmfile.yaml for the complete configuration.
If you already have Prometheus/Jaeger or want to deploy services manually:
# Deploy infrastructure
helm install redis ./charts/redis
helm install postgres ./charts/postgres
# Deploy services
helm install hasher ./charts/hasher
helm install trimmer ./charts/trimmer
helm install store ./charts/store
helm install maker ./charts/maker# Deploy only hasher service
helm install hasher ./charts/hasher
# Deploy only Redis
helm install redis ./charts/redis# Check all pods
kubectl get pods
# Check pipeline data flow
kubectl port-forward svc/store 3002:3002
curl http://localhost:3002/stats- Service READMEs - Detailed service documentation with API endpoints
- Helm Chart READMEs - Deployment and upgrade instructions
- Observability Guide - Tracing, metrics, and PromQL queries
- GitLab CI Templates - Pipeline configuration reference
Each component can be configured via Helm values or environment variables. See individual component READMEs for specific configuration options.
You can run tests locally using the provided helper scripts. These scripts handle building the Docker images and running the tests in the correct environment.
To run unit tests for a specific service:
# Example for Hasher service
./services/hasher/.scripts/unit/test.sh
# Example for Maker service
./services/maker/.scripts/unit/test.shTo run functional (integration) tests for a specific service:
# Example for Hasher service
./services/hasher/.scripts/functional/test.sh
# Example for Store service
./services/store/.scripts/functional/test.shSee individual service READMEs for local development instructions.
This project is for educational purposes as part of a DevOps coursework.