forked from canonical/charmcraft
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: port Flask tutorial from Juju.is (canonical#1976)
The current Flask tutorial: https://juju.is/docs/sdk/write-your-first-kubernetes-charm-for-a-flask-app This PR will move the tutorial off of Discourse and Charm SDK and onto the Charmcraft RTD. --------- Co-authored-by: Michael DuBelko <michael.dubelko@gmail.com> Co-authored-by: Alex Lowe <alex.lowe@canonical.com>
- Loading branch information
1 parent
3d9db19
commit 3e68507
Showing
13 changed files
with
940 additions
and
2 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
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,70 @@ | ||
First, `install Multipass <https://multipass.run/docs/install-multipass>`_. | ||
|
||
Use Multipass to launch an Ubuntu VM with the name ``charm-dev`` | ||
from the 24.04 blueprint: | ||
|
||
.. code-block:: bash | ||
multipass launch --cpus 4 --disk 50G --memory 4G --name charm-dev 24.04 | ||
Once the VM is up, open a shell into it: | ||
|
||
.. code-block:: bash | ||
multipass shell charm-dev | ||
In order to create the rock, you'll need to install Rockcraft: | ||
|
||
.. code-block:: bash | ||
sudo snap install rockcraft --classic | ||
``LXD`` will be required for building the rock. | ||
Make sure it is installed and initialised: | ||
|
||
.. code-block:: bash | ||
sudo snap install lxd | ||
lxd init --auto | ||
In order to create the charm, you'll need to install Charmcraft: | ||
|
||
.. code-block:: bash | ||
sudo snap install charmcraft --channel latest/stable --classic | ||
.. warning:: | ||
|
||
This tutorial requires version ``3.0.0`` or later of Charmcraft. Check the | ||
version of Charmcraft using ``charmcraft --version`` If you have an older | ||
version of Charmcraft installed, use | ||
``sudo snap refresh charmcraft --channel latest/edge`` to get the latest | ||
edge version of Charmcraft. | ||
|
||
MicroK8s is required to deploy the Flask application on Kubernetes. Install MicroK8s: | ||
|
||
.. code-block:: bash | ||
sudo snap install microk8s --channel 1.31-strict/stable | ||
sudo adduser $USER snap_microk8s | ||
newgrp snap_microk8s | ||
Wait for MicroK8s to be ready using ``sudo microk8s status --wait-ready``. | ||
Several MicroK8s add-ons are required for deployment: | ||
|
||
.. code-block:: bash | ||
sudo microk8s enable hostpath-storage | ||
# Required to host the OCI image of the Flask application | ||
sudo microk8s enable registry | ||
# Required to expose the Flask application | ||
sudo microk8s enable ingress | ||
Juju is required to deploy the Flask application. | ||
Install Juju and bootstrap a development controller: | ||
|
||
.. code-block:: bash | ||
sudo snap install juju --channel 3.5/stable | ||
mkdir -p ~/.local/share | ||
juju bootstrap microk8s dev-controller |
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,14 @@ | ||
# initial hello world Flask app | ||
|
||
import flask | ||
|
||
app = flask.Flask(__name__) | ||
|
||
|
||
@app.route("/") | ||
def index(): | ||
return "Hello, world!\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,16 @@ | ||
# Flask app with a greeting configuration | ||
|
||
import flask | ||
|
||
app = flask.Flask(__name__) | ||
app.config.from_prefixed_env() | ||
|
||
|
||
@app.route("/") | ||
def index(): | ||
greeting = app.config.get("GREETING", "Hello, world!") | ||
return f"{greeting}\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,9 @@ | ||
# configuration snippet for Flask application with a configuration | ||
|
||
config: | ||
options: | ||
greeting: | ||
description: | | ||
The greeting to be returned by the Flask application. | ||
default: "Hello, world!" | ||
type: string |
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 @@ | ||
Flask |
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,220 @@ | ||
########################################### | ||
# IMPORTANT | ||
# Comments matter! | ||
# The docs use the wrapping comments as | ||
# markers for including said instructions | ||
# as snippets in the docs. | ||
########################################### | ||
summary: Getting started with Flask tutorial | ||
|
||
kill-timeout: 180m | ||
|
||
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) | ||
# (don't install charmcraft) | ||
snap install rockcraft --classic | ||
snap install lxd | ||
lxd init --auto | ||
snap install microk8s --channel=1.31-strict/stable | ||
snap refresh juju --channel=3.5/stable --amend | ||
# Juju config setup | ||
lxc network set lxdbr0 ipv6.address none | ||
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 | ||
# [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] | ||
kill $! | ||
# [docs:create-rockcraft-yaml] | ||
rockcraft init --profile flask-framework | ||
# [docs:create-rockcraft-yaml-end] | ||
sed -i "s/name: .*/name: flask-hello-world/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-hello-world_0.1_$(dpkg --print-architecture).rock \ | ||
docker://localhost:32000/flask-hello-world: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-hello-world | ||
# [docs:charm-init-end] | ||
# [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-hello-world | ||
# [docs:add-juju-model-end] | ||
juju set-model-constraints -m flask-hello-world arch=$(dpkg --print-architecture) | ||
# [docs:deploy-juju-model] | ||
juju deploy ./flask-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ | ||
flask-hello-world --resource \ | ||
flask-app-image=localhost:32000/flask-hello-world: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-hello-world | ||
# [docs:deploy-nginx-end] | ||
# [docs:config-nginx] | ||
juju config nginx-ingress-integrator \ | ||
service-hostname=flask-hello-world path-routes=/ | ||
# [docs:config-nginx-end] | ||
# give Juju some time to deploy the apps | ||
juju wait-for application flask-hello-world --query='status=="active"' --timeout 10m | ||
juju wait-for application nginx-ingress-integrator --query='status=="active"' --timeout 10m | ||
# [docs:curl-init-deployment] | ||
curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1 | ||
# [docs:curl-init-deployment-end] | ||
cd .. | ||
cat greeting_app.py > app.py | ||
sed -i "s/version: .*/version: 0.2/g" rockcraft.yaml | ||
rockcraft pack | ||
# [docs:docker-update] | ||
rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ | ||
oci-archive:flask-hello-world_0.2_$(dpkg --print-architecture).rock \ | ||
docker://localhost:32000/flask-hello-world:0.2 | ||
# [docs:docker-update-end] | ||
cat greeting_charmcraft.yaml >> ./charm/charmcraft.yaml | ||
cd charm | ||
charmcraft pack | ||
# [docs:refresh-deployment] | ||
juju refresh flask-hello-world \ | ||
--path=./flask-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ | ||
--resource flask-app-image=localhost:32000/flask-hello-world:0.2 | ||
# [docs:refresh-deployment-end] | ||
# give Juju some time to refresh the app | ||
juju wait-for application flask-hello-world --query='status=="active"' --timeout 10m | ||
# curl and check that the response is Hello | ||
curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1 | grep Hello | ||
# [docs:change-config] | ||
juju config flask-hello-world greeting='Hi!' | ||
# [docs:change-config-end] | ||
# make sure that the application updates | ||
juju wait-for application flask-hello-world --query='status=="maintenance"' --timeout 10m | ||
juju wait-for application flask-hello-world --query='status=="active"' --timeout 10m | ||
# curl and check that the response is now Hi | ||
curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1 | grep Hi | ||
cd .. | ||
cat visitors_migrate.py >> migrate.py | ||
cat visitors_app.py > app.py | ||
sed -i "s/version: .*/version: 0.3/g" rockcraft.yaml | ||
echo "psycopg2-binary" >> requirements.txt | ||
rockcraft pack | ||
# [docs:docker-2nd-update] | ||
rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ | ||
oci-archive:flask-hello-world_0.3_$(dpkg --print-architecture).rock \ | ||
docker://localhost:32000/flask-hello-world:0.3 | ||
# [docs:docker-2nd-update-end] | ||
cat visitors_charmcraft.yaml >> ./charm/charmcraft.yaml | ||
cd charm | ||
charmcraft pack | ||
# [docs:refresh-2nd-deployment] | ||
juju refresh flask-hello-world \ | ||
--path=./flask-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ | ||
--resource flask-app-image=localhost:32000/flask-hello-world:0.3 | ||
# [docs:refresh-2nd-deployment-end] | ||
# [docs:deploy-postgres] | ||
juju deploy postgresql-k8s --channel=14/stable --trust | ||
juju integrate flask-hello-world postgresql-k8s | ||
# [docs:deploy-postgres-end] | ||
# give Juju some time to deploy and refresh the apps | ||
juju wait-for application postgresql-k8s --query='status=="active"' --timeout 10m | ||
juju wait-for application flask-hello-world --query='status=="active"' --timeout 30m | juju status --relations | ||
juju status --relations | ||
curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1 | grep Hi | ||
curl http://flask-hello-world/visitors --resolve flask-hello-world:80:127.0.0.1 | grep 1 | ||
curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1 | grep Hi | ||
curl http://flask-hello-world/visitors --resolve flask-hello-world:80:127.0.0.1 | grep 2 | ||
# 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-hello-world_0.1_$(dpkg --print-architecture).rock flask-hello-world_0.2_$(dpkg --print-architecture).rock \ | ||
flask-hello-world_0.3_$(dpkg --print-architecture).rock rockcraft.yaml app.py \ | ||
requirements.txt migrate.py | ||
# Remove the juju model | ||
juju destroy-model flask-hello-world --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,42 @@ | ||
# Flask application that keeps track of visitors using a database | ||
|
||
import datetime | ||
import os | ||
|
||
import flask | ||
import psycopg2 | ||
|
||
app = flask.Flask(__name__) | ||
app.config.from_prefixed_env() | ||
|
||
DATABASE_URI = os.environ["POSTGRESQL_DB_CONNECT_STRING"] | ||
|
||
|
||
@app.route("/") | ||
def index(): | ||
with psycopg2.connect(DATABASE_URI) as conn, conn.cursor() as cur: | ||
user_agent = flask.request.headers.get('User-Agent') | ||
timestamp = datetime.datetime.now() | ||
|
||
cur.execute( | ||
"INSERT INTO visitors (timestamp, user_agent) VALUES (%s, %s)", | ||
(timestamp, user_agent) | ||
) | ||
conn.commit() | ||
|
||
|
||
greeting = app.config.get("GREETING", "Hello, world!") | ||
return f"{greeting}\n" | ||
|
||
|
||
@app.route("/visitors") | ||
def visitors(): | ||
with psycopg2.connect(DATABASE_URI) as conn, conn.cursor() as cur: | ||
cur.execute("SELECT COUNT(*) FROM visitors") | ||
total_visitors = cur.fetchone()[0] | ||
|
||
return f"{total_visitors}\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,6 @@ | ||
# requires snippet for Flask application with a database | ||
|
||
requires: | ||
postgresql: | ||
interface: postgresql_client | ||
optional: false |
Oops, something went wrong.