-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(extension): Add option to enable async workers in Flask and Djan…
…go (#1986) Related to [Rockcraft PR](canonical/rockcraft#747) and [Paas Charm PR](canonical/paas-charm#11). Adds charm option to enable Async Gunicorn Workers.
- Loading branch information
1 parent
33519b4
commit 3128c62
Showing
7 changed files
with
280 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from time import sleep | ||
|
||
import flask | ||
|
||
app = flask.Flask(__name__) | ||
|
||
|
||
@app.route("/") | ||
def index(): | ||
return "Hello, world!\n" | ||
|
||
|
||
@app.route("/io") | ||
def pseudo_io(): | ||
sleep(2) | ||
return "ok\n" | ||
|
||
if __name__ == "__main__": | ||
app.run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Flask | ||
gevent |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
########################################### | ||
# IMPORTANT | ||
# Comments matter! | ||
# The docs use the wrapping comments as | ||
# markers for including said instructions | ||
# as snippets in the docs. | ||
########################################### | ||
summary: How to create async Flask Charm | ||
|
||
kill-timeout: 90m | ||
|
||
environment: | ||
|
||
execute: | | ||
# Move everything to $HOME so that Juju deployment works | ||
mv *.yaml *.py *.txt $HOME | ||
cd $HOME | ||
# Don't use the staging store for this test | ||
unset CHARMCRAFT_STORE_API_URL | ||
unset CHARMCRAFT_UPLOAD_URL | ||
unset CHARMCRAFT_REGISTRY_URL | ||
# Add setup instructions | ||
snap install rockcraft --channel=latest/edge --classic | ||
snap install microk8s --channel=1.31-strict/stable | ||
snap install juju --channel=3/stable | ||
mkdir -p ~/.local/share | ||
# MicroK8s config setup | ||
microk8s status --wait-ready | ||
microk8s enable hostpath-storage | ||
microk8s enable registry | ||
microk8s enable ingress | ||
# Bootstrap controller | ||
juju bootstrap microk8s dev-controller | ||
cd $HOME | ||
# [docs:create-venv] | ||
sudo apt-get update && sudo apt-get install python3-venv -y | ||
python3 -m venv .venv | ||
source .venv/bin/activate | ||
pip install -r requirements.txt | ||
# [docs:create-venv-end] | ||
flask run -p 8000 & | ||
retry -n 5 --wait 2 curl --fail localhost:8000 | ||
# [docs:curl-flask] | ||
curl localhost:8000 | ||
# [docs:curl-flask-end] | ||
# [docs:curl-flask-async-app] | ||
curl localhost:8000/io | ||
# [docs:curl-flask-async-app-end] | ||
kill $! | ||
# [docs:create-rockcraft-yaml] | ||
rockcraft init --profile flask-framework | ||
# [docs:create-rockcraft-yaml-end] | ||
sed -i "s/name: .*/name: flask-async-app/g" rockcraft.yaml | ||
sed -i "s/amd64/$(dpkg --print-architecture)/g" rockcraft.yaml | ||
# [docs:pack] | ||
rockcraft pack | ||
# [docs:pack-end] | ||
# [docs:ls-rock] | ||
ls *.rock -l | ||
# [docs:ls-rock-end] | ||
# [docs:skopeo-copy] | ||
rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ | ||
oci-archive:flask-async-app_0.1_$(dpkg --print-architecture).rock \ | ||
docker://localhost:32000/flask-async-app:0.1 | ||
# [docs:skopeo-copy-end] | ||
# [docs:create-charm-dir] | ||
mkdir charm | ||
cd charm | ||
# [docs:create-charm-dir-end] | ||
# [docs:charm-init] | ||
charmcraft init --profile flask-framework --name flask-async-app | ||
# [docs:charm-init-end] | ||
sed -i "s/paas-charm.*/https:\/\/github.com\/canonical\/paas-charm\/archive\/async-workers.tar.gz/g" requirements.txt | ||
# [docs:charm-pack] | ||
charmcraft pack | ||
# [docs:charm-pack-end] | ||
# [docs:ls-charm] | ||
ls *.charm -l | ||
# [docs:ls-charm-end] | ||
# [docs:add-juju-model] | ||
juju add-model flask-async-app | ||
# [docs:add-juju-model-end] | ||
juju set-model-constraints -m flask-async-app arch=$(dpkg --print-architecture) | ||
# [docs:deploy-juju-model] | ||
juju deploy ./flask-async-app_ubuntu-22.04-$(dpkg --print-architecture).charm \ | ||
flask-async-app --resource \ | ||
flask-app-image=localhost:32000/flask-async-app:0.1 | ||
# [docs:deploy-juju-model-end] | ||
# [docs:deploy-nginx] | ||
juju deploy nginx-ingress-integrator --channel=latest/edge --base ubuntu@20.04 | ||
juju integrate nginx-ingress-integrator flask-async-app | ||
# [docs:deploy-nginx-end] | ||
# [docs:config-nginx] | ||
juju config nginx-ingress-integrator \ | ||
service-hostname=flask-async-app path-routes=/ | ||
# [docs:config-nginx-end] | ||
# give Juju some time to deploy the apps | ||
juju wait-for application flask-async-app --query='status=="active"' --timeout 10m | ||
juju wait-for application nginx-ingress-integrator --query='status=="active"' --timeout 10m | ||
# [docs:curl-init-deployment] | ||
curl http://flask-async-app --resolve flask-async-app:80:127.0.0.1 | ||
# [docs:curl-init-deployment-end] | ||
# [docs:config-async] | ||
juju config flask-async-app webserver-worker-class=gevent | ||
# [docs:config-async-end] | ||
juju wait-for application flask-async-app --query='status=="active"' --timeout 10m | ||
# test the async flask service | ||
NUM_REQUESTS=15 | ||
ASYNC_RESULT='TRUE' | ||
echo "Firing $NUM_REQUESTS requests to http://flask-async-app/io..." | ||
overall_start_time=$(date +%s) | ||
for i in $(seq 1 $NUM_REQUESTS); do | ||
( | ||
start_time=$(date +%s) | ||
echo "Request $i start time: $start_time" | ||
curl -s http://flask-async-app/io --resolve flask-async-app:80:127.0.0.1 | ||
end_time=$(date +%s) | ||
pass_time=$((end_time - start_time)) | ||
echo "Request $i end time: $end_time == $pass_time" | ||
) & | ||
done | ||
wait | ||
end_time=$(date +%s) | ||
overall_passtime=$((end_time - overall_start_time)) | ||
echo "Total pass time: $overall_passtime" | ||
if [ $((3 < overall_passtime)) -eq 1 ]; then | ||
echo "Error!" | ||
ASYNC_RESULT='FALSE' | ||
exit 2 | ||
fi | ||
[ "$ASYNC_RESULT" == 'TRUE' ] | ||
# Back out to main directory for clean-up | ||
cd .. | ||
# [docs:clean-environment] | ||
# exit and delete the virtual environment | ||
deactivate | ||
rm -rf charm .venv __pycache__ | ||
# delete all the files created during the tutorial | ||
rm flask-async-app_0.1_$(dpkg --print-architecture).rock rockcraft.yaml app.py \ | ||
requirements.txt migrate.py | ||
# Remove the juju model | ||
juju destroy-model flask-async-app --destroy-storage --no-prompt --force | ||
# [docs:clean-environment-end] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
.. _write-a-kubernetes-charm-for-an-async-flask-app: | ||
|
||
How to write a Kubernetes charm for an async Flask app | ||
====================================================== | ||
|
||
In this how-to guide you will configure a 12-factor Flask | ||
application to use asynchronous Gunicorn workers to be | ||
able to serve to multiple users easily. | ||
|
||
Make the rock async | ||
=================== | ||
|
||
To make the rock async, make sure to put the following in its ``requirements.txt`` | ||
file: | ||
|
||
.. literalinclude:: code/flask-async/requirements.txt | ||
|
||
Pack the rock using ``rockcraft pack`` and redeploy the charm with the new rock using | ||
[``juju refresh``](https://juju.is/docs/juju/juju-refresh). | ||
|
||
Configure the async application | ||
------------------------------- | ||
|
||
Now let's enable async Gunicorn workers. We will | ||
expect this configuration option to be available in the Flask app configuration | ||
under the ``webserver-worker-class`` key. Verify that the new configuration | ||
has been added by running: | ||
|
||
.. code:: bash | ||
juju config flask-async-app | grep -A 6 webserver-worker-class: | ||
The result should contain the key. | ||
|
||
The worker class can be changed using Juju: | ||
|
||
.. literalinclude:: code/flask-async/task.yaml | ||
:language: bash | ||
:start-after: [docs:config-async] | ||
:end-before: [docs:config-async-end] | ||
:dedent: 2 | ||
|
||
Test that the workers are operating in parallel by sending multiple | ||
simultaneous requests with curl: | ||
|
||
.. code:: bash | ||
curl --parallel --parallel-immediate --resolve flask-async-app:80:127.0.0.1 \ | ||
http://flask-async-app/io http://flask-async-app/io http://flask-async-app/io \ | ||
http://flask-async-app/io http://flask-async-app/io | ||
and they will all return at the same time. | ||
|
||
The results should arrive simultaneously and contain five instances of ``ok``: | ||
|
||
.. terminal:: | ||
|
||
ok | ||
ok | ||
ok | ||
ok | ||
ok | ||
|
||
It can take up to a minute for the configuration to take effect. When the | ||
configuration changes, the charm will re-enter the active state. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,4 @@ How-To | |
charm-to-poetry | ||
charm-to-python | ||
shared-cache | ||
flask-async |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters