In this article, we’ll use a TODO application written by Flask and JavaScript with a MongoDB database, and learn how to deploy it on Kubernetes. This article is intended for beginners, so don’t worry if you haven’t been in depth with Kubernetes clustering before!

We’ll be using K3s, a lightweight Kubernetes distribution that’s perfect for quick starts. But first let’s talk about what we want to achieve.

First, I’ll introduce the sample application. This actually simplifies a lot of details, but it illustrates common use cases. Then we’ll get familiar with the process of containerizing applications. Before we move on, I’ll talk about how we can use containers to make our development easier, especially if we’re working in a team, or if we’re working in a new environment and want to lighten the developer’s load.

Once we have containerized our applications, the next step is to deploy them on Kubernetes. While we can create services, Ingress, and gateways manually, we can use Knative to support our applications at all times.

Setting up the application

We’ll use a simple TODO application to demonstrate the front end, REST API back end, and MongoDB working together. Thanks to this example from Prashant Shahi. I made a few minor changes, purely for educational purposes:

Github.com/prashant-sh…

Git Clone code base

git clone https://github.com/benjamintanweihao/Flask-MongoDB-K3s-KNative-TodoApp
Copy the code

Next, we’ll examine the catalog to see what’s going on:


 cd Flask-MongoDB-K3s-KNative-TodoApp
 tree

Copy the code

The folder structure is a typical Flask application. The Entry Point is app.py, which also contains REST APIs. The Templates folder contains the files that will be rendered as HTML:

Opening app.py, we can see all the main parts:

├ ─ ─ app. Py ├ ─ ─ requirements. TXT ├ ─ ─ the static │ ├ ─ ─ assets │ │ ├ ─ ─ style.css. CSS │ │ ├ ─ ─ twemoji. Js │ │ └ ─ ─ twemoji. Min. Js └ ─ ─ Templates ├─ index.html ├─ update.htmlCopy the code

From the code snippet above, you can see that your application needs MongoDB as a database. Using the lists() method, you can see examples of how routes are defined (i.e. @app.route (“/list “)), how to get data from MongoDB, and how templates are rendered.

mongodb_host = os.environ.get('MONGO_HOST', 'localhost') mongodb_port = int(os.environ.get('MONGO_PORT', '27017')) client = MongoClient(mongodb_host, mongodb_port) db = client.camp2016 todos = db.todo app = Flask(__name__) title = "TODO with Flask" @app.route("/list") def lists (): #Display the all Tasks todos_l = todos.find() a1="active" return render_template('index.html',a1=a1,todos=todos_l,t=title,h=heading) if __name__ == "__main__": env = os.environ.get('APP_ENV', 'development') port = int(os.environ.get('PORT', 5000)) debug= False if env == 'production' else True app.run(host='0.0.0.0', port=port, debug=debug)Copy the code

The other thing to note here is the use of MONGO_HOST and MONGO_PORT environment variables and Flask related environment variables. Of these, the most important is Debug. When the variable is set to True, the Flask server automatically reloads when changes are detected and occur. This is especially handy during development and is a feature we want to take advantage of.

Developed with the Docker container

When working with applications, I used to spend a lot of time setting up the environment and installing all the dependencies. After that, I can get up and running by adding new features. However, this only describes an ideal scenario, right?

How many times have you gone back to an application you’ve developed (say, six months ago) only to find yourself slowly sinking into dependency hell? Dependencies are often a flexible target, and your application may not function properly unless you take steps to lock down the object. One way to solve this problem is to package all dependencies into a Docker container.

Another feature Docker brings is automation. This means no more copy-and-paste commands, no more setting up databases and things like that.

Flask application of Docker

Here is the Dockerfile:

FROM alpine:3.7 COPY. /app WORKDIR /app RUN apk add --no-cache bash git nginx uwsgi uwsgi-python py2-pip \ && pip2 install --upgrade pip \ && pip2 install -r requirements.txt \ && rm -rf /var/cache/apk/* EXPOSE 5000 ENTRYPOINT ["python"]Copy the code

We start with a minimal (in terms of size and functionality) base image. The contents of the application then go to the /app directory in the container. Next, we execute a series of commands to install all the requirements for Python, Nginx Web Server, and Flask applications. These are exactly the steps needed to set up the application on the new system.

You can build Docker containers like this:

% docker build -t <yourusername>/todo-app .
Copy the code

You should see output like this:

#... Successfully built c650af8b7942 Successfully tagged benjamintanweihao/todo-app:latestCopy the code

The directing?

Should you go through the same process of creating a Dockerfile for MongoDB? This has been tried before, and can be demonstrated at hub.docker.com/_/mongo. But now…

One way is to start the MongoDB container and then start the Flask container. However, suppose you want to add caching and decide to introduce Redis containers. The process of starting each container can quickly become tedious. The solution is Docker Compose, a tool that allows you to define and run multiple Docker containers, which fits our current situation.

Docker Compose

The following is a Docker compose documents, Docker – compose. Yaml:

services:
  flaskapp:
    build: .
    image: benjamintanweihao/todo-app:latest
    ports:
      - 5000:5000
    container_name: flask-app
    environment:
      - MONGO_HOST=mongo
      - MONGO_PORT=27017
    networks:
      - todo-net
    depends_on:
      - mongo
    volumes:
      - .:/app # <--- 
  mongo:
    image: mvertes/alpine-mongo
    ports:
      - 27017:27017
    networks:
      - todo-net

networks:
  todo-net:
    driver: bridge
Copy the code

Even if you’re not familiar with Docker Compose, the YAML file here isn’t complicated. Let’s look at the important part.

At the very beginning, this file defines the services made up of FlaskApp and Mongo, and specifies the network to bridge to. This creates a network connection so that the containers defined in the service can communicate with each other.

Each service defines mirroring, port mapping, and the network defined earlier. Environment variables are also defined in FlaskApp (check app.py to see if they are indeed the same).

I want to draw your attention to the volume specified in the Flask application. What we are doing here is mapping the host’s current directory (which should be the project directory containing app.py) to the container’s /app directory why do we do this? Recall that in Dockerfile, we copied the app into the /app directory as follows:

COPY . /app
Copy the code

Suppose you want to make a change to your application. You can’t easily change app.py in a container. By mapping the local directory, you’re basically overwriting the container’s app.py with a local copy of your directory. So, assuming the Flask application is in debug mode (which it is if you haven’t changed anything at this point), when you start the container and make a change, the rendered output will reflect that change.

However, it is important to realize that app.py in the container is still the old version, and you still need to remember to build the new image (hopefully you have CI/CD set to do this automatically)

Let’s see how this works. Run the following command:

docker-compose up
Copy the code

Next you’ll see:

Creating network "flask-mongodb-k3s-knative-todoapp_my-net" with driver "bridge" Creating flask-mongodb-k3s-knative-todoapp_mongo_1 ... done Creating flask-app ... done Attaching to flask-mongodb-k3s-knative-todoapp_mongo_1, flask-app # ... more output truncated flask-app | * Serving Flask app "app" (lazy loading) flask-app | * Environment: production flask-app | WARNING: Do not use the development server in a production environment. flask-app | Use a production WSGI server instead. flask-app | * Debug mode: On the flask - app | * Running on http://0.0.0.0:5000/ (Press CTRL + C to quit) flask - app | * Restarting with stat mongo_1 | 2021-05-15T15:41:37.993+0000 I NETWORK [listener] connection accepted from 172.23.0.1:48844 #2 (2 connections now open) Mongo_1 | 2021-05-15 T15:41:37. 993 + 0000 I NETWORK [conn2] received client metadata from 172.23.0.1:48844 conn2: {driver: {name: "PyMongo", version: "3.11.4"}, OS: {type: "Linux", name: "", architecture: "x86_64", version: "5.8.0-53 - generic"}, platform: "retaining 2.7.15. Final. 0"} flask - app | * Debugger is active! flask-app | * Debugger PIN: 183-021-098Copy the code

Start now in your browser: http://localhost:5000

If you see this, congratulations! Flask and Mongo work normally together. Feel free to use the application to get a feel for it.

Now let’s make a small change to app.py in the application title:

index d322672.. 1c447ba 100644 --- a/app.py +++ b/app.py -heading = "tOdO Reminder" +heading = "TODO Reminder!!!!!"Copy the code

Save the file and reload the application:

When you are done, you can type the following command:

docker-compose down
Copy the code

Deploy the application on Kubernetes

So far, we have containerized our application and its supporting services (now just MongoDB). How do we start deploying our application to Kubernetes?

Before we do that, let’s install Kubernetes. For this, I chose K3s because it is the easiest way to install Kubernetes and get it up and running.

% curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --no-deploy=traefik"  sh -s -
Copy the code

After a while, you can install Kubernetes:

[INFO] Finding Release for Channel stable [INFO] Using V1.20.6 + K3S1 as Release [INFO] Downloading Hash https://github.com/k3s-io/k3s/releases/download/v1.20.6+k3s1/sha256sum-amd64.txt # truncated... [INFO] systemd: Starting k3sCopy the code

Verify that K3s is set correctly:

% kubectl get no
NAME      STATUS   ROLES                  AGE     VERSION
artemis   Ready    control-plane,master   2m53s   v1.20.6+k3s1
Copy the code

MongoDB

There are several ways to do this. You can use the image we created, MongoDB Operator or Helm:

helm install mongodb-release bitnami/mongodb --set architecture=standalone --set auth.enabled=false
Copy the code
Please be patient while the chart is being deployed MongoDB(R) can be accessed on the following DNS name(s) and ports from within your cluster: mongodb-release.default.svc.cluster.local To connect to your database, create a MongoDB(R) client container: kubectl run --namespace default mongodb-release-client --rm --tty -i --restart='Never' - the env = "MONGODB_ROOT_PASSWORD = $MONGODB_ROOT_PASSWORD" - image docker. IO/bitnami/mongo: 4.4.6 - debian - 10 - r0 - command bash Then, run the following command: mongo admin --host "mongodb-release" To connect to your database from outside the cluster execute the following Commands: kubectl port-forward --namespace default SVC /mongodb-release 27017:27017 & mongo --host 127.0.0.1Copy the code

Install Knative and Istio

In this article, we will use Knative. Built on top of Kubernetes, Knative makes it easy for developers to deploy and run applications without having to know many details of Kubernetes.

Knative consists of two parts: Serving and Eventing. In this section, we discuss the Serving section. Using Knative Serving, you can create elastically scalable, secure, and stateless services in seconds, which is what we need TODO with our TODO application! Before we do that, let’s install Knative:

The following instructions are based on:

Knative. Dev/docs/instal…

Kubectl apply -f https://github.com/knative/serving/releases/download/v0.22.0/serving-crds.yaml kubectl apply - f https://github.com/knative/serving/releases/download/v0.22.0/serving-core.yaml kubectl apply - f https://github.com/knative/net-istio/releases/download/v0.22.0/istio.yaml kubectl apply - f https://github.com/knative/net-istio/releases/download/v0.22.0/net-istio.yamlCopy the code

This sets up Knative and Istio. You may be wondering why we need Istio. The reason is that Knative needs an Ingress Controller that allows it to perform traffic distribution (for example, versions 1 and 2 of a Todo application need to run simultaneously) and automatic HTTP request retries.

Does Istio have an alternative? Gloo (docs.solo.io/ glo-edge /m…) . But Traefik is not currently supported, which is why we had to disable it when we installed K3s. Since Istio is the default and most supported, we will use it.

Now wait for all Knative-Serving pods to run:

kubectl get pods --namespace knative-serving -w NAME READY STATUS RESTARTS AGE controller-57956677cf-2rqqd 1/1 Running 0  3m39s webhook-ff79fddb7-mkcrv 1/1 Running 0 3m39s autoscaler-75895c6c95-2vv5b 1/1 Running 0 3m39s activator-799bbf59dc-t6v8k 1/1 Running 0 3m39s istio-webhook-5f876d5c85-2hnvc 1/1 Running 0 44s networking-istio-6bbc6b9664-shtd2 1/1 Running 0 44sCopy the code

Set up custom fields

By default, Knative Serving uses example.com as the default field. If you followed the instructions to set up K3s, you should install a load balancer. This means that with some Settings, you can create custom domains using A DNS service like sslip. IO.

Sslip. IO is a service that returns an IP address when queried using a host name with an embedded IP address. For example, a URL such as 192.168.0.1.sslip. IO points to 192.168.0.1. This is excellent service and you don’t have to buy your own domain name.

Continue and apply the following manifest:

kubectl apply -f https://storage.googleapis.com/knative-nightly/serving/latest/serving-default-domain.yaml
Copy the code

If you open serving -default-domain-yaml, you need to notice the following in the spec:

# other parts truncated     
spec:
    serviceAccountName: controller
    containers:
        - name: default-doma
          image: ko://knative.dev/serving/cmd/default-domain
          args: ["-magic-dns=sslip.io"]
Copy the code

This will enable the DNS that you will need to use in the next step.

Test to see that everything is ok

Download the KN binary file. You can check out the link: knative.dev/development… . Be sure to rename the binary KN and put it somewhere in $PATH. Once this is resolved, proceed to create the sample Hello World service. I have benjamintanweihao/helloworld python mirror pushed to the Docker Hub:

% kn service create helloworld-python --image=docker.io/benjamintanweihao/helloworld-python --env TARGET="Python Sample v1"
Copy the code

This produces the following output:

Creating service 'helloworld-python' in namespace 'default': 0.037s The Route is still working to reflect The latest desired Specification. 0.099s Configuration "Helloworld-python" Is waiting for a Revision to become ready. 29.314s reconciled has not yet been reconciled. 29.446s Waiting for load balancer to be ready 29.605s reconciled to serve. Service 'helloworld-python' created to latest revision 'helloworld-python-00001' is available at URL: http://helloworld-python.default.192.168.86.26.sslip.ioCopy the code

Enter the following code to list all deployed Knative services in all namespaces:

% kn service  list -A
Copy the code

With Kubectl, this becomes:

% kubectl get ksvc -A
Copy the code

To delete the service, simply do the following:

kn service delete helloworld-python # or kubectl delete ksvc helloworld-python
Copy the code

If you haven’t already done so, make sure the TODO application image is pushed to DockerHub. Remember to replace {username} with DockerHub ID:

% docker push {username}/todo-app:latest
Copy the code

After pushing an image, you can run the kn command to create the TODO service. Remember to replace {username} with DockerHub ID:

kn service create todo-app --image=docker.io/{username}/todo-app --env MONGO_HOST="mongodb-release.default.svc.cluster.local" 
Copy the code

If all goes well, you’ll see:

Creating service 'todo-app' in namespace 'default': 0.022s The Route is still working to reflect The latest desired Specification. 0.085s Configuration "todo-app" is Waiting for a Revision to become ready. 4.586s... 4.608s reconciled. 4.675s Waiting for load balancer to be ready 4.974s reconciled to serve.service 'todo-app' created to latest revision 'todo-app-00001' is available at URL: http://todo-app.default.192.168.86.26.sslip.ioCopy the code

Visit todo now – app. Default. 192.168.86.26. Sslip. IO (or in an output of the last line of the content) you should see the application! Now let’s see what Knative does for you. KNative launches a service for you with a single command and provides you with a URL that you can access from the cluster.

My knowledge of Knative has barely scratched the surface, but I hope this tutorial inspires you to learn more about it! When I started looking at Knative, I didn’t quite understand what it did. Hopefully this example will give us a sense of how amazing Knative is and how convenient it is.

conclusion

In this article, we took a brief look at web applications built using Python and requiring MongoDB, and learned how to:

  • Use Docker to container TODO applications
  • Use Docker to mitigate dependencies
  • Use Docker for development
  • Use Docker Compose to package multiple containers
  • Install K3s
  • Install Knative (Serving) and Istio
  • Use Helm MongoDB
  • Deploy TODO applications using Knative

While migrating your application to Kubernetes is certainly not an easy task, containerizing your application will usually get you halfway there. Of course, there are many things not covered in this article, such as security and extensibility.

K3s is a lightweight Kubernetes distribution that makes it extremely easy to test and run Kubernetes workloads on a laptop/desktop, making it user-friendly for individual developers. It is also suitable for large-scale cluster deployment at the edge.

When I started working on Knative, I didn’t quite understand what it did. Hopefully this example will help us understand the appeal of Knative and the convenience it brings. In fact, one of the highlights of Knative is “the ability to launch a scalable, secure, stateless service in seconds.”

I’ll talk more about Knative in future articles and explore its core features in more depth. I hope you can follow this tutorial and apply them to your application!