Skip to content

Commit 89d8d64

Browse files
committed
add deployement
1 parent 565faa3 commit 89d8d64

23 files changed

+1034
-23
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,6 @@ ENV/
9898
/site
9999

100100
# mypy
101-
.mypy_cache/
101+
.mypy_cache/
102+
103+
cdk.out/

Dockerfile

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
1+
FROM laurents/uvicorn-gunicorn-fastapi:python3.7-slim
2+
# Ref https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/issues/15
3+
# Cuts image size by 50%
4+
# FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
25

36
ENV CURL_CA_BUNDLE /etc/ssl/certs/ca-certificates.crt
47

58
COPY README.md /app/README.md
69
COPY titiler/ /app/titiler/
710
COPY setup.py /app/setup.py
811

9-
RUN pip install /app/.
12+
RUN pip install -e /app/. --no-cache-dir

README.md

+115-8
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,120 @@ A lightweight Cloud Optimized GeoTIFF tile server.
77

88
# Deployment
99

10-
**To Do**
10+
The stack is deployed by the [aws cdk](https://aws.amazon.com/cdk/) utility. It will handle tasks such as generating a docker image and packaging handlers automatically.
11+
12+
1. Instal cdk and set up CDK in your AWS account - Only need once per account
13+
```bash
14+
$ npm install cdk -g
15+
16+
$ cdk bootstrap # Deploys the CDK toolkit stack into an AWS environment
17+
```
18+
19+
2. Install dependencies
20+
21+
```bash
22+
# Note: it's recommanded to use virtualenv
23+
$ git clone https://github.com/developmentseed/titiler.git
24+
$ cd titiler && pip install -e .[deploy]
25+
```
26+
27+
3. Pre-Generate CFN template
28+
```bash
29+
$ cdk synth # Synthesizes and prints the CloudFormation template for this stack
30+
```
31+
32+
4. Edit [stack/config.py](stack/config.py)
33+
34+
```python
35+
PROJECT_NAME = "titiler"
36+
STAGE = os.environ.get("STAGE", "dev")
37+
38+
# // Service config
39+
# Min/Max Number of ECS images
40+
MIN_ECS_INSTANCES = 2
41+
MAX_ECS_INSTANCES = 50
42+
43+
# CPU value | Memory value
44+
# 256 (.25 vCPU) | 0.5 GB, 1 GB, 2 GB
45+
# 512 (.5 vCPU) | 1 GB, 2 GB, 3 GB, 4 GB
46+
# 1024 (1 vCPU) | 2 GB, 3 GB, 4 GB, 5 GB, 6 GB, 7 GB, 8 GB
47+
# 2048 (2 vCPU) | Between 4 GB and 16 GB in 1-GB increments
48+
# 4096 (4 vCPU) | Between 8 GB and 30 GB in 1-GB increments
49+
TASK_CPU = 1024
50+
TASK_MEMORY = 2048
51+
```
52+
53+
5. Deploy
54+
```bash
55+
$ cdk deploy # Deploys the stack(s) named STACKS into your AWS account
56+
```
1157

1258
# Test locally
1359
```bash
60+
$ git clone https://github.com/developmentseed/titiler.git
61+
62+
$ pip install -e .
1463
$ uvicorn titiler.main:app --reload
1564
```
16-
17-
### Docker
65+
Or with Docker
1866
```
1967
$ docker-compose build
2068
$ docker-compose up
2169
```
2270

23-
## Authors
24-
Created by [Development Seed](<http://developmentseed.org>)
71+
# API
72+
73+
### Doc
74+
75+
`:endpoint:/docs`
76+
![](https://user-images.githubusercontent.com/10407788/78325903-011c9680-7547-11ea-853f-50e0fb0f4d92.png)
77+
78+
### Tiles
79+
80+
`:endpoint:/v1/{z}/{x}/{y}[@{scale}x][.{ext}]`
81+
- **z**: Mercator tiles's zoom level.
82+
- **x**: Mercator tiles's column.
83+
- **y**: Mercator tiles's row.
84+
- **scale**: Tile size scale, default is set to 1 (256x256). OPTIONAL
85+
- **ext**: Output image format, default is set to None and will be either JPEG or PNG depending on masked value. OPTIONAL
86+
- **url**: Cloud Optimized GeoTIFF URL. **REQUIRED**
87+
- **bidx**: Coma (',') delimited band indexes. OPTIONAL
88+
- **nodata**: Overwrite internal Nodata value. OPTIONAL
89+
- **rescale**: Coma (',') delimited Min,Max bounds. OPTIONAL
90+
- **color_formula**: rio-color formula. OPTIONAL
91+
- **color_map**: rio-tiler color map name. OPTIONAL
92+
93+
### Metadata
94+
95+
`:endpoint:/v1/tilejson.json` - Get tileJSON document
96+
- **url**: Cloud Optimized GeoTIFF URL. **REQUIRED**
97+
- **tile_format**: Output image format, default is set to None and will be either JPEG or PNG depending on masked value.
98+
- **tile_scale**: Tile size scale, default is set to 1 (256x256). OPTIONAL
99+
- **kwargs**: Other options will be forwarded to the `tiles` url.
100+
101+
`:endpoint:/v1/bounds` - Get general image bounds
102+
- **url**: Cloud Optimized GeoTIFF URL. **REQUIRED**
103+
104+
`:endpoint:/v1/info` - Get general image info
105+
- **url**: Cloud Optimized GeoTIFF URL. **REQUIRED**
106+
107+
`:endpoint:/v1/metadata` - Get image statistics
108+
- **url**: Cloud Optimized GeoTIFF URL. **REQUIRED**
109+
- **bidx**: Coma (',') delimited band indexes. OPTIONAL
110+
- **nodata**: Overwrite internal Nodata value. OPTIONAL
111+
- **pmin**: min percentile, default is 2. OPTIONAL
112+
- **pmax**: max percentile, default is 98. OPTIONAL
113+
- **max_size**: Max image size from which to calculate statistics, default is 1024. OPTIONAL
114+
- **histogram_bins**: Histogram bins, default is 20. OPTIONAL
115+
- **histogram_range**: Coma (',') delimited histogram bounds. OPTIONAL
25116

117+
## UI
26118

27-
## Project structure
119+
`:endpoint:/index.html` - Full UI (histogram, predefined rescaling, ...)
120+
121+
`:endpoint:/simple_viewer.html` - Simple UI (no histogram, manual rescaling, ...)
122+
123+
# Project structure
28124

29125
```
30126
titiler/ - titiler python module.
@@ -44,9 +140,16 @@ titiler/ - titiler python module.
44140
├── ressources/ - application ressources (enums, constants, ...).
45141
├── templates/ - html/xml models.
46142
├── main.py - FastAPI application creation and configuration.
47-
└── utils.py - utility functions.
48-
```
143+
├── utils.py - utility functions.
144+
145+
stack/
146+
├── app.py - AWS Stack definition (vpc, cluster, ecs, alb ...)
147+
├── config.py - Optional parameters for the stack definition [EDIT THIS]
148+
149+
OpenAPI/
150+
└── openapi.json - OpenAPI document.
49151
152+
```
50153

51154
## Contribution & Development
52155

@@ -67,3 +170,7 @@ This repo is set to use `pre-commit` to run *my-py*, *flake8*, *pydocstring* and
67170
```bash
68171
$ pre-commit install
69172
```
173+
174+
## Authors
175+
Created by [Development Seed](<http://developmentseed.org>)
176+

cdk.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"app": "python3 stack/app.py"
3+
}

setup.py

+8
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,17 @@
1616
extra_reqs = {
1717
"dev": ["pytest", "pytest-cov", "pytest-asyncio", "pre-commit"],
1818
"server": ["uvicorn", "click==7.0"],
19+
"deploy": [
20+
"aws-cdk.core",
21+
"aws-cdk.aws_ecs",
22+
"aws-cdk.aws_ec2",
23+
"aws-cdk.aws_autoscaling",
24+
"aws-cdk.aws_ecs_patterns",
25+
],
1926
"test": ["mock", "pytest", "pytest-cov", "pytest-asyncio", "requests"],
2027
}
2128

29+
2230
setup(
2331
name="titiler",
2432
version="0.1.0",

stack/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""AWS App."""

stack/app.py

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
"""Construct App."""
2+
3+
from typing import Any, Union
4+
5+
import os
6+
7+
from aws_cdk import (
8+
core,
9+
aws_ec2 as ec2,
10+
aws_ecs as ecs,
11+
aws_ecs_patterns as ecs_patterns,
12+
)
13+
14+
import config
15+
16+
17+
class titilerStack(core.Stack):
18+
"""Titiler ECS Fargate Stack."""
19+
20+
def __init__(
21+
self,
22+
scope: core.Construct,
23+
id: str,
24+
cpu: Union[int, float] = 256,
25+
memory: Union[int, float] = 512,
26+
mincount: int = 1,
27+
maxcount: int = 50,
28+
code_dir: str = "./",
29+
**kwargs: Any,
30+
) -> None:
31+
"""Define stack."""
32+
super().__init__(scope, id, *kwargs)
33+
34+
vpc = ec2.Vpc(self, f"{id}-vpc", max_azs=2)
35+
36+
cluster = ecs.Cluster(self, f"{id}-cluster", vpc=vpc)
37+
38+
fargate_service = ecs_patterns.ApplicationLoadBalancedFargateService(
39+
self,
40+
f"{id}-service",
41+
cluster=cluster,
42+
cpu=cpu,
43+
memory_limit_mib=memory,
44+
desired_count=mincount,
45+
public_load_balancer=True,
46+
listener_port=80,
47+
task_image_options=dict(
48+
image=ecs.ContainerImage.from_asset(
49+
code_dir, exclude=["cdk.out", ".git"]
50+
),
51+
container_port=80,
52+
environment=dict(
53+
CPL_TMPDIR="/tmp",
54+
GDAL_CACHEMAX="25%",
55+
GDAL_DISABLE_READDIR_ON_OPEN="EMPTY_DIR",
56+
GDAL_HTTP_MERGE_CONSECUTIVE_RANGES="YES",
57+
GDAL_HTTP_MULTIPLEX="YES",
58+
GDAL_HTTP_VERSION="2",
59+
MODULE_NAME="titiler.main",
60+
PYTHONWARNINGS="ignore",
61+
VARIABLE_NAME="app",
62+
VSI_CACHE="TRUE",
63+
VSI_CACHE_SIZE="1000000",
64+
WORKERS_PER_CORE="5",
65+
LOG_LEVEL="error",
66+
),
67+
),
68+
)
69+
70+
scalable_target = fargate_service.service.auto_scale_task_count(
71+
min_capacity=mincount, max_capacity=maxcount
72+
)
73+
74+
# https://github.com/awslabs/aws-rails-provisioner/blob/263782a4250ca1820082bfb059b163a0f2130d02/lib/aws-rails-provisioner/scaling.rb#L343-L387
75+
scalable_target.scale_on_request_count(
76+
"RequestScaling",
77+
requests_per_target=50,
78+
scale_in_cooldown=core.Duration.seconds(240),
79+
scale_out_cooldown=core.Duration.seconds(30),
80+
target_group=fargate_service.target_group,
81+
)
82+
83+
# scalable_target.scale_on_cpu_utilization(
84+
# "CpuScaling", target_utilization_percent=70,
85+
# )
86+
87+
fargate_service.service.connections.allow_from_any_ipv4(
88+
port_range=ec2.Port(
89+
protocol=ec2.Protocol.ALL,
90+
string_representation="All port 80",
91+
from_port=80,
92+
),
93+
description="Allows traffic on port 80 from NLB",
94+
)
95+
96+
97+
app = core.App()
98+
99+
# Tag infrastructure
100+
for key, value in {
101+
"Project": config.PROJECT_NAME,
102+
"Stack": config.STAGE,
103+
"Owner": os.environ.get("OWNER"),
104+
"Client": os.environ.get("CLIENT"),
105+
}.items():
106+
if value:
107+
core.Tag.add(app, key, value)
108+
109+
stackname = f"{config.PROJECT_NAME}-{config.STAGE}"
110+
titilerStack(
111+
app,
112+
stackname,
113+
cpu=config.TASK_CPU,
114+
memory=config.TASK_MEMORY,
115+
mincount=config.MIN_ECS_INSTANCES,
116+
maxcount=config.MAX_ECS_INSTANCES,
117+
)
118+
app.synth()

stack/config.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""STACK Configs."""
2+
3+
import os
4+
5+
PROJECT_NAME = "titiler"
6+
STAGE = os.environ.get("STAGE", "dev")
7+
8+
# // Service config
9+
# Min/Max Number of ECS images
10+
MIN_ECS_INSTANCES = 2
11+
MAX_ECS_INSTANCES = 50
12+
13+
# CPU value | Memory value
14+
# 256 (.25 vCPU) | 0.5 GB, 1 GB, 2 GB
15+
# 512 (.5 vCPU) | 1 GB, 2 GB, 3 GB, 4 GB
16+
# 1024 (1 vCPU) | 2 GB, 3 GB, 4 GB, 5 GB, 6 GB, 7 GB, 8 GB
17+
# 2048 (2 vCPU) | Between 4 GB and 16 GB in 1-GB increments
18+
# 4096 (4 vCPU) | Between 8 GB and 30 GB in 1-GB increments
19+
TASK_CPU = 1024
20+
TASK_MEMORY = 2048

titiler/api/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"""titiler."""
1+
"""titiler.api"""

titiler/api/api_v1/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"""titiler."""
1+
"""titiler.api.api_v1"""
+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"""titiler."""
1+
"""titiler.api.api_v1.endpoints"""

titiler/api/api_v1/endpoints/metadata.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
from titiler.core import config
2020
from titiler.models.mapbox import TileJSON
2121
from titiler.ressources.enums import ImageType
22+
from titiler.api.utils import info as cogInfo
2223

23-
24+
_info = partial(run_in_threadpool, cogInfo)
2425
_bounds = partial(run_in_threadpool, cogeo.bounds)
2526
_metadata = partial(run_in_threadpool, cogeo.metadata)
2627
_spatial_info = partial(run_in_threadpool, cogeo.spatial_info)
@@ -96,6 +97,16 @@ async def bounds(
9697
return await _bounds(url)
9798

9899

100+
@router.get("/info", responses={200: {"description": "Return basic info on COG."}})
101+
async def info(
102+
response: Response,
103+
url: str = Query(..., description="Cloud Optimized GeoTIFF URL."),
104+
):
105+
"""Handle /info requests."""
106+
response.headers["Cache-Control"] = "max-age=3600"
107+
return await _info(url)
108+
109+
99110
@router.get(
100111
"/metadata", responses={200: {"description": "Return the metadata of the COG."}}
101112
)

0 commit comments

Comments
 (0)