Skip to content

Commit d484a29

Browse files
authored
Merge pull request #5 from GNS-Science/feature/serverlss_setup
Feature/serverless setup
2 parents d8839e0 + 17b4da2 commit d484a29

12 files changed

+9566
-13
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,5 @@ ENV/
112112

113113
# mkdocs build dir
114114
site/
115+
node_modules
116+
node_modules

README.md

+26
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
ref: https://docs.graphene-python.org/projects/django/en/latest/tutorial-relay/
22

33

4+
## the Sqlite3 on lambda issue
5+
6+
This project is a good candidate for using a sqlite3 database, especially in the exploratory, POC stages.
7+
8+
But we had to solve some compatablity issues to get this working on AWS lambda . [See this guide](./SQLITE_CUSTOM_LAMBDA_BUILD.md) for more info.
9+
410
## Some Useful commands
511

612
### build an ERM model
@@ -24,4 +30,24 @@ poetry run python manage.py graph_models -o nshm_model.png nshm pipeline
2430
```
2531
poetry run python manage.py dumpdata nshm.OpenquakeHazardTask -o pipeline/fixtures/oht.json --indent=2
2632
poetry run python manage.py loaddata pipeline/fixtures/oht.json
33+
```
34+
35+
### update static files
36+
37+
These are just the static components for admin and graphiql
38+
39+
```
40+
poetry run python manage.py collectstatic -c
41+
```
42+
43+
### run server options
44+
45+
```
46+
poetry run python manage.py runserver
47+
poetry run python manage.py runserver_plus
48+
```
49+
50+
```
51+
poetry shell
52+
npx sls wsgi serve
2753
```

SQLITE_CUSTOM_LAMBDA_BUILD.md

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Building a modern SQLite3 library for Django 4 for AWS lambda + Python 3.10
2+
3+
Chris B Chamberlain, August 31, 2023.
4+
5+
## Why we're even trying this
6+
7+
We want a light weight, read-only sql database for django, that we can run in an AWS lambda function. It has been done, and the zappa project even make a read-write version, which gives some valuable tips:
8+
9+
- https://github.com/FlipperPA/django-s3-sqlite
10+
- https://github.com/FlipperPA/django-s3-sqlite/tree/main/shared-objects/python-3-8
11+
12+
## Issue 1) AWS lambda sqlite3 libary is outdated.
13+
14+
Modern django expects `sqlite3>=3.83`. But currently the AWS python10 lambda environment offers us `sqlite3 version: 3.7.17`
15+
16+
So, following this guide https://charlesleifer.com/blog/compiling-sqlite-for-use-with-python-applications/
17+
18+
we're able to build and test a modern sqlite (currenlty 3.44), but unfortunately this still doesn't work on lambda...
19+
20+
## Issue 2) the custom sqlite3 libary must be linked against the `libc` version available on AWS lambda.
21+
22+
So, while our locally compiled sqlite3 library works locally, in lambda we get:
23+
24+
```ImportError: /lib64/libm.so.6: version `GLIBC_2.29' not found (required by /var/task/_sqlite3.cpython-310-x86_64-linux-gnu.so)```.
25+
26+
NB. the version above may vary from system to system, since it's based on the local build environment.
27+
28+
Currently, AWS lambda provides us with `GLIBC_2.26`.
29+
30+
The recipe below resolved this problem for us.
31+
32+
### Background reading, tips & techniques
33+
34+
I found these articles very helpful with figuring out what was actually going on here.
35+
36+
- https://www.cyberithub.com/solved-glibc-2-29-not-found-error-on-ubuntu-linux/ good, but not a workable fix for lambda
37+
- https://stackoverflow.com/questions/4032373/linking-against-an-old-version-of-libc-to-provide-greater-application-coverage
38+
- https://stackoverflow.com/questions/69692703/compiling-python-3-10-at-amazon-linux-2
39+
- https://stackoverflow.com/a/69879426
40+
- https://stackoverflow.com/a/851229 very useful big-picture
41+
- https://stackoverflow.com/a/49355708 same issue, it's not easy
42+
-https://unix.stackexchange.com/questions/487303/how-to-find-the-name-of-the-so-file-which-contains-the-code-for-a-specific-funct
43+
44+
## A Recipe for running sqlite3 on AWS lambda
45+
46+
To get a build that works on AWS lambda we can use a suitable EC2 Linux2 instance and the following steps:
47+
48+
### 1) Launch an AWS EC2 instance
49+
50+
This is a simple way to get a lambda-compatible build enivironment:
51+
52+
I used a T2.medium instance, it's a bit faster than the smaller instances. Use the [AWS Linux 2 image](https://ap-southeast-2.console.aws.amazon.com/ec2/home?region=ap-southeast-2#Images:visibility=public-images;imageId=ami-0dab9ecf8f21f9ff3) or whatever is currently equivalent to the lambda OS.
53+
54+
The thing that really matters is the version of libc that's installed. We want to see `ldd (GNU libc) 2.26` which is exactly what we see on lambda:
55+
56+
```
57+
$> ldd --version
58+
ldd (GNU libc) 2.26
59+
...
60+
```
61+
62+
NB We've added a check for the current version in the projects' lambda startup function (wsgi.py) which is enabled when the environment varaible `DEBUG` is True.
63+
64+
### 2) install the build tools
65+
66+
with a console, e.g. `ssh`` or the AWS web ui.
67+
68+
```
69+
sudo yum groupinstall "Development Tools"
70+
sudo yum -y install bzip2-devel libffi-devel openssl11 openssl11-devel
71+
sudo yum install tcl
72+
```
73+
74+
### 3) install python 3.10.x
75+
76+
```
77+
wget https://www.python.org/ftp/python/3.10.13/Python-3.10.13.tgz
78+
tar xzf Python-3.10.13.tgz
79+
cd Python-3.10.13/
80+
./configure --enable-optimizations
81+
sudo make altinstall
82+
```
83+
84+
And now check that you have a working **python3.10** and **pip3**:
85+
86+
```
87+
which python3.10
88+
python3.10 --version
89+
pip3.10 --version
90+
```
91+
92+
### 4) install virtualenv
93+
94+
```
95+
pip3.10 install virtualenv
96+
virtualenv --help
97+
```
98+
99+
### 5) install & configure sqlite
100+
101+
At this time the latest version is 3.44. Recall that we need >= 3.8.3 for django compatablity, and to avoid the original `deterministic=True requires SQLite 3.8.3 or higher` error.
102+
103+
```
104+
wget https://www.sqlite.org/src/tarball/sqlite.tar.gz
105+
tar xzf sqlite.tar.gz
106+
cd sqlite
107+
./configure
108+
make sqlite3.c
109+
```
110+
111+
### 6) build the custom sqlite library (via pysqlite build script)
112+
113+
The basis fo this recipe is [laid out here](https://charlesleifer.com/blog/compiling-sqlite-for-use-with-python-applications/) by the maintainer of pysqlite, Charles Liefer.
114+
115+
Note that it's not stricly necessary to use the virtualenv option , but I chose that path.
116+
117+
**6.1)** Create and activate a virtual environment
118+
```
119+
virtualenv -p /usr/local/bin/python3.10 sqlite344
120+
cd sqlite344
121+
source bin/activate
122+
```
123+
124+
125+
**6.2)** Clone the repo to a different name (avoids import issues)
126+
127+
```
128+
git clone https://github.com/coleifer/pysqlite3 pysqlite3-build
129+
cd pysqlite3-build
130+
```
131+
132+
**6.3)** build library using the src and header files from the sqlite config in step 5.
133+
```
134+
cp ../../sqlite/sqlite3.[ch] .
135+
python setup.py build_static
136+
```
137+
138+
**6.4** check the libary locally
139+
```
140+
python setyp.py install
141+
$ python
142+
>>> from pysqlite3 import dbapi2 as sqlite3
143+
>>> sqlite3.sqlite_version
144+
'3.44.0'
145+
```
146+
147+
**6.5** Indentify and copy the library binary
148+
```
149+
ls ~/sqlite344/pysqlite3-build/build/lib.linux-x86_64-cpython-310/pysqlite3/_sqlite3.cpython-310-x86_64-linux-gnu.so
150+
```
151+
152+
This file you'll want to transfer from ec2 to your dev system, e.g.
153+
```
154+
scp -i "~/.aws/aws_ec2_keypair.pem" [email protected]:/home/ec2-user/sqlite344/pysqlite3/build/lib.linux-x86_64-cpython-310/pysqlite3/_sqlite3.cpython-310-x86_64-linux-gnu.so .
155+
```
156+
157+
the `_sqllite3.*.so` file needs to be in the root of our django lamdba project. See [serverless.yml](serverless.yml) which defines the contents of our lambda package.
8.05 MB
Binary file not shown.

nzshm_model_graphql_api/settings.py

+29-8
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,26 @@
1111
"""
1212

1313
from pathlib import Path
14+
import os
15+
# import sqlite3
16+
# if sqlite3.sqlite_version < '3.44': # local binary _ssqlite3.so is '3.44', but 3.37 also works fine
17+
# f = Path("_sqlite3.so")
18+
# print("checking for _sqlite.so with path: %s is found: %s" % (str(f.absolute()), f.exists()))
19+
# raise RuntimeError("Found sqllite version %s, which is unsupported by django")
1420

1521
# Build paths inside the project like this: BASE_DIR / 'subdir'.
1622
BASE_DIR = Path(__file__).resolve().parent.parent
1723

18-
1924
# Quick-start development settings - unsuitable for production
2025
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
2126

2227
# SECURITY WARNING: keep the secret key used in production secret!
2328
SECRET_KEY = 'django-insecure-*6mdqrx8xj5fkb4d@iy3yc#6u2@3hnsh1tc_a&p9os6z%o6xlg'
2429

2530
# SECURITY WARNING: don't run with debug turned on in production!
26-
DEBUG = True
31+
DEBUG = bool(os.getenv('DEBUG'))
2732

28-
ALLOWED_HOSTS = []
33+
ALLOWED_HOSTS = ["5qwlrdxd4a.execute-api.ap-southeast-2.amazonaws.com", "localhost"]
2934

3035

3136
# Application definition
@@ -45,6 +50,7 @@
4550

4651
MIDDLEWARE = [
4752
'django.middleware.security.SecurityMiddleware',
53+
'whitenoise.middleware.WhiteNoiseMiddleware',
4854
'django.contrib.sessions.middleware.SessionMiddleware',
4955
'django.middleware.common.CommonMiddleware',
5056
'django.middleware.csrf.CsrfViewMiddleware',
@@ -116,10 +122,6 @@
116122
USE_TZ = True
117123

118124

119-
# Static files (CSS, JavaScript, Images)
120-
# https://docs.djangoproject.com/en/4.2/howto/static-files/
121-
122-
STATIC_URL = 'static/'
123125

124126
# Default primary key field type
125127
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
@@ -130,4 +132,23 @@
130132
GRAPH_MODELS = {
131133
'all_applications': False,
132134
'group_models': True,
133-
}
135+
}
136+
137+
# Static files (CSS, JavaScript, Images)
138+
# https://docs.djangoproject.com/en/4.2/howto/static-files/
139+
140+
STATIC_URL = 'static/'
141+
STATIC_ROOT = str(BASE_DIR / 'staticfiles')
142+
143+
# using whitenoise to simplify static resources
144+
# ref https://whitenoise.readthedocs.io/en/latest/#quickstart-for-django-apps
145+
STORAGES = {
146+
# ...
147+
"staticfiles": {
148+
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
149+
},
150+
}
151+
152+
SECURE_REFERRER_POLICY = "origin"
153+
SECURE_CONTENT_TYPE_NOSNIFF = False
154+
WHITENOISE_STATIC_PREFIX = "static/"

nzshm_model_graphql_api/wsgi.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,28 @@
88
"""
99

1010
import os
11-
11+
from pkg_resources import parse_version
12+
from pathlib import Path
1213
from django.core.wsgi import get_wsgi_application
14+
import sqlite3
15+
import subprocess
16+
17+
# We want to know the sqlite3 is good to go, and in lambda this ain't ncessarily so.
18+
if os.getenv('DEBUG'):
19+
20+
cmd = ('uname', '-a')
21+
print(subprocess.check_output(cmd, text=True).split('\n')[0])
22+
23+
cmd = ('ldd', '--version')
24+
print( subprocess.check_output(cmd, text=True).split('\n')[0] )
25+
26+
print("sqlite3 version: %s" % sqlite3.sqlite_version)
27+
f = Path("_sqlite3.cpython-310-x86_64-linux-gnu.so")
28+
print("checking for _sqlite.so with path: %s is found: %s" % (str(f.absolute()), f.exists()))
29+
30+
if parse_version(sqlite3.sqlite_version) < parse_version('3.37'):
31+
# our custom binary is '3.44', but 3.37 also works fine
32+
raise RuntimeError("sqlite version %s is not supported" % sqlite3.sqlite_version)
1333

1434
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nzshm_model_graphql_api.settings')
1535

0 commit comments

Comments
 (0)