Last active
March 24, 2025 10:55
-
-
Save macel94/827c1d7641c13df322d2a252abf22989 to your computer and use it in GitHub Desktop.
Azure Container App Jobs: Why I think they're Great
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FROM mcr.microsoft.com/dotnet/sdk:latest | |
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 | |
RUN az extension add --name azure-devops | |
# Install powershell core | |
RUN dotnet tool install --global PowerShell | |
# Install PowerApps CLI | |
RUN dotnet tool install --global Microsoft.PowerApps.CLI.Tool | |
# nvm requirements | |
RUN apt-get update | |
RUN echo "y" | apt-get install curl | |
# nvm env vars | |
RUN mkdir -p /usr/local/nvm | |
ENV NVM_DIR=/usr/local/nvm | |
# IMPORTANT: set the exact version | |
ENV NODE_VERSION=v23.6.0 | |
# Install latest nvm | |
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash | |
RUN /bin/bash -c "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION && nvm use --delete-prefix $NODE_VERSION" | |
# add node and npm to the PATH | |
ENV NODE_PATH=$NVM_DIR/versions/node/$NODE_VERSION/bin | |
ENV PATH=$NODE_PATH:$PATH | |
#Install cli-microsoft365 | |
RUN npm i -g @pnp/cli-microsoft365 | |
# Add .NET global tools to the PATH | |
ENV PATH="$PATH:/root/.dotnet/tools" | |
# Can be 'linux-x64', 'linux-arm64', 'linux-arm', 'rhel.6-x64'. | |
ENV TARGETARCH=linux-x64 | |
WORKDIR /azp | |
COPY infrastructure/agents-images/azure-devops/azure-pipelines-agent/start.sh . | |
RUN chmod +x start.sh | |
ENTRYPOINT [ "./start.sh" ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FROM mcr.microsoft.com/powershell:latest | |
# Note: The DEBIAN_FRONTEND export avoids warnings when you go on to work with your container. | |
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ | |
&& apt-get -y install git | |
# nvm requirements | |
RUN apt-get update | |
RUN echo "y" | apt-get install curl | |
# nvm env vars | |
RUN mkdir -p /usr/local/nvm | |
ENV NVM_DIR=/usr/local/nvm | |
# IMPORTANT: set the exact version | |
ENV NODE_VERSION=v23.6.0 | |
# Install latest nvm | |
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash | |
RUN /bin/bash -c "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION && nvm use --delete-prefix $NODE_VERSION" | |
# add node and npm to the PATH | |
ENV NODE_PATH=$NVM_DIR/versions/node/$NODE_VERSION/bin | |
ENV PATH=$NODE_PATH:$PATH | |
#Install cli-microsoft365 | |
RUN npm i -g @pnp/cli-microsoft365 | |
SHELL ["/usr/bin/pwsh", "-c"] | |
RUN $ErrorActionPreference='Stop'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
// For format details, see https://aka.ms/devcontainer.json. For config options, see the | |
// README at: https://github.com/devcontainers/templates/tree/main/src/powershell | |
{ | |
"name": "PowerShell", | |
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile | |
"build": { "dockerfile": "DevContainer-Dockerfile" }, | |
"features": { | |
"ghcr.io/devcontainers/features/dotnet:2": {}, | |
"ghcr.io/devcontainers/features/common-utils:2": { | |
"installZsh": "true", | |
"username": "vscode", | |
"upgradePackages": "false", | |
"nonFreePackages": "true" | |
}, | |
"ghcr.io/devcontainers/features/powershell:1": {}, | |
"ghcr.io/devcontainers/features/azure-cli:1": {}, | |
"ghcr.io/devcontainers/features/docker-outside-of-docker": {} | |
}, | |
"postCreateCommand": "sudo chsh vscode -s \"$(which pwsh)\"", | |
// Configure tool-specific properties. | |
"customizations": { | |
// Configure properties specific to VS Code. | |
"vscode": { | |
// Set *default* container specific settings.json values on container create. | |
"settings": { | |
"terminal.integrated.defaultProfile.linux": "pwsh" | |
}, | |
// Add the IDs of extensions you want installed when the container is created. | |
"extensions": [ | |
"ms-vscode.powershell", | |
"ms-azuretools.vscode-docker", | |
"ms-vscode-remote.remote-containers", | |
"GitHub.copilot", | |
"GitHub.copilot-chat", | |
"ms-dotnettools.csdevkit", | |
"microsoft-IsvExpTools.powerplatform-vscode", | |
"ms-vscode-remote.remote-wsl", | |
] | |
} | |
} | |
// Use 'forwardPorts' to make a list of ports inside the container available locally. | |
// "forwardPorts": [], | |
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. | |
// "remoteUser": "root" | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: $(Build.DefinitionName)_$(Date:yyyyMMdd)$(Rev:.r) | |
appendCommitMessageToRunName: false | |
trigger: none | |
pool: | |
name: container-apps | |
stages: | |
- stage: DoWhateverWithYourAgent | |
jobs: | |
- job: SampleJob | |
timeoutInMinutes: 480 # Set it as you want, remember not to exceed what was set for the az container app job | |
displayName: 'Execute SampleJob' | |
steps: | |
- checkout: self | |
clean: true | |
- task: PowerShell@2 | |
displayName: 'Run a script from your repo, having the dependencies already installed' | |
inputs: | |
filePath: './src/scripts/sample.ps1' | |
failOnStderr: true | |
showWarnings: true | |
pwsh: true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# To read secrets if the file SetLocals.ps1 exists, execute it | |
$filePath = "../../../../SetLocals.ps1" | |
if (Test-Path $filePath) { | |
Write-Host "Reading secrets from $filePath" | |
. $filePath | |
} | |
else{ | |
Write-Host "No secrets file found" | |
} | |
########################### WARNING!!!!!!!!! | |
# Create this app pool manually, in the organization you specify, before running the script | |
$AZP_POOL="container-apps" | |
$RESOURCE_GROUP="azdevops-azcontapps-jobs-sample" | |
$LOCATION="westeurope" | |
$ENVIRONMENT="${env:ORGANIZATION}-jobs-sample" | |
$JOB_NAME="j-${env:ORGANIZATION}-azdo-agent-job" | |
$PLACEHOLDER_JOB_NAME="pj-${env:ORGANIZATION}-placeholder-job" | |
$SUBSCRIPTION_ID="your-sub-id" | |
$CONTAINER_IMAGE_NAME="azure-pipelines-agent:$(Get-Date -Format 'ddMMyyyy')" | |
$CONTAINER_REGISTRY_NAME="your-reg" | |
$ORGANIZATION_URL = "https://dev.azure.com/${env:ORGANIZATION}" | |
docker build --pull --rm -f "path-to-your-ag-dockerfile/Agent-DockerFile" -t automations-agent:latest . | |
if (-not $env:AZP_TOKEN -or -not $env:ORGANIZATION) { | |
throw "AZP_TOKEN or ORGANIZATION environment variables are not set. Please set them in the SetLocals.ps1 file." | |
} | |
# az login | |
az account set --subscription $SUBSCRIPTION_ID | |
az account show | |
az extension add --name containerapp --upgrade --allow-preview true | |
az provider register --namespace Microsoft.App | |
az provider register --namespace Microsoft.OperationalInsights | |
az group create ` | |
--name "$RESOURCE_GROUP" ` | |
--location "$LOCATION" | |
az containerapp env create ` | |
--name "$ENVIRONMENT" ` | |
--resource-group "$RESOURCE_GROUP" ` | |
--location "$LOCATION" | |
az acr create --name "$CONTAINER_REGISTRY_NAME" --resource-group "$RESOURCE_GROUP" --location "$LOCATION" --sku Basic --admin-enabled true | |
# The default would be to build from a remote repository, but we are building from a local Dockerfile and pushing manually | |
az acr login --name "$CONTAINER_REGISTRY_NAME" | |
docker tag automations-agent:latest "$CONTAINER_REGISTRY_NAME.azurecr.io/$CONTAINER_IMAGE_NAME" | |
docker push "$CONTAINER_REGISTRY_NAME.azurecr.io/$CONTAINER_IMAGE_NAME" | |
az containerapp env create -n "$ENVIRONMENT" -g "$RESOURCE_GROUP" --location "$LOCATION" | |
az containerapp job create -n "$PLACEHOLDER_JOB_NAME" -g "$RESOURCE_GROUP" --environment "$ENVIRONMENT" --trigger-type Manual --replica-timeout 300 --replica-retry-limit 0 --replica-completion-count 1 --parallelism 1 --image "$CONTAINER_REGISTRY_NAME.azurecr.io/$CONTAINER_IMAGE_NAME" --cpu "2.0" --memory "4Gi" --secrets "personal-access-token=$env:AZP_TOKEN" "organization-url=$ORGANIZATION_URL" --env-vars "AZP_TOKEN=secretref:personal-access-token" "AZP_URL=secretref:organization-url" "AZP_POOL=$AZP_POOL" "AZP_PLACEHOLDER=1" "AZP_AGENT_NAME=placeholder-agent" --registry-server "$CONTAINER_REGISTRY_NAME.azurecr.io" | |
az containerapp job start -n "$PLACEHOLDER_JOB_NAME" -g "$RESOURCE_GROUP" | |
az containerapp job execution list --name "$PLACEHOLDER_JOB_NAME" --resource-group "$RESOURCE_GROUP" --output table --query '[].{Status: properties.status, Name: name, StartTime: properties.startTime}' | |
# wait for the placeholder to correctly be registered in your azdevops pool as described in the official guide before deleting it | |
# https://learn.microsoft.com/en-us/azure/container-apps/tutorial-ci-cd-runners-jobs?pivots=container-apps-jobs-self-hosted-ci-cd-azure-pipelines&tabs=powershell | |
az containerapp job delete -n "$PLACEHOLDER_JOB_NAME" -g "$RESOURCE_GROUP" | |
# Max execution time is 8 hours (28800 seconds) | |
az containerapp job create -n "$JOB_NAME" -g "$RESOURCE_GROUP" --environment "$ENVIRONMENT" --trigger-type Event --replica-timeout 28800 --replica-retry-limit 0 --replica-completion-count 1 --parallelism 1 --image "$CONTAINER_REGISTRY_NAME.azurecr.io/$CONTAINER_IMAGE_NAME" --min-executions 0 --max-executions 10 --polling-interval 30 --scale-rule-name "azure-pipelines" --scale-rule-type "azure-pipelines" --scale-rule-metadata "poolName=$AZP_POOL" "targetPipelinesQueueLength=1" --scale-rule-auth "personalAccessToken=personal-access-token" "organizationURL=organization-url" --cpu "2.0" --memory "4Gi" --secrets "personal-access-token=$env:AZP_TOKEN" "organization-url=$ORGANIZATION_URL" --env-vars "AZP_TOKEN=secretref:personal-access-token" "AZP_URL=secretref:organization-url" "AZP_POOL=$AZP_POOL" --registry-server "$CONTAINER_REGISTRY_NAME.azurecr.io" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 $AZP_PLACEHOLDER is set, skip cleanup | |
if [ -n "$AZP_PLACEHOLDER" ]; then | |
echo 'Running in placeholder mode, skipping cleanup' | |
return | |
fi | |
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..." | |
echo "Agent package URL: $AZP_AGENT_PACKAGE_LATEST_URL" | |
curl -LsS $AZP_AGENT_PACKAGE_LATEST_URL | tar -xz & wait $! | |
source ./env.sh | |
trap 'cleanup; exit 0' EXIT | |
trap 'cleanup; exit 130' INT | |
trap 'cleanup; exit 143' TERM | |
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..." | |
trap 'cleanup; exit 0' EXIT | |
trap 'cleanup; exit 130' INT | |
trap 'cleanup; exit 143' TERM | |
chmod +x ./run.sh | |
# If $AZP_PLACEHOLDER is set, skipping running the agent | |
if [ -n "$AZP_PLACEHOLDER" ]; then | |
echo 'Running in placeholder mode, skipping running the agent' | |
else | |
# 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.sh --once & wait $! | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment