-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(extension): Add option to enable async workers in Flask and Django #1986
Open
alithethird
wants to merge
27
commits into
canonical:main
Choose a base branch
from
alithethird:flask-async-worker
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 14 commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
69c697e
Feat(extension): Added option to enable async workers in Flask and Dj…
alithethird 3e26ed8
Merge branch 'main' into flask-async-worker
alithethird a4d82c0
Chore(doc): Add Spread test/tutorial doc
alithethird d9aa901
Merge branch 'flask-async-worker' of https://github.com/alithethird/c…
alithethird 33c8796
Merge branch 'main' into flask-async-worker
alithethird 7fa50ec
Chore(docs): Lint docs
alithethird 9657bc3
Merge branch 'flask-async-worker' of https://github.com/alithethird/c…
alithethird 364e3cc
Run CI
alithethird a5c872f
Chore(test): Fix spread test
alithethird 5000827
Chore(test): Fix charmcraft version in spread test
alithethird 5d7739e
Chore(docs): Changed tutorial to how-to. Updated spread test.
alithethird 0a83b2a
Chore(): Fix spread test
alithethird d0176be
Merge branch 'main' into flask-async-worker
alithethird a69f8db
Chore(): Change microk8s version in spread test
alithethird b8e39a6
chore(doc): Applied comments
alithethird cee3cb4
Merge branch 'main' into flask-async-worker
alithethird 812c906
Merge branch 'main' into flask-async-worker
alithethird e9e240f
Merge branch 'main' into flask-async-worker
alithethird 5e7a762
Merge branch 'main' into flask-async-worker
alithethird 40eaa15
chore(test): Update spread test to use rockcraft latest/edge
alithethird a78aa82
Merge branch 'flask-async-worker' of https://github.com/alithethird/c…
alithethird 0852a04
chore(doc): Update howto
alithethird 361d165
chore(doc): Update docs
alithethird 12640ab
Merge branch 'main' into flask-async-worker
alithethird a7be9a8
Merge branch 'main' into flask-async-worker
alithethird b0899b2
Merge branch 'main' into flask-async-worker
alithethird 8d2a695
chore(): Change option description.
alithethird File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,194 @@ | ||
########################################### | ||
# 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 | ||
# (Ran into issues in prepare section) | ||
# snap install rockcraft --channel=latest/edge --classic | ||
# Install the latest rockcraft snap | ||
# (This can be removed after the Rockcraft PR is merged) | ||
# The PR: https://github.com/canonical/rockcraft/pull/747 | ||
snap install snapcraft --channel=latest/edge --classic | ||
# Download rockcraft async-workers branch and alithethird fork | ||
git clone -b flask-django-extention-async-workers https://github.com/alithethird/rockcraft | ||
cd rockcraft | ||
snapcraft pack | ||
snap install --dangerous --classic rockcraft_* | ||
# snap refresh charmcraft --channel=latest/edge --amend | ||
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 --revision 122 | ||
alithethird marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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,48 @@ | ||
====================================================== | ||
How to write a Kubernetes charm for an Async Flask app | ||
alithethird marked this conversation as resolved.
Show resolved
Hide resolved
|
||
====================================================== | ||
|
||
In this how to we will configure 12 Factor Flask application | ||
to use asynchronous Gunicorn workers to be able to serve | ||
to multiple users easily. | ||
alithethird marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Make the rock async | ||
=================== | ||
|
||
Before packing the rock make sure to put the following in ``requirements.txt`` | ||
alithethird marked this conversation as resolved.
Show resolved
Hide resolved
|
||
file: | ||
|
||
.. literalinclude:: code/flask-async/requirements.txt | ||
|
||
Configure the async application | ||
alithethird marked this conversation as resolved.
Show resolved
Hide resolved
|
||
=============================== | ||
alithethird marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Now let's enable async Gunicorn workers using a configuration option. We will | ||
alithethird marked this conversation as resolved.
Show resolved
Hide resolved
|
||
expect this configuration option to be available in the Flask app configuration | ||
under the keyword ``webserver-worker-class``. Verify that the new configuration | ||
alithethird marked this conversation as resolved.
Show resolved
Hide resolved
|
||
has been added using | ||
``juju config flask-async-app | grep -A 6 webserver-worker-class:`` which should | ||
show the configuration option. | ||
alithethird marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
.. note:: | ||
|
||
The ``grep`` command extracts a portion of the configuration to make | ||
it easier to check whether the configuration option has been added. | ||
alithethird marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 | ||
|
||
alithethird marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Now you can run | ||
``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`` | ||
in they will all return at the same time. | ||
alithethird marked this conversation as resolved.
Show resolved
Hide resolved
alithethird marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
.. note:: | ||
|
||
It might take a short time for the configuration to take effect. | ||
alithethird marked this conversation as resolved.
Show resolved
Hide resolved
|
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not thrilled about having the name here include
class
but the description saymethod
- is this standard language? If not, can we come up with something more standardised?(Non-blocker, except that if we change the name we'd need to change it before release)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a good catch! Thank you. In the description by
method
I meantthe way of handling requests
. I can see how this language can be confusing. I will update the description to make it clearer.