Introdução
Por padrão temos no Azure Devops a opção de escalar os agents por Azure Virtual machine scale sets vm-scale-set, porém, temos a opção de utilizar o KEDA para escalar os agents.
Já temos um artigo no blog da Microsoft sobre o KEDA, porém, mais a ideia desse artigo é mostrar como utilizar o KEDA para escalar os agents do Azure Devops no Kubernetes.
O código fonte desse artigo está disponível no github
Pré-requisitos
- Ter um cluster Kubernetes criando um cluster Kubernetes
- Ter um container registry criando um container registry
- Integrar o Kubernetes com o container registry integrando o Kubernetes com o container registry
- Habilitar o KEDA no Kubernetes habilitando o KEDA no Kubernetes
- Ter uma organização no Azure Devops criando uma organização no Azure Devops
- Configurar a organização do Azure Devops para permitir ao menos 5 jobs em paralelo
- Ter o docker instalado instalando o docker
- Ter o kubeclt instalado instalando o kubeclt
- Ter o Azure Cli instalado instalando o Azure Cli
Configurando o Azure Devops
Criando um agent pool
Após a criação da organização no Azure Devops, vamos criar um agent pool, para isso, vamos em Organization settings -> Agent pools -> Add pool.
No Pool type selecione Self-hosted e no name digite o nome do pool, no meu caso keda-pool.
No Pipeline permissions deixe marcado Grant access permissions to all pipelines e clique no botão Create.
Após a criação do agent pool, será necessário pegar qual é o id do mesmo.
Temos algumas maneiras que como pegar o id do agent pool, uma delas é acessando a url do agent pool, no meu caso https://dev.azure.com/xxxx-sua-organizationn/_apis/distributedtask/pools?poolname=keda-pool, essa chamada vai retornar um json com o id do agent pool.
Abra o json em qualquer ferramenta como o http://jsonviewer.stack.hu/ e procure o id, no meu caso o id é 18 como mostra a imagem abaixo.
Criando um PAT no Azure Devops
Vamos precisar de um token do Azure Devops para que o agent consiga se registrar no Azure Devops.
Siga os passos para criar o PAT criando um PAT no Azure Devops
Após a criação do PAT, salve o token em algum lugar, pois vamos precisar dele mais a frente.
Criando uma imagem docker para o agent e publicando no container registry
Criando um Dockerfile
Para criar uma imagem, siga os passos abaixo.
Crie um arquivo Dockerfile com o conteúdo abaixo.
FROM ubuntu:20.04
RUN DEBIAN_FRONTEND=noninteractive apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \
apt-transport-https \
apt-utils \
ca-certificates \
curl \
git \
iputils-ping \
jq \
lsb-release \
software-properties-common
RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash
# Can be 'linux-x64', 'linux-arm64', 'linux-arm', 'rhel.6-x64'.
ENV TARGETARCH=linux-x64
WORKDIR /azp
COPY ./start.sh .
RUN chmod +x start.sh
ENTRYPOINT [ "./start.sh" ]
Na mesma pasta que foi criado o Dockerfile, crie um arquivo chamado start.sh com o conteúdo abaixo.
#!/bin/bash
set -e
if [ -z "$AZP_URL" ]; then
echo 1>&2 "error: missing AZP_URL environment variable"
exit 1
fi
if [ -z "$AZP_TOKEN_FILE" ]; then
if [ -z "$AZP_TOKEN" ]; then
echo 1>&2 "error: missing AZP_TOKEN environment variable"
exit 1
fi
AZP_TOKEN_FILE=/azp/.token
echo -n $AZP_TOKEN > "$AZP_TOKEN_FILE"
fi
unset AZP_TOKEN
if [ -n "$AZP_WORK" ]; then
mkdir -p "$AZP_WORK"
fi
export AGENT_ALLOW_RUNASROOT="1"
cleanup() {
if [ -e config.sh ]; then
print_header "Cleanup. Removing Azure Pipelines agent..."
# If the agent has some running jobs, the configuration removal process will fail.
# So, give it some time to finish the job.
while true; do
./config.sh remove --unattended --auth PAT --token $(cat "$AZP_TOKEN_FILE") && break
echo "Retrying in 30 seconds..."
sleep 30
done
fi
}
print_header() {
lightcyan='\033[1;36m'
nocolor='\033[0m'
echo -e "${lightcyan}$1${nocolor}"
}
# Let the agent ignore the token env variables
export VSO_AGENT_IGNORE=AZP_TOKEN,AZP_TOKEN_FILE
print_header "1. Determining matching Azure Pipelines agent..."
AZP_AGENT_PACKAGES=$(curl -LsS \
-u user:$(cat "$AZP_TOKEN_FILE") \
-H 'Accept:application/json;' \
"$AZP_URL/_apis/distributedtask/packages/agent?platform=$TARGETARCH&top=1")
AZP_AGENT_PACKAGE_LATEST_URL=$(echo "$AZP_AGENT_PACKAGES" | jq -r '.value[0].downloadUrl')
if [ -z "$AZP_AGENT_PACKAGE_LATEST_URL" -o "$AZP_AGENT_PACKAGE_LATEST_URL" == "null" ]; then
echo 1>&2 "error: could not determine a matching Azure Pipelines agent"
echo 1>&2 "check that account '$AZP_URL' is correct and the token is valid for that account"
exit 1
fi
print_header "2. Downloading and extracting Azure Pipelines agent..."
curl -LsS $AZP_AGENT_PACKAGE_LATEST_URL | tar -xz & wait $!
source ./env.sh
print_header "3. Configuring Azure Pipelines agent..."
./config.sh --unattended \
--agent "${AZP_AGENT_NAME:-$(hostname)}" \
--url "$AZP_URL" \
--auth PAT \
--token $(cat "$AZP_TOKEN_FILE") \
--pool "${AZP_POOL:-Default}" \
--work "${AZP_WORK:-_work}" \
--replace \
--acceptTeeEula & wait $!
print_header "4. Running Azure Pipelines agent..."
if ! grep -q "template" <<< "$AZP_AGENT_NAME"; then
echo "Cleanup Traps Enabled"
trap 'cleanup; exit 0' EXIT
trap 'cleanup; exit 130' INT
trap 'cleanup; exit 143' TERM
fi
chmod +x ./run-docker.sh
# To be aware of TERM and INT signals call run.sh
# Running it with the --once flag at the end will shut down the agent after the build is executed
./run-docker.sh "$@" --once & wait $!
O seu arquivo start.sh tem que ter o EOL do unix/linux , caso esteja no windows, você pode usar o dos2unix para converter o arquivo ou utilizar o notepad++ e configurar o EOL para unix/linux.
Criando a imagem e publicando no ACR
Abra um terminal na pasta onde está o Dockerfile e o start.sh e execute o comandos abaixo.
# cria a imagem
docker build -t azure-devops-agent:latest .
# loga no seu ACR
docker login -u <user> -p <password> <seu acr name>
# cria a tag da imagem
docker tag azure-devops-agent:latest <seu acr name>/azure-devops-agent:latest
# envia a imagem para o ACR
docker push <seu acr name>/azure-devops-agent:latest
Criando os arquivos YML para o Kubernetes
Criando o deployment do Azure DevOps Agent
Pegue o PAT que foi gerado nos passos anteriores e vamos precisar converter o mesmo para base64.
Va no site https://base64.guru/converter e cole o PAT gerado e clique em encode e salve o valor gerado.
Crie um arquivo chamado deployment.yml com o conteúdo abaixo.
apiVersion: v1
kind: Secret
metadata:
name: azdevops
data:
AZP_TOKEN: 'seu pat gerado convertido para base64'
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: azdevops-deployment
labels:
app: azdevops-agent
spec:
replicas: 1
selector:
matchLabels:
app: azdevops-agent
template:
metadata:
labels:
app: azdevops-agent
spec:
containers:
- name: azdevops-agent
#nome do seu ACR que foi criado
image: nomedoseuacr.azurecr.io/azure-devops-agent:latest
env:
- name: AZP_URL
value: "https://dev.azure.com/sua-organizacao" #url da sua organização
- name: AZP_POOL
value: "keda-pool"
- name: AZP_TOKEN
valueFrom:
secretKeyRef:
name: azdevops
key: AZP_TOKEN
Fazendo o deploy do deployment no Kubernetes
Primeiro vamos instalar Kubernetes CLI para o Azure, fazer o login no cluster e executar o deploy.
# instalando o Kubernetes CLI
az aks install-cli
# logando no seu cluster
az aks get-credentials --resource-group myResourceGroup --name myAKSCluster
# criando um namespace para o seu deployment
kubectl create namespace azdevops
# fazendo o deploy do deployment
kubectl apply -f deployment.yml -n azdevops
# verificando se o pod foi criado
kubectl get pods -n azdevops
# vai ter uma resposta parecido com essa
NAME READY STATUS RESTARTS AGE
azdevops-deployment-86b9c58cbd-nm45l 1/1 Running 0 13s
Agora vá no seu Azure Devops no pool que criamos no passo anterior e verifique se o agente foi adicionado e verifique se ele está online.
Criando um pipeline
Crie um projeto default na sua organização do Azure DevOps. Clique aqui para saber como criar um projeto
Após a criação do projeto, vamos criar um pipeline. Clique aqui para saber como criar um pipeline
Quando aparecer a opção do review do seu pipeline, mude o pool para o que criamos, no meu caso foi o keda-pool e clique save and run.
Vá no seu pool e verifique se o agente está rodando a build.
Criando o Scale Object do KEDA
Crie um arquivo chamado scale-object.yml com o conteúdo abaixo.
Esse padrão de arquivo do KEDA, vai fazer o scaling por deployment, ao executar seus agentes como deployment, você não tem controle sobre qual pod é killed ao reduzir o scaling.
apiVersion: v1
kind: Secret
metadata:
name: pipeline-auth
data:
personalAccessToken: 'seu pat gerado convertido para base64'
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: pipeline-trigger-auth
spec:
secretTargetRef:
- parameter: personalAccessToken
name: pipeline-auth
key: personalAccessToken
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: azure-pipelines-scaledobject
spec:
scaleTargetRef:
name: azdevops-deployment
minReplicaCount: 1
maxReplicaCount: 5 #número máximo de replicas
triggers:
- type: azure-pipelines
metadata:
poolID: "18" #seu id do pool
organizationURLFromEnv: "AZP_URL"
authenticationRef:
name: pipeline-trigger-auth
Vamos fazer o deploy do arquivo scale-object.yml, rode os comandos abaixo.
# cria os arquivo necessários para o KEDA
kubectl apply -f scale-object.yml -n azdevops
# verifica se o scaled object foi criado
kubectl get scaledobject -n azdevops
# vai ter uma resposta parecido com essa
NAME SCALETARGETKIND SCALETARGETNAME MIN MAX TRIGGERS AUTHENTICATION READY ACTIVE FALLBACK AGE
azure-pipelines-scaledobject apps/v1.Deployment azdevops-deployment 1 5 azure-pipelines pipeline-trigger-auth True False False 73s
Após o deploy dos manifestos do KEDA, vamos executar o mesmo pipeline várias vezes para ver o scaling acontecer.
Conclusão
Como visto com o KEDA temos mais uma opção para escalar os agents de uma maneira simples e eficiente, lembrando que qualquer outra tools necessária para o seu processo de build/deploy tem que ser instalada junto no seu Dockerfile.
O KEDA também suporta mais alguns parâmetros para trigger, como podemos ver abaixo.
triggers:
- type: azure-pipelines
metadata:
# Optional: Name of the pool in Azure DevOps
poolName: "{agentPoolName}"
# Optional: Learn more in 'How to determine your pool ID'
poolID: "{agentPoolId}"
# Optional: Azure DevOps organization URL, can use TriggerAuthentication as well
organizationURLFromEnv: "AZP_URL"
# Optional: Azure DevOps Personal Access Token, can use TriggerAuthentication as well
personalAccessTokenFromEnv: "AZP_TOKEN"
# Optional: Target queue length
targetPipelinesQueueLength: "1" # Default 1
activationTargetPipelinesQueueLength: "5" # Default 0
# Optional: Parent template to read demands from
parent: "{parent ADO agent name}"
# Optional: Demands string to read demands from ScaledObject
demands: "{demands}"
authenticationRef:
name: pipeline-trigger-auth