12-Factor Web Development: Juju Secrets and Background Workers

Since the last post we have added support for Juju secrets and background tasks. Configurations can now be juju secrets and will be made available as flattened environment variables to the app. You can find an example below. Background tasks are supported by providing all the environment variables that are available to the web application (such as the database) to any pebble services in the rock that end with the -worker or -scheduler postfix. These are available on all the supported frameworks: Flask (stable), Django (soon to be stable) and Go and FastAPI (experimental).

We have also been busy with community work. In the last few months we had talks at PyCon Ohio and at Ubucon Latin America. We will also have 3 talks on the weekend of the 14th of November at PyCon Hong Kong (by @charlie4284), PyCon Ireland (by @jdkandersson) and PyCon Sweden (by @javierdelapuente).

We have published Django documentation now and the stable release will be soon:

We are also working hard on the FastAPI and Go documentation, see our matrix channel for links to PRs and documents where we are working on new features or documentation! You can also ask any questions or get help from us there. Now onto a quick demonstration of how Juju secrets work with 12-factor web apps.

Juju Secrets

Juju secrets are a way of securely passing sensitive data to a charm. You can read more about them in how to manage secrets. In the context of a web application, these might be tokens for accessing a service, such as stripe to accept payments from users. These sensitive data can now be passed to your application as a Juju secret. To illustrate how this works, let’s create a simple Flask application to demonstrate how the secret will be passed. This feature is also available in Django, Go and FastAPI. If you would like to follow along, complete the setup instructions in the Hands-on approach portion of the Flask tutorial and create and change into a directory called juju-secret-app. We will use the following Flask app for demonstration purposes:

import os

import flask

app = flask.Flask(__name__)


@app.route("/")
def index():
    print(os.environ)
    return "Hello, world!\n"


if __name__ == "__main__":
    app.run()

The print(os.environ) will show all the environment variables that are passed to the application, including the credentials we will pass as a secret. Don’t forget the requirements.txt file with Flask and then we can pack the rock and load it into MicroK8s:

rockcraft init --profile flask-framework
rockcraft pack
rockcraft.skopeo --insecure-policy copy \
  --dest-tls-verify=false \
  oci-archive:juju-secret-app_0.1_amd64.rock \
  docker://localhost:32000/juju-secret-app:0.1

Next, we need to create the charm:

mkdir charm
cd charm
charmcraft init --profile flask-framework --name juju-secret-app

Open charmcraft.yaml and add the following secret as a configuration at the end:

config:
  options:
    credentials:
      description: |
        Sensitive credentials to a service.
      type: secret

Pack the charm and deploy it:

charmcraft pack
juju add-model juju-secret-app
juju deploy ./juju-secret-app_ubuntu-22.04-amd64.charm \
  juju-secret-app \
  --resource flask-app-image=localhost:32000/juju-secret-app:0.1

We can keep an eye on the deployment progress using juju status --watch 5s and wait for the application to become active. We can then create the credentials as a juju secret, which will have a username and password:

$ juju add-secret credentials username=api password=86298f988fe8d1e2794bf5fa6943fe58
secret:cs310dnmp25c7b1k73a0
$ juju grant-secret credentials juju-secret-app
$ juju config juju-secret-app credentials=secret:cs310dnmp25c7b1k73a0

Check the application has finished restarting using juju status which should report it to be active again:

Model            Controller      Cloud/Region        Version  SLA          Timestamp
juju-secret-app  dev-controller  microk8s/localhost  3.5.4    unsupported  16:11:57+11:00

App              Version  Status  Scale  Charm            Channel  Rev  Address         Exposed  Message
juju-secret-app           active      1  juju-secret-app             0  10.152.183.243  no       

Unit                Workload  Agent  Address      Ports  Message
juju-secret-app/0*  active    idle   10.1.157.75

Then we can send a request to the root endpoint using the IP address shown in the Unit section of juju status for the juju-secret-app (curl 10.1.157.75:8000 for the case above) which should respond with Hello, world!. To see the environment variables that are set use microk8s.kubectl exec pod/juju-secret-app-0 -n juju-secret-app -c flask-app -- pebble logs. You should see the output of the print(os.environ) in the logs which should include the FLASK_CREDENTIALS_USERNAME and FLASK_CREDENTIALS_PASSWORD environment variables with the values from the juju secret.

The environment variable name is derived by combining the name of the configuration in charmcraft.yaml for the secret with the keys in the secret separated using _, capitalising all characters and adding the usual FLASK_ prefix.

In this demo, we created a simple Flask application that logs the environment variables when the root endpoint is called. We deployed this application using a charm with the credentials configuration as a secret. Then we created a Juju secret, passed it to the app charm and saw that the secret was inserted into the environment variables when we called the root endpoint. Thank you for reading and stay in touch on the matrix channel!

2 Likes