JSTAcademy
0 XP
Dashboard
Crash Courses
DevOps on AWS — CloudFormation, CDK & CloudWatch
19 min
PhD+250 XP
Crash Courses · PhD

DevOps on AWS — CloudFormation, CDK & CloudWatch

Infrastructure as code, CI/CD pipelines, and production observability
19 min read+250 XP on completionCert: AWS Crash Course
Tap any word in the text below to start reading from there.

DevOps on AWS — CloudFormation, CDK & CloudWatch

Why Infrastructure as Code

Clicking through the AWS console creates infrastructure nobody can reproduce. Infrastructure as Code (IaC) means:

  • Reproducibility — run the same template in dev, staging, prod
  • Version control — infra changes go through pull requests
  • Auditability — what changed and when is in git history
  • Automation — pipelines deploy infra changes automatically

CDK — Infrastructure in TypeScript

CDK lets you write AWS infrastructure in TypeScript (or Python, Java, etc.) with full IDE support, type safety, and reuse.

import * as cdk from 'aws-cdk-lib'
import * as s3 from 'aws-cdk-lib/aws-s3'
import * as lambda from 'aws-cdk-lib/aws-lambda'
import * as apigateway from 'aws-cdk-lib/aws-apigateway'

export class MyAppStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    // S3 bucket for uploads
    const bucket = new s3.Bucket(this, 'UploadsBucket', {
      encryption: s3.BucketEncryption.S3_MANAGED,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      versioned: true,
    })

    // Lambda function
    const handler = new lambda.Function(this, 'ApiHandler', {
      runtime: lambda.Runtime.NODEJS_20_X,
      code: lambda.Code.fromAsset('lambda'),
      handler: 'index.handler',
      environment: {
        BUCKET_NAME: bucket.bucketName,
      },
    })

    // Grant Lambda read/write access to the bucket
    bucket.grantReadWrite(handler)

    // API Gateway in front of Lambda
    new apigateway.RestApi(this, 'Api', {
      defaultIntegration: new apigateway.LambdaIntegration(handler),
    })
  }
}
cdk synth   # Generate CloudFormation template (inspect what will be created)
cdk diff    # Show changes vs. deployed stack
cdk deploy  # Deploy the stack

CI/CD with GitHub Actions → AWS

The most common pipeline pattern in 2024: GitHub Actions triggers on push, builds and pushes a Docker image to ECR, then deploys to ECS Fargate.

# .github/workflows/deploy.yml
name: Deploy to ECS

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/github-actions-role
          aws-region: us-east-1

      - name: Login to ECR
        id: ecr-login
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build and push image
        run: |
          IMAGE=${{ steps.ecr-login.outputs.registry }}/my-app:${{ github.sha }}
          docker build -t $IMAGE .
          docker push $IMAGE
          echo "IMAGE=$IMAGE" >> $GITHUB_ENV

      - name: Deploy to ECS
        run: |
          aws ecs update-service \
            --cluster my-cluster \
            --service my-service \
            --force-new-deployment

OIDC for GitHub Actions — use IAM roles with OIDC instead of long-lived access keys. GitHub Actions requests a short-lived token from AWS; no secrets stored in GitHub.

CloudWatch Observability

The three pillars of observability: metrics, logs, traces.

Structured logging from Lambda/Node.js:

export const handler = async (event) => {
  console.log(JSON.stringify({
    level: 'INFO',
    requestId: event.requestContext?.requestId,
    path: event.rawPath,
    message: 'Request received',
  }))

  try {
    const result = await processRequest(event)
    return { statusCode: 200, body: JSON.stringify(result) }
  } catch (err) {
    console.error(JSON.stringify({
      level: 'ERROR',
      message: err.message,
      stack: err.stack,
    }))
    return { statusCode: 500, body: 'Internal Server Error' }
  }
}

CloudWatch Alarms — essential alarms for production:

Lambda Error Rate    > 1%  → PagerDuty/SNS alert
Lambda P99 Duration > 3s  → Slack alert
RDS CPU             > 80% → Slack alert
ALB 5xx Rate        > 0.1% → PagerDuty alert
SQS Queue Depth     > 1000 → Slack alert (processing backed up)

Log Insights query for error analysis:

fields @timestamp, level, message, requestId
| filter level = "ERROR"
| sort @timestamp desc
| limit 20
0%