End-to-End Microservices Deployment on AWS EKS: CI/CD with Jenkins, Docker, Kubernetes & Argo CD

End-to-End Microservices Deployment on AWS EKS: CI/CD with Jenkins, Docker, Kubernetes & Argo CD

Source: Dev.to

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 source: 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 hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse 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 ''' }
} Enter fullscreen mode Exit fullscreen mode 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 ''' }
} Enter fullscreen mode Exit fullscreen mode 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)