top of page
Writer's pictureSathish Kumar

Kubernetes- Time based scaling of deployments with python client

Updated: Jan 15, 2021


Note: If you have missed my previous articles on Docker and Kubernetes, you can find them here.
Application deployment models evolution.
Getting started with Docker.
Docker file and images.
Publishing images to Docker Hub and re-using them
Docker- Find out what's going on
Docker Networking- Part 1
Docker Networking- Part 2
Docker Swarm-Multi-Host container Cluster
Docker Networking- Part 3 (Overlay Driver)
Introduction to Kubernetes
Kubernetes- Diving in (Part 1)
Kubernetes-Diving in (Part2)- Services
Kubernetes- Infrastructure As Code with Yaml (part 1)
Kubernetes- Infrastructure As Code Part 2- Creating PODs with YAML
Kubernetes Infrastructure-as-Code part 3- Replicasets with YAML
Kubernetes Infrastructure-as-Code part 4 - Deployments and Services with YAML
Deploying a microservices APP with Kubernetes
 

Kubernetes enables microservices to be deployed easily and enables scaling, monitoring of deployed objects. With replicasets, additional PoDs of a similar type can be deployed at runtime.


Consider a scenario where a microservice application similar to the voting app is deployed. If there is a spike in the number of users, additional PoDs can be deployed. If we think about it from a operations (DevOps) point of view the administrator of the app is presented with the following choices:


a) Predict the maximum number of users and deploy replicas based on the maximum load- This would mean resources (like CPU cycles, memory etc) are wasted most of the time.


b)Have someone (or a team) continuously monitor user traffic and deploy replicas when required- Needs additional operational staffing.


This leads to the question- Is it possible to deploy Kubernetes objects on the fly based on some external variables? The answer is yes. You see, Kubernetes offers APIs to deploy objects in many popular programming languages like Python, Go, Java etc. With a script/program it is possible to monitor the number of users of the app and dynamically scale up or scale down the number of replicas.


I am going to show you how to do this with my favorite language which is Python. We are going to scale replicas in a deployment based on time of day.


If you intend to follow along, you need to have the following components installed:

1) Python 3

2) Pip for python

3) Kubernetes client for python

4) Any editor (I prefer Eclipse with PyDev and remote systems plugin)


On a Ubuntu system, you can install all these components with the apt-get package manager




root@sathish-vm2:/home/sathish# apt-get install python3
..................
root@sathish-vm2:/home/sathish# apt-get -y install python3-pip
.......................
root@sathish-vm2:/home/sathish# pip3 install kubernetes
.......................
Successfully installed cachetools-4.1.1 google-auth-1.23.0 kubernetes-12.0.0 python-dateutil-2.8.1 requests-oauthlib-1.3.0 rsa-4.6 websocket-client-0.57.0

If everything installed correctly, you should be able to import Kubernetes without error
root@sathish-vm2:/home/sathish# python3
Python 3.8.5 (default, Jul 28 2020, 12:59:40)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import kubernetes
>>>

Here is the YAML file required for the code

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80

Here is my python code

from os import path
import yaml
from kubernetes import client, config

def main():
    # Configs can be set in Configuration class directly or using helper
    # utility. If no argument provided, the config will be loaded from
    # default location.
    config.load_kube_config()

    with open(path.join(path.dirname(__file__), "deploy.yaml")) as f:
        dep = yaml.safe_load(f)
        k8s_apps_v1 = client.AppsV1Api()
        resp = k8s_apps_v1.create_namespaced_deployment(
            body=dep, namespace="default")
        print("Deployment created. status='%s'" % resp.metadata.name)


if __name__ == '__main__':
    main()

Note: Many examples similar to the above are available here.


I am going to run this and check things out.

root@sathish-vm2:/home/sathish/python# python3 deploypod.py
Deployment created. status='nginx-deployment'
root@sathish-vm2:/home/sathish/python# kubectl get deployments
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   1/1     1            1           96s

Now that the barebones example works, I am going to modify things a bit and create more replicas, for this purpose I am defining a update_nginix function

def update_ngnix (replicas=3,imagename="nginx"):
    
    config.load_kube_config()
    k8s_apps_v1 = client.AppsV1Api()
    container = client.V1Container(
        name="nginx",
        image=imagename,
        ports=[client.V1ContainerPort(container_port=80)],
        )
    
    template = client.V1PodTemplateSpec(
        metadata=client.V1ObjectMeta(labels={"app": "nginx"}),
        spec=client.V1PodSpec(containers=[container]))
    
    spec = client.V1DeploymentSpec(
        replicas=replicas,
        template=template,
        selector={'matchLabels': {'app': 'nginx'}})
    # Instantiate the deployment object
    
    deployment = client.V1Deployment(
        api_version="apps/v1",
        kind="Deployment",
        metadata=client.V1ObjectMeta(name=DEPLOYMENT_NAME),
        spec=spec)
    deployment.spec.replicas=replicas
    
    api_response = k8s_apps_v1.patch_namespaced_deployment(
        name=DEPLOYMENT_NAME,
        namespace="default",
        body=deployment)
    print("Deployment updated. status='%s'" % str(api_response.status))

Let me add one more condition: I want to run 3 replicas from a given time for 3 hours- then the deployment should scale down to 1 replica. Let's define the function for this


def scale_replicas_time(scal_up_hour):

    now=time.localtime()
    hr=int(now.tm_hour)
    endtime=hr+3
    if hr >=scal_up_hour and hr < endtime:
        return True

Finally, I want the program to check for the condition every hour and call "update_replica" if it's scale_up_hour. I can handle this in the main function


if __name__ == '__main__':
    create_ngnix_deployment()
    while True:
        #5 PM
        if scale_replicas_time(17): 
            update_ngnix(replicas=3)
        else:
            update_ngnix(replicas=1)
        #sleep for an hour
        time.sleep(3600)

Here is a complete listing of code:

from os import path
import yaml
from kubernetes import client, config
from datetime import datetime
import time

DEPLOYMENT_NAME="nginx-deployment"



def create_ngnix_deployment():
 
    config.load_kube_config()
    k8s_apps_v1 = client.AppsV1Api()
    with open(path.join(path.dirname(__file__), "deploy.yaml")) as f:
        dep = yaml.safe_load(f)
        
        resp = k8s_apps_v1.create_namespaced_deployment(
            body=dep, namespace="default")
        print("Deployment created. status='%s'" % resp.metadata.name)

def update_ngnix (replicas=3,imagename="nginx"):
    
    config.load_kube_config()
    k8s_apps_v1 = client.AppsV1Api()
    container = client.V1Container(
        name="nginx",
        image=imagename,
        ports=[client.V1ContainerPort(container_port=80)],
        )
    
    template = client.V1PodTemplateSpec(
        metadata=client.V1ObjectMeta(labels={"app": "nginx"}),
        spec=client.V1PodSpec(containers=[container]))
    
    spec = client.V1DeploymentSpec(
        replicas=replicas,
        template=template,
        selector={'matchLabels': {'app': 'nginx'}})
    # Instantiate the deployment object
    
    deployment = client.V1Deployment(
        api_version="apps/v1",
        kind="Deployment",
        metadata=client.V1ObjectMeta(name=DEPLOYMENT_NAME),
        spec=spec)
    deployment.spec.replicas=replicas
    
    api_response = k8s_apps_v1.patch_namespaced_deployment(
        name=DEPLOYMENT_NAME,
        namespace="default",
        body=deployment)
    print("Deployment updated. status='%s'" % str(api_response.status))

def scale_replicas_time(scal_up_hour):

    now=time.localtime()
    hr=int(now.tm_hour)
    print("current hour" + str(hr))
    endtime=hr+3
    if hr >=scal_up_hour and hr < endtime:
        return True
        
      


if __name__ == '__main__':
    create_ngnix_deployment()
    while True:
        if scale_replicas_time(17):
            update_ngnix(replicas=3)
        else:
            update_ngnix(replicas=1)
        #sleep for an hour
        time.sleep(3600)

Let's run the code

root@sathish-vm2:/home/sathish/python# python3 deploy.py
Deployment created. status='nginx-deployment'
current hour 12 <<<<<<<
Deployment updated. status='{'available_replicas': None,
 'collision_count': None,
 'conditions': [{'last_transition_time': datetime.datetime(2020, 11, 7, 12, 53, 20, tzinfo=tzlocal()),
                 'last_update_time': datetime.datetime(2020, 11, 7, 12, 53, 20, tzinfo=tzlocal()),
                 'message': 'Created new replica set '
                            '"nginx-deployment-7848d4b86f"',
                 'reason': 'NewReplicaSetCreated',
                 'status': 'True',
                 'type': 'Progressing'}],
 'observed_generation': None,
 'ready_replicas': None,
 'replicas': None,
 'unavailable_replicas': None,
 'updated_replicas': None}'
 
 root@sathish-vm2:/home/sathish# kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   1/1     1            1           100s

Changing parameter in function main so it falls within scaling window


if __name__ == '__main__':
    create_ngnix_deployment()
    while True:
        if scale_replicas_time(12):
            update_ngnix(replicas=3)
        else:
            update_ngnix(replicas=1)
        #sleep for an hour
        time.sleep(3600)
        


root@sathish-vm2:/home/sathish# kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   1/3     3            1           19s
root@sathish-vm2:/home/sathish# kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   1/3     3            1           26s
root@sathish-vm2:/home/sathish# kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   2/3     3            2           30s
root@sathish-vm2:/home/sathish#
root@sathish-vm2:/home/sathish# kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   2/3     3            2           34s
root@sathish-vm2:/home/sathish# kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           36s

Hope this article was useful in understanding the basics of Kubernetes python client and would be a good starting point for those who want to play around with it. Thanks for your visit and happy weekend :)



1,654 views0 comments
Never Miss a Post. Subscribe Now!

Thanks for Subscribing!

© 2020 Sathish Kumar Srinivasan

bottom of page