Cómo crear un experimento para AKS en Azure Chaos Studio desde Azure CLI

El pasado 15 de Noviembre se anunció que Azure Chaos Studio por fin está en GA 🥳 Si no has tenido todavía oportunidad de echarle un vistazo, se trata de un servicio que nos permite crear experimentos del caos dentro de nuestra infraestructura tanto en máquinas virtuales como en AKS. Pero no solo eso sino que además, lo que lo diferencia de otros proyectos/productos del mercado, es que al estar en Azure nos permite simular caídas de otros servicios PaaS que son propios de la nube de Microsoft, como por ejemplo: Azure Active Directory, Azure Key Vault, CosmosDB, Redis, Network Security Groups, etcétera. En este artículo quiero compartir contigo los pasos que he seguido para ejecutar un experimento en un cluster de AKS usando Azure CLI, ya que la documentación oficial está un poquito desactualizada.

Clúster para las pruebas

Antes de poder ejecutar un experimento necesitas tener un clúster donde poder realizar las pruebas. Si no tienes una aplicación de ejemplo con la que jugar puedes utilizar los manifiestos de mi Tour of heroes. Con este script puedes crear el clúster y acondicionar el mismo para tener esta aplicación lista para que falle 😙

LOCATION=westeurope
AKS_NAME="azchaos-k8s"
RESOURCE_GROUP="az-chaos-studio-demo"
echo "Creating AKS cluster $AKS_NAME ⎈ in resource group $RESOURCE_GROUP 📦 in location $LOCATION 🌍"

az group create --name $RESOURCE_GROUP --location $LOCATION
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
az aks create \
--resource-group $RESOURCE_GROUP \
--name $AKS_NAME \
--enable-azure-monitor-metrics \
--generate-ssh-keys
# Get AKS credentials
az aks get-credentials --resource-group $RESOURCE_GROUP --name $AKS_NAME --overwrite-existing

echo "Deploying tour of heroes"
kubectl create ns tour-of-heroes
echo "Deploying tour of heroes"
kubectl apply -f k8s/manifests --recursive -n tour-of-heroes
echo "Wait for pods to be ready"
kubectl wait --for=condition=Ready pods --all -n tour-of-heroes --timeout=600s
echo "Wait for svc to have external IP"
for svc in $(kubectl get svc -n tour-of-heroes -o jsonpath='{range .items[*]}{@.metadata.name}{":"}{@.spec.type}{"\n"}{end}' | grep LoadBalancer | cut -d: -f1); do
  while true; do
    ip=$(kubectl get svc $svc -n tour-of-heroes -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    if [[ -z "$ip" ]]; then
      echo "Waiting for external IP for $svc..."
      sleep 10
    else
      echo "External IP for $svc is $ip"
      break
    fi
  done
done

echo "Ready to go!"
API_URL="http://$(kubectl get svc tour-of-heroes-api -n tour-of-heroes -o jsonpath='{.status.loadBalancer.ingress[0].ip}')/api/hero"
# Change environment variable of tour-of-heroes-web deployment to use tour-of-heroes-api service
kubectl set env deployment/tour-of-heroes-web -n tour-of-heroes API_URL="http://$(kubectl get svc tour-of-heroes-api -n tour-of-heroes -o jsonpath='{.status.loadBalancer.ingress[0].ip}')/api/hero"
kubectl describe deployment tour-of-heroes-web -n tour-of-heroes | grep API_URL
# Load some heroes
curl --request GET \
  --url $API_URL
curl --request POST \
  --url $API_URL \
  --header 'content-type: application/json' \
  --data '{"name": "Arrow","alterEgo": "Oliver Queen","description": "Multimillonario playboy Oliver Queen (Stephen Amell), quien, cinco años después de estar varado en una isla hostil, regresa a casa para luchar contra el crimen y la corrupción como un vigilante secreto cuya arma de elección es un arco y flechas."}'
curl --request POST \
  --url $API_URL \
  --header 'content-type: application/json' \
  --header 'user-agent: vscode-restclient' \
  --data '{"name": "Batman","alterEgo": "Bruce Wayne","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."}'
curl --request POST \
  --url $API_URL \
  --header 'content-type: application/json' \
  --header 'user-agent: vscode-restclient' \
  --data '{"name": "Captain America","alterEgo": "Steve Rogers","description": "Un joven frágil mejorado a la cima de la perfección humana por un suero experimental S.S.S. (Suero supersoldado) para ayudar a los esfuerzos inminentes del gobierno de Estados Unidos en la Segunda Guerra Mundial. Cerca del final de la guerra, queda atrapado en el hielo y sobrevive en animación suspendida hasta que es descongelado en el presente."}'
echo "Access Tour of heroes web: http://$(kubectl get svc tour-of-heroes-web -n tour-of-heroes -o jsonpath='{.status.loadBalancer.ingress[0].ip}')"
echo "Access Tour of heroes API: http://$(kubectl get svc tour-of-heroes-api -n tour-of-heroes -o jsonpath='{.status.loadBalancer.ingress[0].ip}')/api/hero"

Desplegar Chaos Mesh

Para poder lanzar experimentos desde Azure Chaos Studio a AKS necesitas que este tenga desplegado Chaos Mesh, el cual es un proyecto de la CNCF en el cual se apoya este servicio para poder ejecutar los mismos. En este punto es importante que tengas en cuenta que el namespace donde debe estar desplegado este debe ser chaos-testing, porque de lo contrario no te funcionará.

echo "Install Chaos Mesh using Helm"
helm repo add chaos-mesh https://charts.chaos-mesh.org
helm search repo chaos-mesh
echo "Create the namespace to install Chaos Mesh"
kubectl create ns chaos-testing
echo "Install Chaos Mesh 𐄳"
helm install chaos-mesh chaos-mesh/chaos-mesh \
-n=chaos-testing \
--set chaosDaemon.runtime=containerd \
--set chaosDaemon.socketPath=/run/containerd/containerd.sock \
--set dashboard.securityMode=false
kubectl wait --for=condition=Ready pods --all -n chaos-testing --timeout=600s

Con estos dos pasos ya tienes un entorno sobre el que poder lanzar tus experimentos.

Configurar tu clúster de AKS como target

Como método de seguridad, frente a otros servicios que puedan formar parte de tu suscripción, cada vez que quieres ejecutar este tipo de experimentos debes incluir el recurso en cuestión, en este caso un clúster de AKS, como target de Azure Chaos Studio. Desde el portal es bastante sencillo llevarlo a cabo. Para poder automatizar esta operación desde un script podría utilizar el siguiente:

# Enable this cluster as a target for Chaos Studio
AKS_RESOURCE_ID=$(az aks show --resource-group $RESOURCE_GROUP --name $AKS_NAME --query id --output tsv)
SUBSCRIPTION_ID=$(az account show --query id --output tsv)
API_VERSION="2023-11-01"
az rest --method put \
--url "https://management.azure.com/$AKS_RESOURCE_ID/providers/Microsoft.Chaos/targets/Microsoft-AzureKubernetesServiceChaosMesh?api-version=$API_VERSION" \
--body "{\"properties\":{}}"

Por otro lado, cuando un servicio es un objetivo de estos experimentos puedes especificar que sea para todos los tipos de experimentos posibles o solo para alguno de ellos. En este ejemplo solo voy a permitir que se lancen experimentos del tipo Pod Chaos:

# Enable PodChaos for this target
CAPABILITY="PodChaos-2.1"
az rest --method put \
--url "https://management.azure.com/$AKS_RESOURCE_ID/providers/Microsoft.Chaos/targets/Microsoft-AzureKubernetesServiceChaosMesh/capabilities/$CAPABILITY?api-version=$API_VERSION"  \
--body "{\"properties\":{}}"

Con estas dos indicaciones el clúster permitirá la configuración de experimentos de este tipo.

Crear un experimento

Ahora ya si, con todo configurado, lo único que nos queda es crear un experimento que haga que algo se rompa en este clúster 😙. En mi ejemplo lo que voy a hacer es que los pods que tiene la label app:tour-of-heroes-sql (solo tengo uno, lo cual es malísima práctica y buenísimo ejemplo para este artículo) en el namespace tour-of-heroes se rompa durante los próximos 7 minutos. Para ello, es necesario construir un JSON como el siguiente:

EXPERIMENT_NAME=dbdies-from-cli
cat <<EOF > experiment.json
{
  "location": "$LOCATION",
  "identity": {
    "type": "SystemAssigned"
  },
  "properties": {
    "steps": [
      {
        "name": "AKS pod kill",
        "branches": [
          {
            "name": "AKS pod kill",
            "actions": [
              {
                "type": "continuous",
                "selectorId": "Selector1",
                "duration": "PT7M",
                "parameters": [
                  {
                      "key": "jsonSpec",
                      "value": "{'selector':{'namespaces':['tour-of-heroes'],'labelSelectors':{'app':'tour-of-heroes-sql'}},'mode':'all','action':'pod-failure'}"
                  }
                ],
                "name": "urn:csci:microsoft:azureKubernetesServiceChaosMesh:podChaos/2.1"
              }
            ]
          }
        ]
      }
    ],
    "selectors": [
      {
        "id": "Selector1",
        "type": "List",
        "targets": [
          {
            "type": "ChaosTarget",
            "id": "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerService/managedClusters/$AKS_NAME/providers/Microsoft.Chaos/targets/Microsoft-AzureKubernetesServiceChaosMesh"
          }
        ]
      }
    ]
  }
}
EOF

Como datos importantes, además de la localización y el target, el JSON que aparece como parte de los parámetros proviene de la configuración propia de Chaos Mesh, el cual ha sido convertido a JSON para que encaje en esta configuración de Azure Chaos Studio tal y como se indica en esta parte de la configuración, la cual he adaptado un poco para mi ejemplo 🙃

Una vez que ya tengas este archivo con el reemplazo de las variables, puedes hacer la llamada a la API REST de la siguiente manera:

az rest \
--method put \
--uri "https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Chaos/experiments/$EXPERIMENT_NAME?api-version=$API_VERSION" --body @experiment.json

Sin embargo, esto no es todo, ya que si bien esta llamada crea el experimento la misma no lo ejecuta. Además antes de poder lanzarlo, como otra medida de seguridad (estas pruebas son peligrosas, claro está), necesitas dar permisos sobre el principal Id de la identidad generada para este experimento en concreto para que pueda ser ejecutado dentro del clúster:

# Get the principal ID por this experiment
EXPERIMENT_PRINCIPAL_ID=$(az resource show \
--resource-group $RESOURCE_GROUP \
--name $EXPERIMENT_NAME \
--resource-type Microsoft.Chaos/experiments \
--query identity.principalId \
--output tsv)
# Give the experiment permission to your AKS cluster
az role assignment create \
--role "Azure Kubernetes Service Cluster Admin Role" \
--assignee-object-id $EXPERIMENT_PRINCIPAL_ID \
--scope $AKS_RESOURCE_ID

Y ahora ya si, «darle a ejecutar» con esta última llamada:

# Run the experiment
az rest \
--method post \
--uri "https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Chaos/experiments/$EXPERIMENT_NAME/start?api-version=$API_VERSION"

Para comprobar que se está ejecutando correctamente, puedes echar un vistazo al propio experimento en el portal de Azure:

Y también si quieres puedes acceder al propio dashboard de Chaos Mesh:

# Port forward to access the dashboard in background
kubectl port-forward svc/chaos-dashboard -n chaos-testing 2333:2333

Y ver que efectivamente se está ejecutando dentro de este clúster.

Por último, si quieres ver cómo tu pod muere debido a este experimento puedes utilizar este otro comando:

watch kubectl get pods -n tour-of-heroes

¡Saludos!