En el último artículo hice una breve introducción a lo que es una malla de servicios utilizando Open Service Mesh como ejemplo. Uno de los puntos claves de estos son la capacidad de permitir o denegar el tráfico entre los pods, como ya te mostré ayer. Hoy quiero contarte otra característica que nos permite repartir el tráfico entre varios servicios/despliegues, muy útil cuando implementas estrategias de despliegue como canary deployments.
Aplicación de ejemplo
Para este artículo voy a utilizar la misma aplicación de ejemplo que en el artículo anterior, pero con algunas modificaciones. Todo el código está en el mismo repo de GitHub, pero en un branch llamado traffic-split. Puedes desplegar el ejemplo completo utilizando estos pasos:
# Clone repository
git clone https://github.com/0GiS0/open-service-mesh-demo
# Change branch
git checkout traffic-split
YOUR_DOCKER_HUB_USER="<your docker hub user>"
# Generate docker images
docker build -t $YOUR_DOCKER_HUB_USER/bookstore bookstore/.
docker build -t $YOUR_DOCKER_HUB_USER/moviestore moviestore/.
docker build -t $YOUR_DOCKER_HUB_USER/bookthief bookthief/.
docker build -t $YOUR_DOCKER_HUB_USER/bookbuyer bookbuyer/.
# Publish Movistore image in Docker Hub
docker push $YOUR_DOCKER_HUB_USER/bookstore
docker push $YOUR_DOCKER_HUB_USER/moviestore
docker push $YOUR_DOCKER_HUB_USER/bookthief
docker push $YOUR_DOCKER_HUB_USER/bookbuyer
# Create namespaces
k create ns stores
k create ns bookbuyer
k create ns bookthief
# Add namespaces to osm
export PATH=$PATH:./darwin-amd64/
osm namespace add stores bookbuyer bookthief
# Deploy Movistore in AKS/K8s cluster
kubectl apply -f manifests/
# Install nginx
kubectl create ns ingress-nginx
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install osm-nginx-ingess ingress-nginx/ingress-nginx --namespace ingress-nginx
# Get nginx ingress controller public IP
kubectl get svc -n ingress-nginx osm-nginx-ingess-ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
# Add ingress to access from outside
##### YOU HAVE TO CHANGE THE IP ADDRESS ######
kubectl apply -f ingress/
# Check access
http://bookbuyer.<PUBLIC_IP_INGRESS>.nip.io
http://bookthief.<PUBLIC_IP_INGRESS>.nip.io
http://bookstore.<PUBLIC_IP_INGRESS>.nip.io
http://moviestore.<PUBLIC_IP_INGRESS>.nip.io
Nota: en este ejemplo se asume que ya tienes un clúster y has instalado Open Service Mesh en él. Si no es así, echa un vistazo a este otro artículo.
En el ejemplo anterior tenía una única API llamada bookstore donde se podían comprar o robar libros. En el ejemplo de hoy he añadido una nueva llamada moviestore que hace exactamente lo mismo en cuanto a funcionalidad, excepto robar, con la misma llamada /api/buy:
const express = require('express');
const app = express();
app.use(express.static('public'));
app.set('view engine', 'ejs');
const PORT = process.env.PORT || 4002;
let moviesSold = 0;
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
// GET /
app.get('/', (req, res) => {
res.render('index', { moviesSold });
});
// GET /api/buy
// This route should add new movie sold and return the number of movie sold
app.get('/api/buy', (req, res) => {
moviesSold++;
console.log(`Movies sold: ${moviesSold}`);
return res.send({
moviesSold
});
});
No es como se suele usar esta característica que te voy a contar hoy, pero es mucho más fácil de demostrar cuándo atiende un servicio u otro sin complicar mucho el código, y que sea sencillo de ver 😇
Ahora, el objetivo es que mis clientes bookbuyer y bookthief puedan comprar tanto libros como películas sin alterar sus llamadas. Es decir, necesito que «algo» reparta el peso de las llamadas entre bookstore y moviestore de manera transparente para mí. Hasta este momento, si accedes a cada una de las URLs que nos dan acceso a los diferentes servicios verás que bookthief y bookbuyer funcionan sin problemas pero solo compran/roban libros.
Configurar permisos en las comunicaciones entre los pods
Si bien no es un paso necesario para el ejemplo de hoy, es recomendable que siempre tengas controlada la comunicación entre tus pods. Si no tienes claro si tienes habilitada la propiedad enablePermissiveTrafficPolicyMode puedes validarlo con este comando:
# Check permissive mode
kubectl get meshconfig osm-mesh-config -n osm-system -o jsonpath='{.spec.traffic.enablePermissiveTrafficPolicyMode}{"\n"}'
Si está a true significará que tienes el modo permisivo y no tendrá en cuenta las reglas que tengas o no. Para deshabilitarlo puedes hacerlo con este comando:
# Change to permissive mode to false
kubectl patch meshconfig osm-mesh-config -n osm-system -p '{"spec":{"traffic":{"enablePermissiveTrafficPolicyMode":false}}}' --type=merge
A partir de este momento si no tenías nada configurado, bookbuyer y bookthief dejarán de funcionar. En el artículo anterior te hablé de ello con más detalle, aquí te dejo cuál sería la configuración añadiendo moviestore:
kind: TrafficTarget
apiVersion: access.smi-spec.io/v1alpha3
metadata:
name: bookstore
namespace: stores
spec:
destination:
kind: ServiceAccount
name: bookstore
namespace: stores
sources:
- kind: ServiceAccount
name: bookbuyer
namespace: bookbuyer
- kind: ServiceAccount
name: bookthief
namespace: bookthief
rules:
- kind: HTTPRouteGroup
name: stores-service-routes
matches:
- buy
# - steal
---
kind: TrafficTarget
apiVersion: access.smi-spec.io/v1alpha3
metadata:
name: moviestore
namespace: stores
spec:
destination:
kind: ServiceAccount
name: moviestore
namespace: stores
sources:
- kind: ServiceAccount
name: bookbuyer
namespace: bookbuyer
- kind: ServiceAccount
name: bookthief
namespace: bookthief
rules:
- kind: HTTPRouteGroup
name: stores-service-routes
matches:
- buy
---
apiVersion: specs.smi-spec.io/v1alpha4
kind: HTTPRouteGroup
metadata:
name: stores-service-routes
namespace: stores
spec:
matches:
- name: buy
pathRegex: "/api/buy"
methods:
- GET
- name: steal
pathRegex: "/api/steal"
methods:
- GET
Si has descargado el código, puedes aplicar estas reglas con este comando:
# Update traffic access
kubectl apply -f traffic-access/traffic-policies.yaml
Recurso TrafficSplit
Ahora, la parte importante de este artículo: Para conseguir el reparto de las llamadas entra en juego otro recurso llamado TrafficSplit con el que puedes configurar varios backends para una misma llamada con un peso. Para este ejemplo he configurado uno como el siguiente:
apiVersion: split.smi-spec.io/v1alpha2
kind: TrafficSplit
metadata:
name: bookstore-movistore-split
namespace: stores
spec:
service: bookstore.stores # <root-service>.<namespace>
backends:
- service: bookstore
weight: 50
- service: moviestore
weight: 50
En este caso se reparte la mitad de las llamadas al servicio bookstore y la otra mitad a moviestore. Es importante tener en cuenta que ambos servicios deben tener como targetPort el mismo puerto ya que de lo contrario obtendrás un 503 (Service Unavailable) y te volverás locx durante un rato. Normalmente este tipo de funcionalidades se suele utilizar con diferentes versiones del mismo desarrollo, con el objetivo de añadir una nueva característica o una nueva versión que quieres ir introduciendo en un entorno de forma gradual. Para crear este nuevo recurso puedes hacerlo así:
# Apply Traffic split
kubectl apply -f traffic-split/split-between-bookstore-and-moviestore.yaml
El resultado será como el siguiente:
El código lo tienes en mi GitHub.
¡Saludos!