Overview:
In this tutorial, I would like to demonstrate the use of Kubernetes Adapter Pattern with a simple example.
Kubernetes Adapter Pattern:
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 for the infrastructure/deployment design as well in this modern Microservices era!
Lets consider an application in our Kubernetes cluster. One of the APIs of the application is not compatible with a client which is trying to consume the API.
Kubernetes Adapter Pattern allows us to run a sidecar container along with main application container to convert the API as the client expects without any application code change!
This adapter pattern is a special case of Kubernetes Sidecar Pattern which we had discussed here.
Sample Application:
Let’s consider a simple Microservice for cars in which we get information about a car model based on the given id. The application is successfully deployed and all the clients which are consuming this API are happy except one!
{
"id":1,
"make":"honda",
"model": "civic",
"topSpeed" : 60
}
The unhappy client realized that the topSpeed in the above response is in miles/hour. But the client expectation was to get that information in the kilometers/hour. The Microservice developers are afraid that they do not want to make a change in the code just for 1 client. [This is a simple example to explain the issue. But hopefully you get the idea].
Let’s see how we could make the client happy with Kubernetes Adapter Pattern.
Car Service – Application Container:
- Lets’s create a simple DB using json file for the cars-service as shown here. I have this content in a file called db.json
{
"cars":[
{
"id":1,
"make":"honda",
"model": "civic",
"topSpeed" : 60
},
{
"id":2,
"make":"honda",
"model": "accord",
"topSpeed" : 80
},
{
"id":3,
"make":"nissan",
"model": "370z",
"topSpeed" : 100
}
]
}
- Lets first create a ConfigMap with the json file for the cars-service to use later
kubectl create configmap carsdb --from-file=db.json
- Lets deploy the app using the below deployment.yaml
- The docker container will be injected with the above db.json during deployment.
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: car-service
name: car-service
spec:
replicas: 1
selector:
matchLabels:
app: car-service
template:
metadata:
labels:
app: car-service
spec:
containers:
- image: clue/json-server
name: car-service
ports:
- containerPort: 80
volumeMounts:
- name: db
mountPath: /data/
volumes:
- name: db
configMap:
name: carsdb
- Then Lets create a service to expose the app.
apiVersion: v1
kind: Service
metadata:
labels:
app: car-service
name: car-service
spec:
ports:
- name: 8080-80
port: 8080
protocol: TCP
targetPort: 80
selector:
app: car-service
type: LoadBalancer
- At this point, We should be able to access the application using the loadbalancer URL.
Kubernetes Adapter Pattern Implementation:
Now let’s work on the 1 specific client requirements of converting the topSpeed to km/h unit without touching the main car-service app. We are going to achieve that by attaching nginx to the main car-service app as a sidecar.
- Lets create another config file for nginx.
- Here we process the response from the car-service, convert the topSpeed from miles to kilometers
upstream backend {
server localhost;
}
server {
listen 8080;
default_type application/json;
location /cars_backend {
internal;
proxy_pass http://backend$request_uri;
proxy_redirect off;
}
location /cars {
content_by_lua_block {
-- forward to backend
local a = ngx.location.capture("/cars_backend")
-- convert to diff unit
local func = function (v)
return '"topSpeed": ' .. v[1] * 1.6;
end
-- find a match and replace
local newstr, n, err = ngx.re.sub(a.body, '"topSpeed": ([0-9]+)', func)
-- final response
ngx.say(newstr)
}
}
}
kubectl create configmap nginxconf --from-file=default.conf
- The deployment yaml spec is updated to include the sidecar as shown here
spec:
containers:
- image: openresty/openresty:alpine
name: car-adapter
ports:
- containerPort: 8080
volumeMounts:
- name: conf
mountPath: /etc/nginx/conf.d/
- image: clue/json-server
name: car-service
ports:
- containerPort: 80
volumeMounts:
- name: db
mountPath: /data/
volumes:
- name: db
configMap:
name: carsdb
- name: conf
configMap:
name: nginxconf
- The nginx sidecar is exposing its own port for anyone to access.
- Service yaml is updated as shown here to include additional nginx port
apiVersion: v1
kind: Service
metadata:
labels:
app: car-service
name: car-service
spec:
ports:
- name: 8080-80
port: 8080
protocol: TCP
targetPort: 80
- name: 8081-80
port: 8081
protocol: TCP
targetPort: 8080
selector:
app: car-service
type: LoadBalancer
Now we have included a special client requirement as a separate sidecar without modifying the original application.
By using the same uri patterns and using different port, we are able to change the response based on the requirements.
Summary:
We were able to successfully demonstrate the use of Kubernetes Adapter Pattern. We could modify the application response behavior by adding additional container as a sidecar based on the consumer requirements.
Read more about Kubernetes Cloud Design Patterns.
- Microservice Pattern – Kubernetes Init Container Pattern
- Microservice Pattern – Kubernetes Sidecar Pattern
The source code is available here.
Happy learning 🙂