All documentation

Creating an Azure DevOps Agent with Ansible and Terraform in Docker

Published May 16, 2024 Guide

Overview

This guide provides step-by-step instructions to build and run a Docker container configured as an Azure DevOps agent. The container includes Ansible and Terraform, enabling comprehensive automation capabilities for infrastructure and configuration management.

Prerequisites

  • Docker installed on your machine.
  • A script named start.sh to initialize and run the Azure DevOps agent.

Dockerfile

Below is the Dockerfile used to create the Docker image for the Azure DevOps agent with Ansible and Terraform:

dockerfile
## Specify the base image with AMD64 architecture
FROM --platform=linux/amd64 ubuntu:22.04

# Update system and install necessary packages
RUN apt-get update -y && \
    apt-get upgrade -y && \
    apt-get install -y curl git jq libicu70 python3 python3-pip gnupg software-properties-common

# Install Ansible and necessary Python packages
RUN pip3 install ansible pywinrm requests_ntlm

# Install Terraform
RUN curl -fsSL https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg && \
    echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/hashicorp.list && \
    apt-get update -y && \
    apt-get install -y terraform

# Set the target architecture environment variable
ENV TARGETARCH="linux-x64"

# Set the working directory
WORKDIR /azp/

# Copy the start script into the container and set execution permissions
COPY ./start.sh ./
RUN chmod +x ./start.sh

# Create an agent user and set up the home directory
RUN useradd -m -d /home/agent agent && \
    chown -R agent:agent /azp /home/agent

# Switch to the user 'agent'
USER agent

# Set the entry point for the container
ENTRYPOINT ["./start.sh"]

Start Script (start.sh)

Ensure you have a start.sh script with the following content:

sh
#!/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

cleanup() {
  trap "" EXIT

  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 "\n${lightcyan}$1${nocolor}\n"
}

# 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

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..."

chmod +x ./run.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.sh "$@" & wait $!

Building the Docker Image

  1. Navigate to the Directory: Ensure you are in the directory containing your Dockerfile and start.sh script.sh
cd /path/to/your/dockerfile-directory

Build the Docker Image: Use the following command to build your Docker image. Replace azp-agent:linux with your desired image name.

  1. docker build -t azp-agent:linux -f azp-agent-linux.dockerfile .

Running the Docker Container

  1. Run the Docker Container: Use the following command to run the container in detached mode with the necessary environment variables:sh
docker run -d \
  -e AZP_URL="https://dev.azure.com/{ORG}" \
  -e AZP_TOKEN="{PAT}" \
  -e AZP_POOL="Default" \
  -e AZP_AGENT_NAME="Docker Agent - Linux" \
  --name "azp-agent-linux-01" \
  azp-agent:linux

View Logs: Monitor the logs to ensure the container is running correctly:

docker logs -f azp-agent-linux-01

Attach to the Container: If you need to interact with the running container, attach to it:

  1. docker attach azp-agent-linux-01

Using the Docker Image in Azure DevOps Pipeline

To integrate this Docker image with your Azure DevOps pipeline, follow these steps:

  1. Pipeline Configuration: Use the following YAML configuration in your Azure DevOps pipeline to run the container and execute Ansible playbooks:yaml
  2. pool: vmImage: 'ubuntu-latest' steps: - script: | docker run -v $(System.DefaultWorkingDirectory):/workspace -w /workspace azp-agent:linux ansible-playbook your_playbook.yml displayName: 'Run Ansible Playbook in Docker' env: # Add any necessary environment variables here example_var: $(example_var)

Summary

This documentation provides a comprehensive guide to building and running a Docker container that includes Ansible and Terraform for use in Azure DevOps pipelines. By following these steps, you can ensure that your automation tools are available within a consistent and reproducible environment.