Skip to content
Home / Skills / Aws / Compute
AW

Compute

Aws core v1.0.0

AWS Compute Services

Overview

This skill covers AWS compute services including EC2, Lambda, ECS, and Fargate. Understanding when to use each compute option is critical for building scalable, cost-effective applications on AWS.


Key Concepts

Compute Decision Tree

┌─────────────────────────────────────────────────────────────┐
│                  AWS Compute Decision Tree                   │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  What's your workload?                                       │
│       │                                                      │
│       ├── Short-lived, event-driven?                        │
│       │       └── Lambda (serverless)                       │
│       │                                                      │
│       ├── Long-running containers?                          │
│       │       ├── Want to manage servers?                   │
│       │       │       └── ECS on EC2                        │
│       │       └── Serverless containers?                    │
│       │               └── ECS on Fargate                    │
│       │                                                      │
│       ├── Kubernetes required?                              │
│       │       └── EKS (Fargate or EC2)                      │
│       │                                                      │
│       └── Full server control needed?                       │
│               └── EC2                                        │
│                                                              │
│  Cost vs Control Spectrum:                                   │
│                                                              │
│  Lambda ◄──── Fargate ◄──── ECS/EC2 ◄──── EC2              │
│  (Least      (Medium)      (More)        (Full             │
│   Control)                               Control)           │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Lambda Limits

ResourceDefault Limit
Memory128 MB - 10 GB
Timeout15 minutes max
Package Size50 MB (zipped), 250 MB (unzipped)
Concurrent Executions1000 per region
Payload Size6 MB sync, 256 KB async

Best Practices

1. Right-Size Compute Resources

Match instance types to workload requirements.

2. Use Spot Instances for Fault-Tolerant Workloads

Save up to 90% on EC2 costs for interruptible workloads.

3. Enable Auto Scaling

Scale based on demand to optimize cost and performance.

4. Use Graviton Processors

ARM-based processors offer better price/performance.

5. Implement Proper Health Checks

Ensure load balancers can detect unhealthy instances.


Code Examples

Example 1: Lambda Function with CDK

import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as iam from 'aws-cdk-lib/aws-iam';

export class OrderServiceStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Lambda function with best practices
    const orderFunction = new lambda.Function(this, 'OrderFunction', {
      runtime: lambda.Runtime.NODEJS_20_X,
      architecture: lambda.Architecture.ARM_64, // Graviton - better price/performance
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/order'),
      
      // Memory/timeout configuration
      memorySize: 1024,
      timeout: cdk.Duration.seconds(30),
      
      // Environment
      environment: {
        TABLE_NAME: orderTable.tableName,
        LOG_LEVEL: 'INFO',
        POWERTOOLS_SERVICE_NAME: 'order-service',
      },
      
      // Observability
      tracing: lambda.Tracing.ACTIVE,
      logRetention: logs.RetentionDays.ONE_WEEK,
      
      // Performance
      reservedConcurrentExecutions: 100, // Limit concurrency
      
      // Insights
      insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_135_0,
    });

    // Grant permissions
    orderTable.grantReadWriteData(orderFunction);
    
    // API Gateway integration
    const api = new apigateway.RestApi(this, 'OrderApi', {
      restApiName: 'Order Service',
      deployOptions: {
        stageName: 'prod',
        tracingEnabled: true,
        loggingLevel: apigateway.MethodLoggingLevel.INFO,
        metricsEnabled: true,
      },
    });

    const ordersResource = api.root.addResource('orders');
    ordersResource.addMethod('POST', 
      new apigateway.LambdaIntegration(orderFunction, {
        proxy: true,
        allowTestInvoke: false,
      })
    );
  }
}

Example 2: ECS Fargate Service

import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';

export class OrderServiceEcsStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // VPC
    const vpc = new ec2.Vpc(this, 'OrderVpc', {
      maxAzs: 3,
      natGateways: 1,
    });

    // ECS Cluster
    const cluster = new ecs.Cluster(this, 'OrderCluster', {
      vpc,
      containerInsights: true,
      enableFargateCapacityProviders: true,
    });

    // Task Definition
    const taskDefinition = new ecs.FargateTaskDefinition(this, 'OrderTaskDef', {
      memoryLimitMiB: 512,
      cpu: 256,
      runtimePlatform: {
        operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
        cpuArchitecture: ecs.CpuArchitecture.ARM64, // Graviton
      },
    });

    // Container
    const container = taskDefinition.addContainer('OrderContainer', {
      image: ecs.ContainerImage.fromEcrRepository(orderRepo, 'latest'),
      logging: ecs.LogDrivers.awsLogs({
        streamPrefix: 'order-service',
        logRetention: logs.RetentionDays.ONE_WEEK,
      }),
      environment: {
        SPRING_PROFILES_ACTIVE: 'prod',
      },
      healthCheck: {
        command: ['CMD-SHELL', 'curl -f http://localhost:8080/actuator/health || exit 1'],
        interval: cdk.Duration.seconds(30),
        timeout: cdk.Duration.seconds(5),
        retries: 3,
      },
    });

    container.addPortMappings({
      containerPort: 8080,
      protocol: ecs.Protocol.TCP,
    });

    // Service with Auto Scaling
    const service = new ecs.FargateService(this, 'OrderService', {
      cluster,
      taskDefinition,
      desiredCount: 2,
      minHealthyPercent: 100,
      maxHealthyPercent: 200,
      
      // Capacity providers for cost optimization
      capacityProviderStrategies: [
        {
          capacityProvider: 'FARGATE_SPOT',
          weight: 2,
          base: 0,
        },
        {
          capacityProvider: 'FARGATE',
          weight: 1,
          base: 1, // Always have 1 on-demand
        },
      ],
      
      // Circuit breaker
      circuitBreaker: {
        rollback: true,
      },
    });

    // Auto Scaling
    const scaling = service.autoScaleTaskCount({
      minCapacity: 2,
      maxCapacity: 10,
    });

    scaling.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 70,
      scaleInCooldown: cdk.Duration.seconds(60),
      scaleOutCooldown: cdk.Duration.seconds(60),
    });

    scaling.scaleOnRequestCount('RequestScaling', {
      requestsPerTarget: 1000,
      targetGroup: targetGroup,
    });

    // Load Balancer
    const alb = new elbv2.ApplicationLoadBalancer(this, 'OrderALB', {
      vpc,
      internetFacing: true,
    });

    const listener = alb.addListener('HttpListener', {
      port: 443,
      certificates: [certificate],
    });

    const targetGroup = listener.addTargets('OrderTargets', {
      port: 8080,
      targets: [service],
      healthCheck: {
        path: '/actuator/health',
        interval: cdk.Duration.seconds(30),
        healthyThresholdCount: 2,
        unhealthyThresholdCount: 3,
      },
      deregistrationDelay: cdk.Duration.seconds(30),
    });
  }
}

Example 3: EC2 Auto Scaling Group

import * as autoscaling from 'aws-cdk-lib/aws-autoscaling';

export class OrderServiceEc2Stack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Launch Template
    const launchTemplate = new ec2.LaunchTemplate(this, 'OrderLaunchTemplate', {
      instanceType: ec2.InstanceType.of(
        ec2.InstanceClass.T4G, 
        ec2.InstanceSize.MEDIUM
      ),
      machineImage: ec2.MachineImage.latestAmazonLinux2023({
        cpuType: ec2.AmazonLinuxCpuType.ARM_64,
      }),
      securityGroup: appSecurityGroup,
      role: instanceRole,
      
      userData: ec2.UserData.custom(`#!/bin/bash
        yum update -y
        yum install -y docker
        systemctl start docker
        systemctl enable docker
        
        # Pull and run application
        aws ecr get-login-password --region ${region} | docker login --username AWS --password-stdin ${accountId}.dkr.ecr.${region}.amazonaws.com
        docker pull ${ecrRepo}:latest
        docker run -d -p 8080:8080 ${ecrRepo}:latest
      `),
      
      blockDevices: [
        {
          deviceName: '/dev/xvda',
          volume: ec2.BlockDeviceVolume.ebs(20, {
            encrypted: true,
            volumeType: ec2.EbsDeviceVolumeType.GP3,
          }),
        },
      ],
    });

    // Auto Scaling Group with Mixed Instances
    const asg = new autoscaling.AutoScalingGroup(this, 'OrderASG', {
      vpc,
      minCapacity: 2,
      maxCapacity: 10,
      desiredCapacity: 2,
      
      mixedInstancesPolicy: {
        instancesDistribution: {
          onDemandBaseCapacity: 1,
          onDemandPercentageAboveBaseCapacity: 25,
          spotAllocationStrategy: autoscaling.SpotAllocationStrategy.CAPACITY_OPTIMIZED,
        },
        launchTemplate: launchTemplate,
        launchTemplateOverrides: [
          { instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.MEDIUM) },
          { instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.LARGE) },
          { instanceType: ec2.InstanceType.of(ec2.InstanceClass.M6G, ec2.InstanceSize.MEDIUM) },
        ],
      },
      
      healthCheck: autoscaling.HealthCheck.elb({
        grace: cdk.Duration.minutes(5),
      }),
      
      updatePolicy: autoscaling.UpdatePolicy.rollingUpdate({
        maxBatchSize: 1,
        minInstancesInService: 1,
        pauseTime: cdk.Duration.minutes(5),
      }),
    });

    // Scaling Policies
    asg.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 70,
    });

    asg.scaleOnMetric('QueueDepthScaling', {
      metric: sqsQueue.metricApproximateNumberOfMessagesVisible(),
      scalingSteps: [
        { upper: 0, change: -1 },
        { lower: 100, change: +1 },
        { lower: 500, change: +3 },
      ],
      adjustmentType: autoscaling.AdjustmentType.CHANGE_IN_CAPACITY,
    });
  }
}

Example 4: Lambda Powertools

// Lambda handler with Powertools
import { Logger } from '@aws-lambda-powertools/logger';
import { Tracer } from '@aws-lambda-powertools/tracer';
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';
import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware';
import { captureLambdaHandler } from '@aws-lambda-powertools/tracer/middleware';
import { logMetrics } from '@aws-lambda-powertools/metrics/middleware';
import middy from '@middy/core';

const logger = new Logger({ serviceName: 'order-service' });
const tracer = new Tracer({ serviceName: 'order-service' });
const metrics = new Metrics({ namespace: 'OrderService', serviceName: 'order-service' });

interface OrderEvent {
  body: string;
  requestContext: { requestId: string };
}

const createOrderHandler = async (event: OrderEvent) => {
  const correlationId = event.requestContext.requestId;
  
  // Add correlation ID to all logs
  logger.appendKeys({ correlationId });
  
  logger.info('Processing order request');
  
  // Create traced segment
  const segment = tracer.getSegment();
  const subsegment = segment?.addNewSubsegment('processOrder');
  
  try {
    const order = JSON.parse(event.body);
    
    // Trace external calls
    tracer.putAnnotation('customerId', order.customerId);
    tracer.putMetadata('orderItems', order.items);
    
    const result = await processOrder(order);
    
    // Record metrics
    metrics.addMetric('OrderCreated', MetricUnits.Count, 1);
    metrics.addMetric('OrderValue', MetricUnits.Count, result.total);
    
    logger.info('Order created successfully', { orderId: result.id });
    
    return {
      statusCode: 201,
      body: JSON.stringify(result),
    };
  } catch (error) {
    logger.error('Order creation failed', error as Error);
    metrics.addMetric('OrderFailed', MetricUnits.Count, 1);
    
    subsegment?.addError(error as Error);
    
    throw error;
  } finally {
    subsegment?.close();
  }
};

export const handler = middy(createOrderHandler)
  .use(injectLambdaContext(logger))
  .use(captureLambdaHandler(tracer))
  .use(logMetrics(metrics));

Example 5: Container Optimization

# Multi-stage build for Java application
FROM eclipse-temurin:21-jdk-alpine AS builder

WORKDIR /app

# Cache dependencies
COPY pom.xml .
COPY .mvn .mvn
COPY mvnw .
RUN ./mvnw dependency:go-offline -B

# Build application
COPY src src
RUN ./mvnw package -DskipTests -Pnative

# Runtime stage
FROM eclipse-temurin:21-jre-alpine

# Security: non-root user
RUN addgroup -S app && adduser -S app -G app
USER app

WORKDIR /app

# Copy only the jar
COPY --from=builder /app/target/*.jar app.jar

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
  CMD wget -q --spider http://localhost:8080/actuator/health || exit 1

# JVM tuning for containers
ENV JAVA_OPTS="-XX:+UseContainerSupport \
  -XX:MaxRAMPercentage=75.0 \
  -XX:InitialRAMPercentage=50.0 \
  -XX:+UseG1GC \
  -XX:+UseStringDeduplication \
  -Djava.security.egd=file:/dev/./urandom"

EXPOSE 8080

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

Anti-Patterns

❌ Over-Provisioning

# WRONG - paying for unused capacity
Resources:
  Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: m5.4xlarge  # 16 vCPU, 64 GB for a simple web app

# ✅ CORRECT - right-size based on metrics
      InstanceType: t3.medium  # Start small, scale up based on data

❌ Lambda Cold Start Ignorance

// WRONG - connection per invocation
export const handler = async () => {
  const client = new DynamoDBClient({}); // Cold start penalty
  // ...
};

// ✅ CORRECT - reuse connections
const client = new DynamoDBClient({}); // Initialized once
export const handler = async () => {
  // Reuse client
};

References