Getting Started
In this walk through you will build a Python web app, package it up and deploy it as an Acorn app. The app will interact with Redis and Postgres, which both will be packaged along with the web app in a single Acorn image.
The guide makes use of Python, Redis and Postgres here, but you don't need to be familiar with those technologies, as the examples should be understandable without preliminary knowledge in those.
Prerequisites
To run this example, you will need to have the Acorn CLI installed and administrative access to a Kubernetes cluster. Here you can find some documentation on how to get there:
- Acorn CLI
- Access to a Kubernetes cluster through
kubectl
from your CLI. Some great options for local development are Rancher Desktop, K3s, k3d, or Docker Desktop. It also works with any other Kubernetes distribution, for example a managed instance hosted in a major cloud provider or a cluster provided by your work environment.
Step 1. Prepare the cluster
Installing the Acorn server-side components into your cluster is as easy as running
acorn install
Note: Installing Acorn into a Kubernetes cluster requires cluster-admin privileges. Please see our architecture overview to learn what components will be deployed.
Step 2. Create your app
Let's start by creating a few files that compose our Python web app. You can also find all these files in the docs/flask
directory of the examples GitHub repo if you'd rather start from there.
# Create a directory structure for our web app
mkdir acorn-test-app
# Change into the root of the new directory tree
cd acorn-test-app
The Web App
Now create the basic Python web app in a file called app.py
, which stores a visitor count in the Redis Cache and fetches a list of items from a Postgres Database.
import logging as log
import os
import psycopg2
import redis
from flask import Flask, render_template_string
# HTML Jinja2 Template which will be shown in the browser
page_template = '''
<div style="margin: auto; text-align: center;">
<h1>{{ welcome_text }}</h1><br>
You're visitor #{{ visitors }} to learn what squirrels love the most:<br>
<ul>
{%- for food in foods %}
<li>{{ food }}</li>
{%- endfor %}
</ul>
</div>
'''
# Defining the Flask Web App
app = Flask(__name__)
cache = redis.StrictRedis(host='cache', port=6379)
# The website root will show the page_template rendered with
# - visitor count fetched from Redis Cache
# - list of food fetched from Postgres DB
# - welcome text passed in as environment variable
@app.route('/')
def root():
visitors = cache_get_visitor_count()
food = db_get_squirrel_food()
return render_template_string(page_template, visitors=visitors, foods=food, welcome_text=os.getenv("WELCOME", "Hey Acorn user!"))
# Fetch the squirrel food from the Postgres database
def db_get_squirrel_food():
conn = psycopg2.connect(
host="db",
database="acorn",
user=os.environ['PG_USER'],
password=os.environ['PG_PASS'],
)
cur = conn.cursor()
cur.execute("SELECT food FROM squirrel_food;")
return [x[0] for x in cur.fetchall()] # Return the list of food items
# Increment the visitor count in the Redis cache and return the new value
def cache_get_visitor_count():
return cache.incr('visitors')
Python Requirements
Alongside the Python code, we need the list of dependencies to install. Save the following in the file requirements.txt
:
flask
psycopg2-binary
redis
Dockerfile
Now we have all the code and want to bundle it up in a Docker container.
Create the Dockerfile
with the following content:
FROM python:3-alpine
WORKDIR /app
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
ADD requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["flask", "run"]
Step 3. Author your Acornfile
Create your Acornfile with the following contents. Each item will be explained below, this file demonstrates a lot of what Acorn can do.
args: {
// Configure your personal welcome text
welcome: "Hello Acorn User!!"
}
containers: {
app: {
build: "."
env: {
"PG_USER": "postgres"
"PG_PASS": "secret://quickstart-pg-pass/token"
"WELCOME": args.welcome
if args.dev { "FLASK_ENV": "development" }
}
dependsOn: [
"db",
"cache"
]
if args.dev { dirs: "/app": "./" }
ports: publish: "5000/http"
}
cache: {
image: "redis:alpine"
ports: "6379/tcp"
}
db: {
image: "postgres:alpine"
env: {
"POSTGRES_DB": "acorn"
"POSTGRES_PASSWORD": "secret://quickstart-pg-pass/token"
}
dirs: {
if !args.dev {
"/var/lib/postgresql/data": "volume://pgdata?subpath=data"
}
}
files: {
"/docker-entrypoint-initdb.d/00-init.sql": "CREATE TABLE squirrel_food (food text);"
"/docker-entrypoint-initdb.d/01-food.sql": std.join([for food in localData.food {"INSERT INTO squirrel_food VALUES ('\(food)');"}], "\n")
}
ports: "5432/tcp"
}
}
localData: {
food: [
"acorns",
"hazelnuts",
"walnuts"
]
}
volumes: {
if !args.dev {
"pgdata": {
accessModes: "readWriteOnce"
}
}
}
secrets: {
"quickstart-pg-pass": {
type: "token"
}
}
Explaining the Acornfile
args
section: describes a set of arguments that can be passed in by the user of this Acorn imageA help text will be auto-generated using the comment just above the arg:
$ acorn run . --help
Usage of Acornfile:
--welcome string Configure your personal welcome text
containers
section: describes the set of containers your Acorn app consists of- Note:
app
,db
andcache
are custom names of your containers app
- Our Python Flask Appbuild
: build from Dockerfile that we createdenv
: environment variables, statically defined, referencing a secret or referencing an Acorn argumentdependsOn
: dependencies that have to be up and running before the app is started (here it is waiting for the database and the cache to be running)ports
: using thepublish
type, we expose the app inside the cluster but also outside of it using an auto-generated ingress resource (more on this later)dirs
: Directories to mount into the container filesystemif !args.dev
: The following block applies only if built-in development mode is disabled. (more on the development mode later)dirs: "/app": "./"
: Mount the current directory to the /app dir, which is where the code resides inside the container as per theDockerfile
. This is to enable hot-reloading of code.
cache
- Redisimage
: existing OCI/Docker image to use (here: from DockerHub library)ports
: no type defined, defaults tointernal
, which makes it available to the other containers in this Acorn app
db
- Postgres Database Serverimage
,env
,ports
: nothing new heredirs
: Directories to mount into the container filesystemif !args.dev
: The following block applies only if the built-in development mode is disabled (more on the development mode later)volume://pgdata?subpath=data
: references a volume defined in the top-levelvolumes
section in the Acornfile and specifies the subpathdata
as the mountpoint.
files
: Similar todirs
but only for files. Additionally, content can be created in-line and even utilize generating functions.
- Note:
localData
: Set of variables for this Acorn appfood
: Custom variable, defining a list of food which is accessed incontainers.db.files
to pre-fill the database.
volumes
: (persistent) data volumes to be used by any container in the Acorn apppgdata
custom volume name, referenced incontainers.db.dirs
accessModes
: (list of) modes to allow access to this volume
secrets
: set of secrets that can be auto-generated and used by any container in the Acorn appquickstart-pg-pass
: custom secret name, referenced bycontainers.app.env
andcontainers.db.env
type
: There are several secret types. Here, a token (random string) will be generated for you at runtime.
Step 4. Run your Acorn app
To start your Acorn app just run:
acorn run -n awesome-acorn .
or customize the welcome text argument via:
acorn run -n awesome-acorn . --welcome "Let's Get Started"
The -n awesome-acorn
gives this app a specific name so that the rest of the steps can refer to it. If you omit -n
, a random two-word name will be generated.
Step 5. Access your app
Due to the configuration ports: publish: "5000/http"
under containers.app
, our web app will be exposed outside of our Kubernetes cluster using the cluster's ingress controller.
Checkout the running apps via
acorn apps
$ acorn apps
NAME IMAGE HEALTHY UP-TO-DATE CREATED ENDPOINTS MESSAGE
awesome-acorn 2d73c8a0493f 3 3 121m ago http://app-awesome-acorn-56d50c8c0915.local.oss-acorn.io => app:5000 OK
You probably already noticed the link right there in the ENDPOINTS
column. It will take you to your Python Flask App.
Step 6. Development Mode
Now that we have a way to package and deploy our app, lets look at how we can configure the Acornfile to enable the development flow. In this mode, we will be able to make changes and see them updated inside the app container in real time.
To enable the Acorn development mode, first stop the app and then re-run with the dev
command.
acorn stop awesome-acorn
acorn dev -n awesome-acorn
In development mode, Acorn will watch the local directory for changes and synchronize them to the running Acorn app. In general, changes to the Acornfile are directly synchronized, e.g. adding environment variables, etc. Depending on the change, the deployed containers will be recreated.
The following lines additionally enable hot-reloading of code by mounting the current local directory into the app container:
containers: {
app: {
// ...
if args.dev { dirs: "/app": "./" }
//...
}
//...
}
In this case, additionally if args.dev { "FLASK_ENV": "development" }
enables Flask's development mode.
Running in development mode, Acorn will keep a session open, streaming all the container logs to your terminal and notifying you of any changes that are happening. Press Ctrl-c
to end the session and terminate the running app.
To test it, you can change something in the app.py
.
For example, add a line to the HTML template at the top and change it to
# HTML Jinja2 Template which will be shown in the browser
page_template = '''
<div style="margin: auto; text-align: center;">
<h1>{{ welcome_text }}</h1><br>
<h2>This is a change :)</h2>
You're visitor #{{ visitors }} to learn what squirrels love the most:<br>
<ul>
{%- for food in foods %}
<li>{{ food }}</li>
{%- endfor %}
</ul>
</div>
'''
You will see the change applied when you reload the application's page in your browser.
Step 7. Build and Push your Acorn image
Ready to release your Acorn app into the wild? Let's package it up in a single Acorn image and distribute it via an OCI registry.
Note: This example uses GitHub's container registry
ghcr.io
. You want to push to DockerHub instead? The prefix isdocker.io
. Just make sure that you actually have write access to the target repository.
# Login into your OCI registry, if needed (interactive)
acorn login ghcr.io
# Build the Acorn image and tag it to your liking
acorn build -t ghcr.io/acorn-io/getting-started:v0.0.1 .
# Push the newly built Acorn image
acorn push ghcr.io/acorn-io/getting-started:v0.0.1
Now, everyone else can run your Acorn image via
acorn run --name awesome-acorn ghcr.io/acorn-io/getting-started:v0.0.1
Interacting with an Acorn app
Execute a command inside the running container
You can get an interactive shell into any running app or container with:
acorn exec awesome-acorn
If there is more than one container in an app, you will be prompted to pick one. You can also run a specific command instead of getting a shell:
acorn exec awesome-acorn env
Reveal the auto-generated database secret
# List all Acorn Secrets
$ acorn secrets
ALIAS NAME TYPE KEYS CREATED
awesome-acorn.quickstart-pg-pass quickstart-pg-pass-sqlv9 token [token] 139m ago
# Reveal the one for the current app
$ acorn secret reveal awesome-acorn.quickstart-pg-pass
NAME TYPE KEY VALUE
quickstart-pg-pass-sqlv9 token token mssl8692sk47tfklx9bqnqflw7pqrk2ldb6cd9tckjlttpk4vsvpvl
Start and Stop your app
That's easy!
acorn stop awesome-acorn
and
acorn start awesome-acorn
Remove your app
If you're done with the app, wipe it from the cluster via
acorn rm awesome-acorn
Dangerous Pro-Tip: to remove all Acorn apps at once:
acorn rm $(acorn apps -qa)