kubernetes
How to Deploy and Scale Production-Ready Microservices on Kubernetes with Helm & ArgoCD
A complete step-by-step architectural guide to deploying resilient, auto-scaling microservices on Kubernetes. Learn manifest design, Ingress TLS, HPA, and GitOps synchronization.
By Shubhankar Tripathi
•
•
18 min read
## Introduction
Deploying microservices to a production Kubernetes cluster requires far more than running `kubectl apply -f deployment.yaml`. A true enterprise-ready setup demands **high availability**, **automated rolling upgrades**, **zero-downtime health probing**, **declarative Helm packaging**, and **GitOps automation** with ArgoCD.
In this tutorial, you will build and deploy a fault-tolerant microservice stack from scratch. You will configure production-grade resource quotas, implement Horizontal Pod Autoscaling (HPA), provision TLS certificates with cert-manager, and set up continuous synchronization with ArgoCD.
:::info
**Architecture Overview**:
The architecture you are deploying consists of an Ingress-NGINX controller terminating TLS, routing traffic to an API service with PodDisruptionBudgets, backed by an automated ArgoCD GitOps reconciliation engine.
:::
---
## Step 1 — Configuring Production-Ready Pod Manifests
Before packaging with Helm, let's examine the essential anatomy of a production-hardened Pod specification. Notice how we specify non-root security contexts, CPU/Memory requests, and strict liveness and readiness probes.
Create a file named `deployment.yaml`:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
namespace: production
labels:
app.kubernetes.io/name: payments-api
app.kubernetes.io/instance: prod-v1
app.kubernetes.io/managed-by: helm
spec:
replicas: 3
revisionHistoryLimit: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 0
selector:
matchLabels:
app.kubernetes.io/name: payments-api
template:
metadata:
labels:
app.kubernetes.io/name: payments-api
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
fsGroup: 10001
containers:
- name: payments-api
image: ghcr.io/bitcodematrix/payments-service:v2.4.1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
protocol: TCP
env:
- name: NODE_ENV
value: "production"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
failureThreshold: 2
```
:::tip
**Key Architectural Insight**:
Setting `maxUnavailable: 0` ensures that during rolling updates, old pods are never terminated before new healthy pods pass their `readinessProbe`. This is the cornerstone of zero-downtime Kubernetes deployments.
:::
---
## Step 2 — Packaging Services with Helm v3
Instead of maintaining hardcoded YAML manifests across development, staging, and production environments, we package our microservice into a flexible Helm chart.
Initialize your Helm chart structure:
```bash
helm create payments-chart
cd payments-chart
```
Define the parameterized `values.yaml` for production overrides:
```yaml
# values.yaml
replicaCount: 3
image:
repository: ghcr.io/bitcodematrix/payments-service
pullPolicy: IfNotPresent
tag: "v2.4.1"
service:
type: ClusterIP
port: 8080
ingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/rate-limit: "100"
hosts:
- host: api.bitcodematrix.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: payments-api-tls
hosts:
- api.bitcodematrix.com
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 12
targetCPUUtilizationPercentage: 75
targetMemoryUtilizationPercentage: 80
```
Render and validate your Helm templates locally before deployment:
```bash
helm template payments-api . -f values.yaml --namespace production | kubectl apply --dry-run=client -f -
```
---
## Step 3 — Configuring Horizontal Pod Autoscaling (HPA)
To dynamically respond to traffic spikes without manual intervention, configure a Horizontal Pod Autoscaler backed by metrics-server.
Create `hpa.yaml`:
```yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: payments-api-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: payments-api
minReplicas: 3
maxReplicas: 15
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: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
```
:::warning
**Avoid Flapping (Thrashing)**:
Notice the `stabilizationWindowSeconds: 300` on `scaleDown`. This cooldown period prevents rapid scale-up and scale-down oscillation caused by bursty network traffic.
:::
---
## Step 4 — Implementing GitOps Delivery with ArgoCD
GitOps shifts the responsibility of cluster modification from human operators to an automated controller that continuously reconciles cluster state against Git.
Apply the declarative ArgoCD `Application` manifest:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: payments-production
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: 'https://github.com/bitcodematrix/k8s-gitops-manifests.git'
targetRevision: main
path: environments/production/payments
helm:
valueFiles:
- values.yaml
destination:
server: 'https://kubernetes.default.svc'
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
```
Apply the application to your ArgoCD control plane:
```bash
kubectl apply -f argocd-application.yaml -n argocd
```
Check sync status using the ArgoCD CLI:
```bash
argocd app get payments-production
```
Output:
```text
Name: argocd/payments-production
Project: default
Server: https://kubernetes.default.svc
Namespace: production
URL: https://argocd.bitcodematrix.com/applications/payments-production
Repo: https://github.com/bitcodematrix/k8s-gitops-manifests.git
Target: main
Path: environments/production/payments
Sync Window: Sync Allowed
Sync Status: Synced to main (a8f93e1)
Health Status: Healthy
```
---
## Step 5 — Verification and Load Testing
Let's verify our autoscaling response by generating synthetic HTTP load using `hey` or `k6`:
```bash
# Run 10,000 requests with 50 concurrent workers
hey -n 10000 -c 50 -m GET https://api.bitcodematrix.com/healthz
```
Monitor the pods and HPA scaling in real time:
```bash
kubectl get hpa payments-api-hpa -n production --watch
```
You will observe the replica count smoothly expand from 3 to 9 pods, distributing request volume seamlessly across cluster worker nodes.
---
## Conclusion
You have now established a complete, enterprise-grade deployment workflow for Kubernetes microservices. By combining **Helm modular templates**, **non-root security policies**, **proactive HPA autoscaling**, and **ArgoCD declarative GitOps**, your services are self-healing, auditable, and resilient to sudden traffic surges.
In the next tutorial of this series, we will dive into **eBPF-powered network observability using Cilium and Hubble**.