Como ya te dije en el último artículo, es hora de ir viendo cómo encaja Dapr en los diferentes entornos donde solemos trabajar. Hoy le toca el turno a Kubernetes y en este artículo quiero compartir contigo cómo encaja mi ejemplo de Tour of Heroes con Dapr en él.
Entorno de Kubernetes
Si bien puedes usar cualquier clúster de Kubernetes, para mostrarte todo lo que tengo preparado para este artículo he usado kind. La configuración que he usado para mi clúster es la siguiente:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30070
hostPort: 30070
- containerPort: 30080
hostPort: 30080
- containerPort: 30090
hostPort: 30090
- role: worker
- role: worker
Esto es así porque quiero acceder a tres servicios desde mi máquina local, como veremos después.
Para crear un clúster haciendo uso de esta configuración sólo debo ejecutar lo siguiente:
# Create k8s cluster for dapr
kind create cluster --name dapr-k8s --config kind-cluster-config.yaml
Instalar Dapr en un clúster de Kubernetes
Ahora que ya tenemos un entorno donde jugar, lo siguiente que necesito es instalar Dapr en él. Hay dos formas de hacerlo: usando el CLI de Dapr o a través de su chart de Helm, para configuraciones avanzadas. En este artículo vamos a hacerlo con la primera opción:
# Install dapr
dapr init --kubernetes --wait
# Verify installation
dapr status -k
El primer comando despliega todos los servicios necesarios de Dapr. He utilizado la opción –wait para que no nos devuelva el control del terminal hasta que todos los elementos estén listos para continuar. Una vez finalice, con dapr status veremos lo que se ha desplegado para el funcionamiento de Dapr en este entorno:
Fácil ¿verdad? Ahora solo nos queda alguna aplicación con la que trastear.
Desplegar aplicación de ejemplo con Dapr en Kubernetes
Como te decía al inicio de este artículo, he seguido jugando con mi Tour of heroes, en este caso para poder desplegarlo ahora en Kubernetes , en su versión con Dapr. Para ello he hecho que tanto tour-of-heroes-api como tour-of-villains-api generen una imagen de Docker que queda almacenada en sus respectivos repos, gracias a GitHub Packages.
Por otro lado, en el repo de tour of heroes, he creado un nuevo directorio con todos los manifiestos a desplegar:
Como ves, hay diferentes recursos a tener en cuenta. Vamos a ver los más relevantes (el resto los tienes en mi repo en GitHub):
backend
En este directorio nos encontramos las dos APIs que he ido integrando con Dapr, a través de los diferentes bloques, en los artículos anteriores. Esta es la pinta que tiene tour-of-heroes-api:
apiVersion: v1
kind: Service
metadata:
labels:
app: tour-of-heroes-api
name: tour-of-heroes-api
spec:
type: NodePort
ports:
- name: web
port: 5222
targetPort: 5222
nodePort: 30080
selector:
app: tour-of-heroes-api
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: tour-of-heroes-api
name: tour-of-heroes-api
spec:
replicas: 1
revisionHistoryLimit: 1
selector:
matchLabels:
app: tour-of-heroes-api
template:
metadata:
labels:
app: tour-of-heroes-api
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "tour-of-heroes-api"
dapr.io/app-port: "5222"
dapr.io/enable-api-logging: "true"
dapr.io/config: "tracing"
spec:
containers:
- image: ghcr.io/0gis0/tour-of-heroes-dotnet-api/tour-of-heroes-api-dapr:2086efd
name: tour-of-heroes-api
ports:
- containerPort: 5222
name: web
En el archivo backend/tour-of-heroes-api.yaml lo que tenemos es un servicio de tipo NodePort, que hace referencia al Deployment del mismo archivo, donde desplegamos la imagen de la API y hacemos uso de anotaciones para inyectar el sidecar de dapr, así como añadir ciertas configuraciones. Puedes encontrar el listado de todas las anotaciones en este enlace.
En el caso de tour-of-villains-api es igual, solo que en esta cambia el puerto de escucha de la aplicación y que esta no estaba integrada con ningún secret store, por lo que le pasamos el secreto de Kubernetes, de la misma carpeta, como variable de entorno:
apiVersion: v1
kind: Service
metadata:
labels:
app: tour-of-villains-api
name: tour-of-villains-api
spec:
type: NodePort
ports:
- name: web
port: 80
targetPort: 5111
nodePort: 30090
selector:
app: tour-of-villains-api
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: tour-of-villains-api
name: tour-of-villains-api
spec:
replicas: 3
revisionHistoryLimit: 1
selector:
matchLabels:
app: tour-of-villains-api
template:
metadata:
labels:
app: tour-of-villains-api
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "tour-of-villains-api"
dapr.io/app-port: "5111"
dapr.io/enable-api-logging: "true"
dapr.io/config: "tracing"
spec:
containers:
- env:
- name: ConnectionStrings__DefaultConnection
valueFrom:
secretKeyRef:
key: password
name: sql-connection-string
image: ghcr.io/0gis0/tour-of-villains-api/tour-of-villains-api-dapr:fce0c6d
name: tour-of-villains-api
ports:
- containerPort: 5111
name: web
Nota: Los puertos 30080 y 30090, para los respectivos servicios, son los que configuré para poder acceder desde mi local al clúster de kind.
dapr-components
A día de hoy estas dos APIs hacen uso de un state store, secret store, se comunican entre ellas a través del patrón publicador suscriptor, además de que tour of heroes pida villanos a la otra. Todo ello a través de Dapr. Para que esto sea posible los componentes asociados deben estar configurados, por lo que en esta carpeta se recogen todas las configuraciones necesarias para este entorno. Como la principal magia de Dapr es cambiar de configuración/servicio sin tocar el código, para este ejemplo he cambiado el secret store de los dos que te mostré a la integración con los secretos de Kubernetes:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: heroessecretstore
spec:
type: secretstores.kubernetes
version: v1
metadata: []
# apiVersion: dapr.io/v1alpha1
# kind: Component
# metadata:
# name: heroessecretstore
# spec:
# type: secretstores.local.file
# version: v1
# metadata:
# - name: secretsFile
# value: secrets.json #path to secrets file
# - name: nestedSeparator
# value: ":"
# apiVersion: dapr.io/v1alpha1
# kind: Component
# metadata:
# name: heroessecretstore
# spec:
# type: secretstores.azure.keyvault
# version: v1
# metadata:
# - name: vaultName
# value: "azkeyvaultdaprexp"
# - name: azureTenantId
# value: "<YOUR_TENANT_ID>"
# - name: azureClientId
# value: "<YOUR_CLIENT_ID>"
# - name: azureClientSecret
# value : "<YOUR_CLIENT_SECRET>"
En este caso cuando vaya a buscar el secreto con nombre sql-connection-string en lugar de ir al archivo secrets.json o Azure Key Vault lo buscará en los secretos de Kubernetes y se corresponderá con el anterior mencionado. El resto de componentes mantienen la misma configuración que te mostré para Docker Compose, ya que he mantenido el mismo nombre para el despliegue de Redis en Kubernetes.
dapr-config
Después de lo que te mostré sobre trazas en Dapr también he querido traérmelo a este entorno. El archivo de configuración es el mismo:
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: tracing
spec:
tracing:
samplingRate: "1"
zipkin:
endpointAddress: "http://zipkin:9411/api/v2/spans"
Solo que aquí hace referencia al servicio de Zipkin también desplegado.
db
Este directorio está dedicado a todo lo relativo a la base de datos en la que se apoyan las APIs, pero ninguno de estos objetos están vinculados con Dapr directamente.
services
Por último, en esta carpeta están los dos servicios ya comentados: Redis para la state store y para el topic en el patrón publicador suscriptor y Zipkin para recibir las trazas de los servicios.
¿Cómo pruebo el entorno?
Ahora que ya he dado un repaso a lo más importante, lo único que queda es mostrarte cómo probarlo. Lo primero que necesitas hacer es desplegar todos los manifiestos:
# Deploy demo
kubectl apply -f dapr-k8s-manifests --recursive
# Wait until pods are ready
brew install watch
watch kubectl get pods
# Check services
kubectl get svc
Una vez que lo tengas todo listo puedes ejecutar los siguientes comandos con cURL y comprobar que todo funciona correctamente:
# Variables for cURL commands
HERO_API_URL=http://localhost:30080/api/hero
VILLAIN_API_URL=http://localhost:30090/villain
# Check if hero api is working
curl $HERO_API_URL | jq
curl --header "Content-Type: application/json" \
--request POST \
--data '{
"name": "Batman",
"description": "Un multimillonario magnate empresarial y filántropo dueño de Empresas Wayne en Gotham City. Después de presenciar el asesinato de sus padres, el Dr. Thomas Wayne y Martha Wayne en un violento y fallido asalto cuando era niño, juró venganza contra los criminales, un juramento moderado por el sentido de la justicia.",
"alterEgo": "Bruce Wayne"
}' \
$HERO_API_URL | jq
# Get heroes again
curl $HERO_API_URL | jq
# Check if villain api is working
curl --header "Content-Type: application/json" \
--request POST \
--data '{
"name": "Octopus",
"hero":{
"name": "Spiderman",
"description": "un joven huérfano neoyorquino que adquiere superpoderes después de ser mordido por una araña radiactiva, y cuya ideología como héroe se ve reflejada primordialmente en la expresión «un gran poder conlleva una gran responsabilidad».2021 Suele ser asociado con una personalidad bromista, amable, inventiva y optimista, lo que le ha llevado a ser catalogado como el «vecino amigable» de cualquiera lo cual, aunado a sus vivencias caracterizadas por los problemas cotidianos.",
"alterEgo": "Peter Parker"
},
"description": "Es un científico loco muy inteligente y algo fornido que tiene cuatro apéndices fuertes que se asemejan a los tentáculos de un pulpo, que se extienden desde la parte posterior de su cuerpo y pueden usarse para varios propósitos."
}' \
$VILLAIN_API_URL | jq
# Check if pub sub is working viewing logs
kubectl logs -l app=tour-of-heroes-api -c tour-of-heroes-api
# Check if service to service invocation is working
curl $HERO_API_URL/villain/spiderman | jq
Por último puedes acceder al dashboard de Dapr y ver la configuración de tus aplicaciones, componentes, etcétera:
# Forward a port to Dapr dashboard
dapr dashboard -k -p 9999
Así como el servidor de Zipkin:
# Access to zipkin to see traces
http://localhost:30070
y ver las trazas generadas:

El código del ejemplo lo tienes en mi GitHub.
¡Saludos!