Deploying a Real-time Data Pipeline in Minikube with Kubernetes

architecture

During this blog post series, we have built a real-time cryptocurrency data pipeline using Kafka, Spark, Cassandra, and visualized it using Grafana. In this blog post, we'll go over how to use Kubernetes to deploy each component.

What is Kubernetes? What is Minikube?

Kubernetes is a powerful open-source container orchestration platform that automates the deployment, scaling, and management of containerized applications. It allows you to easily manage and scale containerized applications in a clustered environment.

Minikube, on the other hand, is a tool that enables you to run a single-node Kubernetes cluster locally on your development machine. It provides a simplified way to learn and experiment with Kubernetes without the need for a full-scale cluster.

Getting Started with Minikube

Before we dive into deploying our real-time data pipeline components, let's set up Minikube.

For Linux, you can follow these steps to install Minikube:

curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube

Once Minikube is installed, you can start your cluster using this command:

minikube start

Interacting with Your Cluster

Now that Minikube is up and running, let's learn how to interact with your Kubernetes cluster using the kubectl command-line tool. Here are some basic commands to get you started:

To list all the pods in your cluster:

kubectl get pods

To view detailed information about a specific pod:

kubectl describe pod <pod-name>

To deploy, manage, and troubleshoot applications in Kubernetes, you can create and apply configuration files in YAML format.

Deploying Components

Deploying the Java Producer

Let's start with the Java producer, which generates real-time cryptocurrency data for our pipeline. I used the official Maven Docker image to build the Java producer Docker image.

Dockerfile:

FROM maven:3.8.6-openjdk-11-slim
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN mvn package
CMD ["java", "-jar", "target/coinbase-producer-1.jar"]

java-coinbase-producer.yaml:

apiVersion: v1
kind: Service
metadata:
  name: producer-service
spec:
  selector:
    app: producer
  clusterIP: None
  ports: []
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: producer
  labels:
    app: producer
spec:
  replicas: 1
  selector:
    matchLabels:
      app: producer
  template:
    metadata:
      labels:
        app: producer
    spec:
      containers:
      - name: producer
        image: kwangjong/java-coinbase-producer:latest

Deploying Apache Kafka

Apache Kafka is a fundamental component of our data pipeline. To deploy kafaka in minikube, I am using bitnami/kafka and offical zookeeper docker images.

kafka-broker.yaml:

apiVersion: v1
kind: Service
metadata:
  name: kafka-service
spec:
  selector:
    app: kafka
  ports:
  - port: 9092
    targetPort: 9092
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kafka
  labels:
    app: kafka
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kafka
  template:
    metadata:
      labels:
        app: kafka
    spec:
      containers:
      - name: kafka
        image: bitnami/kafka
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9092
        - containerPort: 9093
        env:
        - name: KAFKA_BROKER_ID
          value: "1"
        - name: KAFKA_ZOOKEEPER_CONNECT
          value: zookeeper-service:2181
        - name: KAFKA_LISTENERS
          value: PLAINTEXT://:9092,PLAINTEXT_HOST://:9093 
        - name: KAFKA_ADVERTISED_LISTENERS
          value: PLAINTEXT://kafka-service:9092,PLAINTEXT_HOST://localhost:9093
        - name: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP
          value: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
        - name: KAFKA_INTER_BROKER_LISTENER_NAME
          value: PLAINTEXT

zookeeper.yaml:

apiVersion: v1
kind: Service
metadata:
  name: zookeeper-service
spec:
  selector:
    app: zookeeper
  ports:
  - port: 2181
    targetPort: 2181
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: zookeeper
  labels:
    app: zookeeper
spec:
  replicas: 1
  selector:
    matchLabels:
      app: zookeeper
  template:
    metadata:
      labels:
        app: zookeeper
    spec:
      containers:
      - name: zookeeper
        image: library/zookeeper
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 2181
        env:
        - name: ZOOKEEPER_CLIENT_PORT
          value: "2181"
        - name: ZOOKEEPER_TICK_TIME
          value: "2000"

Deploying Spark

Dockerfile:

FROM sbtscala/scala-sbt:graalvm-ce-22.3.3-b1-java17_1.9.6_3.3.1
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN cd spark_stream_processor; sbt compile
CMD cd spark_stream_processor; sbt run

spark-stream-processor.yaml:

apiVersion: v1
kind: Service
metadata:
  name: spark-service
spec:
  selector:
    app: spark
  clusterIP: None
  ports: []
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spark
  labels:
    app: spark
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spark
  template:
    metadata:
      labels:
        app: spark
    spec:
      containers:
      - name: spark
        image: kwangjong/spark-stream-processor

Deploying Cassandra

For cassandra, you need to import copy .cql file and load it after cassandra has succesfully started.

Dockerfile:

FROM cassandra:latest

COPY ./cassandra-setup.cql /cassandra-setup.cql
RUN sed -i "s/rpc_address: localhost/rpc_address: 0.0.0.0/g" /etc/cassandra/cassandra.yaml
RUN sed -i "s/# broadcast_rpc_address: 1.2.3.4/broadcast_rpc_address: 1.2.3.4/g" /etc/cassandra/cassandra.yaml

RUN mkdir /home/cassandra
RUN chown cassandra:users /home/cassandra

USER cassandra
CMD cassandra -f | tee ~/cassandra.log & \
    echo waiting for cassandra to start && \
    tail -n 0 -F ~/cassandra.log | grep -q "Created default superuser role" && \
    echo cassandra is ready && \
    cqlsh 127.0.0.1 -f /cassandra-setup.cql && \
    echo loaded cassandra-setup.cql && \
    tail -f /dev/null

cassandra.yaml:

apiVersion: v1
kind: Service
metadata:
  name: cassandra-service
spec:
  selector:
    app: cassandra
  ports:
  - port: 9042
    targetPort: 9042
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cassandra
  labels:
    app: cassandra
spec:
  replicas: 1
  selector:
    matchLabels:
      app: cassandra
  template:
    metadata:
      labels:
        app: cassandra
    spec:
      containers:
      - name: cassandra
        image: kwangjong/cassandra
        ports:
        - containerPort: 9042

Deploying Grafana

Dockerfile:

FROM grafana/grafana:latest
# Install required plugin for cassandra
RUN grafana-cli plugins install hadesarchitect-cassandra-datasource

# Copy custom configuration files
COPY grafana.ini /etc/grafana/grafana.ini
COPY cassandra.yaml /etc/grafana/provisioning/datasources/cassandra.yaml
COPY dashboard.yaml /etc/grafana/provisioning/dashboards/dashboard.yaml
COPY dashboards /var/lib/grafana/dashboards

grafana.yaml:

apiVersion: v1
kind: Service
metadata:
  name: grafana-service
spec:
  selector:
    app: grafana
  type: NodePort
  ports:
  - port: 3000
    targetPort: 3000
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grafana
  labels:
    app: grafana
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grafana
  template:
    metadata:
      labels:
        app: grafana
    spec:
      containers:
      - name: grafana
        image: kwangjong/grafana
        ports:
        - containerPort: 3000

Connecting to Grafana

Once Grafana is deployed, you can connect to it using the following command:

minikube service grafana-service --url

This will provide you with a URL to access the Grafana web interface.

Conclusion

In this blog post, we've introduced Kubernetes and Minikube and guided you through the process of setting up Minikube, deploying essential components of our real-time data pipeline, and connecting to Grafana for data visualization. This foundation will enable you to experiment with real-time data processing in a Kubernetes environment.

Links