DevOps & Cloud
Microservice Architecture with Kubernetes: Pod, Service & Deployment
Kubernetes (K8s) is an open-source platform that has become the de facto standard for container orchestration. It automates deployment, scaling, and management of applications in microservice architectures. In this article, I will examine the core components of Kubernetes, microservice deployment strategies, and package management with Helm in detail.
Since I transitioned to Kubernetes in my projects, the reliability of production environments has significantly improved thanks to zero-downtime deployments, automatic scaling, and self-healing capabilities. A properly configured K8s cluster greatly reduces operational overhead.
Kubernetes Core Components
Pod: The Smallest Deployment Unit
A Pod is the smallest and most basic unit in Kubernetes. It contains one or more containers, shared storage, and network resources.
# pod.yaml - Basic pod definition
apiVersion: v1
kind: Pod
metadata:
name: api-server
labels:
app: myapi
version: v1
environment: production
spec:
containers:
- name: api
image: ghcr.io/myorg/myapi:1.0.0
ports:
- containerPort: 8080
protocol: TCP
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: ConnectionStrings__DefaultConnection
valueFrom:
secretKeyRef:
name: db-credentials
key: connection-string
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
startupProbe:
httpGet:
path: /health/startup
port: 8080
failureThreshold: 30
periodSeconds: 10
restartPolicy: AlwaysDeployment: Declarative Update Management
A Deployment defines the desired state of Pods and manages update strategies. It automatically creates ReplicaSets and provides zero-downtime deployment through rolling updates.
# deployment.yaml - Production-ready deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapi-deployment
labels:
app: myapi
spec:
replicas: 3
selector:
matchLabels:
app: myapi
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: myapi
version: v1
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapi
topologyKey: kubernetes.io/hostname
containers:
- name: api
image: ghcr.io/myorg/myapi:1.0.0
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: myapi-config
- secretRef:
name: myapi-secrets
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
---
# service.yaml - ClusterIP and LoadBalancer
apiVersion: v1
kind: Service
metadata:
name: myapi-service
spec:
selector:
app: myapi
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: ClusterIP
---
# Ingress - External access
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapi-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/rate-limit-window: "1m"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: api-tls-cert
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapi-service
port:
number: 80ConfigMap and Secret Management
Separating application configuration and sensitive data from the container image is one of Kubernetes' core principles. ConfigMap is used for environment variables and configuration files, while Secrets are used for sensitive data like passwords and API keys:
# configmap.yaml - Application configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: myapi-config
namespace: production
data:
ASPNETCORE_ENVIRONMENT: "Production"
Logging__LogLevel__Default: "Information"
AllowedOrigins: "https://example.com,https://www.example.com"
Cache__DefaultExpirationMinutes: "30"
FeatureFlags__NewDashboard: "true"
---
# secret.yaml - Sensitive data (base64 encoded)
apiVersion: v1
kind: Secret
metadata:
name: myapi-secrets
namespace: production
type: Opaque
data:
ConnectionStrings__DefaultConnection: SG9zdD1wb3N0Z3Jlczt...
Redis__ConnectionString: cmVkaXM6NjM3OQ==
JWT__SecretKey: c3VwZXItc2VjcmV0LWtleQ==
---
# External Secrets with vault integration (recommended)
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: myapi-external-secrets
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: myapi-secrets
data:
- secretKey: ConnectionStrings__DefaultConnection
remoteRef:
key: myapi/production
property: db-connection-stringFor ConfigMap and Secret changes to take effect in pods, the deployment needs to be restarted. You can automate this using tools like Reloader or Helm hooks.
PersistentVolumeClaim for Data Persistence
For stateful applications (databases, file storage), you can request persistent storage using PersistentVolumeClaim:
# StatefulSet with persistent storage for database
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: production
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: "myapp"
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgres-credentials
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-credentials
key: password
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "1"
memory: "2Gi"
volumeClaimTemplates:
- metadata:
name: postgres-storage
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: gp3
resources:
requests:
storage: 50GiStatefulSet is a more appropriate choice than Deployment for stateful applications like databases. It preserves data integrity by assigning each pod a stable identity and persistent storage.
Namespace Management and Isolation
Namespaces are used to logically separate cluster resources. They provide isolation for different environments (dev, staging, production) and different teams:
# Create namespaces
kubectl create namespace production
kubectl create namespace staging
kubectl create namespace development
# Namespace-level resource quotas
kubectl apply -f - <<EOF
apiVersion: v1
kind: ResourceQuota
metadata:
name: production-quota
namespace: production
spec:
hard:
requests.cpu: "8"
requests.memory: "16Gi"
limits.cpu: "16"
limits.memory: "32Gi"
pods: "50"
services: "20"
EOF
# Namespace-level network policy
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: production
EOFWith ResourceQuota, you can limit the resources each namespace can use. With NetworkPolicy, you can control traffic between namespaces to enforce security boundaries.
Horizontal Pod Autoscaler
HPA is used to automatically respond to traffic increases:
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapi-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapi-deployment
minReplicas: 2
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: 25
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Percent
value: 100
periodSeconds: 30Monitoring with Prometheus
In Kubernetes environments, Prometheus is the standard solution for metric collection and monitoring. With ServiceMonitor, you can automatically discover application metrics:
# servicemonitor.yaml - Application monitoring with Prometheus
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: myapi-monitor
namespace: production
labels:
release: prometheus
spec:
selector:
matchLabels:
app: myapi
endpoints:
- port: http
path: /metrics
interval: 30s
scrapeTimeout: 10s
---
# PrometheusRule for alert definitions
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: myapi-alerts
namespace: production
spec:
groups:
- name: myapi.rules
rules:
- alert: HighErrorRate
expr: |
rate(http_requests_total{status=~"5..", app="myapi"}[5m])
/ rate(http_requests_total{app="myapi"}[5m]) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate detected"
description: "Error rate above 5% in the last 5 minutes"
- alert: HighLatency
expr: |
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket{app="myapi"}[5m])) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "High response time detected"The Prometheus + Grafana combination allows you to visualize metrics such as CPU, memory, HTTP request counts, error rates, and response times, and define alert rules for proactive incident management.
Package Management with Helm
Helm Chart Structure
Helm is a package manager used for packaging and distributing Kubernetes applications.
# Chart.yaml
apiVersion: v2
name: myapi
description: My API Helm Chart
version: 1.0.0
appVersion: "1.0.0"
# values.yaml - Default values
replicaCount: 3
image:
repository: ghcr.io/myorg/myapi
tag: "1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
host: api.example.com
tls: true
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilization: 70Deployment with Helm:
# Install chart
helm install myapi ./charts/myapi -f values-production.yaml -n production
# Upgrade
helm upgrade myapi ./charts/myapi -f values-production.yaml -n production
# Rollback
helm rollback myapi 1 -n production
# Status check
helm status myapi -n productionMicroservice Communication Patterns
For inter-service communication in Kubernetes environments:
- ClusterIP Service: Synchronous HTTP/gRPC communication within the cluster
- Headless Service: DNS-based solution for service discovery
- Service Mesh (Istio/Linkerd): mTLS, traffic management, observability
- Message Queue: Asynchronous communication with RabbitMQ or Kafka
Practical Tips and Summary
Keep these points in mind when managing microservices with Kubernetes:
- Resource limits: Always define CPU and memory limits for every container
- Health probes: Properly configure liveness, readiness, and startup probes
- Pod anti-affinity: Distribute Pods across different nodes for high availability
- Namespace isolation: Separate environments (dev, staging, prod) with namespaces
- GitOps: Apply declarative deployment management with ArgoCD or Flux
When properly configured, Kubernetes largely solves the scaling, reliability, and management challenges of microservice architectures. Starting small and scaling as needed is the healthiest approach.
Related Articles
Microservices Architecture with .NET: Design and Implementation
Design microservices architecture with .NET. Service communication, Docker, and orchestration strategies.
Docker Container Guide: Dockerfile, Compose & Multi-Stage Builds
Docker containerization guide. Writing Dockerfiles, multi-service Docker Compose, multi-stage build optimization, and production deployment.
CI/CD Pipeline: Automated Testing & Deployment with GitHub Actions
CI/CD pipeline setup with GitHub Actions. Automated testing, builds, Docker image creation, and AWS deployment.
Have a Flutter Project?
I build high-performance Flutter applications for iOS, Android, and web.
Get in Touch