Overview:
Design Patterns are repeatable & reusable solutions for commonly occurring problems in the software/architectural design and they encourage the developers to design a highly cohesive and loosely coupled applications. Design patterns can be used in the infrastructure/deployment design as well in this modern microservices era! We are going to take a look one such design pattern – Sidecar pattern in this article.
Sidecar pattern can be related to Decorator design pattern in the software design which can add additional functionality to the existing instance at run time.
Problem Statement:
Microservices follow the single responsibility principle and they are designed to handle the specific business sub-domain/bounded-context well. A developer while developing the microservice application might have some assumptions like some properties/jars/files should be present on certain path for the application to work fine. For ex: Let’s consider nginx – a webserver – running inside a docker container to serve some static content for our application. The nginx application is able to successfully serve the content from certain path from the container.
Even though this approach works just great, some recent business requirement might say once in a while they would like to change the theme of the static html page. Developers will be able to quickly apply the change and push it to the version control system. But any change in the html content will require rebuilding the docker image and re-deployment with new image. This is a problem. We need a solution for our nginx server to serve the content by pulling the latest changes.
Lets see how we could solve using sidecar pattern. [This example is chosen to explain the sidecar pattern. In real life, We might go with AWS Cloudfront + S3 combination to serve the static content. Also not everyone uses cloud. There are many organizations which are still using the on-prem solutions].
Sample Application:
- I have a very simple HTML file as shown here.
<html>
<body bgcolor="#FFFFFF">
<h1><strong>This is sidecar pattern demo!</strong></h1>
</body>
</html>
- I create a Dockerfile as shown here and build a docker image with the name vinsdocker/nginx
FROM nginx
ADD index.html /usr/share/nginx/html/index.html
- If I can run the below command, I am able to see the content at localhost
docker run -p 80:80 vinsdocker/nginx
Kubernetes Resources:
I am going to deploy the above docker container in a kubernetes cluster. For that I am creating deployment and service resources.
- Deployment yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: vinsdocker/nginx
name: nginx
ports:
- containerPort: 80
- service yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx
name: nginx
spec:
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
- After applying these commands, I am able to access the application from the Kubernetes cluster. At this point, all the end users can access the application.
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
- Our requirement is to periodically change the content based on the business requirement and make the nginx serve the latest content w/o rebuilding docker image in a kubernetes cluster.
Shared Volume:
In kubernetes pod, we can run multiple containers. All these containers can share volumes. We are going to make use of this concept to accomplish our requirement. That is, we would be creating a separate container called sidecar which is responsible for downloading the latest content. Once it is downloaded, it will place the new content for the nginx container to access in the shared volume. So that nginx can serve the latest content.
Sidecar:
Aim of this sidecar container is to add the required additional functionality without touching the main app we have built above.
- Let’s add simple shell script to pull the content from some source every 5 seconds. I use an environment variable STATIC_SOURCE through which I would be passing the github file path.
#!/bin/bash
while :
do
wget --no-cache -O temp.html $STATIC_SOURCE
mv temp.html html/index.html
sleep 5
done
- Dockerfile – build this as vinsdocker/sidecar
FROM alpine
WORKDIR /usr/share/nginx
ADD content-refresher.sh content-refresher.sh
ENTRYPOINT sh content-refresher.sh
- The above deployment.yaml is updated to include the sidecar container now.
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: vinsdocker/sidecar
name: sidecar
env:
- name: STATIC_SOURCE
value: https://raw.githubusercontent.com/vinsguru/vinsguru-blog-code-samples/master/kubernetes/sidecar-pattern/static-content/index.html
volumeMounts:
- name: shared-data
mountPath: /usr/share/nginx/html
- image: vinsdocker/nginx
name: nginx
ports:
- containerPort: 80
volumeMounts:
- name: shared-data
mountPath: /usr/share/nginx/html/
volumes:
- name: shared-data
emptyDir: {}
- Apply this command to redeploy our app with sidecar.
kubectl apply -f deployment.yaml
- Now whenever we change the content in the static file and push it to github, sidecar pulls the content and make nginx serve the updated content.
Summary:
We were able to successfully demonstrate sidecar pattern in this article and it is a good idea to add additional functionalities to the existing app without modifying the app. For ex: We can add logging, monitoring etc to the existing app. It is also reusable and not language specific. For ex: If someone else with apache-server instead of nginx needs a similar feature, we can simply include the sidecar container we have built.
Happy coding 🙂