Files
gh-emillindfors-claude-mark…/commands/lambda-advanced.md
2025-11-29 18:25:52 +08:00

10 KiB

description
description
Advanced Lambda topics including extensions, container images, and local development

You are helping the user with advanced Rust Lambda topics including custom extensions, container images, and enhanced local development.

Your Task

Guide the user through advanced Lambda patterns and deployment options.

Lambda Extensions

Extensions run alongside your function to provide observability, security, or governance capabilities.

When to Build Extensions

  • Custom monitoring/observability
  • Secret rotation
  • Configuration management
  • Security scanning
  • Custom logging

Creating a Rust Extension

Add to Cargo.toml:

[dependencies]
lambda-extension = "0.13"
tokio = { version = "1", features = ["macros"] }
tracing = "0.1"

Basic extension:

use lambda_extension::{service_fn, Error, LambdaEvent, NextEvent};
use tracing::info;

async fn handler(event: LambdaEvent) -> Result<(), Error> {
    match event.next {
        NextEvent::Shutdown(_e) => {
            info!("Shutting down extension");
        }
        NextEvent::Invoke(_e) => {
            info!("Function invoked");
            // Collect telemetry, logs, etc.
        }
    }
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt().init();

    let extension_name = "my-rust-extension";
    lambda_extension::run(service_fn(handler)).await
}

Deploy Extension as Layer

# Build extension
cargo lambda build --release --extension

# Create layer
aws lambda publish-layer-version \
  --layer-name my-rust-extension \
  --zip-file fileb://target/lambda/extensions/my-extension.zip \
  --compatible-runtimes provided.al2023 \
  --compatible-architectures arm64

# Add to function
cargo lambda deploy \
  --layers arn:aws:lambda:region:account:layer:my-rust-extension:1

Logging Extension Example

use lambda_extension::{service_fn, Error, LambdaLog, LambdaLogRecord};
use std::fs::OpenOptions;
use std::io::Write;

async fn handler(logs: Vec<LambdaLog>) -> Result<(), Error> {
    let mut file = OpenOptions::new()
        .create(true)
        .append(true)
        .open("/tmp/extension-logs.txt")?;

    for log in logs {
        match log.record {
            LambdaLogRecord::Function(record) => {
                writeln!(file, "[FUNCTION] {}", record)?;
            }
            LambdaLogRecord::Extension(record) => {
                writeln!(file, "[EXTENSION] {}", record)?;
            }
            _ => {}
        }
    }

    Ok(())
}

Container Images

Deploy Lambda as container image instead of ZIP (max 10GB vs 250MB).

When to Use Containers

Use containers when:

  • Large dependencies (>250MB uncompressed)
  • Custom system libraries
  • Complex build process
  • Team familiar with Docker
  • Need exact runtime control

Use ZIP when:

  • Simple deployment
  • Fast iteration
  • Smaller functions
  • Standard dependencies

Dockerfile for Rust Lambda

FROM public.ecr.aws/lambda/provided:al2023-arm64

# Install Rust
RUN yum install -y gcc && \
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && \
    source $HOME/.cargo/env

# Copy source
WORKDIR /var/task
COPY Cargo.toml Cargo.lock ./
COPY src ./src

# Build
RUN source $HOME/.cargo/env && \
    cargo build --release && \
    cp target/release/bootstrap ${LAMBDA_RUNTIME_DIR}/bootstrap

CMD ["bootstrap"]

Multi-stage Build (Smaller Image)

# Build stage
FROM rust:1.75-slim as builder

WORKDIR /app
COPY Cargo.toml Cargo.lock ./
COPY src ./src

RUN cargo build --release --target x86_64-unknown-linux-musl

# Runtime stage
FROM public.ecr.aws/lambda/provided:al2023

COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/bootstrap \
    ${LAMBDA_RUNTIME_DIR}/bootstrap

CMD ["bootstrap"]

Build and Deploy Container

# Build image
docker build -t my-rust-lambda .

# Tag for ECR
docker tag my-rust-lambda:latest \
  123456789012.dkr.ecr.us-east-1.amazonaws.com/my-rust-lambda:latest

# Login to ECR
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin \
  123456789012.dkr.ecr.us-east-1.amazonaws.com

# Push
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-rust-lambda:latest

# Create/update Lambda
aws lambda create-function \
  --function-name my-rust-lambda \
  --package-type Image \
  --code ImageUri=123456789012.dkr.ecr.us-east-1.amazonaws.com/my-rust-lambda:latest \
  --role arn:aws:iam::123456789012:role/lambda-role

Local Development

# Start local Lambda emulator
cargo lambda watch

# Invoke in another terminal
cargo lambda invoke --data-ascii '{"test": "data"}'

# With specific event file
cargo lambda invoke --data-file events/api-gateway.json

Option 2: LocalStack (Full AWS Emulation)

# Install LocalStack
pip install localstack

# Start LocalStack
localstack start

# Deploy to LocalStack
samlocal deploy

# Or with cargo-lambda
cargo lambda build --release
aws --endpoint-url=http://localhost:4566 lambda create-function \
  --function-name my-function \
  --runtime provided.al2023 \
  --role arn:aws:iam::000000000000:role/lambda-role \
  --handler bootstrap \
  --zip-file fileb://target/lambda/bootstrap.zip

Option 3: SAM Local

# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: .
      Handler: bootstrap
      Runtime: provided.al2023

# Start local API
sam local start-api

# Invoke function
sam local invoke MyFunction -e events/test.json

AWS Recommendation: Don't use layers for Rust dependencies.

Why: Rust compiles to a single static binary. All dependencies are included at compile time.

Exception: Use layers for:

  • Lambda Extensions
  • Shared native libraries (rare)
  • Non-Rust resources (config files, ML models)

VPC Configuration

Connect Lambda to VPC for private resource access.

cargo lambda deploy \
  --subnet-ids subnet-12345 subnet-67890 \
  --security-group-ids sg-12345

Performance impact:

  • Cold start: +10-15 seconds (Hyperplane ENI creation)
  • Warm start: No impact

Mitigation:

  • Use multiple subnets/AZs
  • Keep functions warm
  • Consider NAT Gateway for internet access

Reserved Concurrency

Limit concurrent executions:

aws lambda put-function-concurrency \
  --function-name my-function \
  --reserved-concurrent-executions 10

Use cases:

  • Protect downstream resources
  • Cost control
  • Predictable scaling

Asynchronous Invocation

Configure Destinations

# On success, send to SQS
aws lambda put-function-event-invoke-config \
  --function-name my-function \
  --destination-config '{
    "OnSuccess": {
      "Destination": "arn:aws:sqs:us-east-1:123:success-queue"
    },
    "OnFailure": {
      "Destination": "arn:aws:sns:us-east-1:123:failure-topic"
    }
  }'

Dead Letter Queue

// Lambda automatically retries failed async invocations
// Configure DLQ for ultimate failures

#[tokio::main]
async fn main() -> Result<(), Error> {
    run(service_fn(function_handler)).await
}

async fn function_handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
    // If this fails after retries, goes to DLQ
    process_event(&event.payload).await?;
    Ok(Response::success())
}

Event Source Mappings

SQS with Batch Processing

use aws_lambda_events::event::sqs::SqsEvent;

async fn handler(event: LambdaEvent<SqsEvent>) -> Result<(), Error> {
    // Process batch concurrently
    let futures = event.payload.records
        .into_iter()
        .map(|record| async move {
            let body: Message = serde_json::from_str(&record.body?)?;
            process_message(body).await
        });

    futures::future::try_join_all(futures).await?;

    Ok(())
}

Configure batch size:

aws lambda create-event-source-mapping \
  --function-name my-function \
  --event-source-arn arn:aws:sqs:us-east-1:123:my-queue \
  --batch-size 10 \
  --maximum-batching-window-in-seconds 5

Advanced Error Handling

Partial Batch Responses (SQS)

use lambda_runtime::{LambdaEvent, Error};
use aws_lambda_events::event::sqs::{SqsEvent, SqsBatchResponse};

async fn handler(event: LambdaEvent<SqsEvent>) -> Result<SqsBatchResponse, Error> {
    let mut failed_ids = Vec::new();

    for record in event.payload.records {
        match process_record(&record).await {
            Ok(_) => {},
            Err(e) => {
                tracing::error!("Failed to process: {}", e);
                if let Some(msg_id) = record.message_id {
                    failed_ids.push(msg_id);
                }
            }
        }
    }

    Ok(SqsBatchResponse {
        batch_item_failures: failed_ids
            .into_iter()
            .map(|id| sqs::SqsBatchItemFailure { item_identifier: id })
            .collect(),
    })
}

Multi-Region Deployment

# Deploy to multiple regions
for region in us-east-1 us-west-2 eu-west-1; do
  echo "Deploying to $region"
  cargo lambda deploy --region $region my-function
done

Blue/Green Deployments

# Create alias
aws lambda create-alias \
  --function-name my-function \
  --name production \
  --function-version 1

# Gradual rollout
aws lambda update-alias \
  --function-name my-function \
  --name production \
  --routing-config '{"AdditionalVersionWeights": {"2": 0.1}}'

# Full cutover
aws lambda update-alias \
  --function-name my-function \
  --name production \
  --function-version 2

Testing Strategies

Integration Tests with LocalStack

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_with_localstack() {
        // Set LocalStack endpoint
        std::env::set_var("AWS_ENDPOINT_URL", "http://localhost:4566");

        let event = create_test_event();
        let response = function_handler(event).await.unwrap();

        assert_eq!(response.status, "success");
    }
}

Load Testing

# Artillery config (artillery.yml)
config:
  target: "https://function-url.lambda-url.us-east-1.on.aws"
  phases:
    - duration: 60
      arrivalRate: 10
      name: "Warm up"
    - duration: 300
      arrivalRate: 100
      name: "Load test"

scenarios:
  - flow:
      - post:
          url: "/"
          json:
            test: "data"

# Run
artillery run artillery.yml

Guide the user through these advanced topics based on their specific needs and architecture requirements.