Saber el coste de tus aplicaciones en Kubernetes con Kubecost en Microsoft Azure

Es habitual encontrarse un clúster de Kubernetes donde hospedas más de una aplicación con objetivos a veces diferentes y, obviamente, con un consumo diferente de los recursos. Es por ello que es frecuente encontrarnos con la pregunta «¿Cuánto cuesta cada aplicación que tengo alojada en mi clúster?». Para ello existen herramientas como Kubecost que te permiten calcular este coste de manera automática para ti. En este artículo te cuento cómo configurarlo para recursos en Microsoft Azure.

Clúster de prueba

Si todavía no tienes, lo primero que necesitas es un clúster de prueba.

#Variables
RESOURCE_GROUP="kubecost-demo"
LOCATION="westeurope"
AKS_NAME="kubecost-aks"
# Create Resource Group
az group create --name $RESOURCE_GROUP --location $LOCATION
# Create AKS cluster
az aks create \
--resource-group $RESOURCE_GROUP \
--name $AKS_NAME \
--node-count 3 \
--node-vm-size Standard_B4ms \
--generate-ssh-keys 
# Get AKS credentials
az aks get-credentials --resource-group $RESOURCE_GROUP --name $AKS_NAME

Exportar informes de Cost Management

Para tener un detalle más fino de lo que están realmente consumiendo tus proyectos, puedes exportar informes diarios de Cost Management. Con esto consigues no solo medir cuánto se está gastando de tu clúster sino que puedes añadirle otros recursos de la plataforma de Azure que también forman parte de la foto:

# Get subscription Id
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
# Register Microsoft.CostManagementExports 
az provider register --namespace Microsoft.CostManagementExports --wait
# Create a storage account for the cost reports
AZURE_STORAGE_NAME="kubecostreportstorage"
az storage account create \
--resource-group $RESOURCE_GROUP \
--name $AZURE_STORAGE_NAME
# Create container
CONTAINER_NAME="costmanagement"
az storage container create \
--name $CONTAINER_NAME \
--account-name $AZURE_STORAGE_NAME
# Get storage account id
AZURE_STORAGE_ACCOUNT_ID=$(az storage account show \
--resource-group $RESOURCE_GROUP \
--name $AZURE_STORAGE_NAME \
--query id -o tsv)
# Get storage access key
AZURE_STORAGE_ACCESS_KEY=$(az storage account keys list --resource-group $RESOURCE_GROUP --account-name $AZURE_STORAGE_NAME --query '[0].value' -o tsv)
# Export a report
# It will take a few hours to generate the first report, after which Kubecost can use the Azure Storage API to pull that data.
CONTAINER_PATH="reports"
az costmanagement export create \
--name CostManagementExport \
--type Usage \
--scope "subscriptions/$SUBSCRIPTION_ID" \
--storage-account-id $AZURE_STORAGE_ACCOUNT_ID \
--storage-container $CONTAINER_NAME \
--timeframe MonthToDate \
--recurrence Daily \
--recurrence-period from="2022-05-20T00:00:00Z" to="2022-12-31T00:00:00Z" \
--schedule-status Active \
--storage-directory $CONTAINER_PATH
# See your current exports
az costmanagement export list --scope "subscriptions/$SUBSCRIPTION_ID" -o table

Una vez configurada la exportación esta se hará de manera diaria. Es posible que tarde unas horas en tener el primer reporte disponible, para que Kubecost pueda alimentarse de él.

Dar acceso a la API Rate Card

Otro de los accesos que debemos configurar, para tener toda la información posible, es la posibilidad de hacer llamas a la API Rate Card de Azure, de la que también se aprovecha Kubecost. Para ello necesitas crear un clúster role con los siguientes permisos y crear un service principal al que asignárselo:

# Make sure you have the following registrations in your subscription
az provider register --namespace Microsoft.Compute --wait
az provider register --namespace Microsoft.Resources --wait
az provider register --namespace Microsoft.ContainerService --wait
az provider register --namespace Microsoft.Commerce --wait
# Define a role definition
cat <<EOF > ./ratecard-role-def.json
{
    "Name": "ApiRateCardRole",
    "IsCustom": true,
    "Description": "Rate Card query role",
    "Actions": [
        "Microsoft.Compute/virtualMachines/vmSizes/read",
        "Microsoft.Resources/subscriptions/locations/read",
        "Microsoft.Resources/providers/read",
        "Microsoft.ContainerService/containerServices/read",
        "Microsoft.Commerce/RateCard/read"
    ],
    "AssignableScopes": [
        "/subscriptions/$SUBSCRIPTION_ID"
    ]
}
EOF
# Create role
az role definition create --role-definition @ratecard-role-def.json --verbose
# Get custom roles in the tenant
az role definition list \
--query "[?roleType=='CustomRole'].{roleName:roleName,roleId:name,roleType:roleType}" -o table
# az login with an admin
az login
# Create a service principal
az ad sp create-for-rbac --skip-assignment --name "kubecost" > auth.json
# Get the client id
CLIENT_ID=$(jq -r '.appId' auth.json)
PASSWORD=$(jq -r '.password' auth.json)
TENANT_ID=$(jq -r '.tenant' auth.json)
# Assign API Rate Card role to the service principal
az role assignment create \
--role "ApiRateCardRole" \
--assignee $CLIENT_ID --scope /subscriptions/$SUBSCRIPTION_ID

Para comprobar que tu nuevo service principal funciona correctamente, puedes logarte con él e intentar hacer la siguiente petición a la API:

# Login using service principal
az login --service-principal -u $CLIENT_ID -p $PASSWORD --tenant $TENANT_ID --allow-no-subscriptions
# Test the API call works with this service principal  (It can take a while...)
#https://azure.microsoft.com/es-es/support/legal/offer-details/
OFFER_ID="MS-AZR-0003p" # Pay as you go
az rest --method get \
--uri "https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/providers/Microsoft.Commerce/RateCard?api-version=2016-08-31-preview&%24filter=OfferDurableId+eq+'$OFFER_ID'+and+Currency+eq+'USD'+and+Locale+eq+'en-US'+and+RegionInfo+eq+'US'" \
--verbose

Nota: esta llamada puede tardar más de un minuto en completarse. También debes fijarte que estás recuperando los datos correctos para el tipo de oferta que tienes en Azure. Puedes saber el identificador de tu suscripción de manera sencilla yendo al portal de Azure > Subscriptions y seleccionar la suscripción en cuestión. En el apartado Overview tienes un apartado llamado Offer ID.

Offer ID de una suscripción de Azure

Configurar Kubecost en tu clúster

Ahora que ya tienes todo lo que necesitas, ha llegado el momento de configurar Kubecost. Lo primero que necesitas es crear un namespace donde almacenaremos todo lo relacionado con este:

# Create a namespace for kubecost
kubectl create namespace kubecost

y después generaremos un archivo con los parámetros necesarios para que pueda conectarse a la cuenta de almacenamiento donde estarán los CSV de Cost Management.

# Create a json file with azure storage configuration
cat <<EOF > ./azure-storage-config.json
{
  "azureSubscriptionID": "$SUBSCRIPTION_ID",
  "azureStorageAccount": "$AZURE_STORAGE_NAME",
  "azureStorageAccessKey": "$AZURE_STORAGE_ACCESS_KEY",
  "azureStorageContainer": "$CONTAINER_NAME",
  "azureContainerPath": "$CONTAINER_PATH",
  "azureCloud": "public"
}
EOF
# Create a secret with this file
kubectl create secret generic azure-storage-creds --from-file=azure-storage-config.json -n kubecost

Como ves, con este archivo lo que hacemos es crear un secreto dentro del namespace kubecost.

Y ahora ya si instalamos Kubecost con todos los parámetros que hemos ido recopilando por el camino:

# Add kubecost repo
helm repo add kubecost https://kubecost.github.io/cost-analyzer/
# Install kubecost
# Values: https://github.com/kubecost/cost-analyzer-helm-chart/blob/develop/cost-analyzer/values.yaml
helm install kubecost kubecost/cost-analyzer --namespace kubecost \
--set kubecostToken="Z2lzZWxhLnRvcnJlc0ByZXR1cm5naXMubmV0xm343yadf98" \
--set kubecostProductConfigs.azureStorageSecretName="azure-storage-creds" \
--set kubecostProductConfigs.azureSubscriptionID=$SUBSCRIPTION_ID \
--set kubecostProductConfigs.azureClientID=$CLIENT_ID \
--set kubecostProductConfigs.azureClientPassword=$PASSWORD \
--set kubecostProductConfigs.azureTenantID=$TENANT_ID \
--set kubecostProductConfigs.createServiceKeySecret=true \
--set kubecostProductConfigs.azureOfferDurableID=$OFFER_ID 

Por último, comprueba que todos los pods del despliegue están ejecutándose correctamente:

# Check that all pods are running
kubectl get pods -n kubecost -w

Despliega en este clúster aquellas aplicaciones que consideres.

Ver los resultados a través de la interfaz

Después de ya tener tus aplicaciones hospedadas. Puedes revisar el análisis a través de su interfaz. Para acceder a ella puedes hacer un port forward al siguiente servicio:

# See the UI
kubectl port-forward --namespace kubecost deployment/kubecost-cost-analyzer 9090
# Next, navigate to http://localhost:9090 in a web browser.

Deberías ver algo parecido a lo siguiente:

Reparto de los costes en los diferentes namespaces con Kubecost

Por otro lado, puedes generar tags en otros recursos en Azure que estén asociados con las aplicaciones, pero que no formen parte del clúster como tal, de tal forma que se tenga en cuenta también el gasto que está generando. Aquí tienes más información de cómo hacerlo.

¡Saludos!