Modern DevOps Practices with Docker and Kubernetes
Introduction
DevOps has transformed how teams build, deploy, and maintain applications. In this guide, we'll cover modern DevOps practices using Docker, Kubernetes, and cloud-native tools.
Docker Best Practices
Multi-Stage Builds
dockerfile1# Stage 1: Build 2FROM node:18-alpine AS builder 3WORKDIR /app 4COPY package*.json ./ 5RUN npm ci 6COPY . . 7RUN npm run build 8 9# Stage 2: Production 10FROM node:18-alpine 11WORKDIR /app 12ENV NODE_ENV=production 13 14# Install production dependencies 15COPY package*.json ./ 16RUN npm ci --only=production 17 18# Copy built application 19COPY /app/dist ./dist 20 21# Create non-root user 22RUN addgroup -g 1001 -S nodejs 23RUN adduser -S nodejs -u 1001 24USER nodejs 25 26EXPOSE 3000 27CMD ["node", "dist/app.js"]
Docker Compose for Development
yaml1version: '3.8' 2services: 3 api: 4 build: . 5 ports: 6 - "3000:3000" 7 environment: 8 - NODE_ENV=development 9 - DATABASE_URL=postgresql://db:5432/dev 10 volumes: 11 - .:/app 12 - /app/node_modules 13 command: npm run dev 14 depends_on: 15 - db 16 - redis 17 18 db: 19 image: postgres:15-alpine 20 environment: 21 - POSTGRES_DB=dev 22 - POSTGRES_USER=dev 23 - POSTGRES_PASSWORD=dev 24 volumes: 25 - postgres_data:/var/lib/postgresql/data 26 ports: 27 - "5432:5432" 28 29 redis: 30 image: redis:7-alpine 31 ports: 32 - "6379:6379" 33 volumes: 34 - redis_data:/data 35 36volumes: 37 postgres_data: 38 redis_data:
Kubernetes Deployment
Deployment Configuration
yaml1# deployment.yaml 2apiVersion: apps/v1 3kind: Deployment 4metadata: 5 name: api-deployment 6 labels: 7 app: api 8spec: 9 replicas: 3 10 selector: 11 matchLabels: 12 app: api 13 template: 14 metadata: 15 labels: 16 app: api 17 spec: 18 containers: 19 - name: api 20 image: myapp:latest 21 ports: 22 - containerPort: 3000 23 env: 24 - name: NODE_ENV 25 value: "production" 26 - name: DATABASE_URL 27 valueFrom: 28 secretKeyRef: 29 name: db-secret 30 key: connection-string 31 resources: 32 requests: 33 memory: "128Mi" 34 cpu: "100m" 35 limits: 36 memory: "256Mi" 37 cpu: "200m" 38 livenessProbe: 39 httpGet: 40 path: /health 41 port: 3000 42 initialDelaySeconds: 30 43 periodSeconds: 10 44 readinessProbe: 45 httpGet: 46 path: /ready 47 port: 3000 48 initialDelaySeconds: 5 49 periodSeconds: 5
Service Configuration
yaml1# service.yaml 2apiVersion: v1 3kind: Service 4metadata: 5 name: api-service 6spec: 7 selector: 8 app: api 9 ports: 10 - port: 80 11 targetPort: 3000 12 type: LoadBalancer
Horizontal Pod Autoscaler
yaml1# hpa.yaml 2apiVersion: autoscaling/v2 3kind: HorizontalPodAutoscaler 4metadata: 5 name: api-hpa 6spec: 7 scaleTargetRef: 8 apiVersion: apps/v1 9 kind: Deployment 10 name: api-deployment 11 minReplicas: 2 12 maxReplicas: 10 13 metrics: 14 - type: Resource 15 resource: 16 name: cpu 17 target: 18 type: Utilization 19 averageUtilization: 70 20 - type: Resource 21 resource: 22 name: memory 23 target: 24 type: Utilization 25 averageUtilization: 80
CI/CD Pipeline with GitHub Actions
yaml1# .github/workflows/deploy.yml 2name: Deploy to Production 3 4on: 5 push: 6 branches: [ main ] 7 pull_request: 8 branches: [ main ] 9 10jobs: 11 test: 12 runs-on: ubuntu-latest 13 14 services: 15 postgres: 16 image: postgres:15 17 env: 18 POSTGRES_PASSWORD: postgres 19 options: >- 20 --health-cmd pg_isready 21 --health-interval 10s 22 --health-timeout 5s 23 --health-retries 5 24 ports: 25 - 5432:5432 26 27 steps: 28 - uses: actions/checkout@v4 29 30 - name: Setup Node.js 31 uses: actions/setup-node@v4 32 with: 33 node-version: '18' 34 cache: 'npm' 35 36 - name: Install dependencies 37 run: npm ci 38 39 - name: Run tests 40 run: npm test 41 env: 42 DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test 43 44 - name: Build application 45 run: npm run build 46 47 build-and-push: 48 needs: test 49 runs-on: ubuntu-latest 50 51 steps: 52 - uses: actions/checkout@v4 53 54 - name: Set up Docker Buildx 55 uses: docker/setup-buildx-action@v3 56 57 - name: Login to DockerHub 58 uses: docker/login-action@v3 59 with: 60 username: ${{ secrets.DOCKER_USERNAME }} 61 password: ${{ secrets.DOCKER_PASSWORD }} 62 63 - name: Build and push 64 uses: docker/build-push-action@v5 65 with: 66 context: . 67 push: true 68 tags: | 69 ${{ secrets.DOCKER_USERNAME }}/myapp:latest 70 ${{ secrets.DOCKER_USERNAME }}/myapp:${{ github.sha }} 71 cache-from: type=gha 72 cache-to: type=gha,mode=max 73 74 deploy: 75 needs: build-and-push 76 runs-on: ubuntu-latest 77 78 steps: 79 - name: Checkout 80 uses: actions/checkout@v4 81 82 - name: Configure kubectl 83 uses: azure/setup-kubectl@v3 84 with: 85 version: 'latest' 86 87 - name: Set up Kubernetes context 88 run: | 89 echo "${{ secrets.KUBECONFIG }}" > kubeconfig.yaml 90 export KUBECONFIG=kubeconfig.yaml 91 kubectl config use-context ${{ secrets.K8S_CONTEXT }} 92 93 - name: Deploy to Kubernetes 94 run: | 95 kubectl set image deployment/api-deployment \ 96 api=${{ secrets.DOCKER_USERNAME }}/myapp:${{ github.sha }} 97 kubectl rollout status deployment/api-deployment
Infrastructure as Code with Terraform
hcl1# main.tf 2provider "aws" { 3 region = "us-east-1" 4} 5 6# VPC 7resource "aws_vpc" "main" { 8 cidr_block = "10.0.0.0/16" 9 enable_dns_hostnames = true 10 11 tags = { 12 Name = "production-vpc" 13 } 14} 15 16# EKS Cluster 17resource "aws_eks_cluster" "main" { 18 name = "production-cluster" 19 role_arn = aws_iam_role.eks_cluster.arn 20 21 vpc_config { 22 subnet_ids = [ 23 aws_subnet.public_1.id, 24 aws_subnet.public_2.id, 25 aws_subnet.private_1.id, 26 aws_subnet.private_2.id 27 ] 28 } 29 30 depends_on = [ 31 aws_iam_role_policy_attachment.eks_cluster_policy 32 ] 33} 34 35# RDS Database 36resource "aws_db_instance" "main" { 37 identifier = "production-db" 38 engine = "postgres" 39 engine_version = "15.3" 40 instance_class = "db.t3.micro" 41 allocated_storage = 20 42 43 db_name = "mydb" 44 username = var.db_username 45 password = var.db_password 46 47 vpc_security_group_ids = [aws_security_group.rds.id] 48 db_subnet_group_name = aws_db_subnet_group.main.name 49 50 backup_retention_period = 7 51 backup_window = "03:00-04:00" 52 maintenance_window = "sun:04:00-sun:05:00" 53 54 deletion_protection = true 55 skip_final_snapshot = false 56 57 tags = { 58 Environment = "production" 59 } 60}
Monitoring with Prometheus and Grafana
yaml1# prometheus-config.yaml 2apiVersion: v1 3kind: ConfigMap 4metadata: 5 name: prometheus-config 6 namespace: monitoring 7data: 8 prometheus.yml: | 9 global: 10 scrape_interval: 15s 11 evaluation_interval: 15s 12 13 scrape_configs: 14 - job_name: 'kubernetes-pods' 15 kubernetes_sd_configs: 16 - role: pod 17 relabel_configs: 18 - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] 19 action: keep 20 regex: true 21 - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] 22 action: replace 23 target_label: __metrics_path__ 24 regex: (.+) 25 - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port] 26 action: replace 27 regex: ([^:]+)(?::\d+)?;(\d+) 28 replacement: $1:$2 29 target_label: __address__ 30 - action: labelmap 31 regex: __meta_kubernetes_pod_label_(.+) 32 - source_labels: [__meta_kubernetes_namespace] 33 action: replace 34 target_label: kubernetes_namespace 35 - source_labels: [__meta_kubernetes_pod_name] 36 action: replace 37 target_label: kubernetes_pod_name 38 39 - job_name: 'node-exporter' 40 static_configs: 41 - targets: ['node-exporter:9100']
Logging with ELK Stack
yaml1# fluentd-config.yaml 2apiVersion: v1 3kind: ConfigMap 4metadata: 5 name: fluentd-config 6 namespace: logging 7data: 8 fluent.conf: | 9 <source> 10 @type tail 11 path /var/log/containers/*.log 12 pos_file /var/log/fluentd-containers.log.pos 13 tag kubernetes.* 14 read_from_head true 15 <parse> 16 @type json 17 time_format %Y-%m-%dT%H:%M:%S.%NZ 18 </parse> 19 </source> 20 21 <filter kubernetes.**> 22 @type kubernetes_metadata 23 </filter> 24 25 <match **> 26 @type elasticsearch 27 host elasticsearch-logging 28 port 9200 29 logstash_format true 30 logstash_prefix kubernetes 31 include_tag_key true 32 type_name access_log 33 <buffer> 34 flush_mode interval 35 flush_interval 5s 36 </buffer> 37 </match>
Security Best Practices
yaml1# network-policy.yaml 2apiVersion: networking.k8s.io/v1 3kind: NetworkPolicy 4metadata: 5 name: api-network-policy 6spec: 7 podSelector: 8 matchLabels: 9 app: api 10 policyTypes: 11 - Ingress 12 - Egress 13 ingress: 14 - from: 15 - namespaceSelector: 16 matchLabels: 17 name: ingress-nginx 18 ports: 19 - protocol: TCP 20 port: 3000 21 egress: 22 - to: 23 - podSelector: 24 matchLabels: 25 app: postgres 26 ports: 27 - protocol: TCP 28 port: 5432 29 - to: 30 - podSelector: 31 matchLabels: 32 app: redis 33 ports: 34 - protocol: TCP 35 port: 6379