Skip to content

Commit 4bf584d

Browse files
committed
Add post walking through building and scaling a python function
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <[email protected]>
1 parent 777f7d0 commit 4bf584d

File tree

3 files changed

+350
-0
lines changed

3 files changed

+350
-0
lines changed
Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
---
2+
title: How to Build and Scale Python Functions with OpenFaaS
3+
description: Learn how to build, scale and monitor your first Python function with OpenFaaS, including how to add secrets and pip modules.
4+
date: 2024-11-27
5+
categories:
6+
- functions
7+
- python
8+
- autoscaling
9+
- pip
10+
dark_background: true
11+
image: /images/2024-11-build-scale-python/background.png
12+
author_staff_member: alex
13+
hide_header_image: true
14+
---
15+
16+
In this quickstart, we'll guide you through how to build, scale and monitor your first Python function with OpenFaaS.
17+
18+
We'll do the following:
19+
20+
* Meet two official Python templates
21+
* Build a function and test it locally
22+
* Scale the function horizontally
23+
* Scale the function to zero
24+
* Monitor it during a load test
25+
* Consume a secret and a pip module, to make authenticated API requests
26+
27+
According to Datadog's [State of Serverless report (2023)](https://www.datadoghq.com/state-of-serverless/), Node.js and Python remain the most popular choice for serverless functions on AWS Lambda, with Python coming in at a close second to JavaScript/Typescript. With Python also being the de-factor language for interacting with AI and LLMs, we may see Python shifting into the top spot in the coming years.
28+
29+
Many users like the convenience that a serverless platform offers, whether run as a managed cloud service, or self-hosted on Kubernetes or [an IoT/edge device](https://www.openfaas.com/blog/meet-openfaas-edge/), like with OpenFaaS.
30+
31+
On a recent call with developers at a large travel company, they said they were considering OpenFaaS as a way to ship changes to their product without affecting its core, this is a similar mindset to a microservices architecture. However, microservices require a lot of boiler-plate code, and create operational overhead, which is where OpenFaaS can simplify managing, scaling, and monitoring code as functions.
32+
33+
Unlike cloud solutions, the basic workload for an OpenFaaS function is not a zip file, but a Open Container Initiative (OCI) image. This means that you can use any language, framework or tooling that can produce a Docker image, with a HTTP server listening on port 8080. Container images can be built quickly and easily with CI/CD platforms like [GitHub Actions](https://docs.actuated.com/examples/openfaas-publish/) or [GitLab CI](https://docs.openfaas.com/reference/cicd/gitlab/).
34+
35+
## Introducing the official Python templates
36+
37+
We've had a few variations of Python templates over the years since the first one in 2017, and even today, we still have two choices that make sense for most users, rather than one that does everything.
38+
39+
Both templates use the same HTTP request/response style for their handler, and "pip" along with a requirements.txt file to manage dependencies.
40+
41+
* `python3-http` - based upon Alpine Linux, for the absolute smallest image size and fastest start-up time - use it with pure Python modules only.
42+
* `python3-http-debian` - the same as the above, but it uses a Debian base image, which is more compatible with some Python modules that require native dependencies.
43+
44+
Some people ask why you wouldn't use the Alpine image, then add a build toolchain back in, the answer is that: it's slower at build time, results in a much bigger image at runtime, and uses non-standard musl libc, instead of glibc, which is what most native modules expect.
45+
46+
Here's the basic handler:
47+
48+
```python
49+
def handle(event, context):
50+
return {
51+
"statusCode": 200,
52+
"body": "Hello from OpenFaaS!",
53+
"headers": {"Content-Type": "text/plain"}
54+
}
55+
```
56+
57+
As you can see, the minimal example handler takes in an event, and context which contain everything you could ever need to know about the HTTP request, then returns a dictionary with a status code, body and headers.
58+
59+
Any Dockerfile, dependency management, HTTP server, and other details are all abstracted way into the template. You can fork and modify the template if you wish, but most customers will use it as is.
60+
61+
There are more advanced options for working with a database, storing state between requests, and making use of private pip repositories, these are [all detailed in the documentation](https://docs.openfaas.com/languages/python/).
62+
63+
## Building a new function
64+
65+
First of all, we need to fetch the Python templates from GitHub, then we will scaffold a new function called "hello":
66+
67+
```sh
68+
faas-cli template store pull python3-http
69+
```
70+
71+
This command brings down both variations.
72+
73+
Next, set your container registry in the `OPENFAAS_PREFIX` environment variable, and then scaffold a new function.
74+
75+
If you haven't chosen a registry such as the Docker Hub, ECR, or GCR, etc yet, then you can use `ttl.sh` instead for a public image, that will be deleted after 24 hours. ttl.sh is likely to be slower than a production registry, but it's a good way to get started.
76+
77+
78+
```sh
79+
export OPENFAAS_PREFIX="ttl.sh/alexellis"
80+
81+
faas-cli new --lang python3-http hello
82+
```
83+
84+
This will create:
85+
86+
```
87+
./hello
88+
./hello/tox.ini
89+
./hello/handler_test.py
90+
./hello/requirements.txt
91+
./hello/handler.py
92+
./hello.yml
93+
```
94+
95+
Multiple functions can be added to the same stack YAML file by passing the `--append` flag to `faas-cli new`.
96+
97+
To make the command shorter, let's now rename `hello.yml` to `stack.yml`, so we can skip the `--file` flag in future commands.
98+
99+
```sh
100+
mv hello.yml stack.yml
101+
```
102+
103+
Now there's a way to test the function without even deploying it, so long as it doesn't try to access the network of the Kubernetes cluster.
104+
105+
```sh
106+
faas-cli local-run --tag=digest --watch
107+
```
108+
109+
You'll be able to invoke the function using the URL you were given in the output.
110+
111+
When you're ready, you can publish the image to the remote registry, and deploy it to your OpenFaaS cluster.
112+
113+
```sh
114+
faas-cli up
115+
```
116+
117+
Next, you can use `faas-cli list` to view the deployed function, and `faas-cli describe hello` to see the URL and other details.
118+
119+
```bash
120+
# faas-cli describe hello
121+
122+
Name: hello
123+
Status: Ready
124+
Replicas: 1
125+
Available Replicas: 1
126+
Invocations: 1
127+
Image: ttl.sh/alexellis/hello:latest
128+
Function Process: python index.py
129+
URL: http://127.0.0.1:8080/function/hello
130+
Async URL: http://127.0.0.1:8080/async-function/hello
131+
Usage:
132+
RAM: 24.00 MB
133+
CPU: 5 Mi
134+
```
135+
136+
One of the key things we are looking for is that the Status says "Ready", then we know the function is ready to be invoked using its URL.
137+
138+
## Scaling the function
139+
140+
There are some default parameters for scaling functions, which do not require any labels or additional configuration.
141+
142+
The defaults are: 50 Requests Per Second, minimum replicas of 1, maximum replicas of 20, all of these settings can be altered by changing the labels on the function.
143+
144+
Roughly speaking, if a function receives 100 requests per second, two replicas will be created, if it receives 200 requests per second, four replicas will be created, and so forth.
145+
146+
Each OpenFaaS Function can handle dozens, hundreds, if not thousands of requests per second, so unlike a product like AWS Lambda, each replica is not tied to one ongoing request.
147+
148+
You can tune the way functions scale using CPU, RAM, concurrent requests, RPS, or custom metrics in Prometheus.
149+
150+
Not all functions are alike, so you will need to test out an tune the parameters for each, once they start getting heavier load.
151+
152+
See also: [Autoscaling docs](https://docs.openfaas.com/architecture/autoscaling/)
153+
154+
There are sophisticated ways to run load tests on functions using tools like [Grafana k6.io](https://k6s.io) to generate realistic user traffic, but for now, we can use `hey` which is a simple HTTP load generator.
155+
156+
First, install `hey`:
157+
158+
```sh
159+
curl -sLS https://get.arkade.dev | sudo sh
160+
arkade get hey
161+
```
162+
163+
Then run the following command to generate 10 concurrent requests for a period of 5 minutes, not exceeding 100 QPS per concurrent worker:
164+
165+
```sh
166+
hey -c 10 -z 5m -q 100 http://127.0.0.1:8080/function/hello
167+
```
168+
169+
In another window, you can run the following to see the number of replicas that are running:
170+
171+
```sh
172+
kubectl get function/hello -n openfaas-fn -o wide --watch
173+
174+
NAME IMAGE READY HEALTHY REPLICAS AVAILABLE UNAVAILABLE
175+
hello ttl.sh/alexellis/hello:latest True True 1 1
176+
hello ttl.sh/alexellis/hello:latest True True 2 2
177+
hello ttl.sh/alexellis/hello:latest True True 3 3
178+
...
179+
```
180+
181+
And so forth. OpenFaaS functions use a Kubernetes Deployment object, and Pods, so you can also see those objects if you're interested:
182+
183+
```bash
184+
kubectl get deploy/hello -n openfaas-fn -o wide
185+
186+
kubectl get pod -n openfaas-fn -l faas_function=hello -o wide
187+
```
188+
189+
Once the load test has completed, you will see the amount of replicas drop off back to the minimum of 1.
190+
191+
## Scaling to zero
192+
193+
If you open the [OpenFaaS Dashboard](https://docs.openfaas.com/openfaas-pro/dashboard/), you'll see the following mid-test:
194+
195+
![](/images/2024-11-build-scale-python/dashboard-stats.png)
196+
197+
Next, if you click on the Task "Configure scale to zero", you'll get the labels you can copy and paste into your stack.yaml to turn it on for this function.
198+
199+
To enable scale to zero, we need to add a couple of labels to the function's YAML file. Here's an example:
200+
201+
```diff
202+
functions:
203+
hello:
204+
+ labels:
205+
+ com.openfaas.scale.zero: true
206+
+ com.openfaas.scale.zero-duration: 5m
207+
```
208+
209+
Now, you can run `faas-cli up` again to apply the changes.
210+
211+
Leave the function idle for 5 minutes, and watch the following, you'll see it go to "0" under REPLICAS:
212+
213+
```sh
214+
kubectl get function/hello -n openfaas-fn -o wide --watch
215+
```
216+
217+
The dashboard will also show the function as 0/0 replicas.
218+
219+
Next, you can scale back up by invoking the function via the Invoke button on the dashboard, or via curl.
220+
221+
You can learn more about scaling to zero in the docs: [Scale-to-zero](https://docs.openfaas.com/openfaas-pro/scale-to-zero/)
222+
223+
## Monitoring the function
224+
225+
OpenFaaS has very [detailed Prometheus metrics built into all its components](https://docs.openfaas.com/architecture/metrics/).
226+
227+
Rather than understanding each of these up front, you can deploy our [official Grafana dashboards](https://docs.openfaas.com/openfaas-pro/grafana-dashboards/) to a self-hosted Grafana instance, or a managed Grafana instance on Grafana Cloud.
228+
229+
The overview page shows all the functions in your cluster, and their invocation rate, latency, and error rate.
230+
231+
![Overview dashboard](https://docs.openfaas.com/images/grafana/overview-dashboard.png)
232+
233+
On this page you can ask questions such as:
234+
235+
* Is this function scaling too quickly or too slowly?
236+
* Are any requests erroring?
237+
* Is RAM being recovered when idle, or is there a memory leak?
238+
* Are there any functions that are mainly idle, but could be set to scale to zero?
239+
* Are request durations increasing beyond acceptable levels?
240+
* Do we need to alter the RAM/vCPU allocated to any particular function?
241+
242+
The "spotlight" version has a drop-down that you can use to dial into a specific function without the noise of the rest of the system getting in the way.
243+
244+
## Add a pip module and consume a secret
245+
246+
There are various examples in the documentation on [how to work with SQL](https://docs.openfaas.com/languages/python/#example-with-postgresql), which we don't need to repeat here, so let's add a simple pip module to show you how it's done.
247+
248+
Let's use a module that can interact with GitHub's API, and fetch the number of stars for a repository that we read from the HTTP request.
249+
250+
```bash
251+
faas-cli new --lang python3-http-debian github-stars
252+
```
253+
254+
First, add the module to `github-stars/requirements.txt`:
255+
256+
```diff
257+
+ PyGithub
258+
```
259+
260+
Create a [Personal Access Token on GitHub](https://github.com/settings/tokens), and save it as "token.txt", then create an OpenFaaS secret for the function:
261+
262+
```sh
263+
faas-cli secret create github-token --from-file=token.txt
264+
```
265+
266+
Now, you can modify the handler to fetch the number of stars for a repository.
267+
268+
Edit `github-stars/handler.py`:
269+
270+
```python
271+
import os
272+
import json
273+
from github import Github
274+
from github import Auth
275+
276+
def read_secret(name):
277+
with open(f"/var/openfaas/secrets/{name}") as f:
278+
return f.read().strip()
279+
280+
def handle(event, context):
281+
282+
body = event.body
283+
if not body:
284+
return {
285+
"statusCode": 400,
286+
"body": "Missing body",
287+
"headers": {"Content-Type": "text/plain"}
288+
}
289+
290+
bodyJson = json.loads(body)
291+
292+
repo = bodyJson.get("repo")
293+
294+
token = read_secret("github-token")
295+
296+
auth = Auth.Token(token)
297+
g = Github(auth=auth)
298+
r = g.get_repo(repo)
299+
g.close()
300+
301+
return {
302+
"statusCode": 200,
303+
"body": f"{repo} has {r.stargazers_count} stars",
304+
"headers": {"Content-Type": "text/plain"}
305+
}
306+
307+
```
308+
309+
Now update stack.yml to include the secret:
310+
311+
```diff
312+
functions:
313+
hello:
314+
+ secrets:
315+
+ - github-token
316+
```
317+
318+
Deploy the function and try it out:
319+
320+
```sh
321+
faas-cli up
322+
```
323+
324+
If you use the `--append` flag to have your two functions in the same file, you can make development more efficient by passing in the `--filter github-stars` flag to have the commands only work on a single function.
325+
326+
```sh
327+
curl -H "Content-type: application/json" -d '{"repo": "openfaas/faas"}' http://127.0.0.1:8080/function/github-stars
328+
329+
openfaas/faas has 25209 stars
330+
```
331+
332+
## Watch me walk through these steps
333+
334+
I recorded a video walking through most of the steps you find in this blog post, you can watch it back to see it live and if you're having any problems, perhaps find out what you may be doing differently.
335+
336+
{% include youtube.html id="igv9LRPzZbE" %}
337+
338+
## Wrapping up
339+
340+
In this blog post we walked through how to build, scale, and monitor a Python function with OpenFaaS. That included autoscaling in response to a basic load test with the hey load-generation tool, saclign to zero after a period of inactivity, and monitoring with the OpenFaaS Dashboard and Grafana.
341+
342+
We then went ahead and added a secret to a function and accessed GitHub's API using a custom Pip module to fetch the number of stars for a repository.
343+
344+
You can find out more about what we covered in the documentation:
345+
346+
* [Scale to zero](https://docs.openfaas.com/openfaas-pro/scale-to-zero/)
347+
* [Autoscaling](https://docs.openfaas.com/architecture/autoscaling/)
348+
* [Metrics](https://docs.openfaas.com/architecture/metrics/)
349+
* [Grafana dashboards](https://docs.openfaas.com/openfaas-pro/grafana-dashboards/)
350+
* [Python templates](https://docs.openfaas.com/languages/python/)
121 KB
Loading
199 KB
Loading

0 commit comments

Comments
 (0)