Docker의 자체 호스팅 에이전트 실행

Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019

이 문서에서는 Docker에서 Azure Pipelines 에이전트를 실행하기 위한 지침을 제공합니다. Azure Pipelines에서 자체 호스팅 에이전트를 설정하여 Docker를 사용하여 Windows Server Core(Windows 호스트용) 또는 Ubuntu 컨테이너(Linux 호스트용) 내에서 실행할 수 있습니다. 이 기능은 Azure Container Instances와 같은 외부 오케스트레이션을 사용하여 에이전트를 실행하려는 경우에 유용합니다. 이 문서에서는 에이전트 자체 업데이트 처리를 포함하여 전체 컨테이너 예제를 안내합니다.

Windows와 Linux는 모두 컨테이너 호스트로 지원됩니다. Windows 컨테이너는 Windows vmImage에서 실행되어야 합니다. Docker에서 에이전트를 실행하려면 Azure Pipelines 또는 Azure DevOps Server에 docker run연결하도록 에이전트를 구성하는 몇 가지 환경 변수를 전달합니다. 마지막으로 필요에 맞게 컨테이너 를 사용자 지정합니다. 작업 및 스크립트는 컨테이너 PATH에서 사용할 수 있는 특정 도구에 따라 달라질 수 있으며 이러한 도구를 사용할 수 있는지 확인해야 합니다.

이 기능을 사용하려면 에이전트 버전 2.149 이상이 필요합니다. Azure DevOps 2019는 호환되는 에이전트 버전과 함께 제공되지 않았습니다. 그러나 Docker 에이전트를 실행하려는 경우 올바른 에이전트 패키지를 애플리케이션 계층에 업로드할 수 있습니다.

Windows

Hyper-V 사용

Hyper-V는 Windows에서 기본적으로 사용하도록 설정되지 않습니다. 컨테이너 간에 격리를 제공하려면 Hyper-V를 사용하도록 설정해야 합니다. 그렇지 않으면 Windows용 Docker가 시작되지 않습니다.

참고 항목

머신에서 가상화를 사용하도록 설정해야 합니다. 일반적으로 기본적으로 사용하도록 설정됩니다. 그러나 Hyper-V 설치가 실패하는 경우 가상화를 사용하도록 설정하는 방법에 대한 시스템 설명서를 참조하세요.

Windows용 Docker 설치

Windows 10을 사용하는 경우 Docker Community Edition을 설치할 수 있습니다. Windows Server 2016의 경우 Docker Enterprise Edition설치합니다.

Windows 컨테이너를 사용하도록 Docker 전환

기본적으로 Windows용 Docker는 Linux 컨테이너를 사용하도록 구성됩니다. Windows 컨테이너 실행을 허용하려면 Windows용 Docker에서 Windows 디먼을 실행하고 있는지 확인합니다.

Dockerfile 만들기 및 빌드

다음으로 Dockerfile을 만듭니다.

  1. 명령 프롬프트가 엽니다.

  2. 새 디렉터리를 만듭니다.

    mkdir "C:\azp-agent-in-docker\"
    
  3. 이 새 디렉터리로 이동합니다.

    cd "C:\azp-agent-in-docker\"
    
  4. 다음 콘텐츠를 라는 C:\azp-agent-in-docker\azp-agent-windows.dockerfile파일에 저장합니다.

    FROM mcr.microsoft.com/windows/servercore:ltsc2022
    
    WORKDIR /azp/
    
    COPY ./start.ps1 ./
    
    CMD powershell .\start.ps1
    
  5. 다음 콘텐츠를 다음과 같이 저장합니다 C:\azp-agent-in-docker\start.ps1.

    function Print-Header ($header) {
      Write-Host "`n${header}`n" -ForegroundColor Cyan
    }
    
    if (-not (Test-Path Env:AZP_URL)) {
      Write-Error "error: missing AZP_URL environment variable"
      exit 1
    }
    
    if (-not (Test-Path Env:AZP_TOKEN_FILE)) {
      if (-not (Test-Path Env:AZP_TOKEN)) {
        Write-Error "error: missing AZP_TOKEN environment variable"
        exit 1
      }
    
      $Env:AZP_TOKEN_FILE = "\azp\.token"
      $Env:AZP_TOKEN | Out-File -FilePath $Env:AZP_TOKEN_FILE
    }
    
    Remove-Item Env:AZP_TOKEN
    
    if ((Test-Path Env:AZP_WORK) -and -not (Test-Path $Env:AZP_WORK)) {
      New-Item $Env:AZP_WORK -ItemType directory | Out-Null
    }
    
    New-Item "\azp\agent" -ItemType directory | Out-Null
    
    # Let the agent ignore the token env variables
    $Env:VSO_AGENT_IGNORE = "AZP_TOKEN,AZP_TOKEN_FILE"
    
    Set-Location agent
    
    Print-Header "1. Determining matching Azure Pipelines agent..."
    
    $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$(Get-Content ${Env:AZP_TOKEN_FILE})"))
    $package = Invoke-RestMethod -Headers @{Authorization=("Basic $base64AuthInfo")} "$(${Env:AZP_URL})/_apis/distributedtask/packages/agent?platform=win-x64&`$top=1"
    $packageUrl = $package[0].Value.downloadUrl
    
    Write-Host $packageUrl
    
    Print-Header "2. Downloading and installing Azure Pipelines agent..."
    
    $wc = New-Object System.Net.WebClient
    $wc.DownloadFile($packageUrl, "$(Get-Location)\agent.zip")
    
    Expand-Archive -Path "agent.zip" -DestinationPath "\azp\agent"
    
    try {
      Print-Header "3. Configuring Azure Pipelines agent..."
    
      .\config.cmd --unattended `
        --agent "$(if (Test-Path Env:AZP_AGENT_NAME) { ${Env:AZP_AGENT_NAME} } else { hostname })" `
        --url "$(${Env:AZP_URL})" `
        --auth PAT `
        --token "$(Get-Content ${Env:AZP_TOKEN_FILE})" `
        --pool "$(if (Test-Path Env:AZP_POOL) { ${Env:AZP_POOL} } else { 'Default' })" `
        --work "$(if (Test-Path Env:AZP_WORK) { ${Env:AZP_WORK} } else { '_work' })" `
        --replace
    
      Print-Header "4. Running Azure Pipelines agent..."
    
      .\run.cmd
    } finally {
      Print-Header "Cleanup. Removing Azure Pipelines agent..."
    
      .\config.cmd remove --unattended `
        --auth PAT `
        --token "$(Get-Content ${Env:AZP_TOKEN_FILE})"
    }
    
  6. 해당 디렉터리 내에서 다음 명령을 실행합니다.

    docker build --tag "azp-agent:windows" --file "./azp-agent-windows.dockerfile" .
    

    최종 이미지에 태그가 지정됩니다 azp-agent:windows.

이미지 시작

이제 이미지를 만들었으므로 컨테이너를 실행할 수 있습니다. 그러면 최신 버전의 에이전트가 설치되고 구성되고 에이전트가 실행됩니다. 선택한 Azure DevOps 또는 Azure DevOps Server 인스턴스의 지정된 에이전트 풀( Default 기본적으로 에이전트 풀)을 대상으로 합니다.

docker run -e AZP_URL="<Azure DevOps instance>" -e AZP_TOKEN="<Personal Access Token>" -e AZP_POOL="<Agent Pool Name>" -e AZP_AGENT_NAME="Docker Agent - Windows" --name "azp-agent-windows" azp-agent:windows

네트워크 문제가 발생하는 경우 매개 변수를 --network 지정해야 할 수 있습니다.

docker run --network "Default Switch" < . . . >

컨테이너를 중지하고 에이전트 + CCtrl를 제거할 수 있도록 하려면 지정하고 --tty 플래그를 지정 --interactive 해야 할 수도 있습니다(또는 간단히-it).

docker run --interactive --tty < . . . >

모든 파이프라인 작업에 대한 새 에이전트 컨테이너를 원하는 경우 명령에 플래그를 전달 --once합니다run.

docker run < . . . > --once

플래그를 --once 사용하면 Kubernetes 또는 Azure Container Instances와 같은 컨테이너 오케스트레이션 시스템을 사용하여 작업이 완료되면 컨테이너의 새 복사본을 시작할 수 있습니다.

선택적 환경 변수를 사용하여 에이전트 이름, 에이전트 풀 및 에이전트 작업 디렉터리를 제어할 수 있습니다.

Linux

Docker 설치

Linux 배포판에 따라 Docker Community Edition 또는 Docker Enterprise Edition을 설치할 수 있습니다.

Dockerfile 만들기 및 빌드

다음으로 Dockerfile을 만듭니다.

  1. 터미널을 엽니다.

  2. 새 디렉터리를 만듭니다(권장).

    mkdir ~/azp-agent-in-docker/
    
  3. 이 새 디렉터리로 이동합니다.

    cd ~/azp-agent-in-docker/
    
  4. 다음 콘텐츠를 다음과 같이 저장합니다 ~/azp-agent-in-docker/azp-agent-linux.dockerfile.

    • Alpine의 경우:

      FROM alpine
      
      RUN apk update
      RUN apk upgrade
      RUN apk add bash curl git icu-libs jq
      
      ENV TARGETARCH="linux-musl-x64"
      
      WORKDIR /azp/
      
      COPY ./start.sh ./
      RUN chmod +x ./start.sh
      
      RUN adduser -D agent
      RUN chown agent ./
      USER agent
      # Another option is to run the agent as root.
      # ENV AGENT_ALLOW_RUNASROOT="true"
      
      ENTRYPOINT ./start.sh
      
    • Ubuntu 22.04의 경우:

      FROM ubuntu:22.04
      
      RUN apt update -y && apt upgrade -y && apt install curl git jq libicu70 -y
      
      # Also can be "linux-arm", "linux-arm64".
      ENV TARGETARCH="linux-x64"
      
      WORKDIR /azp/
      
      COPY ./start.sh ./
      RUN chmod +x ./start.sh
      
      # Create agent user and set up home directory
      RUN useradd -m -d /home/agent agent
      RUN chown -R agent:agent /azp /home/agent
      
      USER agent
      # Another option is to run the agent as root.
      # ENV AGENT_ALLOW_RUNASROOT="true"
      
      ENTRYPOINT ./start.sh
      

    에이전트를 루트로 실행하려면 줄의 ENV AGENT_ALLOW_RUNASROOT="true" 주석 처리를 제거하고 이 줄 앞에 사용자를 추가 agent 하지 않습니다.

    참고 항목

    태스크는 컨테이너에서 제공해야 하는 실행 파일에 따라 달라질 수 있습니다. 예를 들어 명령 및 태스크를 zipRUN apt install -y 실행 ArchiveFiles 하려면 명령과 unzipExtractFiles 패키지를 추가해야 합니다. 또한 에이전트가 사용할 Linux Ubuntu 이미지이므로 필요에 따라 이미지를 사용자 지정할 수 있습니다. 예: .NET 애플리케이션을 빌드해야 하는 경우 Ubuntu에 .NET SDK 또는 .NET 런타임 설치 문서를 따라 이미지에 추가할 수 있습니다.

  5. 다음 콘텐츠를 저장하여 ~/azp-agent-in-docker/start.shUnix 스타일(LF) 줄 끝 사용해야 합니다.

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

    참고 항목

    또한 Kubernetes 또는 Azure Container Instances와 같은 컨테이너 오케스트레이션 시스템을 사용하여 작업이 완료되면 컨테이너의 새 복사본을 시작해야 합니다.

  6. 해당 디렉터리 내에서 다음 명령을 실행합니다.

    docker build --tag "azp-agent:linux" --file "./azp-agent-linux.dockerfile" .
    

    최종 이미지에 태그가 지정됩니다 azp-agent:linux.

이미지 시작

이제 이미지를 만들었으므로 컨테이너를 실행할 수 있습니다. 그러면 최신 버전의 에이전트가 설치되고 구성되고 에이전트가 실행됩니다. 선택한 Azure DevOps 또는 Azure DevOps Server 인스턴스의 지정된 에이전트 풀( Default 기본적으로 에이전트 풀)을 대상으로 합니다.

docker run -e AZP_URL="<Azure DevOps instance>" -e AZP_TOKEN="<Personal Access Token>" -e AZP_POOL="<Agent Pool Name>" -e AZP_AGENT_NAME="Docker Agent - Linux" --name "azp-agent-linux" azp-agent:linux

컨테이너를 중지하고 에이전트 + CCtrl를 제거할 수 있도록 하려면 지정하고 --tty 플래그를 지정 --interactive 해야 할 수도 있습니다(또는 간단히-it).

docker run --interactive --tty < . . . >

모든 파이프라인 작업에 대한 새 에이전트 컨테이너를 원하는 경우 명령에 플래그를 전달 --once합니다run.

docker run < . . . > --once

플래그를 --once 사용하면 Kubernetes 또는 Azure Container Instances와 같은 컨테이너 오케스트레이션 시스템을 사용하여 작업이 완료되면 컨테이너의 새 복사본을 시작할 수 있습니다.

선택적 환경 변수를 사용하여 에이전트 이름, 에이전트 풀 및 에이전트 작업 디렉터리를 제어할 수 있습니다.

환경 변수

환경 변수 설명
AZP_URL Azure DevOps 또는 Azure DevOps Server 인스턴스의 URL입니다.
AZP_TOKEN 에이전트를 구성할 AZP_URL수 있는 권한이 있는 사용자가 만든 에이전트 풀(읽기, 관리) 범위가 있는 PAT(개인 액세스 토큰).
AZP_AGENT_NAME 에이전트 이름(기본값: 컨테이너 호스트 이름).
AZP_POOL 에이전트 풀 이름(기본값: Default).
AZP_WORK 작업 디렉터리(기본값: _work).

도구 추가 및 컨테이너 사용자 지정

기본 빌드 에이전트를 만들었습니다. Dockerfile을 확장하여 추가 도구 및 해당 종속성을 포함하거나 이 컨테이너를 기본 계층으로 사용하여 사용자 고유의 컨테이너를 빌드할 수 있습니다. 다음 항목이 그대로 남아 있는지 확인합니다.

  • 스크립트는 start.sh Dockerfile에서 호출됩니다.
  • 스크립트는 start.sh Dockerfile의 마지막 명령입니다.
  • 파생 컨테이너가 Dockerfile에 명시된 종속성을 제거하지 않는지 확인합니다.

Docker 컨테이너 내에서 Docker 사용

Docker 컨테이너 내에서 Docker를 사용하려면 Docker 소켓을 바인딩하여 탑재합니다.

주의

이렇게 하면 보안에 심각한 영향을 미칩니다. 컨테이너 내의 코드는 이제 Docker 호스트에서 루트로 실행할 수 있습니다.

이 작업을 수행하려는 경우 Docker.com 바인딩 탑재 설명서를 참조하세요.

Azure Kubernetes Service 클러스터 사용

주의

Docker 제한의 Docker로 인해 AKS 1.19 이상에서는 Docker 기반 작업이 작동하지 않는다는 점을 고려하세요. Docker가 Kubernetes 1.19에서 컨테이너로 대체되었고 Docker-in-Docker를 사용할 수 없게 되었습니다.

Azure Kubernetes Service 배포 및 구성

빠른 시작의 단계를 수행합니다. Azure Portal을 사용하여 AKS(Azure Kubernetes Service) 클러스터를 배포합니다. 그런 다음 PowerShell 또는 셸 콘솔에서 명령줄을 kubectl 사용할 수 있습니다.

Azure Container Registry 배포 및 구성

빠른 시작: Azure Portal을 사용하여 Azure Container Registry에서 새 레지스트리 만들기의 단계를 수행합니다. 그런 다음 Azure Container Registry에서 컨테이너를 푸시하고 끌어올 수 있습니다.

비밀 구성 및 복제본(replica) 집합 배포

  1. AKS 클러스터에서 비밀을 만듭니다.

    kubectl create secret generic azdevops \
      --from-literal=AZP_URL=https://dev.azure.com/yourOrg \
      --from-literal=AZP_TOKEN=YourPAT \
      --from-literal=AZP_POOL=NameOfYourPool
    
  2. 컨테이너 레지스트리에 컨테이너를 푸시하려면 다음 명령을 실행합니다.

    docker push "<acr-server>/azp-agent:<tag>"
    
  3. 기존 AKS 클러스터에 대한 Container Registry 통합을 구성합니다.

    참고 항목

    Azure Portal에 여러 구독이 있는 경우 먼저 이 명령을 사용하여 구독을 선택하세요.

    az account set --subscription "<subscription id or subscription name>"
    
    az aks update -n "<myAKSCluster>" -g "<myResourceGroup>" --attach-acr "<acr-name>"
    
  4. 다음 콘텐츠를 다음과 같이 저장합니다 ~/AKS/ReplicationController.yml.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: azdevops-deployment
      labels:
        app: azdevops-agent
    spec:
      replicas: 1 # here is the configuration for the actual agent always running
      selector:
        matchLabels:
          app: azdevops-agent
      template:
        metadata:
          labels:
            app: azdevops-agent
        spec:
          containers:
          - name: kubepodcreation
            image: <acr-server>/azp-agent:<tag>
            env:
              - name: AZP_URL
                valueFrom:
                  secretKeyRef:
                    name: azdevops
                    key: AZP_URL
              - name: AZP_TOKEN
                valueFrom:
                  secretKeyRef:
                    name: azdevops
                    key: AZP_TOKEN
              - name: AZP_POOL
                valueFrom:
                  secretKeyRef:
                    name: azdevops
                    key: AZP_POOL
            volumeMounts:
            - mountPath: /var/run/docker.sock
              name: docker-volume
          volumes:
          - name: docker-volume
            hostPath:
              path: /var/run/docker.sock
    

    이 Kubernetes YAML은 클러스터에서 실행되는 수 또는 에이전트를 나타내는 복제본(replica) 집합 및 배포 replicas: 1 를 만듭니다.

  5. 다음 명령을 실행합니다.

    kubectl apply -f ReplicationController.yml
    

이제 에이전트가 AKS 클러스터를 실행합니다.

사용자 지정 MTU 매개 변수 설정

컨테이너 작업에서 사용하는 네트워크에 MTU 값을 지정할 수 있습니다(k8s 클러스터의 docker-in-docker 시나리오에 유용).

환경 변수 AGENT_DOCKER_MTU_VALUE 설정하여 MTU 값을 설정한 다음 자체 호스팅 에이전트를 다시 시작해야 합니다. 여기에서 에이전트 다시 시작 및 각 개별 에이전트에 대한 다양한 환경 변수 설정에 대해 자세히 알아볼 수 있습니다.

이렇게 하면 작업 컨테이너에 대한 네트워크 매개 변수를 설정할 수 있습니다. 이 명령의 사용은 컨테이너 네트워크 구성 중 다음 명령을 사용하는 것과 유사합니다.

-o com.docker.network.driver.mtu=AGENT_DOCKER_MTU_VALUE

Docker 컨테이너 내에서 Docker를 사용하여 볼륨 탑재

Docker 컨테이너가 다른 Docker 컨테이너 내에서 실행되는 경우 둘 다 호스트의 디먼을 사용하므로 모든 탑재 경로는 컨테이너가 아닌 호스트를 참조합니다.

예를 들어 호스트에서 외부 Docker 컨테이너로 경로를 탑재하려는 경우 다음 명령을 사용할 수 있습니다.

docker run ... -v "<path-on-host>:<path-on-outer-container>" ...

호스트에서 내부 Docker 컨테이너로 경로를 탑재하려는 경우 다음 명령을 사용할 수 있습니다.

docker run ... -v "<path-on-host>:<path-on-inner-container>" ...

그러나 외부 컨테이너의 경로를 내부 컨테이너에 탑재할 수는 없습니다. 이 작업을 수행하려면 ENV 변수를 선언해야 합니다.

docker run ... --env DIND_USER_HOME=$HOME ...

그런 다음 다음 명령을 사용하여 외부 컨테이너에서 내부 컨테이너를 시작할 수 있습니다.

docker run ... -v "${DIND_USER_HOME}:<path-on-inner-container>" ...

일반적인 오류

Windows를 사용하는 경우 다음 오류가 발생합니다.

standard_init_linux.go:178: exec user process caused "no such file or directory"

git-scm을 다운로드하고 설치하여 Git Bash를 설치합니다.

다음 명령을 실행합니다.

dos2unix ~/azp-agent-in-docker/Dockerfile
dos2unix ~/azp-agent-in-docker/start.sh
git add .
git commit -m "Fixed CR"
git push

다시 시도하세요. 더 이상 오류가 발생하지 않습니다.