Advanced Cloud Implementation: Microservices & DevOps (Part 2)

Advanced Cloud Implementation

Building on Part 1 fundamentals, we'll now implement advanced cloud architectures with microservices, containers, and DevOps practices. This guide provides complete, production-ready examples you can deploy immediately.

Containerization with Docker

Why Containers?

  • Consistency: Same environment across dev, test, prod
  • Portability: Run anywhere Docker is supported
  • Efficiency: Lightweight compared to VMs
  • Scalability: Quick startup and shutdown

Complete Microservice Implementation

Let's build a complete user management microservice with Node.js, MongoDB, and Docker. This example demonstrates real-world patterns used in production systems.

1. Application Dependencies

First, define our Node.js application dependencies in package.json:

{
  "name": "user-service",
  "version": "1.0.0",
  "main": "server.js",
  "dependencies": {
    "express": "^4.18.0",
    "mongoose": "^6.0.0",
    "cors": "^2.8.5"
  },
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  }
}

2. Microservice Application Code

Create the main application file server.js with REST API endpoints, database connection, and error handling:

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(cors());
app.use(express.json());

// User model with validation
const User = mongoose.model('User', {
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  createdAt: { type: Date, default: Date.now }
});

// Health check endpoint for load balancers
app.get('/health', (req, res) => {
  res.json({ 
    status: 'healthy', 
    timestamp: new Date(),
    service: 'user-service'
  });
});

// Get all users with pagination
app.get('/users', async (req, res) => {
  try {
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 10;
    const skip = (page - 1) * limit;
    
    const users = await User.find().skip(skip).limit(limit);
    const total = await User.countDocuments();
    
    res.json({
      users,
      pagination: { page, limit, total, pages: Math.ceil(total / limit) }
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Create new user
app.post('/users', async (req, res) => {
  try {
    const user = new User(req.body);
    await user.save();
    res.status(201).json(user);
  } catch (error) {
    if (error.code === 11000) {
      res.status(400).json({ error: 'Email already exists' });
    } else {
      res.status(400).json({ error: error.message });
    }
  }
});

// Database connection with retry logic
const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/userdb');
    console.log('Connected to MongoDB');
  } catch (error) {
    console.error('MongoDB connection error:', error);
    setTimeout(connectDB, 5000); // Retry after 5 seconds
  }
};

connectDB();

app.listen(PORT, () => {
  console.log();
});

3. Docker Configuration

Create a Dockerfile using multi-stage builds and security best practices:

# Use official Node.js runtime as base image
FROM node:16-alpine

# Set working directory
WORKDIR /app

# Copy package files first for better caching
COPY package*.json ./

# Install dependencies
RUN npm install --production && npm cache clean --force

# Copy application code
COPY . .

# Expose port
EXPOSE 3000

# Create non-root user for security
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3   CMD curl -f http://localhost:3000/health || exit 1

# Start application
CMD ["npm", "start"]

4. Docker Compose for Local Development

Create docker-compose.yml to orchestrate multiple services locally:

version: '3.8'

services:
  user-service:
    build: .
    ports:
      - "3000:3000"
    environment:
      - MONGODB_URI=mongodb://mongo:27017/userdb
      - NODE_ENV=development
    depends_on:
      - mongo
    restart: unless-stopped
    networks:
      - app-network

  mongo:
    image: mongo:5.0
    ports:
      - "27017:27017"
    volumes:
      - mongo_data:/data/db
    environment:
      - MONGO_INITDB_DATABASE=userdb
    restart: unless-stopped
    networks:
      - app-network

volumes:
  mongo_data:

networks:
  app-network:
    driver: bridge

5. Build and Test the Service

Commands to build, run, and test your containerized microservice:

# Build and start all services
docker-compose up -d

# Check service status
docker-compose ps

# View logs
docker-compose logs user-service

# Test health endpoint
curl http://localhost:3000/health

# Test user creation
curl -X POST http://localhost:3000/users   -H "Content-Type: application/json"   -d '{"name":"John Doe","email":"john@example.com"}'

# Test user retrieval
curl http://localhost:3000/users

Kubernetes Orchestration

Kubernetes provides production-grade container orchestration with auto-scaling, service discovery, and rolling updates. Let's deploy our microservice to Kubernetes.

Complete Kubernetes Deployment

Create k8s/deployment.yaml with deployment, service, and configuration:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
  labels:
    app: user-service
    version: v1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
        version: v1
    spec:
      containers:
      - name: user-service
        image: user-service:latest
        ports:
        - containerPort: 3000
        env:
        - name: MONGODB_URI
          value: "mongodb://mongo-service:27017/userdb"
        - name: NODE_ENV
          value: "production"
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: user-service
  labels:
    app: user-service
spec:
  selector:
    app: user-service
  ports:
  - port: 80
    targetPort: 3000
    protocol: TCP
  type: LoadBalancer
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: user-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: user-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

Deploy to Kubernetes

Commands to deploy and manage your application in Kubernetes:

# Apply deployment configuration
kubectl apply -f k8s/deployment.yaml

# Check deployment status
kubectl get deployments
kubectl get pods
kubectl get services

# Scale deployment manually
kubectl scale deployment user-service --replicas=5

# Check auto-scaling status
kubectl get hpa

# View application logs
kubectl logs -l app=user-service

# Port forward for local testing
kubectl port-forward service/user-service 8080:80

Serverless Architecture with AWS Lambda

Serverless computing eliminates server management while providing automatic scaling and pay-per-use pricing. Let's implement the same user service using AWS Lambda and DynamoDB.

Lambda Function Implementation

Create lambda/handler.js with complete CRUD operations:

const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();

// Main Lambda handler function
exports.handler = async (event) => {
  console.log('Event:', JSON.stringify(event, null, 2));
  
  const { httpMethod, path, body, queryStringParameters } = event;
  
  try {
    // Route requests based on HTTP method and path
    switch (httpMethod) {
      case 'GET':
        if (path === '/health') {
          return createResponse(200, { 
            status: 'healthy', 
            timestamp: new Date().toISOString(),
            service: 'user-service-lambda'
          });
        }
        if (path === '/users') {
          return await getUsers(queryStringParameters);
        }
        break;
        
      case 'POST':
        if (path === '/users') {
          return await createUser(JSON.parse(body || '{}'));
        }
        break;
        
      case 'PUT':
        if (path.startsWith('/users/')) {
          const userId = path.split('/')[2];
          return await updateUser(userId, JSON.parse(body || '{}'));
        }
        break;
        
      case 'DELETE':
        if (path.startsWith('/users/')) {
          const userId = path.split('/')[2];
          return await deleteUser(userId);
        }
        break;
        
      default:
        return createResponse(405, { error: 'Method not allowed' });
    }
    
    return createResponse(404, { error: 'Not found' });
    
  } catch (error) {
    console.error('Error:', error);
    return createResponse(500, { error: error.message });
  }
};

// Get users with pagination
async function getUsers(queryParams = {}) {
  const limit = parseInt(queryParams.limit) || 10;
  const lastKey = queryParams.lastKey ? JSON.parse(queryParams.lastKey) : null;
  
  const params = {
    TableName: 'Users',
    Limit: limit
  };
  
  if (lastKey) {
    params.ExclusiveStartKey = lastKey;
  }
  
  const result = await dynamodb.scan(params).promise();
  
  return createResponse(200, {
    users: result.Items,
    lastKey: result.LastEvaluatedKey,
    count: result.Count
  });
}

// Create new user
async function createUser(userData) {
  const { name, email } = userData;
  
  if (!name || !email) {
    return createResponse(400, { error: 'Name and email are required' });
  }
  
  const user = {
    id: AWS.util.uuid.v4(),
    name,
    email,
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString()
  };
  
  const params = {
    TableName: 'Users',
    Item: user,
    ConditionExpression: 'attribute_not_exists(email)'
  };
  
  try {
    await dynamodb.put(params).promise();
    return createResponse(201, user);
  } catch (error) {
    if (error.code === 'ConditionalCheckFailedException') {
      return createResponse(400, { error: 'Email already exists' });
    }
    throw error;
  }
}

// Update existing user
async function updateUser(userId, updateData) {
  const { name, email } = updateData;
  
  const params = {
    TableName: 'Users',
    Key: { id: userId },
    UpdateExpression: 'SET #name = :name, email = :email, updatedAt = :updatedAt',
    ExpressionAttributeNames: { '#name': 'name' },
    ExpressionAttributeValues: {
      ':name': name,
      ':email': email,
      ':updatedAt': new Date().toISOString()
    },
    ReturnValues: 'ALL_NEW'
  };
  
  const result = await dynamodb.update(params).promise();
  return createResponse(200, result.Attributes);
}

// Delete user
async function deleteUser(userId) {
  const params = {
    TableName: 'Users',
    Key: { id: userId }
  };
  
  await dynamodb.delete(params).promise();
  return createResponse(204, {});
}

// Helper function to create HTTP response
function createResponse(statusCode, body) {
  return {
    statusCode,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization'
    },
    body: JSON.stringify(body)
  };
}

Serverless Framework Configuration

Create serverless.yml for infrastructure as code deployment:

service: user-service-serverless

provider:
  name: aws
  runtime: nodejs16.x
  region: us-east-1
  stage: ${opt:stage, 'dev'}
  
  # IAM permissions for DynamoDB
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource: 
        - "arn:aws:dynamodb:${self:provider.region}:*:table/Users"
        - "arn:aws:dynamodb:${self:provider.region}:*:table/Users/index/*"
  
  # Environment variables
  environment:
    STAGE: ${self:provider.stage}
    REGION: ${self:provider.region}

functions:
  api:
    handler: handler.handler
    events:
      - http:
          path: /{proxy+}
          method: ANY
          cors: true
      - http:
          path: /
          method: ANY
          cors: true
    timeout: 30
    memorySize: 256

# AWS Resources
resources:
  Resources:
    # DynamoDB Table
    UsersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: Users
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
          - AttributeName: email
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        GlobalSecondaryIndexes:
          - IndexName: EmailIndex
            KeySchema:
              - AttributeName: email
                KeyType: HASH
            Projection:
              ProjectionType: ALL
            BillingMode: PAY_PER_REQUEST
        BillingMode: PAY_PER_REQUEST
        
    # CloudWatch Log Group
    ApiLogGroup:
      Type: AWS::Logs::LogGroup
      Properties:
        LogGroupName: /aws/lambda/${self:service}-${self:provider.stage}-api
        RetentionInDays: 14

# Plugins
plugins:
  - serverless-offline
  - serverless-dotenv-plugin

# Package configuration
package:
  exclude:
    - node_modules/**
    - .git/**
    - .env
    - README.md

Deploy Serverless Application

Commands to deploy and test your serverless application:

# Install Serverless Framework globally
npm install -g serverless

# Install project dependencies
npm install

# Deploy to AWS
serverless deploy

# Deploy specific function
serverless deploy function -f api

# Test locally
serverless offline

# View logs
serverless logs -f api -t

# Remove deployment
serverless remove

CI/CD Pipeline with GitHub Actions

Implement automated testing, building, and deployment pipeline for continuous integration and delivery.

Complete GitHub Actions Workflow

Create .github/workflows/deploy.yml for automated deployments:

name: Deploy Cloud Application

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

env:
  AWS_REGION: us-east-1
  ECR_REPOSITORY: user-service
  EKS_CLUSTER_NAME: production-cluster

jobs:
  # Test job runs on all branches
  test:
    runs-on: ubuntu-latest
    
    services:
      mongodb:
        image: mongo:5.0
        ports:
          - 27017:27017
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '16'
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run linting
      run: npm run lint
    
    - name: Run unit tests
      run: npm test
      env:
        MONGODB_URI: mongodb://localhost:27017/test
    
    - name: Run integration tests
      run: npm run test:integration
      env:
        MONGODB_URI: mongodb://localhost:27017/test

  # Security scanning
  security:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Run security audit
      run: npm audit --audit-level high
    
    - name: Run Snyk security scan
      uses: snyk/actions/node@master
      env:
        SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

  # Build and push Docker image
  build:
    needs: [test, security]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v3
    
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ env.AWS_REGION }}
    
    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1
    
    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v4
      with:
        images: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}
        tags: |
          type=ref,event=branch
          type=sha,prefix={{branch}}-
          type=raw,value=latest,enable={{is_default_branch}}
    
    - name: Build and push Docker image
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        cache-from: type=gha
        cache-to: type=gha,mode=max

  # Deploy to Kubernetes
  deploy-k8s:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v3
    
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ env.AWS_REGION }}
    
    - name: Update kubeconfig
      run: |
        aws eks update-kubeconfig --region ${{ env.AWS_REGION }} --name ${{ env.EKS_CLUSTER_NAME }}
    
    - name: Deploy to Kubernetes
      run: |
        # Update image in deployment
        kubectl set image deployment/user-service user-service=${{ needs.build.outputs.image-tag }}
        
        # Wait for rollout to complete
        kubectl rollout status deployment/user-service --timeout=300s
        
        # Verify deployment
        kubectl get pods -l app=user-service

  # Deploy serverless (parallel to K8s)
  deploy-serverless:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '16'
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ env.AWS_REGION }}
    
    - name: Deploy with Serverless Framework
      run: |
        npm install -g serverless
        serverless deploy --stage production

Infrastructure as Code with Terraform

Manage your cloud infrastructure using code for version control, repeatability, and collaboration.

Complete AWS Infrastructure

Create terraform/main.tf for complete AWS infrastructure:

# Configure Terraform and AWS provider
terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  
  # Remote state storage
  backend "s3" {
    bucket = "my-terraform-state-bucket"
    key    = "user-service/terraform.tfstate"
    region = "us-east-1"
  }
}

provider "aws" {
  region = var.aws_region
  
  default_tags {
    tags = {
      Project     = "user-service"
      Environment = var.environment
      ManagedBy   = "terraform"
    }
  }
}

# Data sources
data "aws_availability_zones" "available" {
  state = "available"
}

# VPC and Networking
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "${var.project_name}-vpc"
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${var.project_name}-igw"
  }
}

# Public subnets for load balancers
resource "aws_subnet" "public" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 1}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]

  map_public_ip_on_launch = true

  tags = {
    Name = "${var.project_name}-public-subnet-${count.index + 1}"
    Type = "public"
  }
}

# Private subnets for application workloads
resource "aws_subnet" "private" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 10}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name = "${var.project_name}-private-subnet-${count.index + 1}"
    Type = "private"
  }
}

# NAT Gateways for private subnet internet access
resource "aws_eip" "nat" {
  count  = 2
  domain = "vpc"

  tags = {
    Name = "${var.project_name}-nat-eip-${count.index + 1}"
  }
}

resource "aws_nat_gateway" "main" {
  count         = 2
  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id

  tags = {
    Name = "${var.project_name}-nat-${count.index + 1}"
  }

  depends_on = [aws_internet_gateway.main]
}

# Route tables
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name = "${var.project_name}-public-rt"
  }
}

resource "aws_route_table" "private" {
  count  = 2
  vpc_id = aws_vpc.main.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.main[count.index].id
  }

  tags = {
    Name = "${var.project_name}-private-rt-${count.index + 1}"
  }
}

# Route table associations
resource "aws_route_table_association" "public" {
  count          = 2
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "private" {
  count          = 2
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private[count.index].id
}

# Security Groups
resource "aws_security_group" "eks_cluster" {
  name_prefix = "${var.project_name}-eks-cluster"
  vpc_id      = aws_vpc.main.id

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.project_name}-eks-cluster-sg"
  }
}

resource "aws_security_group" "eks_nodes" {
  name_prefix = "${var.project_name}-eks-nodes"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port = 0
    to_port   = 65535
    protocol  = "tcp"
    self      = true
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.project_name}-eks-nodes-sg"
  }
}

# EKS Cluster
resource "aws_eks_cluster" "main" {
  name     = var.cluster_name
  role_arn = aws_iam_role.cluster.arn
  version  = "1.24"

  vpc_config {
    subnet_ids              = concat(aws_subnet.public[*].id, aws_subnet.private[*].id)
    endpoint_private_access = true
    endpoint_public_access  = true
    security_group_ids      = [aws_security_group.eks_cluster.id]
  }

  enabled_cluster_log_types = ["api", "audit", "authenticator", "controllerManager", "scheduler"]

  depends_on = [
    aws_iam_role_policy_attachment.cluster_AmazonEKSClusterPolicy,
    aws_cloudwatch_log_group.eks_cluster,
  ]

  tags = {
    Name = var.cluster_name
  }
}

# EKS Node Group
resource "aws_eks_node_group" "main" {
  cluster_name    = aws_eks_cluster.main.name
  node_group_name = "main-nodes"
  node_role_arn   = aws_iam_role.node.arn
  subnet_ids      = aws_subnet.private[*].id

  scaling_config {
    desired_size = var.node_desired_size
    max_size     = var.node_max_size
    min_size     = var.node_min_size
  }

  update_config {
    max_unavailable = 1
  }

  instance_types = var.node_instance_types
  capacity_type  = "ON_DEMAND"
  disk_size      = 20

  depends_on = [
    aws_iam_role_policy_attachment.node_AmazonEKSWorkerNodePolicy,
    aws_iam_role_policy_attachment.node_AmazonEKS_CNI_Policy,
    aws_iam_role_policy_attachment.node_AmazonEC2ContainerRegistryReadOnly,
  ]

  tags = {
    Name = "${var.project_name}-node-group"
  }
}

# CloudWatch Log Group for EKS
resource "aws_cloudwatch_log_group" "eks_cluster" {
  name              = "/aws/eks/${var.cluster_name}/cluster"
  retention_in_days = 7
}

# ECR Repository
resource "aws_ecr_repository" "app" {
  name                 = var.project_name
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }

  tags = {
    Name = "${var.project_name}-ecr"
  }
}

# Output values
output "cluster_endpoint" {
  description = "Endpoint for EKS control plane"
  value       = aws_eks_cluster.main.endpoint
}

output "cluster_security_group_id" {
  description = "Security group ids attached to the cluster control plane"
  value       = aws_eks_cluster.main.vpc_config[0].cluster_security_group_id
}

output "ecr_repository_url" {
  description = "ECR repository URL"
  value       = aws_ecr_repository.app.repository_url
}

Deploy Infrastructure

Commands to deploy and manage your infrastructure:

# Initialize Terraform
terraform init

# Plan deployment
terraform plan -var-file="production.tfvars"

# Apply changes
terraform apply -var-file="production.tfvars"

# Show current state
terraform show

# Destroy infrastructure (when needed)
terraform destroy -var-file="production.tfvars"

Monitoring and Observability

Implement comprehensive monitoring, logging, and alerting for production systems.

Application Monitoring Setup

Add monitoring to your Node.js application:

const AWS = require('aws-sdk');
const cloudwatch = new AWS.CloudWatch();

// Custom metrics helper
class MetricsCollector {
  constructor(namespace = 'UserService') {
    this.namespace = namespace;
    this.cloudwatch = new AWS.CloudWatch();
  }

  async publishMetric(metricName, value, unit = 'Count', dimensions = {}) {
    const params = {
      Namespace: this.namespace,
      MetricData: [{
        MetricName: metricName,
        Value: value,
        Unit: unit,
        Timestamp: new Date(),
        Dimensions: Object.entries(dimensions).map(([Name, Value]) => ({ Name, Value }))
      }]
    };
    
    try {
      await this.cloudwatch.putMetricData(params).promise();
    } catch (error) {
      console.error('Error publishing metric:', error);
    }
  }

  async publishTimer(metricName, startTime, dimensions = {}) {
    const duration = Date.now() - startTime;
    await this.publishMetric(metricName, duration, 'Milliseconds', dimensions);
  }
}

const metrics = new MetricsCollector();

// Middleware for request monitoring
app.use(async (req, res, next) => {
  const startTime = Date.now();
  
  res.on('finish', async () => {
    const duration = Date.now() - startTime;
    
    // Publish metrics
    await metrics.publishMetric('RequestCount', 1, 'Count', {
      Method: req.method,
      Route: req.route?.path || req.path,
      StatusCode: res.statusCode.toString()
    });
    
    await metrics.publishMetric('ResponseTime', duration, 'Milliseconds', {
      Method: req.method,
      Route: req.route?.path || req.path
    });
    
    // Log request details
    console.log(JSON.stringify({
      timestamp: new Date().toISOString(),
      method: req.method,
      path: req.path,
      statusCode: res.statusCode,
      duration,
      userAgent: req.get('User-Agent'),
      ip: req.ip
    }));
  });
  
  next();
});

// Enhanced error handling with metrics
app.use(async (error, req, res, next) => {
  console.error('Application error:', error);
  
  // Publish error metric
  await metrics.publishMetric('ErrorCount', 1, 'Count', {
    ErrorType: error.name || 'UnknownError',
    Route: req.route?.path || req.path
  });
  
  res.status(500).json({
    error: 'Internal server error',
    requestId: req.id || 'unknown'
  });
});

Production Best Practices

Security

  • Use IAM roles instead of access keys
  • Enable encryption at rest and in transit
  • Implement network segmentation with VPCs
  • Regular security audits and penetration testing
  • Use secrets management services

Performance

  • Implement caching strategies (Redis, CloudFront)
  • Use CDNs for static content delivery
  • Database query optimization and indexing
  • Auto-scaling based on metrics
  • Load balancing and health checks

Reliability

  • Multi-AZ deployments for high availability
  • Circuit breaker patterns for fault tolerance
  • Comprehensive monitoring and alerting
  • Disaster recovery and backup strategies
  • Blue-green deployments for zero downtime

Next Steps in Your Cloud Journey

  1. Practice: Deploy these examples in your own AWS account
  2. Experiment: Modify configurations and observe the results
  3. Scale: Add more services and implement service mesh
  4. Optimize: Focus on cost optimization and performance tuning
  5. Specialize: Choose specific areas like ML, IoT, or data analytics
  6. Certify: Pursue advanced cloud certifications
  7. Contribute: Share your knowledge and contribute to open source

Cloud computing continues to evolve rapidly with new services and capabilities. The key to success is hands-on practice, continuous learning, and staying current with cloud provider announcements and industry best practices.

Start with these examples, adapt them to your needs, and gradually build more complex, production-ready systems. Remember that cloud architecture is about solving business problems efficiently and securely.

Ready to Test Your Knowledge?

Put your skills to the test with our comprehensive quiz platform

Feedback