Live Cricket Scores: Flask App Setup & Deployment via Jenkins and Kubernetes

Piyush KabraPiyush Kabra
9 min read

πŸ“Œ Introduction

In this tutorial, we'll develop a real-time cricket score tracker named CricketLive. This application fetches live scores and match schedules using the Cricbuzz API, displays them through a Flask web interface, containerizes the application with Docker, and deploys it on a Kubernetes cluster. We'll also set up a CI/CD pipeline using Jenkins for continuous integration and Argo CD for continuous deployment.


1. Build the Flask App

  • Start by creating a simple web application using Flask (a Python web framework).

  • Use the Cricbuzz API to get live cricket scores and upcoming match schedules.

  • Design a clean and attractive HTML page to show this data, updating in real-time for the best user experience.


2. Package the App with Docker

  • Once your app is working, package it into a Docker image so it runs the same way everywhere.

  • Create a Dockerfile that includes everything your Flask app needs (Python, dependencies, source code, etc.).

  • This makes it easy to deploy your app anywhere, whether on your local machine or the cloud.


3. Deploy to Kubernetes

  • Set up a Kubernetes cluster (locally using Minikube or on cloud providers like AWS or GCP).

  • Install ArgoCD in the cluster to help automate application deployments.

  • Write Kubernetes YAML files (like Deployment, Service, and optionally Ingress) to describe how your app should run in the cluster.


4. Automate CI with Jenkins

  • Use Jenkins to automate the building and testing of your app whenever you make changes.

  • Create a Jenkins pipeline that:

    • Pulls the latest code from GitHub.

    • Builds a new Docker image for your Flask app.

    • Pushes the image to a container registry (like Docker Hub or AWS ECR).

    • Updates the Kubernetes deployment YAML with the new image tag.

    • Pushes this updated YAML file to your GitHub repo (which ArgoCD watches).


5. Set Up ArgoCD for Continuous Delivery

  • Create an ArgoCD application using a configuration file (YAML).

  • This file should point to your Kubernetes cluster, namespace, and the Git repo with your Kubernetes manifests.

  • ArgoCD keeps watching the Git repo, and whenever it sees a change (like a new image tag), it automatically deploys the updated version to your cluster.


🧰 Prerequisites

Before we begin, ensure you have the following:

  • Python 3.8+ is installed on your development machine.

  • Docker is installed and running.

  • Access to a Kubernetes cluster (e.g., Minikube, AWS EKS, GCP GKE).

  • Jenkins is installed and configured.

  • Argo CD is installed on your Kubernetes cluster.

  • Accounts for:

    • RapidAPI to access the Cricbuzz API.

    • Docker Hub for container image storage.

    • GitHub for version control and repository hosting.


πŸ™ Step 1: Create a GitHub Repository πŸ“ & Take Cricbuzz API 🏏

  1. Goto rapidapi.com

Search Crickbuzz

Search Crickbuzz official API

Copy the URL & headers part from both the types and paste it in the app.py code, like this


πŸ› οΈ Step 2: Develop the Flask Application

2.1. Set Up the Project Structure

Create the following directory structure:

cricketlive/
β”œβ”€β”€ app.py
β”œβ”€β”€ requirements.txt
└── templates/
    └── index.html

2.2. Implement app.py

from flask import Flask, render_template
import requests
import json
from tabulate import tabulate
import os

app = Flask(__name__)

def fetch_cricket_scores():
    url = 'https://crickbuzz-official-apis.p.rapidapi.com/matches/list'
    headers = {
        'x-rapidapi-key': '831af6d415msh83780108e78df43p127955jsn56ed552814e7',
        'x-rapidapi-host': 'crickbuzz-official-apis.p.rapidapi.com'
    }

    response = requests.get(url, headers=headers)
    matches_data = []

    if response.status_code == 200:
        try:
            data = response.json()

            type_matches = data.get("typeMatches", [])
            for match_type in type_matches:
                for series in match_type.get("seriesMatches", []):
                    wrapper = series.get("seriesAdWrapper", {})
                    for match in wrapper.get("matches", []):
                        # same processing logic
                        ...
        except Exception as e:
            print("Error while processing match data:", e)
    else:
        print("Failed to fetch. Status code:", response.status_code)

    return matches_data


def fetch_upcoming_matches():
    url = 'https://crickbuzz-official-apis.p.rapidapi.com/schedules/international'
    headers = {
    'x-rapidapi-key': '831af6d415msh83780108e78df43p127955jsn56ed552814e7',
    'x-rapidapi-host': 'crickbuzz-official-apis.p.rapidapi.com'
  }

    response = requests.get(url, headers=headers)
    upcoming_matches = []

    if response.status_code == 200:
        try:
            data = response.json()
            match_schedules = data.get('matchScheduleMap', [])

            for schedule in match_schedules:
                if 'scheduleAdWrapper' in schedule:
                    date = schedule['scheduleAdWrapper']['date']
                    matches = schedule['scheduleAdWrapper']['matchScheduleList']

                    for match_info in matches:
                        for match in match_info['matchInfo']:
                            description = match['matchDesc']
                            team1 = match['team1']['teamName']
                            team2 = match['team2']['teamName']
                            match_data = {
                                'Date': date,
                                'Description': description,
                                'Teams': f"{team1} vs {team2}"
                            }
                            upcoming_matches.append(match_data)
                else:
                    print("No match schedule found for this entry.")

        except json.JSONDecodeError as e:
            print("Error parsing JSON:", e)
        except KeyError as e:
            print("Key error:", e)
    else:
        print("Failed to fetch upcoming matches. Status code:", response.status_code)

    return upcoming_matches


@app.route('/')
def index():
    cricket_scores = fetch_cricket_scores()
    upcoming_matches = fetch_upcoming_matches()
    return render_template('index.html', cricket_scores=cricket_scores, upcoming_matches=upcoming_matches)


if __name__ == '__main__':
    app.run(port=int(os.environ.get("PORT", 8080)), host='0.0.0.0', debug=True)

Replace the code of fetch_cricket_scores() & fetch_upcoming_matches() with your API code

2.3. Create requirements.txt, we will use it in Dockerfile

flask==2.2.5   #replace the flask version if required
tabulate
requests

2.4. Design templates/index.html

<!DOCTYPE html>
<html>
<head>
    <title>Cricket Scores</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        /* Custom styles */
        .navbar-brand {
            background-color: #d2dfd9; /* Change the background color */
            color: white; /* Change the text color */
            padding: 10px;
            font-size: 34px;
        }
        .top-bar {
            background-color: rgb(25, 105, 94); /* Change the background color */
            color: #e7f1f4; /* Change the text color */
            padding: 8px;
            font-size: 24px;
            font-family: Arial, sans-serif; /* Change the font family */

        }
        body {
            background-color: #030f13; /* Change the overall background color */
            color: #ece0e0; /* Change the overall text color */
        }
        .custom-card {
        width: 500px; /* Change the width */
        height: 250px; /* Change the height */
        /* Add any other styling properties as needed */
    }
    .custom-upcoming-card {
        width: 500px; /* Change the width */
        height: 150px; /* Change the height */
        /* Add any other styling properties as needed */
    }
    .navbar-brand {
        font-size: 24px; /* Change the font size as needed */
    }
    </style>

</head>
<body>
    <div class="top-bar">
        <div class="container">
            <img src="https://storage.googleapis.com/bkt-static-tt/logo.png" width="50" height="50" class="d-inline-block align-top" alt="">
            CricketScore Pro : By Piyush Kabra
        </div>
    </div>
    <!-- <nav class="navbar navbar-fixed-top navbar-expand-lg navbar-dark bg-primary">
        <a class="navbar-brand" href="#">
            <img src="https://storage.googleapis.com/bkt-static-tt/logo.png" width="50" height="50" class="d-inline-block align-top" alt="">
             TechTrapture Cricket Score App
        </a>
    </nav> -->
    <div class="container mt-4">
        <h2>Recent Matches</h2>
        <div class="row row-cols-1 row-cols-md-2">
            {% for score in cricket_scores %}
                <div class="col mb-4">
                    <div class="card custom-card">
                        <div class="card-body">
                            {{ score | safe }}
                        </div>
                    </div>
                </div>
            {% endfor %}
        </div>

        <h2>Upcoming Matches</h2>
        <div class="row row-cols-1 row-cols-md-2">
            {% for match in upcoming_matches %}
                <div class="col mb-4">
                    <div class="card custom-upcoming-card">
                        <div class="card-body">
                            <ul class="list-group">
                                <li class="list-group-item">
                                    <strong>Date:</strong> {{ match['Date'] }}<br>
                                    <strong>Description:</strong> {{ match['Description'] }}<br>
                                    <strong>Teams:</strong> {{ match['Teams'] }}
                                </li>
                            </ul>
                        </div>
                    </div>
                </div>
            {% endfor %}
        </div>
    </div>

</body>
</html>

Now, run the app.py file locally and check whether the API is working or not.

Locally, two files are there: app.py & templates/index.html, and running app.py will run index.html & app.py

Now, see, it is running successfully


🐳 Step 3: Containerize the Application with Docker

3.1. Create a Dockerfile

FROM python:latest
WORKDIR /app
COPY . ./
RUN pip install -r requirements.txt
EXPOSE 8080
CMD ["python", "app.py"]

3.2. Build and Run the Docker Image

#Docker file and app.py should by in same folder
docker build -t crickflaskapp . # to create a docker image
docker run -dit --name flask-app -p 80:8080 crickflaskapp # to run the container

3.3 🌐Now, Check the Website:-

Now Docker container is exposed, we can access the website using the β€œPublic IP of the machine”:80 πŸ‘‡πŸΌ


☸️ Step 4: Deploy to Kubernetes

4.1. Create Kubernetes Deployment and Service Manifests

Create flask-deploy.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: cricket-flask-app
  name: cricket-flask-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: cricket-flask-app
  strategy: {}
  template:
    metadata:
      labels:
        app: cricket-flask-app
    spec:
      containers:
      - image: kabrajii/crickflaskapp:latest
        name: crickflaskapp

& Create flask-svc.yml

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: cricket-flask-app
  name: cricket-flask-app
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: cricket-flask-app
  type: NodePort
status:
  loadBalancer: {}

4.2. Apply the Manifests:-

kubectl apply -f flask-deploy.yml
kubectl apply -f flask-svc.yml

πŸ”„ Step 5: Set Up Jenkins for Continuous Integration

5.1. Configure Jenkins Pipeline

Create a Jenkinsfile in your repository:

pipeline{
    agent any
    environment{
        DOCKER_USERNAME = "kabrajii"
        APP_NAME = "flask-app"
        IMAGE_TAG = "${BUILD_NUMBER}"
        IMAGE_NAME = "${DOCKER_USERNAME}/${APP_NAME}"


    }


    stages{
        stage('clean the workspace'){
            steps{
                script{
                    cleanWs()
                }
            }
        }

        stage('checkout git scm'){
            steps{
                git branch: 'main', url: 'https://github.com/KabraJiii/flask_app_CICD.git'
            }
        }

        stage('build docker image'){
            steps{
                script{
                    sh "sudo docker build -t ${IMAGE_NAME}:${IMAGE_TAG} ."
                }
            }
        }
        stage('Push the Image to DockerHub') {


            steps {
              withCredentials([usernamePassword(credentialsId: 'Docker', passwordVariable: 'password', usernameVariable: 'user')]) {
                // some block


                    sh """

                        echo ${password} | docker login -u ${user} --password-stdin

                        sudo docker push ${IMAGE_NAME}:"${IMAGE_TAG}"



                    """
            }
            }

        }
        stage('Delete Image Locally') {

            steps {
                sh """
                    sudo docker rmi -f ${IMAGE_NAME}:${IMAGE_TAG}

                """

            }


        }
         stage('Update the deployment file in CD') {

            steps {
                script{
                    withCredentials([usernamePassword(credentialsId: 'GitHub', usernameVariable: 'user', passwordVariable: 'pass')]) {

                        sh """
                            git clone -b main https://${user}:${pass}@github.com/KabraJiii/flask_app_CICD.git

                            cd flask_app_CICD

                            cat flask-deploy.yml

                            cd  /var/lib/jenkins/workspace/CricketAPI-Pipeline/flask_app_CICD

                            cat flask-deploy.yml

                            echo "Changing tag to ${BUILD_NUMBER}"

                            sed -i 's|image: kabrajii/flask-app:.*|image: ${IMAGE_NAME}:${BUILD_NUMBER}|g' flask-deploy.yml

                            echo "changed tag"

                            cat flask-deploy.yml

                            echo "hello" >> hi.txt

                            git add .
                            git commit . -m "Updated the tag to ${BUILD_NUMBER}"
                            git push origin main

                        """

                    }

                }
            }
        }
    }
}

Now save this Jenkinsfile and push it to GitHub, & then create a pipeline πŸ‘‡πŸΌπŸ‘‡πŸΌ

Now run the job & see this output πŸ‘‡πŸΌπŸ‘‡πŸΌ


πŸš€ Step 6: Configure Argo CD for Continuous Deployment

6.1. Install Argo CD

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

6.2. Access Argo CD UI & API Server

# Service Type Load Balancer
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'

# Port Forwarding
kubectl port-forward svc/argocd-server -n argocd 8080:443

Take password from this command :-

argocd admin initial-password -n argocd

Now create the argo application file argocd-application.yml :-

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: cricket-score-app
  namespace: argocd
spec:
  project: default
  source:  #github url from where it will take code
    repoURL: 'https://github.com/KabraJiii/flask_app_CICD.git'
    targetRevision: HEAD
    path: .
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

And apply it :-

kubectl apply -f argocd-application.yml

Now, Go on β€œMinikube IP”:port of svc β€”> http://192.168.233.131:31698/


βœ… Final Output

  • Once everything is set up:

  • Access your application via the external IP provided by the Kubernetes LoadBalancer service.

  • The Flask application will display real-time cricket scores and upcoming matches fetched from the Cricbuzz API.

  • Any changes pushed to your GitHub repository will trigger the Jenkins pipeline, which builds and pushes a new Docker image, updates the Kubernetes manifests, and Argo CD will automatically deploy the updated application.

πŸ“Œ GitHub Repo

Click here

0
Subscribe to my newsletter

Read articles from Piyush Kabra directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Piyush Kabra
Piyush Kabra