DevOps Best Practices: CI/CD Pipelines and Infrastructure as Code
Master modern DevOps practices including automated testing, continuous integration, deployment pipelines, and infrastructure automation.
Table of Contents
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 coverageCode 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: manualBlue-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=0Infrastructure 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: 5Configuration 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 encodedVulnerability 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: falseDisaster 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>></mo></mrow><annotation encoding="application/x-tex">DB_NAME > </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">></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 -deleteRecovery 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 AMConclusion
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.