Introduction to Kubernetes Deployments
In our previous lectures, we explored container orchestration principles and Kubernetes core concepts. Now, we'll put this knowledge into practice by learning how to deploy applications to Kubernetes.
Deploying applications to Kubernetes is like moving into a new apartment building—you need to pack your belongings (containerize your application), find the right apartment (create Kubernetes resources), arrange your furniture (configure your application), connect utilities (set up networking), and establish a maintenance schedule (implement updates and scaling).
In this lecture, we'll explore practical approaches to deploying applications on Kubernetes, covering containerization, deployment strategies, configuration management, scaling, updates, and observability.
Preparing Applications for Kubernetes
Container Best Practices
Before deploying to Kubernetes, ensure your application follows these containerization best practices:
- Single Concern: Each container should do one thing well
- Stateless When Possible: Design containers to be stateless and ephemeral
- Proper Signal Handling: Containers should handle SIGTERM for graceful shutdown
- Non-root User: Run containers as non-root users for security
- Health Checks: Implement health endpoints for liveness and readiness probes
- Efficient Images: Use multi-stage builds and minimal base images
- Proper Logging: Log to stdout/stderr for Kubernetes to collect logs
# Example Dockerfile with best practices
# Stage 1: Build the application
FROM node:16-alpine AS builder
WORKDIR /app
# Copy and install dependencies first (for better layer caching)
COPY package*.json ./
RUN npm ci --only=production
# Copy application code
COPY . .
# Build the application
RUN npm run build
# Stage 2: Production image
FROM node:16-alpine
# Create non-root user
RUN addgroup -g 1001 appuser && \
adduser -u 1001 -G appuser -s /bin/sh -D appuser
WORKDIR /app
# Copy production dependencies and build from the builder stage
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
# Use non-root user
USER appuser
# Set environment variables
ENV NODE_ENV production
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
# Run the application with proper signal handling
CMD ["node", "dist/server.js"]
Application Configuration
Design your application to get configuration from the environment, following the Twelve-Factor App methodology:
// Example Node.js configuration pattern
const config = {
port: process.env.PORT || 3000,
dbHost: process.env.DB_HOST || 'localhost',
dbPort: process.env.DB_PORT || 5432,
dbUser: process.env.DB_USER,
dbPassword: process.env.DB_PASSWORD,
dbName: process.env.DB_NAME || 'app',
logLevel: process.env.LOG_LEVEL || 'info',
// Validate required environment variables
validate() {
const required = ['DB_USER', 'DB_PASSWORD'];
const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) {
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}
return this;
}
};
// Export validated config
module.exports = config.validate();
Health Checks
Implement health endpoints for Kubernetes probes:
// Example health check endpoints for Express.js
const express = require('express');
const app = express();
// Basic health check for liveness probe
app.get('/health', (req, res) => {
res.status(200).send('OK');
});
// Readiness check that verifies dependencies
app.get('/ready', async (req, res) => {
try {
// Check database connection
await db.ping();
// Check other dependencies
await redis.ping();
res.status(200).send('Ready');
} catch (error) {
console.error('Readiness check failed:', error);
res.status(503).send('Not Ready');
}
});
// Startup check for initialization
app.get('/startup', async (req, res) => {
if (app.isInitialized) {
res.status(200).send('Initialized');
} else {
res.status(503).send('Initializing');
}
});
Kubernetes Deployment Approaches
Imperative vs. Declarative
Kubernetes supports two approaches to resource management:
| Imperative | Declarative |
|---|---|
| Focus on how to achieve the result | Focus on what the result should be |
| Direct commands to create/update resources | Define desired state in YAML files |
| Good for quick, one-off tasks | Better for production environments |
| Less repeatable and traceable | Version-controlled, repeatable |
# Imperative approach
kubectl create deployment nginx --image=nginx:1.19
kubectl expose deployment nginx --port=80 --type=ClusterIP
# Declarative approach
kubectl apply -f nginx-deployment.yaml
For production deployments, the declarative approach is strongly recommended as it enables:
- Version control of configuration
- Reproducible deployments
- Self-documenting infrastructure
- GitOps workflows
Kubernetes-Native Deployment
The most direct approach is using native Kubernetes YAML manifests:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
labels:
app: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web-app
image: my-registry/web-app:1.0.0
ports:
- containerPort: 80
resources:
limits:
cpu: "1"
memory: 512Mi
requests:
cpu: "0.2"
memory: 256Mi
env:
- name: LOG_LEVEL
value: "info"
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
selector:
app: web
ports:
- port: 80
targetPort: 80
type: ClusterIP
This approach gives you full control but can become cumbersome as applications grow more complex.
Helm Charts
Helm is a package manager for Kubernetes that streamlines application deployment:
Benefits of using Helm:
- Reusable package format
- Templating for configuration
- Release management
- Rollbacks
- Sharing and reusing charts
# Installing a chart from a repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-release bitnami/nginx
# Creating a custom chart
helm create my-app
# Installing a local chart with custom values
helm install my-release ./my-app --values custom-values.yaml
# Upgrading a release
helm upgrade my-release ./my-app --values new-values.yaml
# Example values.yaml for Helm chart
replicaCount: 3
image:
repository: my-registry/web-app
tag: 1.0.0
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
resources:
limits:
cpu: 1
memory: 512Mi
requests:
cpu: 200m
memory: 256Mi
env:
LOG_LEVEL: info
FEATURE_FLAGS: "payment,notifications"
Kustomize
Kustomize is a configuration customization tool that's built into kubectl:
Kustomize allows you to:
- Maintain base configurations
- Create environment-specific overlays
- Patch resources without modifying the originals
- Generate ConfigMaps and Secrets
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
commonLabels:
app: web-app
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: prod-
patchesStrategicMerge:
- deployment-patch.yaml
replicas:
- name: web-app
count: 5
# overlays/production/deployment-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
template:
spec:
containers:
- name: web-app
resources:
limits:
cpu: "2"
memory: 1Gi
requests:
cpu: "0.5"
memory: 512Mi
env:
- name: ENVIRONMENT
value: "production"
# Applying with Kustomize
kubectl apply -k overlays/production/
Operator Pattern
For complex applications, especially stateful ones, the operator pattern provides application-specific automation:
Popular operator-managed applications include:
- Databases (PostgreSQL, MongoDB, Cassandra)
- Message brokers (Kafka, RabbitMQ)
- Monitoring systems (Prometheus, Elasticsearch)
- Service meshes (Istio, Linkerd)
# Example: Deploying PostgreSQL with an operator
apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PostgresCluster
metadata:
name: hippo
spec:
image: registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-14.5-0
postgresVersion: 14
instances:
- name: instance1
replicas: 3
dataVolumeClaimSpec:
accessModes:
- "ReadWriteOnce"
resources:
requests:
storage: 1Gi
backups:
pgbackrest:
image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.40-0
repos:
- name: repo1
volume:
volumeClaimSpec:
accessModes:
- "ReadWriteOnce"
resources:
requests:
storage: 1Gi
Advanced Deployment Strategies
Rolling Updates
The default strategy in Kubernetes, which gradually replaces old pods with new ones:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # How many pods can be created above desired number
maxUnavailable: 1 # How many pods can be unavailable during update
# ... rest of deployment ...
Pros:
- Zero downtime updates
- Simple to implement (default in Kubernetes)
- No additional resources required
Cons:
- Both versions run simultaneously during update
- Rollback takes time
- Not suitable for all applications (e.g., database schema changes)
Blue-Green Deployment
Run two identical environments (blue and green) and switch traffic all at once:
Version 1] C[Green Environment
Version 2] D[Switch Traffic] --> E[Service] --> C B -.-> F[Decommission or Keep as Rollback]
# Blue deployment (current version)
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app-blue
labels:
app: web-app
version: blue
spec:
replicas: 3
selector:
matchLabels:
app: web-app
version: blue
template:
metadata:
labels:
app: web-app
version: blue
spec:
containers:
- name: web-app
image: my-registry/web-app:1.0.0
# ... container config ...
# Green deployment (new version)
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app-green
labels:
app: web-app
version: green
spec:
replicas: 3
selector:
matchLabels:
app: web-app
version: green
template:
metadata:
labels:
app: web-app
version: green
spec:
containers:
- name: web-app
image: my-registry/web-app:1.1.0
# ... container config ...
# Service initially pointing to blue
apiVersion: v1
kind: Service
metadata:
name: web-app
spec:
selector:
app: web-app
version: blue # Switch to 'green' when ready
ports:
- port: 80
targetPort: 80
Pros:
- Instant cutover with no mixed versions
- Fast rollback (switch service back)
- Complete testing of new version before switching
Cons:
- Requires double the resources
- Potential configuration drift between environments
- More complex implementation
Canary Deployment
Gradually shift traffic from old to new version to test in production with minimal risk:
Stable Version] A --> C[10% Traffic to
Canary Version] D[Success] --> E[Gradually
Increase Canary] E --> F[100% Traffic to
New Version] G[Problem] --> H[Revert to
Stable Version]
# Stable deployment (majority of traffic)
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app-stable
labels:
app: web-app
version: stable
spec:
replicas: 9 # 90% of traffic
selector:
matchLabels:
app: web-app
version: stable
template:
metadata:
labels:
app: web-app
version: stable
spec:
containers:
- name: web-app
image: my-registry/web-app:1.0.0
# ... container config ...
# Canary deployment (small portion of traffic)
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app-canary
labels:
app: web-app
version: canary
spec:
replicas: 1 # 10% of traffic
selector:
matchLabels:
app: web-app
version: canary
template:
metadata:
labels:
app: web-app
version: canary
spec:
containers:
- name: web-app
image: my-registry/web-app:1.1.0
# ... container config ...
# Service selecting both deployments
apiVersion: v1
kind: Service
metadata:
name: web-app
spec:
selector:
app: web-app # Matches both stable and canary
ports:
- port: 80
targetPort: 80
Pros:
- Test new version with real traffic
- Limit impact of issues
- Gradual rollout
- Easy to scale up or down
Cons:
- More complex to implement
- Requires monitoring and metrics
- May require infrastructure changes (e.g., service mesh)
Feature Toggles
Deploy code with new features disabled, then enable them gradually:
with All Features] --> B[Feature Flags
Config] B --> C[Feature A: ON] B --> D[Feature B: ON] B --> E[Feature C: OFF] F[Update Config] --> G[Feature C: 10% of users] G --> H[Feature C: 50% of users] H --> I[Feature C: All users]
# ConfigMap for feature flags
apiVersion: v1
kind: ConfigMap
metadata:
name: feature-flags
data:
flags.json: |
{
"paymentProcessor": "v1",
"newCheckout": false,
"recommendations": true,
"darkMode": {
"enabled": true,
"percentage": 25
}
}
Pros:
- Decouples deployment from release
- Fine-grained control over feature rollout
- A/B testing capabilities
- Quick feature disabling without redeployment
Cons:
- Requires application-level implementation
- Technical debt if flags are not removed
- Testing complexity with multiple combinations
Configuration Management in Kubernetes
Environment-Specific Configuration
Different environments (dev, staging, production) often require different configurations:
Using Kustomize for Environment Configuration
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
configMapGenerator:
- name: app-config
literals:
- LOG_LEVEL=info
# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: dev-
configMapGenerator:
- name: app-config
behavior: merge
literals:
- ENVIRONMENT=development
- FEATURE_FLAGS=all
- DEBUG=true
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: prod-
configMapGenerator:
- name: app-config
behavior: merge
literals:
- ENVIRONMENT=production
- FEATURE_FLAGS=stable
- DEBUG=false
Using Helm for Environment Configuration
# values.yaml (default)
environment: development
logLevel: info
featureFlags: all
debug: true
# production-values.yaml
environment: production
logLevel: warn
featureFlags: stable
debug: false
# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-config
data:
ENVIRONMENT: {{ .Values.environment }}
LOG_LEVEL: {{ .Values.logLevel }}
FEATURE_FLAGS: {{ .Values.featureFlags }}
DEBUG: "{{ .Values.debug }}"
External Configuration Sources
For larger organizations, external configuration management tools can provide more sophisticated features:
Example: External Secrets Operator
# Define a SecretStore
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secretsmanager
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
secretRef:
accessKeyIDSecretRef:
name: aws-credentials
key: access-key-id
secretAccessKeySecretRef:
name: aws-credentials
key: secret-access-key
# Define an ExternalSecret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
spec:
refreshInterval: "15m"
secretStoreRef:
name: aws-secretsmanager
kind: SecretStore
target:
name: db-credentials
data:
- secretKey: username
remoteRef:
key: prod/db/credentials
property: username
- secretKey: password
remoteRef:
key: prod/db/credentials
property: password
Secrets Management Best Practices
- Use Encryption: Enable encryption at rest for etcd
- External Secret Storage: Use a dedicated secret management solution
- Limit Access: Implement strict RBAC for secrets
- Secret Rotation: Automatically rotate secrets on a schedule
- Avoid Environment Variables: Mount secrets as volumes when possible
- Minimize Secret Content: Store only what's necessary
- Audit Secret Access: Monitor and log secret usage
Scaling Applications in Kubernetes
Manual Scaling
The simplest approach is changing the replica count manually:
# Scale a deployment to 5 replicas
kubectl scale deployment web-app --replicas=5
# In YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 5 # Change this value
# ... rest of deployment ...
Horizontal Pod Autoscaler (HPA)
Automatically scales the number of pods based on observed metrics:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 60
- type: Pods
pods:
metric:
name: packets-per-second
target:
type: AverageValue
averageValue: 1k
- type: Object
object:
metric:
name: requests-per-second
describedObject:
apiVersion: networking.k8s.io/v1
kind: Ingress
name: web-app-ingress
target:
type: Value
value: 10k
Vertical Pod Autoscaler (VPA)
Automatically adjusts CPU and memory requests and limits:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web-app-vpa
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: web-app
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: '*'
minAllowed:
cpu: 100m
memory: 50Mi
maxAllowed:
cpu: 1
memory: 500Mi
controlledResources: ["cpu", "memory"]
Cluster Autoscaler
Automatically adjusts the size of the Kubernetes cluster:
Cluster Autoscaler works with most cloud providers and can be configured via flags or annotations.
Scaling Stateful Applications
Scaling stateful applications requires special consideration:
- Read Replicas: Scale read-only replicas while keeping primary nodes fixed
- Sharding: Partition data horizontally across multiple instances
- Database-Specific Operators: Use operators designed for database scaling
- External Database Services: Consider managed database services
# Example: Scaling read replicas in MongoDB
apiVersion: mongodb.com/v1
kind: MongoDB
metadata:
name: mongodb-cluster
spec:
members: 3 # Primary + 2 secondary members
version: 4.2.6
type: ReplicaSet
# Specify the replica set members
replicaSetHorizons:
- horizon: internal
- horizon: external
statefulSet:
spec:
selector:
matchLabels:
app: mongodb-svc
serviceName: mongodb-svc
# Configure auto-scaling for reads
readPreference:
mode: SecondaryPreferred
Monitoring and Observability
Monitoring Stack for Kubernetes
A comprehensive monitoring solution for Kubernetes typically includes:
Prometheus and Grafana
The most popular metrics monitoring solution for Kubernetes:
# Add Prometheus annotations to a Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"
spec:
# ... container config ...
Logging Architecture
Centralized logging is essential for troubleshooting distributed applications:
Tracing and Observability
Distributed tracing helps track requests across multiple services:
Health Probes in Kubernetes
Kubernetes offers three types of health checks:
- Liveness Probe: Determines if a container is running properly
- Readiness Probe: Determines if a container is ready to receive traffic
- Startup Probe: Determines if a container has started successfully
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
template:
spec:
containers:
- name: web-app
image: my-registry/web-app:1.0.0
ports:
- containerPort: 80
# Startup probe - give application time to bootstrap
startupProbe:
httpGet:
path: /startup
port: 80
initialDelaySeconds: 5
periodSeconds: 2
failureThreshold: 30
# Liveness probe - detect deadlocks or frozen processes
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
# Readiness probe - check if ready to receive traffic
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 2
successThreshold: 1
failureThreshold: 3
Practical Exercise: Deploying a Three-Tier Application
Scenario
In this exercise, you'll deploy a complete three-tier application to Kubernetes with best practices:
- Frontend web application (React)
- Backend API service (Node.js)
- Database (MongoDB)
Exercise Tasks
- Create Deployments for the frontend and backend
- Set up a StatefulSet for MongoDB
- Configure Services for networking
- Implement an Ingress resource
- Set up ConfigMaps and Secrets for configuration
- Configure health checks
- Implement horizontal scaling
- Add monitoring annotations
Solution Outline
Here's a starting point for your solution:
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: three-tier-app
# mongodb-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongodb
namespace: three-tier-app
spec:
serviceName: mongodb-service
replicas: 1
selector:
matchLabels:
app: mongodb
template:
metadata:
labels:
app: mongodb
spec:
containers:
- name: mongodb
image: mongo:4.4
ports:
- containerPort: 27017
env:
- name: MONGO_INITDB_ROOT_USERNAME
valueFrom:
secretKeyRef:
name: mongodb-secret
key: username
- name: MONGO_INITDB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mongodb-secret
key: password
volumeMounts:
- name: mongodb-data
mountPath: /data/db
resources:
limits:
cpu: "0.5"
memory: 512Mi
requests:
cpu: "0.2"
memory: 256Mi
livenessProbe:
exec:
command:
- mongo
- --eval
- "db.adminCommand('ping')"
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
volumeClaimTemplates:
- metadata:
name: mongodb-data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
# mongodb-service.yaml
apiVersion: v1
kind: Service
metadata:
name: mongodb-service
namespace: three-tier-app
spec:
clusterIP: None # Headless service for StatefulSet
selector:
app: mongodb
ports:
- port: 27017
targetPort: 27017
# mongodb-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mongodb-secret
namespace: three-tier-app
type: Opaque
data:
username: YWRtaW4= # admin
password: cGFzc3dvcmQxMjM= # password123
# backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: three-tier-app
spec:
replicas: 2
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3000"
prometheus.io/path: "/metrics"
spec:
containers:
- name: backend
image: your-registry/backend:1.0.0
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: PORT
value: "3000"
- name: MONGODB_URI
value: "mongodb://$(MONGODB_USERNAME):$(MONGODB_PASSWORD)@mongodb-service:27017/app?authSource=admin"
- name: MONGODB_USERNAME
valueFrom:
secretKeyRef:
name: mongodb-secret
key: username
- name: MONGODB_PASSWORD
valueFrom:
secretKeyRef:
name: mongodb-secret
key: password
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: log-level
resources:
limits:
cpu: "0.5"
memory: 512Mi
requests:
cpu: "0.2"
memory: 256Mi
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
# backend-service.yaml
apiVersion: v1
kind: Service
metadata:
name: backend-service
namespace: three-tier-app
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 3000
type: ClusterIP
# backend-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: backend-hpa
namespace: three-tier-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: backend
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
# frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: three-tier-app
spec:
replicas: 3
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: your-registry/frontend:1.0.0
ports:
- containerPort: 80
env:
- name: API_URL
value: "http://backend-service"
resources:
limits:
cpu: "0.2"
memory: 256Mi
requests:
cpu: "0.1"
memory: 128Mi
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
# frontend-service.yaml
apiVersion: v1
kind: Service
metadata:
name: frontend-service
namespace: three-tier-app
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 80
type: ClusterIP
# frontend-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: frontend-hpa
namespace: three-tier-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: frontend
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
# app-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: three-tier-app
data:
log-level: "info"
feature-flags: "payments,notifications"
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: three-tier-app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: backend-service
port:
number: 80
tls:
- hosts:
- app.example.com
secretName: app-tls-cert
Complete this exercise by applying these manifests to a Kubernetes cluster. You may need to adjust specific details to match your application and environment.
Kubernetes Deployment Best Practices
Resource Management
- Always specify resource requests and limits to prevent resource starvation
- Set realistic CPU and memory limits based on application profiling
- Implement Horizontal Pod Autoscaling for dynamic workloads
- Consider using the Vertical Pod Autoscaler for initial resource calibration
- Use resource quotas for namespaces to prevent abuse
High Availability
- Use multiple replicas for all critical services
- Implement pod anti-affinity to distribute across nodes
- Use pod disruption budgets to maintain minimum availability during disruptions
- Deploy across multiple zones for zone failure resilience
- Properly configure health checks to detect and recover from failures
Security
- Implement network policies to restrict pod-to-pod communication
- Use RBAC for fine-grained access control
- Run containers as non-root users
- Use read-only filesystems when possible
- Scan images for vulnerabilities before deployment
- Use secure secret management solutions
Networking
- Use Services for stable endpoints
- Implement proper DNS naming conventions
- Consider a service mesh for complex microservice deployments
- Use network policies for microsegmentation
- Configure proper TLS termination for external traffic
Maintainability
- Use labels and annotations consistently for organization
- Implement GitOps workflows for declarative configuration
- Follow a consistent naming convention for all resources
- Use Helm or Kustomize for templating and environment separation
- Document architecture and operational procedures
Conclusion
Deploying applications to Kubernetes is a multi-faceted process that involves containerization, resource definition, configuration management, networking, scaling, and monitoring. By following the patterns and practices covered in this lecture, you can create reliable, scalable, and maintainable deployments that leverage Kubernetes' powerful orchestration capabilities.
Key takeaways from this lecture include:
- Containerization Best Practices: Building efficient, secure containers is the foundation of successful deployments
- Deployment Approaches: From native Kubernetes resources to Helm charts and Kustomize, choose the right approach for your needs
- Advanced Deployment Strategies: Rolling updates, blue-green, and canary deployments provide different trade-offs for updating applications
- Configuration Management: Properly manage application configuration across environments
- Scaling: Leverage Kubernetes' built-in scaling mechanisms to handle varying workloads
- Monitoring and Observability: Implement comprehensive monitoring for visibility into application health and performance
As you continue your Kubernetes journey, remember that effective deployments are not just about technology but also about processes and people. Implement CI/CD pipelines, adopt GitOps workflows, and build team knowledge to ensure long-term success with Kubernetes.
Additional Resources
- Kubernetes Deployments Documentation
- Helm Documentation
- Kustomize Documentation
- OperatorHub - Find and Share Kubernetes Operators
- Kubernetes Logging Architecture
- Horizontal Pod Autoscaling
- Blue-Green Deployment Pattern
- Feature Toggles (Feature Flags)
- Kubernetes Ingress
- The Twelve-Factor App Methodology