top of page
Writer's pictureSathish Kumar

Deploying a sample microservices app with Kubernetes

Updated: Jan 15, 2021


“I Hear and I Forget, I See and I Remember, I Do and I Understand”- One of few inspirational quotes that never gets old. This is especially true when you are in tech/R&D where things change at a breakneck pace. Personally, unless I apply new learning to something practical- I have a tendency to forget it quickly.


When learning about Docker and Kubernetes, I came across a wonderfully simple project in GitHub called voting-app. The application itself is very simple- a voting page lets users vote for either "Cats" or "Dogs" and then there is a result page that displays the number of votes. In this article, I will describe the components of the app (PoDS) and how they interact with each other. Also, I will show you how to deploy the app with Kubernetes.


The voting app is based on a microservices architecture. The below diagram gives an overview of the various components involved.




Note: YAML files for the project can be found here https://github.com/dockersamples/example-voting-app/tree/master/k8s-specifications
For sake of brevity,I am not listing the deployment YAML files. 

Voting App: This component enables voting through a webpage and allows users to choose between a "cat" and a "dog". As it involves user interaction, we should create a service to expose this to users. Let's look at the YAML file for the service.


apiVersion: v1
kind: Service
metadata:
  labels:
    app: vote
  name: vote
  namespace: vote
spec:
  type: NodePort
  ports:
  - name: "vote-service"
    port: 5000
    targetPort: 80
    nodePort: 31000
  selector:
    app: vote

The service is exposed on port 31000, whereas the container port is 80.


result-app: The requirements are similar to the voting app as this app also interacts with users. The nodePort for this is 31001


apiVersion: v1
kind: Service
metadata:
  labels:
    app: result
  name: result
  namespace: vote
spec:
  type: NodePort
  ports:
  - name: "result-service"
    port: 5001
    targetPort: 80
    nodePort: 31001
  selector:
    app: result

redis: Once the user makes a choice on the voting-app, the results are stored on redis which is an in-memory key-value based data store a.k.a in-memory cache.


In many applications (like voting-app) data might be frequently written (or read ) to the database. Think of thousands of users voting per minute. If each vote is updated to db before another vote is cast it will slow down the system to a point it becomes unusable. Hence, it makes sense to have an in-memory cache like redis and periodically write contents of redis DB to a permanent database like Postgres.


The Redis service in the voting app does not interact with users. However, it talks to voting-app PoD and the worker PoD. In other words, this service should be accessible only to other PoDs within the Kubernetes cluster and hence ClusterIP is the ideal choice for service type.


apiVersion: v1
kind: Service
metadata:
  labels:
    app: redis
  name: redis
  namespace: vote
spec:
  type: ClusterIP
  ports:
  - name: "redis-service"
    port: 6379
    targetPort: 6379
  selector:
    app: redis

worker: The worker component periodically reads the contents of redis and writes to DB. As it is a "consumer" of both PoDs we do not need a service type. We can just create a deployment of worker.


db -service: The DB service interacts with worker and result-app. In other words, it communicates only with other PoD's in the cluster. Hence the service type should be clusterIP.


apiVersion: v1
kind: Service
metadata:
  labels:
    app: db
  name: db
  namespace: vote
spec:
  type: ClusterIP
  ports:
  - name: "db-service"
    port: 5432
    targetPort: 5432
  selector:
    app: db

Now let's deploy the app. To deploy the app, clone the project files from github and then use the YAML definitions within the project directory. kubectl create -f when used with a directory as a parameter will parse all YAML files in the directory and create the corresponding Kubernetes objects. votingapp runs in its own namespace "vote" which should be created before deploying objects.



root@sathish-vm2:/home/sathish/votingapp# git clone https://github.com/dockersamples/example-voting-app
Cloning into 'example-voting-app'...
remote: Enumerating objects: 860, done.
remote: Total 860 (delta 0), reused 0 (delta 0), pack-reused 860
Receiving objects: 100% (860/860), 957.36 KiB | 1013.00 KiB/s, done.
Resolving deltas: 100% (300/300), done.
root@sathish-vm2:/home/sathish/votingapp# ls
example-voting-app

The YAML files needed for the project are located in "k8s-specifications" directory



root@sathish-vm2:/home/sathish/votingapp/example-voting-app/k8s-specifications# pwd
/home/sathish/votingapp/example-voting-app/k8s-specifications

root@sathish-vm2:/home/sathish/votingapp/example-voting-app# kubectl create  namespace vote
namespace/vote created

root@sathish-vm2:/home/sathish/votingapp/example-voting-app# kubectl create -f k8s-specifications/
deployment.apps/db created
service/db created
deployment.apps/redis created
service/redis created
deployment.apps/result created
service/result created
deployment.apps/vote created
service/vote created
deployment.apps/worker created

As expected I have 5 PoDs, 5 deployments and 4 service objects (worker does not need a service object).


root@sathish-vm2:/home/sathish/votingapp/example-voting-app# kubectl get pods --namespace=vote
NAME                      READY   STATUS    RESTARTS   AGE
db-684b9b49fd-jqrnc       1/1     Running   0          15s
redis-67db9bd79b-wk7pq    1/1     Running   0          15s
result-86d8966d87-spmgv   1/1     Running   0          15s
vote-6d4876585f-r9hgv     1/1     Running   0          15s
worker-7cbf9df499-x6827   1/1     Running   0          15s
root@sathish-vm2:/home/sathish/votingapp/example-voting-app# kubectl get deployment --namespace=vote
NAME     READY   UP-TO-DATE   AVAILABLE   AGE
db       1/1     1            1           29s
redis    1/1     1            1           29s
result   1/1     1            1           29s
vote     1/1     1            1           29s
worker   1/1     1            1           29s
root@sathish-vm2:/home/sathish/votingapp/example-voting-app# kubectl get service --namespace=vote
NAME     TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
db       ClusterIP   10.103.137.189   <none>        5432/TCP         47s
redis    ClusterIP   10.97.79.106     <none>        6379/TCP         47s
result   NodePort    10.103.54.127    <none>        5001:31001/TCP   47s
vote     NodePort    10.106.237.113   <none>        5000:31000/TCP   47s

I can access both the "vote" and "results" page with cluster host IP and corresponding node ports (31000 and 31001).




Now to the final piece of the puzzle. We deployed a bunch of objects which created a working microservices application. But, how does one component of the application know-how to talk to others i.e how does vote-app know how to connect with Redis?


The answer to this lies in the source code and how Kubernetes name resolution works. Let's examine the code located here



Ignoring other pieces of code, the part we are concerned with is how does the "vote" connect to redis-db. The function responsible for this is get_redis ()



def get_redis():
    if not hasattr(g, 'redis'):
        g.redis = Redis(host="redis", db=0, socket_timeout=5)
    return g.redis

The hostname of the Redis instance the code attempts to connect to is "redis". This name should resolve to a proper IP address within the cluster. This is possible because Kubernetes creates a DNS entry for each object name in the coredns service which gets installed when you deploy a Kubernetes cluster.


root@sathish-vm2:/home/sathish/votingapp/example-voting-app/k8s-specifications# cat redis-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: redis
  name: redis
  namespace: vote
...............Ouput deleted for clarity..............

root@sathish-vm2:/home/sathish/votingapp/example-voting-app# kubectl get pods --all-namespaces
NAMESPACE     NAME                                  READY   STATUS             RESTARTS   AGE
kube-system   coredns-f9fd979d6-d8wzr               1/1     Running            0          16d
kube-system   coredns-f9fd979d6-xcxzc               1/1     Running            0          16d

Looking at resolv.conf

root@sathish-vm2:/home/sathish/votingapp/example-voting-app# cat /etc/resolv.conf
# This file is managed by man:systemd-resolved(8). Do not edit.
#
# This is a dynamic resolv.conf file for connecting local clients to the
# internal DNS stub resolver of systemd-resolved. This file lists all
# configured search domains.
#
# Run "resolvectl status" to see details about the uplink DNS servers
# currently in use.
#
# Third party programs must not access this file directly, but only through the
# symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a different way,
# replace this symlink by a static file or a different symlink.
#
# See man:systemd-resolved.service(8) for details about the supported modes of
# operation for /etc/resolv.conf.

nameserver 127.0.0.53
options edns0

Hence, using names it is possible to connect various components of microservices application together.


Hope this short post was useful. Happy weekend to everyone :)

2,262 views0 comments

Comentarios


bottom of page