End-to-End Microservices Deployment on AWS EKS: CI/CD with Jenkins, Docker, Kubernetes & Argo CD
UserβOrder Microservices Application
Business Use Case (Realistic)
A simple e-commerce backend where:
User Service manages users
Order Service creates orders for users
Services communicate via REST
Each service is independently deployable π§± Architecture Overview
Components
user-service (Spring Boot)
order-service (Spring Boot)
MySQL (separate DB per service)
Docker
Kubernetes (local or EKS)
Git-based deployment 1οΈβ£ Project Structure (Mono-Repo for Learning) microservices-project/
βββ user-service/
β βββ src/main/java/...
β βββ Dockerfile
β βββ pom.xml
βββ order-service/
β βββ src/main/java/...
β βββ Dockerfile
β βββ pom.xml
βββ k8s/
β βββ user-deployment.yaml
β βββ order-deployment.yaml
β βββ mysql-user.yaml
β βββ mysql-order.yaml
β βββ ingress.yaml
2οΈβ£ Develop β User Service
User Entity @entity
public class User { @id @GeneratedValue private Long id; private String name; private String email;
}
REST Controller @RestController
@RequestMapping("/users")
public class UserController { @PostMapping public User createUser(@RequestBody User user) { return userRepo.save(user); } @GetMapping("/{id}") public User getUser(@PathVariable Long id) { return userRepo.findById(id).orElseThrow(); }
}
Health Check (mandatory in prod) @GetMapping("/health")
public String health() { return "UP";
}
3οΈβ£ Develop β Order Service (Calls User Service)
Order Entity
C
@entity
public class Order { @id @GeneratedValue private Long id; private Long userId; private String product;
}
REST Control
@RestController
@RequestMapping("/orders")
public class OrderController { @Autowired RestTemplate restTemplate; @PostMapping public Order createOrder(@RequestBody Order order) { String userUrl = "http://user-service:8080/users/" + order.getUserId(); restTemplate.getForObject(userUrl, String.class); return orderRepo.save(order); }
}
π‘ This is real inter-service communication using Kubernetes DNS. 4οΈβ£ Build β Dockerize Both Services
Dockerfile (same pattern for both Dockerfile: FROM openjdk:17-jdk-slim
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Build Images docker build -t user-service:1.0 . docker build -t order-service:1.0 . 5οΈβ£ Kubernetes β Database Deployment
MySQL for User Service Yaml
apiVersion: apps/v1
kind: Deployment
metadata: name: mysql-user
spec: replicas: 1 template: spec: containers: - name: mysql image: mysql:8 env: - name: MYSQL_DATABASE value: userdb - name: MYSQL_ROOT_PASSWORD value: root 6οΈβ£ Kubernetes β User Service Deploymen
Yaml
apiVersion: apps/v1
kind: Deployment
metadata: name: user-service
spec: replicas: 2 template: spec: containers: - name: user-service image: user-service:1.0 ports: - containerPort: 8080
Service apiVersion: v1
kind: Service
metadata: name: user-service
spec: selector: app: user-service ports: 7οΈβ£ Kubernetes β Order Service Deployment apiVersion: apps/v1
kind: Deployment
metadata: name: order-service
spec: replicas: 2 template: spec: containers: - name: order-service image: order-service:1.0 8οΈβ£ Ingress β Single Entry Point apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: name: app-ingress
spec: rules: 9οΈβ£ Deploy Everything kubectl apply -f k8s/ Bash
kubectl get pods
kubectl get svc
kubectl get ingress π Functional Proof (This is Critical)
Create Use
curl -X POST /users -d '{"name":"Raj","email":"[email protected]"}'
Create Order
Copy code
Bash
curl -X POST /orders -d '{"userId":1,"product":"Laptop"}'
β Order service calls user-service β Kubernetes DNS works β DB persists data
1οΈβ£1οΈβ£ CI/CD Flow (Real Production Model)
CI
Git push
Build
Test
Docker image build
Push to registry
CD (GitOps)
Update image tag in Git
Auto sync to cluster 1οΈβ£2οΈβ£ Rollback Scenario (Very Important)
kubectl rollout undo deployment user-service
OR Git revert (GitOps) 1οΈβ£3οΈβ£ Production-Grade Improvements (Next Level)
ConfigMaps & Secrets
HPA (auto scaling)
Circuit breaker
Distributed tracing
Canary deployme π PART 2 β FULL CI PIPELINE (Jenkins) Trigger β Build β Test β Docker β Push β Update Git
2.1 Jenkins Prerequisites
Jenkins Server Must Have
Docker installed
Git access
Docker registry credentials
Java 17
kubectl (optional)
2.2 Jenkinsfile (Complete & Real)
Place this Jenkinsfile in repo root. pipeline { agent any environment { REGISTRY = "docker.io/yourrepo" IMAGE_TAG = "${BUILD_NUMBER}" } }
}
2.3 What This Pipeline Achieves
β Builds both microservices
β Creates Docker images
β Pushes images to registry
β Updates Git (GitOps trigger)
β No direct kubectl in Jenkins (BEST PRACTICE) Trigger β Build β Test β Docker β Push β Update Git
2.1 Jenkins Prerequisites
Jenkins Server Must Have
Docker installed
Git access
Docker registry credentials
Java 17
kubectl (optional)
2.2 Jenkinsfile (Complete & Real)
Place this Jenkinsfile in repo root. Groovy
pipeline { agent any environment { REGISTRY = "docker.io/yourrepo" IMAGE_TAG = "${BUILD_NUMBER}" } }
}
2.3 What This Pipeline Achieves
β Builds both microservices
β Creates Docker images
β Pushes images to registry
β Updates Git (GitOps trigger)
β No direct kubectl in Jenkins (BEST PRACTICE) CI ends here. CD starts via Argo CD
CI ends here. CD starts via Argo CD PART 3 β ARGO CD (GitOps DEPLOYMENT) 3.1 Why Argo CD (Production Reality)
Jenkins should NOT deploy to Kubernetes directly
Argo CD:
Watches Git
Applies desired state
Auto-rollbacks
Auditable
3.2 Install Argo CD kubectl create namespace argocd
kubectl apply -n argocd \ -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
Expose UI: kubectl port-forward svc/argocd-server -n argocd 8080:443
Login: kubectl get secret argocd-initial-admin-secret -n argocd -o yaml
3.3 GitOps Repository Structure
Copy code
Text
k8s-manifests/
βββ user/
β βββ deployment.yaml
βββ order/
β βββ deployment.yaml
βββ ingress.yaml
3.4 Argo CD Application (Single App) apiVersion: argoproj.io/v1alpha1
kind: Application
metadata: name: microservices-app namespace: argocd
spec: project: default repoURL: https://github.com/your-org/k8s-manifests.git targetRevision: main path: . destination: server: https://kubernetes.default.svc namespace: default syncPolicy: automated: prune: true selfHeal: true
Apply: kubectl apply -f application.yaml 3.5 What Happens Automatically
β Jenkins updates image tag in Git
β Argo CD detects Git change
β Syncs Kubernetes state
β Pods rolling update
β Health checks validated
3.6 Verify Deploymen kubectl get pods
kubectl rollout status deployment/user-service
kubectl rollout status deployment/order-service 3.7 Rollback (This Is POWER)
Option 1 β Git Revert Option 2 β Argo CD UI
Click previous revision
Sync
β Zero downtime rollback
β No Jenkins involved π§ REAL PRODUCTION FLOW (FINAL PICTURE) Developer β
Git Push β
Jenkins (CI) β
Docker Registry β
Git (Manifests) β
Argo CD β
Kubernetes Tools Used (Industry Standard)
CI: Jenkins
GitOps CD: Argo CD
Containers: Docker
Orchestration: Kubernetes BLUE-GREEN & CANARY DEPLOYMENTS WITH ARGO CD: Blue-Green
Two identical environments: Blue (live) and Green (new)
Traffic switches instantly
Fast rollback
Canary
Release to small % of users
Observe metrics
Gradually increase traffic π΅π’ BLUE-GREEN DEPLOYMENT 5.1 Kubernetes Layout (User Service)
We create TWO deployments: user-service-blue (v1 β live)
user-service-green (v2 β new)
Service decides traffic
π Service selector switch = deployment switch
5.2 Blue Deployment (Live)
Copy code
Yaml
apiVersion: apps/v1
kind: Deployment
metadata: name: user-service-blue
spec: replicas: 2 selector: matchLabels: app: user-service version: blue template: metadata: labels: app: user-service version: blue spec: containers: - name: user-service image: yourrepo/user-service:1.0 5.3 Green Deployment (New Version) apiVersion: apps/v1
kind: Deployment
metadata: name: user-service-green
spec: replicas: 2 selector: matchLabels: app: user-service version: green template: metadata: labels: app: user-service version: green spec: containers: - name: user-service image: yourrepo/user-service:2.0 5.4 Service (Traffic Controller) apiVersion: v1
kind: Service
metadata: name: user-service
spec: selector: app: user-service version: blue # LIVE ports: git revert git push
Argo CD restores BLUE π€ CANARY DEPLOYMENT (STEP-BY-STEP)
Now progressive delivery, not instant switch.
5.8 Canary Strategy (Real Production)
Version
Replicas
Traffic v1
9
90%
v2
1
10%
Kubernetes distributes traffic via Service.
5.9 Stable Deployment (v1) apiVersion: apps/v1
kind: Deployment
metadata: name: user-service-stable
spec: replicas: 9 selector: matchLabels: app: user-service track: stable
5.10 Canary Deployment (v2) apiVersion: apps/v1
kind: Deployment
metadata: name: user-service-canary
spec: replicas: 1 selector: matchLabels: app: user-service track: canary
5.11 Shared Service (Traffic Split) apiVersion: v1
kind: Service
metadata: name: user-service
spec: selector: app: user-service ports: Both deployments share: Yaml
labels: app: user-service
β‘ Kubernetes load-balances based on pod count β‘ Canary gets ~10% traffic
5.12 Increase Canary Gradually Step 1: Canary 1 pod (10%)
Step 2: Canary 3 pods (25%)
Step 3: Canary 5 pods (50%)
Step 4: Promote to stable Just update replica count in Git. 5.13 Promote Canary to Stable
Remove stable deployment
Rename canary as stable
Commit β Argo CD sync 5.14 Canary Rollback (Instant) kubectl scale deployment user-service-canary --replicas=0
OR Git revert (recommended) Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to ? It will become hidden in your post, but will still be visible via the comment's permalink. as well , this person and/or CODE_BLOCK:
stage('Checkout') { steps { git branch: 'main', url: 'https://github.com/your-org/microservices-project.git' }
} stage('Build User Service') { steps { dir('user-service') { sh 'mvn clean package' } }
} stage('Build Order Service') { steps { dir('order-service') { sh 'mvn clean package' } }
} stage('Docker Build') { steps { sh ''' docker build -t $REGISTRY/user-service:$IMAGE_TAG user-service/ docker build -t $REGISTRY/order-service:$IMAGE_TAG order-service/ ''' }
} stage('Docker Push') { steps { withDockerRegistry([credentialsId: 'dockerhub-creds', url: '']) { sh ''' docker push $REGISTRY/user-service:$IMAGE_TAG docker push $REGISTRY/order-service:$IMAGE_TAG ''' } }
} stage('Update K8s Manifests Repo') { steps { sh ''' git clone https://github.com/your-org/k8s-manifests.git cd k8s-manifests sed -i "s|image:.*user-service.*|image: $REGISTRY/user-service:$IMAGE_TAG|" user-deployment.yaml sed -i "s|image:.*order-service.*|image: $REGISTRY/order-service:$IMAGE_TAG|" order-deployment.yaml git commit -am "Update images to $IMAGE_TAG" git push ''' }
} CODE_BLOCK:
stage('Checkout') { steps { git branch: 'main', url: 'https://github.com/your-org/microservices-project.git' }
} stage('Build User Service') { steps { dir('user-service') { sh 'mvn clean package' } }
} stage('Build Order Service') { steps { dir('order-service') { sh 'mvn clean package' } }
} stage('Docker Build') { steps { sh ''' docker build -t $REGISTRY/user-service:$IMAGE_TAG user-service/ docker build -t $REGISTRY/order-service:$IMAGE_TAG order-service/ ''' }
} stage('Docker Push') { steps { withDockerRegistry([credentialsId: 'dockerhub-creds', url: '']) { sh ''' docker push $REGISTRY/user-service:$IMAGE_TAG docker push $REGISTRY/order-service:$IMAGE_TAG ''' } }
} stage('Update K8s Manifests Repo') { steps { sh ''' git clone https://github.com/your-org/k8s-manifests.git cd k8s-manifests sed -i "s|image:.*user-service.*|image: $REGISTRY/user-service:$IMAGE_TAG|" user-deployment.yaml sed -i "s|image:.*order-service.*|image: $REGISTRY/order-service:$IMAGE_TAG|" order-deployment.yaml git commit -am "Update images to $IMAGE_TAG" git push ''' }
} CODE_BLOCK:
stage('Checkout') { steps { git branch: 'main', url: 'https://github.com/your-org/microservices-project.git' }
} stage('Build User Service') { steps { dir('user-service') { sh 'mvn clean package' } }
} stage('Build Order Service') { steps { dir('order-service') { sh 'mvn clean package' } }
} stage('Docker Build') { steps { sh ''' docker build -t $REGISTRY/user-service:$IMAGE_TAG user-service/ docker build -t $REGISTRY/order-service:$IMAGE_TAG order-service/ ''' }
} stage('Docker Push') { steps { withDockerRegistry([credentialsId: 'dockerhub-creds', url: '']) { sh ''' docker push $REGISTRY/user-service:$IMAGE_TAG docker push $REGISTRY/order-service:$IMAGE_TAG ''' } }
} stage('Update K8s Manifests Repo') { steps { sh ''' git clone https://github.com/your-org/k8s-manifests.git cd k8s-manifests sed -i "s|image:.*user-service.*|image: $REGISTRY/user-service:$IMAGE_TAG|" user-deployment.yaml sed -i "s|image:.*order-service.*|image: $REGISTRY/order-service:$IMAGE_TAG|" order-deployment.yaml git commit -am "Update images to $IMAGE_TAG" git push ''' }
} CODE_BLOCK:
stage('Checkout') { steps { git branch: 'main', url: 'https://github.com/your-org/microservices-project.git' }
} stage('Build User Service') { steps { dir('user-service') { sh 'mvn clean package' } }
} stage('Build Order Service') { steps { dir('order-service') { sh 'mvn clean package' } }
} stage('Docker Build') { steps { sh ''' docker build -t $REGISTRY/user-service:$IMAGE_TAG user-service/ docker build -t $REGISTRY/order-service:$IMAGE_TAG order-service/ ''' }
} stage('Docker Push') { steps { withDockerRegistry([credentialsId: 'dockerhub-creds', url: '']) { sh ''' docker push $REGISTRY/user-service:$IMAGE_TAG docker push $REGISTRY/order-service:$IMAGE_TAG ''' } }
} stage('Update K8s Manifests Repo') { steps { sh ''' git clone https://github.com/your-org/k8s-manifests.git cd k8s-manifests sed -i "s|image:.*user-service.*|image: $REGISTRY/user-service:$IMAGE_TAG|" user-deployment.yaml sed -i "s|image:.*order-service.*|image: $REGISTRY/order-service:$IMAGE_TAG|" order-deployment.yaml git commit -am "Update images to $IMAGE_TAG" git push ''' }
} CODE_BLOCK:
stage('Checkout') { steps { git branch: 'main', url: 'https://github.com/your-org/microservices-project.git' }
} stage('Build User Service') { steps { dir('user-service') { sh 'mvn clean package' } }
} stage('Build Order Service') { steps { dir('order-service') { sh 'mvn clean package' } }
} stage('Docker Build') { steps { sh ''' docker build -t $REGISTRY/user-service:$IMAGE_TAG user-service/ docker build -t $REGISTRY/order-service:$IMAGE_TAG order-service/ ''' }
} stage('Docker Push') { steps { withDockerRegistry([credentialsId: 'dockerhub-creds', url: '']) { sh ''' docker push $REGISTRY/user-service:$IMAGE_TAG docker push $REGISTRY/order-service:$IMAGE_TAG ''' } }
} stage('Update K8s Manifests Repo') { steps { sh ''' git clone https://github.com/your-org/k8s-manifests.git cd k8s-manifests sed -i "s|image:.*user-service.*|image: $REGISTRY/user-service:$IMAGE_TAG|" user-deployment.yaml sed -i "s|image:.*order-service.*|image: $REGISTRY/order-service:$IMAGE_TAG|" order-deployment.yaml git commit -am "Update images to $IMAGE_TAG" git push ''' }
} CODE_BLOCK:
stage('Checkout') { steps { git branch: 'main', url: 'https://github.com/your-org/microservices-project.git' }
} stage('Build User Service') { steps { dir('user-service') { sh 'mvn clean package' } }
} stage('Build Order Service') { steps { dir('order-service') { sh 'mvn clean package' } }
} stage('Docker Build') { steps { sh ''' docker build -t $REGISTRY/user-service:$IMAGE_TAG user-service/ docker build -t $REGISTRY/order-service:$IMAGE_TAG order-service/ ''' }
} stage('Docker Push') { steps { withDockerRegistry([credentialsId: 'dockerhub-creds', url: '']) { sh ''' docker push $REGISTRY/user-service:$IMAGE_TAG docker push $REGISTRY/order-service:$IMAGE_TAG ''' } }
} stage('Update K8s Manifests Repo') { steps { sh ''' git clone https://github.com/your-org/k8s-manifests.git cd k8s-manifests sed -i "s|image:.*user-service.*|image: $REGISTRY/user-service:$IMAGE_TAG|" user-deployment.yaml sed -i "s|image:.*order-service.*|image: $REGISTRY/order-service:$IMAGE_TAG|" order-deployment.yaml git commit -am "Update images to $IMAGE_TAG" git push ''' }
} - http: paths: path: /users
pathType: Prefix
backend: service: name: user-service port: number: 8080
path: /orders
pathType: Prefix
backend: service: name: order-service port: number: 8080
- path: /users
pathType: Prefix
backend: service: name: user-service port: number: 8080
- path: /orders
pathType: Prefix
backend: service: name: order-service port: number: 8080 - path: /users
pathType: Prefix
backend: service: name: user-service port: number: 8080
- path: /orders
pathType: Prefix
backend: service: name: order-service port: number: 8080 - port: 8080
5.5 Deploy Using Argo CD
Commit all manifests
Argo CD syncs
Only BLUE receives traffic
5.6 Switch Traffic (ZERO DOWNTIME)
Change one line only
selector:
app: user-service
version: green
Commit β Push β Argo CD syncs
β
Traffic moves instantly
β
No pod restart
β
Rollback = revert commit
5.7 Blue-Green Rollback (1 second)