fbpx
Unlock the power of choice with CloudPlex and DigitalOcean Get CloudPlex free for 3 months, and Digital Ocean for 1 month

Developing, deploying and testing Flask applications on Kubernetes – Part II

kubernetes-tutorial

Overview

In the first tutorial of “Developing, deploying and testing Flask applications on Kubernetes”, we discovered how to create a local development environment. We have seen the pros and cons of using a mini-cluster.
We came to the following conclusion: Minikube, Microk8s, or Docker for Mac are great tools for local development. However, for testing and staging environments, highly available clusters are needed to simulate and test the application in a production-like environment.

Creating a Kubernetes Cluster for Your Testing/Production Environment

While creating a real Kubernetes cluster for testing purposes, seems to be a solution for many developers’ problems, it comes with high costs.
One good idea for reducing the costs of running these environments is to implement on-demand testing environments that can be created and provisioned once you need deploying to a testing cluster. Whenever the tests are done, the environment can be removed and cleaned up.
There are several tools in the market that can be used to build, provision, and destroy testing environments. Below are some of the tools you may consider:
  • Terraform: is an open-source infrastructure as code software tool created by HashiCorp. It enables users to provision infrastructure components in a descriptive way.
  • Ansible: is an open-source configuration management and application-deployment tool that can be used to manage software packages and infrastructure states on the datacenter nodes.
  • Cloudplex: Using our visual builder, you can deploy a cluster to many clouds in less than 5 minutes . You can export your cluster then shutdown your cluster with a single click. To recreate the same cluster, you can import the saved configuration. Your cluster is up and running.

Notes about Kubernetes API and resources

Kubernetes provides an API for creating resources on the cluster, then it will make sure that the resources are always available and running in a state predefined by the user. There are several types of resources that can be created and managed by the Kubernetes cluster, below is the list of the resources needed to run our application
  • Deployment: is a resource object that is responsible for creating, managing, and rolling updates for services. This resource is also creating and managing another resource type which is the ReplicaSet.
  • ReplicaSet: is responsible for maintaining the defined replication of the Pods of a given service.
  • Pod: is the smallest execution unit in Kubernetes and for simplicity reasons, you can think of this resource as a container (although a Pod can contain multiple containers in some cases)
  • Service: This resource is responsible for creating interfaces for services and exposing them internally in the cluster or externally.
  • Ingress: This resource is used to define the rules for external access to the cluster.
You can inspect the full list of the resources using kubectl command line:
 $> kubectl api-resources

Creating the Kubernetes Deployment

To deploy the weather applications on a Kubernetes cluster we need to wite the resource definitions files for each of the resources needed by the application. The resource definition files are YAML files that include all the attributes needed for creating the resource.
There are four common configurations sections that need to be covered by these files, these sections are listed below:
  • apiVersion: describes the Kubernetes API versions to be used to create the resource.
  • Kind: describes the type of resource to be created.
  • Metadata: attaches meta-information regarding the resource such as its name or labels.
  • Spec: describes the specifications of the resource(s).
In order to run our application, the first resource that we need here is a Kubernetes Deployment with the following values for each of the above segments:
  • apiVersion: apps/v1
  • Kind: Deployment
  • Metadata: deployment name and labels.
  • Spec: the specification of the deployment object that includes
    • Replicas: The number of Pods to be created and managed
    • Selector: Conditions used to select pods by managed by the deployment
    • Template: The template used to create the Pod containers which defines container name, container docker image, and other options.
The below resource file can be used to create a Kubernetes Deployment for the weather application
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: weather-api
  name: weather-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: weather-api-pod
  template:
    metadata:
      labels:
        app: weather-api-pod
    spec:
      containers:
      - name: weather-api
        image: wshihadeh/weather-api-app:latest
        ports:
        - containerPort: 5000
      restartPolicy: Always
The above Kubernetes Deployment resource will create two Pods for our service each of them has a dedicated IP. You can consume the weather API on the Pods IP. However, it is not recommended to do this for the following reasons:
  • Pod IPs will change if the pod is recreated to rescheduled.
  • You need to use more than one IP if you would like to load balance the traffic between the Pods.
Because of these drawbacks, it is recommended to expose applications in Kubernetes using the service resource. This resource provides us with a single IP that can be used to access the application Pods (it load-balance the traffic automatically) and it helps to expose the application outside the cluster if needed.
To create a service resource for our application we need to create another definition file with the folwing sections:
  • apiVersion: “v1”
  • Kind: “Service”
  • Metadata: The Service name and labels.
  • Spec: The specification of the Service object that includes:
    • Selector: Conditions used to select Pods by managed by the Kubernetes Deployment
    • The type of service: Kubernetes supports a couple of Service types for managing internal and external traffic.
    • port: The Service exposed port.
    • targetPort: The Pod exposed port.
The below YAML file shows the full implementation for the Service resource, as you can see it will be listening on port 8080 and it will forward the requests to the application Pods on port 5000.
apiVersion: v1
kind: Service
metadata:
  labels:
    app: weather-api
  name: weather-api-service
spec:
  ports:
  - port: 8080
    targetPort: 5000
  selector:
    app: weather-api-pod
  type: ClusterIP
Note that we used ClusterIP service, this means the service will be only available for other applications inside the cluster. To expose the Service externally we need to use a different type such as LoadBalancer or NodePort.
Using these type of Services for every single application in the cluster adds overhead on the service management and increase the costs of running the cluster. If you take the case of creating a load balancer for each of our services, costs may increase significantly. Even if in managed Kubernetes, the load balancer is created and taken care of by the cloud provider, still it’s not recommended, unless you have a single Service to expose.
An alternative method is to expose an Ingress controller application externally and define Ingress rules for managing the traffic to the internal service.

Using Traefik Ingress

Kubernetes supports a wide range of Ingress Controllers that can be used to manage the incoming external requests. In this post, we will try Traefik.
Running Traefik on Kubernetes requires creating several resources in order to work as expected. The first step is to create the resources needed for granting Traefik with permissions on Kubernetes. It should be able to collect information about the running applications in the cluster. These resources are listed below
  • ServiceAccount: This resource provides an identity for processes that run in a Pod.
  • ClusterRole: This resource defines rules that represent permissions for accounting resources of the Kubernetes cluster.
  • ClusterRoleBinding: It binds a role to subjects like groups, users, or ServiceAccounts.
The below definition file includes all the resources described above. The ClusterRole defines get, list, and watch for several resources on the cluster such as Services, endpoints, and Secrets. On the other hand, the ClusterBindingRole is linking the created ServiceAccount with the ClusterRole.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller
rules:
  - apiGroups:
      - ""
    resources:
      - services
      - endpoints
      - secrets
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses/status
    verbs:
      - update

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-ingress-controller

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
  - kind: ServiceAccount
    name: traefik-ingress-controller
    namespace: default
The next step is deploying Traefik service itself using a Kubernetes Deployment as shown in the below snippet. It is very important here to add Kubernetes Ingress to the Traefik command line as shown below:
kind: Deployment
apiVersion: apps/v1
metadata:
  name: traefik
  labels:
    app: traefik

spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik-ingress-controller
      containers:
        - name: traefik
          image: traefik:v2.2
          args:
            - --log.level=DEBUG
            - --api
            - --api.insecure
            - --entrypoints.web.address=:80
            - --providers.kubernetesingress
          ports:
            - name: web
              containerPort: 80
            - name: admin
              containerPort: 8080
To be able to access the Traefik Ingress Controller from outside the cluster, we need to define a Service resource for Traefik as shown below.

We use a LoadBalancer service to expose the service to the Kubernetes host. Two ports are exposed one for the managed Services web requests and the other one for the Traefik admin panel.

apiVersion: v1
kind: Service
metadata:
  name: traefik
spec:
  type: LoadBalancer
  selector:
    app: traefik
  ports:
    - protocol: TCP
      port: 80
      name: web
      targetPort: 80
    - protocol: TCP
      port: 8080
      name: admin
      targetPort: 8080
The last step is to define the ingress rule that will be used to forward the requests from the Ingress Controller to the weather service. The below Ingress file defines the needed rule based on the host of the request.
All requests with the host weather.lvh.me will be forwarded to the weather Service.

Note that the defined annotations define which Traefik entry points will be used to serve the requests.

Deploying all the above-defined resource can be done using one of the following commands:
$> kubectl apply -f ${file_name}
$> kubectl create -f ${file_name}

$> kubectl apply ${k8s_files_directory}
$> kubectl create ${k8s_files_directory}

Let’s Run a Load Test on our Production-like Cluster

Testing the performance of web applications is one of the most crucial and important stages in a software lifecycle: It can provide us with the needed metrics and KPIs for the performance of a web app under real use cases and known scenarios and all of that is done before the launch or the release of a new version.
There are several tools that can be used to perform performance testing/user load testing such as LoadRunner, LoadNinja, Apache JMeter, and many others. One of the easiest and straightforward tools for performing user load testing is locust.io. This tool is designed to load-test a web app (or other resources) and figuring out how many concurrent users a it can handle.
We will perform a basic user load testing on our weather applications. The test cases will cover the root and weather endpoints. Below is the test case file:
from locust import HttpLocust, TaskSet, task, between

class WebsiteTasks(TaskSet):
    @task
    def index(self):
        self.client.get("/")

    @task
    def london(self):
        self.client.get("/london/uk/")


class WebsiteUser(HttpLocust):
    task_set = WebsiteTasks
    wait_time = between(5, 15)
To start running the tests, first, we need to start locust with the below command
$>  locust -f  test.py
The next step is to start running the load test from the Locust UI. To do that, use the following URL http://localhost:8089/ and fill the form as shown in the screenshot. This will start a load test with 1000 users and a hatch rate of 2 users per second.
locust-swarm
Once all the 1000 users are loaded and send web requests concurrently, incoming requests may start to fail due to the high load on the services.

The below screenshot shows the statistics of our first trial. The error rate is 5% which is a considerable percentage.

locust
After scaling the weather service to 4 replicas, we can perform the same load test with 1000 users and the same hatch rate.
$> kubectl scale --replicas=4 deployment weather-api
Scaling up our Kubernetes Service improved the performance of the application dramatically, as shown. Not only the error rate dropped to 0 but also the median and the average response time of the requests dropped.
locust

Conclusion

The Flask code used in this tutorial can be found in this repository. With the help of this tutorial, you can pull the same repository, create a Flask app, run it locally using minikube, create the needed Kubernetes Deployments and Services, deploy your cluster to a Google Cloud, EKS or any other public cloud provider. Finally, you can use Traefik as your Ingress controller and play with Locust to load test your cluster.
Start building app

Start building your Kubernetes application

84890cookie-checkDeveloping, deploying and testing Flask applications on Kubernetes – Part II