Using with AWS Lambda
Martin can run in AWS Lambda.
This is useful if you want to serve tiles from a serverless environment, while accessing "nearby" data from a PostgreSQL database or PMTiles file in S3, without exposing the raw file to the world to prevent download abuse and improve performance.
The bucket can stay private.
Martin signs its S3 requests with the credentials Lambda gives the function, so the execution role only needs s3:GetObject on the archive.
Lambda has two deployment models: zip file and container-based. When using zip file deployment, there is an online code editor to edit the yaml configuration. When using container-based deployment, we can pass our configuration on the command line or environment variables.
Everything can be performed via AWS CloudShell, or you can install the AWS CLI and the AWS SAM CLI, and configure authentication. The CloudShell also runs in a particular AWS region.
Container deployment
Lambda images must come from a public or private ECR registry. Pull the image from GHCR and push it to ECR.
$ docker pull ghcr.io/maplibre/martin:1.16.1 --platform linux/arm64
$ aws ecr create-repository --repository-name martin
[…]
"repositoryUri": "493749042871.dkr.ecr.us-east-2.amazonaws.com/martin",
# Read the repositoryUri which includes your account number
$ docker tag ghcr.io/maplibre/martin:1.16.1 493749042871.dkr.ecr.us-east-2.amazonaws.com/martin:latest
$ aws ecr get-login-password --region us-east-2 \
| docker login --username AWS --password-stdin 493749042871.dkr.ecr.us-east-2.amazonaws.com
$ docker push 493749042871.dkr.ecr.us-east-2.amazonaws.com/martin:latest
Open Lambda console and create your function:
- Click "Create function".
- Choose "Container image".
- Put something in "Function name".
- Note: This is an internal identifier, not exposed in the function URL.
- Click "Browse images", and select your repository and the tag.
- If you cannot find it, see if you are in the same region?
- Expand "Container image overrides", and under CMD put the URL of a
.pmtilesfile. Ans3://URL works once the execution role may read the object, see the zip section below. - Set "Architecture" to
arm64to match the platform that we pulled. Lambda has better ARM CPUs than x86. - Click "Create function".
- Find the "Configuration" tab, select "Function URL", "Create function URL".
- Set "Auth type" to
NONE - Do not enable
CORS. Martin already hasCORSsupport, so it will create incorrect duplicate headers. - Click on the "Function URL".
- To debug an issue, open the "Monitor" tab, "View CloudWatch logs", find the most recent Log stream.
Zip deployment
It's possible to deploy the entire codebase from the AWS console, but we will use Serverless Application Model. Our function will consist of a "Layer", containing the Martin binary, and our function itself will contain the configuration in yaml format.
The layer
Download the binary and place it in your staging directory.
The bin directory of your Layer will be added to the PATH.
mkdir -p martin_layer/src/bin/
cd martin_layer
curl -OL https://github.com/maplibre/martin/releases/latest/download/martin-aarch64-unknown-linux-musl.tar.gz
tar -C src/bin/ -xzf martin-aarch64-unknown-linux-musl.tar.gz martin
Every zip-based Lambda function runs a file called bootstrap.
Write the SAM template.
cat <<EOF >template.yaml
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::Serverless-2016-10-31'
Resources:
MartinLayer:
Type: 'AWS::Serverless::LayerVersion'
DeletionPolicy: Delete
Properties:
ContentUri: src
CompatibleRuntimes:
- provided.al2023
CompatibleArchitectures:
- arm64
Outputs:
LayerArn:
Value: !Ref MartinLayer
Export:
Name: !Sub "${AWS::StackName}-LayerArn"
EOF
Run sam deploy --guided.
- Stack Name: Name your CloudFormation stack something like
martin-layer. - Press enter for everything else
- The settings are saved to
samconfig.toml, so you can later dosam deployto update the version, orsam delete.
Now if you visit the Lambda console and select "Layers", you should see your layer.
The function
- Select "Functions", "Create function".
- Put something in "Function name".
- Set "Runtime" to "Amazon Linux 2023".
- Set "Architecture" to "arm64".
- Under "Advanced settings", choose "Enable function URL" with "Auth type" of "NONE".
- Click "Create function".
Add your layer:
- Click "add a layer" (green banner at the top, or the very bottom).
- Choose "Custom layers", and select your layer and its version.
- Click "Add".
Add your configuration file in the function source code:
- Code tab, File, New File:
config.yaml.
pmtiles:
sources:
demotiles: s3://my-bucket/tiles.pmtiles
# a new Lambda instance starts empty, so an in-process cache buys little
cache:
size_mb: 0
A prefix such as s3://my-bucket/tiles/ under pmtiles.paths serves every archive in it instead, which also needs s3:ListBucket.
- Configuration tab, "General configuration", Edit: set "Handler" to
config.yaml. Thebootstrapabove passes the handler name tomartin --config. - Configuration tab, "Permissions": open the execution role and attach a policy that allows
s3:GetObjecton the archive. Nothing else is needed. Lambda hands the function temporary credentials throughAWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEYandAWS_SESSION_TOKEN, and Martin picks them up. - Click Deploy, wait for the success banner, and visit your function URL.
Everything as one SAM stack
The layer, the function, its role and the URL can be one template. This is also how the steps above were verified.
AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::Serverless-2016-10-31'
Parameters:
Bucket:
Type: String
Resources:
MartinLayer:
Type: 'AWS::Serverless::LayerVersion'
DeletionPolicy: Delete
Properties:
ContentUri: layer/
CompatibleRuntimes:
- provided.al2023
CompatibleArchitectures:
- arm64
MartinFunction:
Type: 'AWS::Serverless::Function'
Properties:
Runtime: provided.al2023
Architectures:
- arm64
Layers:
- !Ref MartinLayer
CodeUri: function/
Handler: config.yaml
MemorySize: 512
Timeout: 10
FunctionUrlConfig:
AuthType: NONE
Policies:
- S3ReadPolicy:
BucketName: !Ref Bucket
Outputs:
FunctionUrl:
Value: !GetAtt MartinFunctionUrl.FunctionUrl
layer/ holds bootstrap and bin/martin, function/ holds config.yaml.
Deploy with sam build && sam deploy --guided --parameter-overrides Bucket=my-bucket.
Sizing
At the Lambda defaults of 128 MB and 3 seconds, Martin v1.14.0 on arm64 starts in about 300 ms, peaks at about 45 MB with one PMTiles source in S3, and answers a warm tile request in about 25 ms.
Raise the timeout if the archive is large or far away, and the memory if you serve many sources.
Caching
Martin sets an Etag header on tile responses, and the cache_control option in the configuration file sets a default Cache-Control header.
Put CloudFront in front of the function URL and let it hold the tiles, since every new Lambda instance starts with an empty in-process cache.
Not covered yet
- Connecting to a PostgreSQL database on RDS.
- A CloudFront distribution in front of the function URL, with the motivation and the basics.