Beginner

Ignition: Implement a Lambda Function in AWS

Deploy a Python Lambda function, understand the handler, event-driven architecture, pricing and best practices.

Table of Contents


Course Overview

This hands-on Ignition course guides learners step by step through creating and deploying an end-to-end AWS Lambda function. The concrete use case is a canary — a small standalone program that monitors website availability and stores the results as a JSON file in Amazon S3.

Learning Objectives

  • Select the right runtime for a Lambda function
  • Configure an IAM execution role with minimal required permissions (principle of least privilege)
  • Deploy and test a Lambda function from the AWS console
  • Integrate Lambda with Amazon S3 for result storage
  • Understand configuration parameters: timeout, memory, handler
  • Master the structure of a Python handler and its two required arguments (event, context)

What is AWS Lambda?

AWS Lambda is a serverless compute service that runs code in response to events and automatically manages the underlying compute resources. There is no need to provision or manage servers.

Serverless Model

CharacteristicLambda (Serverless)EC2 (Classic IaaS)
ProvisioningAutomaticManual
ScalingAutomatic (up to thousands of parallel instances)Manual or Auto Scaling
BillingPer invocation + execution duration (ms)Per active instance hour
OS MaintenanceAWSCustomer
Max Execution Time15 minutesUnlimited

Lambda Invocation Lifecycle

sequenceDiagram
    participant Source as Source (Trigger)
    participant Lambda as AWS Lambda Service
    participant Env as Execution Environment
    participant Code as Code (handler.py)
    participant S3 as Amazon S3

    Source->>Lambda: Event (event object)
    Lambda->>Env: Initialize environment (if cold start)
    Env->>Code: Load code + dependencies
    Code->>Code: Execute lambda_handler(event, context)
    Code->>S3: s3.put_object(...)
    Code->>Lambda: Return response
    Lambda->>Source: JSON Response

Cold Start vs. Warm Start

On the first invocation (or after a long period of inactivity), Lambda must initialize a new execution environment — this is the cold start. Subsequent invocations reuse the existing environment — this is the warm start, much faster.

flowchart LR
    A[Invocation] --> B{Environment\navailable?}
    B -- No --> C[Cold Start\n~100ms-1s+]
    B -- Yes --> D[Warm Start\n~1-10ms]
    C --> E[Init global code\nboto3, connections...]
    D --> F[Execute handler\ndirectly]
    E --> F
    F --> G[Return result]

    style C fill:#f9a,stroke:#c00
    style D fill:#9f9,stroke:#060

Strategy: initialize SDK clients (boto3) and connections outside the handler to benefit from environment reuse during warm starts.


Module 1: Deploying a Lambda Function

This module covers the complete deployment of a Lambda function that acts as a website monitoring canary — a program that periodically checks URL availability and records the results in S3.


Demo: Selecting a Lambda Runtime

Accessing Lambda from the AWS Console

  1. In the AWS console, use the search bar to find Lambda
  2. Click Lambda in the results
  3. Click Create a function

Lambda Function Creation Options

OptionDescriptionWhen to Use
Author from scratchStart from an empty templateCustom task or new business logic
Use a blueprintPre-configured templates provided by AWSQuick starting point in different languages
Container imagePre-built Docker image in Amazon ECRComplex dependencies or custom images

For this course, we choose Author from scratch as the task is custom.

Naming and Runtime Selection

  • Function name: myLambdaCanary
  • Selected runtime: Python 3.14

Why Runtime Selection Matters

Selecting a runtime is a critical step because:

  • Each runtime (Python, Node.js, Java, etc.) has its own standard library dependencies
  • Supported language versions may have different behaviors
  • The runtime influences execution time and performance (cold start)
  • Python 3.14 includes urllib — a standard utility for making HTTP requests without external dependencies

Runtimes Supported by AWS Lambda

Python    : 3.14, 3.13, 3.12, 3.11
Node.js   : 22.x, 20.x
Java      : 21, 17, 11, 8
.NET      : 9, 8
Ruby      : 3.4, 3.3
Go        : 1.x (provided.al2023)

Handler Configuration

The handler is the reference to the code entry point. It follows the format:

<file_name>.<function_name>

Example: if the file is named handler.py and the function is lambda_handler:

handler.lambda_handler

To modify this setting in the console:

  1. Go to the Code tab
  2. Scroll down to Runtime settings
  3. Click Edit → modify the Handler field

Demo: Scoping Lambda Function Execution Roles

Concept of Execution Role

Every Lambda function must have an IAM execution role that defines the AWS actions it can perform. By default, AWS creates a basic role that only allows writing logs to Amazon CloudWatch Logs.

flowchart TD
    Lambda[Lambda Function\nmyLambdaCanary] --> Role[IAM Execution Role]
    Role --> P1[AWSLambdaBasicExecutionRole\nCloudWatch Logs: CreateLogGroup\nCreateLogStream, PutLogEvents]
    Role --> P2[Custom Policy\nS3: PutObject on target bucket]

    style Lambda fill:#FF9900,color:#000
    style Role fill:#dd3,color:#000
    style P1 fill:#9cf,color:#000
    style P2 fill:#9cf,color:#000

Why Create a Custom Role

The canary needs to:

  1. Write logs to CloudWatch (basic permission provided automatically)
  2. Store JSON files in Amazon S3 (s3:PutObject permission to add)

Role Configuration Steps

  1. In the Lambda console, go to the Permissions section
  2. Click Create a new role
  3. Leave Create new policy selected
  4. Paste the custom IAM policy (see permissions.json)
  5. Click Create role
  6. Assign the new role to the Lambda function from the dropdown

Verifying Permissions

To confirm the role has the correct permissions:

  • Click View role details in IAM
  • Verify that both policies are attached:
    • AWSLambdaBasicExecutionRole (CloudWatch logs)
    • Custom policy (s3:PutObject)

Principle of Least Privilege

✅ Grant: s3:PutObject only on the target bucket
❌ Avoid: s3:* or full access to all S3 buckets

The permissions.json policy uses "Resource": "*" for educational purposes. In production, restrict to the specific bucket ARN:

"Resource": "arn:aws:s3:::bucket-name/website-health-checks/*"

Demo: Creating Your First Lambda Function

Finalizing the Configuration

After selecting the runtime and configuring the role, adjust the execution parameters:

ParameterDefault ValueConfigured ValueReason
Timeout3 seconds3 minutesSome websites take more than 3s to respond
Memory128 MB256 MBNetwork operations and JSON processing require more memory

Steps to Modify the Configuration

  1. Go to the Configuration tab
  2. Click Edit in the General configuration section
  3. Change the Timeout to 3 minutes (3 min 0 sec)
  4. Change the Memory to 256 MB
  5. Click Save

Note: increasing memory also proportionally increases the allocated CPU power.

Deploying the Code

The code is pasted into the AWS console’s inline editor (or deployed via ZIP file for larger projects). The function is configured to:

  1. Iterate through a list of websites to monitor (WEBSITES)
  2. Perform an HTTP GET request on each with urllib.request
  3. Record the status, HTTP code, and response time
  4. Save the results as JSON in an S3 bucket

Testing and Validation

  1. Click Test in the Lambda console
  2. Create a test event (can be empty {} — the function does not use the event)
  3. Check the execution log: Execution result: succeeded
  4. Go to Amazon S3 → configured bucket → website-health-checks/ folder
  5. Download and open the generated JSON file

Complete Source Code

handler.py — Main Lambda Function

import json
import time
import urllib.error
import urllib.request
from datetime import datetime

import boto3

# Initialize S3 client OUTSIDE the handler to benefit from warm starts
s3 = boto3.client("s3")

# List of sites to monitor (canary targets)
WEBSITES = [
    "https://www.google.com",
    "https://www.github.com",
]

# S3 configuration — externalize as an environment variable in production
S3_BUCKET = "my-monitoring-bucket"
S3_PREFIX = "website-health-checks"

def check_website(url, timeout=5):
    """
    Performs an HTTP GET request on the provided URL.
    Returns a dictionary with the status, HTTP code, and response time.
    """
    start = time.time()
    result = {
        "url": url,
        "available": False,
        "status_code": None,
        "response_time_ms": None,
        "error": None
    }
    try:
        req = urllib.request.Request(url, method="GET")
        with urllib.request.urlopen(req, timeout=timeout) as response:
            result["status_code"] = response.getcode()
            result["available"] = 200 <= response.getcode() < 400
    except urllib.error.HTTPError as e:
        result["status_code"] = e.code
        result["error"] = str(e)
    except urllib.error.URLError as e:
        result["error"] = str(e.reason)
    except Exception as e:
        result["error"] = str(e)
    finally:
        result["response_time_ms"] = int((time.time() - start) * 1000)
    return result

def lambda_handler(event, context):
    """
    Main Lambda entry point.
    Required arguments:
      - event   : JSON object containing the trigger event data
      - context : runtime object with invocation metadata
    """
    results = []
    timestamp = datetime.utcnow().isoformat()

    # Check each site in the list
    for site in WEBSITES:
        results.append(check_website(site))

    # Build the results payload
    payload = {
        "timestamp": timestamp,
        "checks": results
    }

    # Generate a unique S3 key based on the timestamp
    time_suffix = datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')
    key = f"{S3_PREFIX}/healthcheck-{time_suffix}.json"

    # Write results to S3
    s3.put_object(
        Bucket=S3_BUCKET,
        Key=key,
        Body=json.dumps(payload, indent=2),
        ContentType="application/json"
    )

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "Health check completed",
            "s3_key": key,
            "results": results
        })
    }

Key Elements of handler.py

ElementRole
import boto3AWS SDK for Python — allows interaction with S3
s3 = boto3.client("s3")Initialized outside the handler → reused between invocations (warm start)
urllib.requestStandard Python module for HTTP requests — no external dependencies
lambda_handler(event, context)Required entry point — naming configurable in Runtime settings
datetime.utcnow().isoformat()UTC timestamp for traceability
s3.put_object(...)Writes the JSON file to S3 with the correct ContentType

permissions.json — IAM S3 Policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "*"
        }
    ]
}

In production, replace "Resource": "*" with the precise ARN:

"Resource": "arn:aws:s3:::my-canary-bucket/website-health-checks/*"

Example JSON File Generated in S3

{
  "timestamp": "2024-01-15T14:32:01.123456",
  "checks": [
    {
      "url": "https://www.google.com",
      "available": true,
      "status_code": 200,
      "response_time_ms": 145,
      "error": null
    },
    {
      "url": "https://www.github.com",
      "available": false,
      "status_code": 503,
      "response_time_ms": 312,
      "error": "HTTP Error 503: Service Unavailable"
    }
  ]
}

Note: Some sites return a 503 when Lambda attempts to access them. They may block automated requests originating from cloud services.


Anatomy of a Python Lambda Handler

The event Object

The event object is a Python dictionary (converted from JSON) that contains the trigger event data. Its format depends on the service invoking the Lambda:

Invocation TypeEvent Format
Manual test (console)Custom JSON object
S3 EventContains Records[].s3.bucket.name, Records[].s3.object.key
API GatewayContains httpMethod, path, body, headers, queryStringParameters
SQSContains Records[].body, Records[].messageId
EventBridge (Scheduler)Contains detail, source, time
DynamoDB StreamsContains Records[].dynamodb.NewImage

JSON to Python Type Conversion

JSON TypePython Type
objectdict
arraylist
numberint or float
stringstr
Booleanbool
nullNone
# Example: accessing event data
def lambda_handler(event, context):
    # For an order event
    order_id = event.get('order_id')
    amount   = event.get('amount', 0)
    item     = event.get('item', 'Unknown')

The context Object

The context object is automatically passed by Lambda and contains metadata about the current invocation:

def lambda_handler(event, context):
    # Unique invocation identifier
    request_id = context.aws_request_id

    # Time remaining before timeout (in milliseconds)
    remaining_ms = context.get_remaining_time_in_millis()

    # Function name and version
    fn_name    = context.function_name
    fn_version = context.function_version

    # Allocated memory (in MB)
    memory_mb = context.memory_limit_in_mb

    print(f"Invocation {request_id} — {remaining_ms}ms remaining")

Environment Variables

Environment variables allow you to configure the function without modifying the code. This is the best practice for bucket names, endpoints, configuration keys, etc.

Defining a Variable in the Console

  1. Configuration tab → Environment variables
  2. Click EditAdd environment variable
  3. Example: KEY = S3_BUCKET, VALUE = my-canary-bucket

Reading a Variable in Code

import os

def lambda_handler(event, context):
    # Read a required environment variable
    bucket_name = os.environ.get('S3_BUCKET')
    if not bucket_name:
        raise ValueError("Environment variable S3_BUCKET is missing")

    # Read with default value
    prefix = os.environ.get('S3_PREFIX', 'website-health-checks')

Secure Version of handler.py with Environment Variables

import json
import os
import time
import urllib.error
import urllib.request
from datetime import datetime

import boto3

s3 = boto3.client("s3")

# Configuration via environment variables
S3_BUCKET = os.environ.get('S3_BUCKET', 'my-monitoring-bucket')
S3_PREFIX = os.environ.get('S3_PREFIX', 'website-health-checks')
WEBSITES  = os.environ.get('WEBSITES', 'https://www.google.com,https://www.github.com').split(',')

def check_website(url, timeout=5):
    start  = time.time()
    result = {"url": url, "available": False, "status_code": None,
              "response_time_ms": None, "error": None}
    try:
        with urllib.request.urlopen(
            urllib.request.Request(url, method="GET"), timeout=timeout
        ) as response:
            result["status_code"] = response.getcode()
            result["available"]   = 200 <= response.getcode() < 400
    except urllib.error.HTTPError as e:
        result["status_code"] = e.code
        result["error"]       = str(e)
    except urllib.error.URLError as e:
        result["error"] = str(e.reason)
    except Exception as e:
        result["error"] = str(e)
    finally:
        result["response_time_ms"] = int((time.time() - start) * 1000)
    return result

def lambda_handler(event, context):
    timestamp    = datetime.utcnow().isoformat()
    results      = [check_website(site) for site in WEBSITES]
    payload      = {"timestamp": timestamp, "checks": results}
    time_suffix  = datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')
    key          = f"{S3_PREFIX}/healthcheck-{time_suffix}.json"

    s3.put_object(
        Bucket=S3_BUCKET, Key=key,
        Body=json.dumps(payload, indent=2),
        ContentType="application/json"
    )
    return {"statusCode": 200, "body": json.dumps({"message": "Health check completed", "s3_key": key})}

Return Value

The handler can return a JSON-serializable value. The behavior depends on the invocation type:

Invocation TypeReturn Value Behavior
Synchronous (RequestResponse)Returned to the calling client (console, API Gateway)
Asynchronous (Event)Ignored (SQS, S3 Events, EventBridge)
None (no return)The runtime returns null
# Standard return for API Gateway
return {
    "statusCode": 200,
    "headers": {"Content-Type": "application/json"},
    "body": json.dumps({"message": "Success", "data": results})
}

# Simple return
return {"statusCode": 200, "message": "OK"}

Event-Driven Architecture with Lambda

Common Triggers

Lambda can be triggered by many AWS services:

mindmap
  root((AWS Lambda\nTriggers))
    API Gateway
      HTTP REST API
      WebSocket API
    Amazon S3
      s3:ObjectCreated
      s3:ObjectRemoved
    Amazon SQS
      Standard Queue
      FIFO Queue
    DynamoDB Streams
      INSERT / MODIFY / REMOVE
    EventBridge
      Scheduler cron
      Event rules
    SNS
      Push Notifications
    Kinesis
      Data Streams
    ALB
      Application Load Balancer

Diagram: Lambda Triggered by S3, API Gateway and SQS

flowchart TB
    subgraph Triggers
        S3[Amazon S3\nNew file]
        API[API Gateway\nHTTP Request]
        SQS[Amazon SQS\nQueued message]
    end

    subgraph Lambda["AWS Lambda (Execution Environment)"]
        direction TB
        Init[Global init\nboto3 client, config]
        Handler[lambda_handler\nevent, context]
        Logic[Business logic]
        Init --> Handler --> Logic
    end

    subgraph Destinations
        DDB[DynamoDB]
        S3Out[Amazon S3]
        CW[CloudWatch Logs]
    end

    S3 -->|event: s3 record| Handler
    API -->|event: http request| Handler
    SQS -->|event: sqs records| Handler
    Logic --> DDB
    Logic --> S3Out
    Handler -.->|automatic logs| CW

    style Lambda fill:#FF9900,color:#000,stroke:#c60
    style Triggers fill:#e8f4fd,stroke:#0088cc
    style Destinations fill:#e8fde8,stroke:#00aa00

Diagram: Complete Canary Flow

flowchart LR
    subgraph Scheduler["EventBridge Scheduler (optional)"]
        CRON[Cron rule\ne.g.: every 5 min]
    end

    subgraph Lambda["Lambda: myLambdaCanary"]
        direction TB
        A[Iterate WEBSITES]
        B[HTTP GET each URL\nurllib.request]
        C[Collect: status_code\nresponse_time_ms, error]
        D[Build JSON payload]
        A --> B --> C --> D
    end

    subgraph S3["Amazon S3"]
        E[Bucket: my-canary-bucket]
        F[website-health-checks/\nhealthcheck-20240115T143201Z.json]
        E --> F
    end

    subgraph IAM
        G[Execution Role\nAWSLambdaBasicExecutionRole\n+ s3:PutObject]
    end

    CRON -->|Triggers| Lambda
    D -->|s3.put_object| E
    Lambda -.->|Assumes| G
    Lambda -->|Logs| CW[CloudWatch Logs]

    style Lambda fill:#FF9900,color:#000
    style S3 fill:#3F8624,color:#fff
    style IAM fill:#dd3,color:#000

Lambda Configuration

Key Parameters

ParameterMinMaxDefaultImpact
Memory128 MB10 240 MB128 MBMore memory = more CPU + higher cost
Timeout1 sec900 sec (15 min)3 secMust cover the estimated max execution time
Reserved Concurrency0Account limitNot setLimits the max parallelism of the function
Provisioned ConcurrencyN/AAccount limit0Eliminates cold starts (additional cost)
Ephemeral Storage (/tmp)512 MB10 240 MB512 MBTemporary disk space for the environment lifetime

Lambda Layers

A Lambda Layer is a ZIP file containing libraries, custom runtimes, or other dependencies. It is shareable across multiple Lambda functions.

flowchart TB
    subgraph Layer["Lambda Layer (shared)"]
        pandas[pandas==2.0.0]
        numpy[numpy==1.24.0]
        requests[requests==2.31.0]
    end

    subgraph Functions
        F1[Lambda A\nData Processing]
        F2[Lambda B\nAPI Handler]
        F3[Lambda C\nReport Generator]
    end

    Layer --> F1
    Layer --> F2
    Layer --> F3

    style Layer fill:#9cf,stroke:#06c

Layer Use Cases

  • Share Python libraries (pandas, numpy, requests) across multiple Lambdas
  • Separate dependencies from application code → lighter deployment packages
  • Maintain common libraries in one place

Layer Limits

  • Maximum 5 layers per Lambda function
  • Total deployed size (function + layers): 250 MB uncompressed
  • Each layer can be up to 75 MB compressed

AWS Lambda Pricing

Lambda pricing is based on two components:

1. Number of Requests (Invocations)

TierPrice
First million invocations/monthFree
Beyond$0.20 USD per million invocations

2. Execution Duration (Compute Time)

Duration is measured in milliseconds, rounded to the nearest millisecond. Cost depends on allocated memory:

MemoryPrice per GB-second
128 MB~$0.0000000021
256 MB~$0.0000000042
1024 MB~$0.0000000167

Formula:

$$\text{Cost} = \text{Invocations} \times \text{Duration (sec)} \times \frac{\text{Memory (MB)}}{1024} \times \text{Price/GB-sec}$$

Permanent Monthly Free Tier

  • 1 million invocations free
  • 400,000 GB-seconds of free compute

Example: a Lambda at 256 MB that executes for 100ms, invoked 10 million times per month, costs approximately $0.42 USD/month after the free tier.


Best Practices

Cold Start Optimization

# ✅ GOOD: initialization OUTSIDE the handler
import boto3
import os

# These lines execute once during environment initialization
s3_client  = boto3.client('s3')
ddb_client = boto3.resource('dynamodb')
TABLE_NAME = os.environ.get('TABLE_NAME')

def lambda_handler(event, context):
    # Reuse s3_client and ddb_client directly
    table = ddb_client.Table(TABLE_NAME)
    # ...
# ❌ BAD: re-initialization on every invocation
def lambda_handler(event, context):
    s3_client  = boto3.client('s3')           # Created on EVERY invocation
    ddb_client = boto3.resource('dynamodb')    # Same
    # ...

Cold Start Optimization Strategies

StrategyDescription
Out-of-handler initializationSDK clients, DB connections, config → init only once
Reduce package sizePackage only necessary dependencies
Provisioned ConcurrencyPre-initialize N environments → eliminates cold starts (paid)
Choose a lightweight runtimePython/Node.js start faster than Java/.NET
Use LayersSeparate bulky dependencies from business code
Caching in /tmpStore static assets in /tmp (512 MB by default)

Security and Least Privilege Principle

// ✅ Production: precise ARN
{
  "Effect": "Allow",
  "Action": ["s3:PutObject"],
  "Resource": "arn:aws:s3:::my-canary-bucket/website-health-checks/*"
}

// ❌ Too permissive
{
  "Effect": "Allow",
  "Action": ["s3:*"],
  "Resource": "*"
}

IAM security rules for Lambda:

  • Never use s3:* or *:* unless absolutely necessary
  • Restrict Resource to specific ARNs
  • Use IAM conditions to reinforce restrictions (e.g., MFA, source IP)
  • Enable AWS CloudTrail to audit all invocations
  • Encrypt sensitive environment variables with AWS KMS

Idempotency and Error Handling

Lambda may invoke your function more than once in case of errors or automatic retries (especially for asynchronous invocations). Code must be idempotent:

import hashlib
import json

def lambda_handler(event, context):
    # Generate a deterministic key based on event content
    event_hash = hashlib.md5(json.dumps(event, sort_keys=True).encode()).hexdigest()
    key = f"results/{event_hash}.json"

    # Check if the result already exists (idempotency)
    try:
        s3.head_object(Bucket=S3_BUCKET, Key=key)
        return {"statusCode": 200, "message": "Already processed", "s3_key": key}
    except Exception:
        pass  # Object doesn't exist yet, continue

    # Normal processing
    result = process(event)
    s3.put_object(Bucket=S3_BUCKET, Key=key, Body=json.dumps(result))
    return {"statusCode": 200, "s3_key": key}

Advanced Handler Examples

Handler with Environment Variables and Logging

import json
import logging
import os

import boto3

# Logger configuration
logger = logging.getLogger()
logger.setLevel(os.environ.get('LOG_LEVEL', 'INFO'))

# S3 client initialized outside the handler
s3_client = boto3.client('s3')

def upload_to_s3(bucket: str, key: str, content: str) -> None:
    """Upload a text file to S3."""
    try:
        s3_client.put_object(Bucket=bucket, Key=key, Body=content)
    except Exception as e:
        logger.error(f"S3 upload failed: {e}")
        raise

def lambda_handler(event: dict, context) -> dict:
    """Main handler with error handling and logging."""
    try:
        order_id = event['order_id']
        amount   = event['amount']
        item     = event['item']

        bucket_name = os.environ.get('RECEIPT_BUCKET')
        if not bucket_name:
            raise ValueError("Environment variable RECEIPT_BUCKET is missing")

        receipt = f"OrderID: {order_id}\nAmount: ${amount}\nItem: {item}"
        key     = f"receipts/{order_id}.txt"

        upload_to_s3(bucket_name, key, receipt)

        logger.info(f"Order {order_id} processed → s3://{bucket_name}/{key}")

        return {
            "statusCode": 200,
            "message": "Receipt processed successfully"
        }

    except KeyError as e:
        logger.error(f"Missing field in event: {e}")
        return {"statusCode": 400, "message": f"Required field missing: {e}"}

    except Exception as e:
        logger.error(f"Unexpected error: {e}")
        raise

Handler Triggered by S3 Event

When a file is uploaded to S3, Lambda receives an event with the file metadata:

import json
import urllib.parse
import boto3

s3 = boto3.client('s3')

def lambda_handler(event, context):
    # The S3 event contains a list of Records
    for record in event['Records']:
        bucket = record['s3']['bucket']['name']
        key    = urllib.parse.unquote_plus(record['s3']['object']['key'])
        size   = record['s3']['object']['size']

        print(f"New file: s3://{bucket}/{key} ({size} bytes)")

        # Download and process the file
        response = s3.get_object(Bucket=bucket, Key=key)
        content  = response['Body'].read().decode('utf-8')

        # Processing logic
        process_file(content, bucket, key)

    return {"statusCode": 200}

def process_file(content, bucket, key):
    """Custom file processing."""
    print(f"Processing {len(content)} characters from {key}")

S3 Event Format

{
  "Records": [
    {
      "eventName": "ObjectCreated:Put",
      "s3": {
        "bucket": {"name": "my-bucket"},
        "object": {
          "key": "uploads/report-2024.csv",
          "size": 1024
        }
      }
    }
  ]
}

Handler Triggered by API Gateway

import json

def lambda_handler(event, context):
    http_method = event.get('httpMethod', 'GET')
    path        = event.get('path', '/')
    body        = event.get('body', '{}')
    params      = event.get('queryStringParameters') or {}

    if http_method == 'GET':
        name = params.get('name', 'World')
        return {
            "statusCode": 200,
            "headers": {"Content-Type": "application/json"},
            "body": json.dumps({"message": f"Hello, {name}!"})
        }

    elif http_method == 'POST':
        try:
            data = json.loads(body)
        except json.JSONDecodeError:
            return {
                "statusCode": 400,
                "body": json.dumps({"error": "Invalid JSON body"})
            }
        return {
            "statusCode": 201,
            "body": json.dumps({"message": "Resource created", "data": data})
        }

    return {
        "statusCode": 405,
        "body": json.dumps({"error": "Method not allowed"})
    }

API Gateway Event Format

{
  "httpMethod": "POST",
  "path": "/orders",
  "headers": {"Content-Type": "application/json"},
  "queryStringParameters": {"format": "json"},
  "body": "{\"order_id\": \"12345\", \"amount\": 99.99}"
}

Handler Triggered by SQS

import json
import boto3

def lambda_handler(event, context):
    processed = []
    failed    = []

    for record in event['Records']:
        message_id = record['messageId']
        body       = record['body']

        try:
            data = json.loads(body)
            process_message(data)
            processed.append(message_id)
            print(f"Message {message_id} processed successfully")

        except Exception as e:
            print(f"Message {message_id} failed: {e}")
            # Return failed messages so they go back into the queue
            failed.append({"itemIdentifier": message_id})

    # Partial failure report (batchItemFailures)
    return {
        "batchItemFailures": failed
    }

def process_message(data: dict) -> None:
    """Business logic processing."""
    order_id = data.get('order_id')
    print(f"Processing order {order_id}")

Expected Outcomes

After completing this course, you should be able to:

  1. Create a Lambda function from the AWS console with the Python runtime
  2. Configure an IAM execution role with minimal permissions (S3 PutObject + CloudWatch Logs)
  3. Deploy Python code in the inline editor or via ZIP
  4. Test a Lambda function and interpret the results in the console
  5. Verify results stored in Amazon S3
  6. Understand the structure of a Python handler (event, context, return value)
  7. Apply best practices: out-of-handler initialization, environment variables, least privilege

Validation via S3

Success is confirmed by:

  1. Going to Amazon S3 → configured bucket
  2. Navigating to the website-health-checks/ folder
  3. Downloading a healthcheck-YYYYMMDDTHHMMSSZ.json file
  4. Verifying that health check results are present

Additional Resources

ResourceURL
Course source codegithub.com/pluralsight-cloud/aws-lambda-function-implement-ignition
AWS Lambda — Official documentationdocs.aws.amazon.com/lambda/latest/dg/welcome.html
Python handler referencedocs.aws.amazon.com/lambda/latest/dg/python-handler.html
Lambda context object (Python)docs.aws.amazon.com/lambda/latest/dg/python-context.html
Supported Lambda runtimesdocs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html
Lambda layers (Python)docs.aws.amazon.com/lambda/latest/dg/python-layers.html
Lambda pricingaws.amazon.com/lambda/pricing
IAM Best Practicesdocs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html
Boto3 S3 referenceboto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html

Search Terms

ignition · implement · lambda · function · aws · serverless · amazon · web · services · handler · configuration · event · triggered · api · cold · environment · execution · gateway · handler.py · role · runtime · variables · console · deploying

Interested in this course?

Contact us to book it or get a custom training plan for your team.