Skip to content

Commit 0f60b84

Browse files
hrodmnchuckwondo
andauthored
convert Lambda to a container function (#99)
--------- Co-authored-by: Chuck Daniels <[email protected]>
1 parent fba089f commit 0f60b84

File tree

7 files changed

+540
-55
lines changed

7 files changed

+540
-55
lines changed

.dockerignore

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
# Ignore cdk folder
2-
cdk.out
3-
.history
4-
.tox
51
.git
2+
infrastructure/aws/cdk.out
3+
.venv
4+
.mypy_cache
5+
.pytest_cache
6+
.ruff_cache

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## unreleased
4+
5+
* Convert Lambda to a containerized function
6+
37
## 0.3.1
48

59
* Upgrade to `titiler>=0.21,<0.22`

infrastructure/aws/cdk/app.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from aws_cdk import aws_sns as sns
1515
from aws_cdk import aws_sns_subscriptions as subscriptions
1616
from aws_cdk.aws_apigatewayv2_integrations import HttpLambdaIntegration
17+
from aws_cdk.aws_ecr_assets import Platform
1718
from config import AppSettings, StackSettings
1819
from constructs import Construct
1920

@@ -121,19 +122,14 @@ def __init__(
121122
role_arn=app_settings.reader_role_arn,
122123
)
123124

124-
lambda_function = aws_lambda.Function(
125+
lambda_function = aws_lambda.DockerImageFunction(
125126
self,
126127
f"{id}-lambda",
127-
runtime=runtime,
128-
code=aws_lambda.Code.from_docker_build(
129-
path=os.path.abspath(context_dir),
128+
code=aws_lambda.DockerImageCode.from_image_asset(
129+
directory=os.path.abspath(context_dir),
130130
file="infrastructure/aws/lambda/Dockerfile",
131-
platform="linux/amd64",
132-
build_args={
133-
"PYTHON_VERSION": runtime.to_string().replace("python", ""),
134-
},
131+
platform=Platform.LINUX_AMD64,
135132
),
136-
handler="handler.handler",
137133
memory_size=memory,
138134
reserved_concurrent_executions=concurrent,
139135
timeout=Duration.seconds(timeout),
Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,79 @@
11
ARG PYTHON_VERSION=3.12
22

3-
FROM --platform=linux/amd64 public.ecr.aws/lambda/python:${PYTHON_VERSION}
3+
# Build stage - includes all build tools and dependencies
4+
FROM public.ecr.aws/lambda/python:${PYTHON_VERSION} AS builder
5+
6+
# Copy uv for faster dependency management
47
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
58

6-
WORKDIR /tmp
9+
# Install system dependencies needed for compilation
10+
RUN dnf install -y gcc-c++ && dnf clean all
711

8-
# Install system dependencies to compile (numexpr)
9-
RUN dnf install -y gcc-c++
12+
# Set working directory for build
13+
WORKDIR /build
1014

11-
COPY uv.lock .python-version pyproject.toml LICENSE README.md ./
15+
# Copy dependency files first for better caching
16+
COPY README.md uv.lock .python-version pyproject.toml ./
1217
COPY src/titiler/ ./src/titiler/
1318

19+
# Install dependencies to temporary directory with Lambda-specific optimizations
1420
RUN uv export --locked --no-editable --no-dev --extra lambda --format requirements.txt -o requirements.txt && \
15-
uv pip install --compile-bytecode --no-binary pydantic --target /asset -r requirements.txt
21+
uv pip install \
22+
--compile-bytecode \
23+
--no-binary pydantic \
24+
--target /deps \
25+
--no-cache-dir \
26+
--disable-pip-version-check \
27+
-r requirements.txt
28+
29+
# Aggressive cleanup to minimize size and optimize for Lambda container
30+
WORKDIR /deps
31+
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
32+
RUN <<EOF
33+
# Convert .pyc files and remove source .py files for faster cold starts
34+
find . -type f -name '*.pyc' | while read -r f; do n="$(echo "$f" | sed 's/__pycache__\///' | sed 's/.cpython-[0-9]*//')"; cp "$f" "$n"; done
35+
find . -type d -a -name '__pycache__' -print0 | xargs -0 rm -rf
36+
find . -type f -a -name '*.py' -print0 | xargs -0 rm -f
37+
# Remove unnecessary files for Lambda runtime
38+
find . -type d -a -name 'tests' -print0 | xargs -0 rm -rf
39+
find . -type d -a -name 'test' -print0 | xargs -0 rm -rf
40+
rm -rf numpy/doc/ bin/ geos_license Misc/
41+
# Remove unnecessary locale and documentation files
42+
find . -name '*.mo' -delete
43+
find . -name '*.po' -delete
44+
find . -name 'LICENSE*' -delete
45+
find . -name 'README*' -delete
46+
find . -name '*.md' -delete
47+
# Strip debug symbols from shared libraries (preserve numpy.libs)
48+
find . -type f -name '*.so*' -not -path "*/numpy.libs/*" -exec strip --strip-unneeded {} \; 2>/dev/null || true
49+
# Create a manifest file for debugging
50+
du -sh . > /tmp/package_size.txt
51+
EOF
52+
53+
# Final runtime stage - minimal Lambda image optimized for container runtime
54+
FROM public.ecr.aws/lambda/python:${PYTHON_VERSION}
1655

17-
# copy libexpat.so.1 into /asset which is included in LD_LIBRARY_PATH
18-
RUN cp /usr/lib64/libexpat.so.1 /asset/
56+
# Set Lambda-specific environment variables for optimal performance
57+
ENV PYTHONPATH=${LAMBDA_RUNTIME_DIR} \
58+
PYTHONUNBUFFERED=1 \
59+
PYTHONDONTWRITEBYTECODE=1 \
60+
AWS_LWA_ENABLE_COMPRESSION=true
1961

20-
# Reduce package size and remove useless files
21-
RUN cd /asset && find . -type f -name '*.pyc' | while read f; do n=$(echo $f | sed 's/__pycache__\///' | sed 's/.cpython-[0-9]*//'); cp $f $n; done;
22-
RUN cd /asset && find . -type d -a -name '__pycache__' -print0 | xargs -0 rm -rf
23-
RUN cd /asset && find . -type f -a -name '*.py' -print0 | xargs -0 rm -f
24-
RUN find /asset -type d -a -name 'tests' -print0 | xargs -0 rm -rf
25-
RUN rm -rdf /asset/numpy/doc/ /asset/bin /asset/geos_license /asset/Misc
26-
RUN rm -rdf /asset/boto3*
27-
RUN rm -rdf /asset/botocore*
62+
# Copy only the cleaned dependencies from builder stage
63+
# Copy required system library
64+
COPY --from=builder /deps /usr/lib64/libexpat.so.1 ${LAMBDA_RUNTIME_DIR}/
2865

29-
# Strip debug symbols from compiled C/C++ code (except for numpy.libs!)
30-
RUN cd /asset && \
31-
find . -type f -name '*.so*' \
32-
-not -path "./numpy.libs/*" \
33-
-exec strip --strip-unneeded {} \;
66+
# Copy application handler
67+
COPY infrastructure/aws/lambda/handler.py ${LAMBDA_RUNTIME_DIR}/
3468

35-
COPY infrastructure/aws/lambda/handler.py /asset/handler.py
69+
# Ensure handler is executable and optimize permissions
70+
RUN <<EOF
71+
chmod 644 "${LAMBDA_RUNTIME_DIR}"/handler.py
72+
# Pre-compile the handler for faster cold starts
73+
python -c "import py_compile; py_compile.compile('${LAMBDA_RUNTIME_DIR}/handler.py', doraise=True)"
74+
# Create cache directories with proper permissions
75+
mkdir -p /tmp/.cache && chmod 777 /tmp/.cache
76+
EOF
3677

37-
CMD ["echo", "hello world"]
78+
# Set the Lambda handler
79+
CMD ["handler.lambda_handler"]
Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,49 @@
1-
"""AWS Lambda handler."""
1+
"""AWS Lambda handler optimized for container runtime."""
22

33
import logging
4+
import warnings
5+
from typing import Any, Dict
46

57
from mangum import Mangum
68

79
from titiler.multidim.main import app
810

911
logging.getLogger("mangum.lifespan").setLevel(logging.ERROR)
1012
logging.getLogger("mangum.http").setLevel(logging.ERROR)
13+
logging.getLogger("botocore").setLevel(logging.WARNING)
14+
logging.getLogger("urllib3").setLevel(logging.WARNING)
1115

12-
handler = Mangum(app, lifespan="off")
16+
warnings.filterwarnings("ignore", category=UserWarning)
17+
warnings.filterwarnings("ignore", category=FutureWarning)
18+
19+
20+
# Pre-import commonly used modules for faster cold starts
21+
try:
22+
import numpy # noqa: F401
23+
import pandas # noqa: F401
24+
import rioxarray # noqa: F401
25+
import xarray # noqa: F401
26+
except ImportError:
27+
pass
28+
29+
handler = Mangum(
30+
app,
31+
lifespan="off",
32+
api_gateway_base_path=None,
33+
text_mime_types=[
34+
"application/json",
35+
"application/javascript",
36+
"application/xml",
37+
"application/vnd.api+json",
38+
],
39+
)
40+
41+
42+
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
43+
"""Lambda handler with container-specific optimizations."""
44+
response = handler(event, context)
45+
46+
return response
47+
48+
49+
handler.lambda_handler = lambda_handler

infrastructure/aws/package-lock.json

Lines changed: 20 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)