DevOps Best Practices: CI/CD Pipelines and Infrastructure as Code

DevOps practices bridge the gap between development and operations, enabling faster, more reliable software delivery. This guide covers essential DevOps patterns and tools.

Continuous Integration (CI)

Automated Testing Strategy

Implement comprehensive testing in your CI pipeline:

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: "18"
      - name: Install dependencies
        run: npm ci
      - name: Run linting
        run: npm run lint
      - name: Run unit tests
        run: npm run test:unit
      - name: Run integration tests
        run: npm run test:integration
      - name: Generate coverage report
        run: npm run coverage

Code Quality Gates

Enforce quality standards:

// Jest configuration for test coverage
export default {
  collectCoverageFrom: [
    "src/**/*.{js,ts}",
    "!src/**/*.d.ts",
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
};

Continuous Deployment (CD)

Multi-Environment Deployment

Manage deployments across environments:

# staging deployment
deploy-staging:
  stage: deploy
  script:
    - docker build -t myapp:$CI_COMMIT_SHA .
    - docker tag myapp:$CI_COMMIT_SHA myregistry.com/myapp:staging
    - docker push myregistry.com/myapp:staging
    - kubectl set image deployment/myapp app=myregistry.com/myapp:staging
  environment:
    name: staging
    url: https://staging.myapp.com
  only:
    - develop

# production deployment
deploy-production:
  stage: deploy
  script:
    - docker build -t myapp:$CI_COMMIT_SHA .
    - docker tag myapp:$CI_COMMIT_SHA myregistry.com/myapp:$CI_COMMIT_TAG
    - docker push myregistry.com/myapp:$CI_COMMIT_TAG
    - kubectl set image deployment/myapp app=myregistry.com/myapp:$CI_COMMIT_TAG
  environment:
    name: production
    url: https://myapp.com
  only:
    - tags
  when: manual

Blue-Green Deployments

Zero-downtime deployment strategy:

#!/bin/bash
# Blue-green deployment script

BLUE="myapp-blue"
GREEN="myapp-green"

# Determine which environment is live
LIVE=$(kubectl get svc myapp -o jsonpath='{.spec.selector.version}')

if [ "$LIVE" = "blue" ]; then
  TARGET=$GREEN
  NEXT="green"
else
  TARGET=$BLUE
  NEXT="blue"
fi

# Deploy to target environment
kubectl set image deployment/$TARGET app=myregistry.com/myapp:$VERSION

# Wait for deployment to be ready
kubectl rollout status deployment/$TARGET

# Switch traffic to new version
kubectl patch svc myapp -p "{\"spec\":{\"selector\":{\"version\":\"$NEXT\"}}}"

# Clean up old version
kubectl scale deployment/$LIVE --replicas=0

Infrastructure as Code (IaC)

Terraform for Infrastructure Management

Define infrastructure declaratively:

# main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1d0"
  instance_type = "t3.micro"

  tags = {
    Name = "WebServer"
  }
}

resource "aws_db_instance" "default" {
  allocated_storage    = 20
  storage_type         = "gp2"
  engine               = "mysql"
  engine_version       = "8.0"
  instance_class       = "db.t3.micro"
  username             = "admin"
  password             = var.db_password
  parameter_group_name = "default.mysql8.0"
  skip_final_snapshot  = true
}

Kubernetes Manifests

Container orchestration with IaC:

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: myregistry.com/myapp:latest
          ports:
            - containerPort: 3000
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: url
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "256Mi"
              cpu: "200m"
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /ready
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 5

Configuration Management

Environment-Specific Configuration

Manage configuration across environments:

// config.ts
interface Config {
  database: {
    host: string;
    port: number;
    database: string;
    username: string;
    password: string;
  };
  redis: {
    host: string;
    port: number;
  };
  api: {
    port: number;
    corsOrigins: string[];
  };
}

function loadConfig(): Config {
  const env = process.env.NODE_ENV || "development";

  return {
    database: {
      host: process.env.DB_HOST || "localhost",
      port: parseInt(process.env.DB_PORT || "5432"),
      database: process.env.DB_NAME || "myapp",
      username: process.env.DB_USER || "user",
      password: process.env.DB_PASSWORD || "",
    },
    redis: {
      host: process.env.REDIS_HOST || "localhost",
      port: parseInt(process.env.REDIS_PORT || "6379"),
    },
    api: {
      port: parseInt(process.env.PORT || "3000"),
      corsOrigins: (process.env.CORS_ORIGINS || "http://localhost:3000").split(
        ",",
      ),
    },
  };
}

export const config = loadConfig();

Monitoring and Observability

Application Monitoring

Implement comprehensive monitoring:

// monitoring.ts
import { collectDefaultMetrics } from "prom-client";

collectDefaultMetrics();

const httpRequestDuration = new Histogram({
  name: "http_request_duration_seconds",
  help: "Duration of HTTP requests in seconds",
  labelNames: ["method", "route", "status_code"],
});

export function observeRequest(
  method: string,
  route: string,
  statusCode: number,
  duration: number,
) {
  httpRequestDuration
    .labels(method, route, statusCode.toString())
    .observe(duration);
}

Log Aggregation

Centralized logging with structured logs:

// logger.ts
import winston from "winston";

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || "info",
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json(),
  ),
  defaultMeta: { service: "myapp" },
  transports: [
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple(),
      ),
    }),
    new winston.transports.File({ filename: "error.log", level: "error" }),
    new winston.transports.File({ filename: "combined.log" }),
  ],
});

export default logger;

Security in DevOps

Secret Management

Securely manage secrets:

# secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  username: bXlzcWw= # base64 encoded
  password: cGFzc3dvcmQ= # base64 encoded

Vulnerability Scanning

Automated security scanning in pipelines:

# security-scan.yml
security-scan:
  stage: security
  script:
    - docker run --rm -v $(pwd):/src aquasecurity/trivy fs /src
    - npm audit --audit-level high
    - safety check
  allow_failure: false

Disaster Recovery

Backup Strategies

Automated backup solutions:

#!/bin/bash
# backup.sh

DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups"
DB_NAME="myapp"

# Database backup
pg_dump -h <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>D</mi><msub><mi>B</mi><mi>H</mi></msub><mi>O</mi><mi>S</mi><mi>T</mi><mo></mo><mi>U</mi></mrow><annotation encoding="application/x-tex">DB_HOST -U </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.0502em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.08125em;">H</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal" style="margin-right:0.13889em;">OST</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin"></span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">U</span></span></span></span>DB_USER -d <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>D</mi><msub><mi>B</mi><mi>N</mi></msub><mi>A</mi><mi>M</mi><mi>E</mi><mo>&gt;</mo></mrow><annotation encoding="application/x-tex">DB_NAME &gt; </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.0502em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal">A</span><span class="mord mathnormal" style="margin-right:0.05764em;">ME</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&gt;</span></span></span></span>BACKUP_DIR/db_$DATE.sql

# Compress backup
gzip $BACKUP_DIR/db_$DATE.sql

# Upload to cloud storage
aws s3 cp $BACKUP_DIR/db_$DATE.sql.gz s3://myapp-backups/

# Clean up old backups (keep last 7 days)
find $BACKUP_DIR -name "db_*.sql.gz" -mtime +7 -delete

Recovery Testing

Regular disaster recovery testing:

# recovery-test.yml
recovery-test:
  stage: recovery
  script:
    - echo "Starting disaster recovery test"
    - ./scripts/backup.sh
    - ./scripts/simulate-disaster.sh
    - ./scripts/restore.sh
    - ./scripts/verify-recovery.sh
  only:
    schedules:
      - "0 2 * * 0" # Weekly on Sunday at 2 AM

Conclusion

Implementing DevOps best practices requires cultural change and the right tools. Start small, automate gradually, and focus on delivering value while maintaining reliability and security.