Desplegar Open Service Mesh en AKS

Hace justamente un año escribí tres artículos relacionados con Open Service Mesh y cómo podías instalarlo en tu clúster de Kubernetes. Por aquel entonces estaba en la versión 1.0.0. Hoy tengo que volver a retomarlo, en su versión 1.2.3, y con la integración con AKS fuera de preview. En este artículo te cuento cómo usarlo y te comparto el ejemplo de entonces actualizado y funcionando.

Crear un clúster de AKS con el add-on de OSM

Lo bueno es que a día de hoy tienes un addon fuera de preview que te facilita la instalación de OSM en AKS. Es tan sencillo como lo siguiente:

# Variables
RESOURCE_GROUP="open-service-mesh-demo"
LOCATION="northeurope"
AKS_NAME="aks-osm-addon"
# Create resource group
az group create --name $RESOURCE_GROUP --location $LOCATION
# Create AKS cluster with Open Service Mesh enabled
az aks create \
--resource-group $RESOURCE_GROUP \
--name $AKS_NAME \
--node-vm-size Standard_B4ms \
--enable-addons open-service-mesh \
--generate-ssh-keys 
# Get credentials
az aks get-credentials \
--resource-group $RESOURCE_GROUP \
--name $AKS_NAME

Simplemente añadiendo el parámetro –enable-addons open-service-mesh como parte de la creación de tu clúster se instala la versión compatible con la versión de Kubernetes que hayas elegido. En este caso, al no indicar la versión de Kubernetes que queremos se instalará la última versión disponible y por lo tanto la última versión de Open Service Mesh, que es la 1.2.3.

Si quieres puedes lanzar las siguiente comprobaciones para validar que se ha instalado correctamente el add-on:

# Verify that the OSM add-on is installed on your cluster
az aks show --resource-group $RESOURCE_GROUP --name $AKS_NAME  --query 'addonProfiles.openServiceMesh.enabled'
# You can also verify the version, status, and configuration of the OSM mesh that's running on your cluster.
kubectl get deployment -n kube-system osm-controller -o=jsonpath='{$.spec.template.spec.containers[:1].image}'
# Verify the status od the OSM components running on your cluster
kubectl get deployments -n kube-system --selector app.kubernetes.io/name=openservicemesh.io
kubectl get pods -n kube-system --selector app.kubernetes.io/name=openservicemesh.io
kubectl get services -n kube-system --selector app.kubernetes.io/name=openservicemesh.io
# Verify the configuration of your OSM mesh, use kubectl get meshconfig
kubectl get meshconfig osm-mesh-config -n kube-system -o yaml

Desplegar una aplicación de ejemplo

Ahora que ya tienes un clúster con Open Service Mesh con el que jugar lo siguiente que necesitas es una aplicación. Voy a utilizar la misma que hace un año, con sus paquetes actualizados y desplegadas las imágenes en Github Packages.

# Deploy demo application in AKS #
##################################
# Deploy manifests in AKS/K8s cluster
kubectl apply -f manifests/.
# Check if the pods are ready
kubectl get pods -n bookstore
kubectl get pods -n bookbuyer
kubectl get pods -n bookthief
# Check if the services are ready
kubectl get services -n bookstore
kubectl get services -n bookbuyer
kubectl get services -n bookthief

Estas las dejé con servicios del tipo Load Balancer para que comprobaras que antes de que OSM controle los namespaces donde están alojadas, y se inyecte el sidecar, puedes acceder a ellas sin problemas desde Internet.

Instalar OSM CLI

Para poder inyectar las anotaciones necesarias para OSM en los namespaces, además de otras funciones, puedes instalar el CLI de la siguiente manera:

# Install osm cli (1.2.3)
# Specify the OSM version that will be leveraged throughout these instructions
OSM_VERSION=v1.2.3
# macOS curl command only
curl -sL "https://github.com/openservicemesh/osm/releases/download/$OSM_VERSION/osm-$OSM_VERSION-darwin-amd64.tar.gz" | tar -vxzf -
# arm
curl -sL "https://github.com/openservicemesh/osm/releases/download/$OSM_VERSION/osm-$OSM_VERSION-darwin-arm64.tar.gz" | tar -vxzf -

# move the osm binary to your PATH
sudo mv ./darwin-amd64/osm /usr/local/bin/osm
# move the osm binary to your PATH - arm
sudo mv ./darwin-arm64/osm /usr/local/bin/osm
osm version

En este artículo te muestro cómo instalarlo para Mac, tanto con procesador Intel como ARM. En este enlace tienes más información para el resto de sistemas operativos.

Añadir los namespaces a OSM

Ahora que ya tienes la aplicación desplegada y funcionando, el siguiente paso es agregar los namespaces de esta bajo el control de OSM. Para ello puedes utilizar bien el CLI que te acabas de descargar:

# Now we enable osm for their namespaces
osm namespace add bookstore bookbuyer bookthief

O bien, a través del portal de Azure, si accedes a tu clúster de AKS verás un apartado llamado Open Service Mesh donde podrás ver los namespaces que acabas de agregar y/o añadir otros a través del botón Add+.

AKS – Integración con Open Service Mesh – Lista de namespaces

Si ahora reinicias los despliegues asociados a los componentes en estos namespaces:

# deployment restart
kubectl rollout restart deployment/bookthief -n bookthief
kubectl rollout restart deployment/bookbuyer -n bookbuyer
kubectl rollout restart deployment/bookstore -n bookstore

Comprobarás que has perdido el acceso a los mismos, a través de su IP pública, ya que a partir de ahora todo acceso está denegado por defecto.

Instalar Ingress Nginx Controller

Como comenté en el primer artículo de esta serie, lo ideal es tener un Ingress controller como punto de acceso de estos componentes y que sobre este se permita el acceso a través del recurso IngressBackend. Para instalar Nginx para este propósito puedes hacerlo de forma sencilla utilizando Helm:

# Configure nginx ingress controller
kubectl create ns ingress-nginx
# osm needs to monitor this namespace but not inject the sidecar
osm namespace add ingress-nginx --disable-sidecar-injection
# add ingress nginx helm repo
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install nginx-ingress ingress-nginx/ingress-nginx \
 --namespace ingress-nginx \
 --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz

Como ves, es necesario que OSM conozca de la existencia del namespace donde está el Ingress Controller pero, a través del parámetro –disable-sidecar-injection, no necesitamos inyectarle el envoy correspondiente.

Una vez desplegado necesitamos recuperar algunos valores que usaremos después:

# Get nginx ingress controller public IP
INGRESS_PUBLIC_IP=$(kubectl get svc -n ingress-nginx nginx-ingress-ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
NGINX_INGRESS_NS=ingress-nginx # replace <nginx-namespace> with the namespace where Nginx is installed
NGINX_INGRESS_SVC=nginx-ingress-ingress-nginx-controller  # replace <nginx-ingress-controller-service> with the name of the nginx ingress controller service

Crear los Ingress e IngressBackend para los componentes

Para volver a tener acceso a los componentes, con el permiso de OSM, necesitamos crear por cada uno de ellos un Ingress y un IngressBackend:

#### Bookstore ingress and ingress backend ####
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: bookstore-ingress
  namespace: bookstore
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: bookstore.$INGRESS_PUBLIC_IP.nip.io
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: bookstore
                port:
                  number: 80
---
kind: IngressBackend
apiVersion: policy.openservicemesh.io/v1alpha1
metadata:
  name: bookstore-external-access
  namespace: bookstore
spec:
  backends:
    - name: bookstore
      port:
        number: 3000 # targetPort of the service
        protocol: http
  sources:
    - kind: Service
      name: $NGINX_INGRESS_SVC
      namespace: $NGINX_INGRESS_NS
EOF

#### Bookbuyer ingress and ingress backend ####
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: osm-ingress
  namespace: bookbuyer
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: bookbuyer.$INGRESS_PUBLIC_IP.nip.io
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: bookbuyer
                port:
                  number: 80
---
kind: IngressBackend
apiVersion: policy.openservicemesh.io/v1alpha1
metadata:
  name: bookbuyer-external-access
  namespace: bookbuyer
spec:
  backends:
    - name: bookbuyer
      port:
        number: 4000 # targetPort of the service
        protocol: http
  sources:
    - kind: Service
      name: $NGINX_INGRESS_SVC
      namespace: $NGINX_INGRESS_NS
EOF

#### Bookthief ingress and ingress backend ####
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: bookthief-ingress
  namespace: bookthief
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    kubernetes.io/ingress.class: nginx
spec:
  rules:
    - host: bookthief.$INGRESS_PUBLIC_IP.nip.io
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: bookthief
                port:
                  number: 80
---
kind: IngressBackend
apiVersion: policy.openservicemesh.io/v1alpha1
metadata:
  name: bookthief-external-access
  namespace: bookthief
spec:
  backends:
    - name: bookthief
      port:
        number: 4001 # targetPort of the service
        protocol: http
  sources:
    - kind: Service
      name: $NGINX_INGRESS_SVC
      namespace: $NGINX_INGRESS_NS
EOF

Una vez creados puedes acceder a cada uno de ellos con el resultado de esta consulta:

# Now you can access using this URLs
cat <<EOF
http://bookstore.$INGRESS_PUBLIC_IP.nip.io
http://bookbuyer.$INGRESS_PUBLIC_IP.nip.io
http://bookthief.$INGRESS_PUBLIC_IP.nip.io
EOF

También puedes utilizar osm verify para comprobar que los IngressBackend están configurados correctamente:

# Verify that the ingress backends are configured correctly
osm verify ingress --from-service $NGINX_INGRESS_NS/$NGINX_INGRESS_SVC \
--to-pod bookthief/$(kubectl get pods -n bookthief -l app=bookthief -o jsonpath='{.items[0].metadata.name}') \
--to-service bookthief \
--ingress-backend bookthief-external-access --to-port 4001 \
--osm-namespace kube-system
osm verify ingress --from-service $NGINX_INGRESS_NS/$NGINX_INGRESS_SVC \
--to-pod bookstore/$(kubectl get pods -n bookstore -l app=bookstore -o jsonpath='{.items[0].metadata.name}') \
--to-service bookstore \
--ingress-backend bookstore-external-access --to-port 3000 \
--osm-namespace kube-system
osm verify ingress --from-service $NGINX_INGRESS_NS/$NGINX_INGRESS_SVC \
--to-pod bookbuyer/$(kubectl get pods -n bookbuyer -l app=bookbuyer -o jsonpath='{.items[0].metadata.name}') \
--to-service bookbuyer \
--ingress-backend bookbuyer-external-access --to-port 4000 \
--osm-namespace kube-system

Por último, completando lo que conté en el artículo original, podemos limitar el acceso entre los componentes. En este punto todos pueden hablar con todos porque está habilitado el modo permisivo en Open Service Mesh. Puedes comprobarlo con este comando:

# Check permissive mode
kubectl get meshconfig osm-mesh-config -n kube-system -o jsonpath='{.spec.traffic.enablePermissiveTrafficPolicyMode}{"\n"}'

Para deshabilitarlo, puedes lanzar el siguiente patch sobre la configuración del mesh:

# Change to permissive mode to false
kubectl patch meshconfig osm-mesh-config -n kube-system -p '{"spec":{"traffic":{"enablePermissiveTrafficPolicyMode":false}}}'  --type=merge

Si compruebas los diferentes componentes de nuevo verás que la comunicación no es posible de bookbuyer y bookthief a bookstore.

Para configurar la misma necesitamos definir un HTTPRouteGroup:

apiVersion: specs.smi-spec.io/v1alpha4
kind: HTTPRouteGroup
metadata:
  name: bookstore-service-routes
  namespace: bookstore
spec:
  matches:  
  - name: buy-books
    pathRegex: "/api/buy"    
    methods:
    - GET
  - name: steal-books
    pathRegex: "/api/steal"
    methods:
    - GET

Y por último un TrafficTarget

kind: TrafficTarget
apiVersion: access.smi-spec.io/v1alpha3
metadata:
  name: bookstore
  namespace: bookstore
spec:
  destination:
    kind: ServiceAccount
    name: bookstore
    namespace: bookstore
  sources:
  - kind: ServiceAccount
    name: bookbuyer
    namespace: bookbuyer
  - kind: ServiceAccount
    name: bookthief
    namespace: bookthief
  rules:
  - kind: HTTPRouteGroup
    name: bookstore-service-routes
    matches:
    - buy-books
    # - steal-books

Lo que dejaría la foto de la siguiente manera:

Restringir y permitir el tráfico con TrafficTarget

Estos recursos se pueden ver también a través del portal de Azure, si accedes al namespace de bookstore:

Traffic Targets en la integración de Open Service Mesh en AKS

¡Saludos!