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
“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 :)
Comentarios