如何在Azure DevOps中启用Docker层缓存

30
我正在运行以下的yaml脚本来构建Docker镜像并推送到Kubernetes集群,但同时我想在构建yaml脚本时启用Azure DevOps中的Docker层缓存。请问您能否解释一下如何启用或在Azure DevOps中添加任务来实现这个功能?
# Starter pipeline
# Start with a minimal pipeline that you can customize to build and deploy your code.
# Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

variables:
  tag: 'web'
  DockerImageName: 'boiyaa/google-cloud-sdk-nodejs'


steps:
- task: Docker@2
  inputs:
    command: 'build'
    Dockerfile: '**/Dockerfile'
    tags: 'web'
  
- script: |
    echo ${GCLOUD_SERVICE_KEY_STAGING} > ${HOME}/gcp-key.json
               gcloud auth activate-service-account --key-file ${HOME}/gcp-key.json --project ${GCLOUD_PROJECT_ID_STAGING}
               gcloud container clusters get-credentials ${GCLOUD_PROJECT_CLUSTER_ID_STAGING} \
        --zone ${GCLOUD_PROJECT_CLUSTER_ZONE_STAGING} \
        --project ${GCLOUD_PROJECT_ID_STAGING}
  displayName: 'Setup-staging_credentials'


- bash: bash ./deploy/deploy-all.sh staging
  displayName: 'Deploy_script_staging'
7个回答

57

以下是我的解决方法。我从我的注册表(在我的情况下是Azure容器注册表)拉取了最新版本的镜像到Azure DevOps托管的代理程序上。然后,我将--cache-from添加到Docker构建参数中,指向刚刚下载到本地机器/缓存的此最新标记。

- task: Docker@2
  inputs:
    containerRegistry: '$(ContainerRegistryName)'
    command: 'login'

- script: "docker pull $(ACR_ADDRESS)/$(REPOSITORY):latest"
  displayName: Pull latest for layer caching
  continueOnError: true # for first build, no cache

- task: Docker@2
  displayName: build
  inputs:
    containerRegistry: '$(ContainerRegistryName)'
    repository: '$(REPOSITORY)'
    command: 'build'
    Dockerfile: './dockerfile '
    buildContext: '$(BUILDCONTEXT)'
    arguments: '--cache-from=$(ACR_ADDRESS)/$(REPOSITORY):latest' 
    tags: |
      $(Build.BuildNumber)
      latest

- task: Docker@2
  displayName: "push"
  inputs:
    command: push
    containerRegistry: "$(ContainerRegistryName)"
    repository: $(REPOSITORY) 
    tags: |
      $(Build.BuildNumber)
      latest

这个完美运行。你也可以使用Docker任务。 - Michael
1
你真是个天才!顺便提一下,这也适用于docker-compose,你只需要在docker-compose.yaml的build元素下指定cache_from即可。 - Stefan Iancu
很不幸,在我的端上,尽管成功拉取了最新的镜像,但它仍然构建了所有应该被缓存的阶段。我在这里开了一个问题:https://dev59.com/qmsMtIcB2Jgan1znsC6D - cjones
1
非常好的答案。但是为了让它对我起作用,我必须启用buildkit(如docker文档中所述),在我的docker build命令之前添加DOCKER_BUILDKIT=1 - Tyson Williams
1
这个能用于多阶段的Docker镜像吗?因为层不会被缓存。 - Hany Habib
显示剩余3条评论

15

目前Azure DevOps不支持Docker层缓存。 原因如下所述:

在Microsoft托管代理的当前设计中,每个作业都分派给新配置的虚拟机。这些虚拟机在作业完成后被清理,不会被保留,因此不能用于后续的作业。虚拟机的短暂性质防止了缓存的Docker层的重复使用。

但是:

1. 使用自托管代理可以实现Docker层缓存。您可以尝试创建本地代理来运行构建流水线。
您可能需要禁用作业选项“允许脚本访问OAuth令牌”。因为$(System.AccessToken)是通过--build-arg ACCESS_TOKEN=$(System.AccessToken)传递给docker build的,其值每次运行都会发生变化,这将使缓存失效。
2. 您还可以使用Cache任务docker save/load命令将保存的Docker层上传到Azure DevOps服务器,并在未来的运行中恢复它。查看此thread以获取更多信息。
3. 另一种解决方法如此blog所述,是在您的Dockerfile中使用--cache-from和--target
如果上述解决方法不能满足您的需求,您可以向Microsoft开发团队提交功能请求。单击建议一个功能,然后选择Azure DevOps

10

编辑:如评论中指出,实际上这个功能是可以在不使用BuildKit的情况下使用的。在构建过程中使用Docker镜像作为缓存源的示例请参见此处

通过将变量DOCKER_BUILDKIT: 1(请参阅此链接)添加到管道作业并安装buildx,我成功地通过将缓存存储为单独的镜像来实现层缓存。有关基本知识,请参见此链接

以下是Azure DevOps中的一个示例步骤:

- script: |
    image="myreg.azurecr.io/myimage"
    tag=$(Build.SourceBranchName)-$(Build.SourceVersion)
    cache_tag=cache-$(Build.SourceBranchName)

    docker buildx create --use
    docker buildx build \
      -t "${image}:${tag}"
      --cache-from=type=registry,ref=${image}:${cache_tag}\
      --cache-to=type=registry,ref=${image}:${cache_tag},mode=max \
      --push \
      --progress=plain \
      .
  displayName: Build & push image using remote BuildKit layer cache

当然,这会要求每次运行都下载镜像缓存,但对于Docker构建过程中需要长时间安装步骤的图像来说,这肯定更快(在我们的情况下从约8分钟缩短为2分钟)。


4
不确定为什么需要使用BuildKit或者Buildx。在构建之前,您可以直接从注册表拉取您的目标镜像,并使用cache-from选项。例如,如果我们要构建fooimg:tag,则在构建之前先执行docker pull fooimg:tag命令,然后再执行docker build --cache-from fooimg:tag . -t fooimg:tag命令。 - Eugen Mayer
谢谢分享。我不知道他们将这个功能添加到常规的Docker引擎中。 - Sebastian N
我真的很惊讶之前没有听说过这个 - Github 上的原始线程没有提到使用 --cache-from: https://github.com/microsoft/azure-pipelines-tasks/issues/6439关于 ACR 层缓存的线程(https://github.com/Azure/acr/issues/204)提到了 buildx,所以在阅读了 buildx 的文档后,我认为缓存机制是我们需要的。 - Sebastian N
2
请注意,使用BUILDKIT:1将需要您添加“--build-arg BUILDKIT_INLINE_CACHE=1”到构建参数中,以支持将缓存层推送到远程注册表。 - Eugen Mayer
2
仅使用 --cache-from 无法在多阶段构建中工作。实际上,您需要在最终镜像中内联缓存,以便多阶段构建从 MS 托管代理的缓存中受益。 - igor

3

如果你不想把缓存推送到容器注册表,也可以在管道VM中设置“本地”Docker层缓存。以下是需要的步骤:

- task: Docker@2
  displayName: Login to ACR
  inputs:
    command: login
    containerRegistry: $(SERVICE_NAME)
- task: Cache@2
  displayName: Cache task
  inputs:
    key: 'docker | "$(Agent.OS)" | "$(Build.SourceVersion)"'
    path: /tmp/.buildx-cache
    restoreKeys: 'docker | "$(Agent.OS)"'

- bash: |
    docker buildx create --driver docker-container --use
    docker buildx build --cache-to type=local,dest=/tmp/.buildx-cache-new --cache-from type=local,src=/tmp/.buildx-cache --push --target cloud --tag $REGISTRY_NAME/$IMAGE_NAME:$TAG_NAME .
  displayName: Build Docker image

# optional: set up deploy steps here

- task: Docker@2
  displayName: Logout of ACR
  inputs:
    command: logout
    containerRegistry: $(SERVICE_NAME)

关键在于设置 Docker 的 buildx 并使用 --cache-to--cache-from 标志运行它,而不是使用 Azure Docker 任务。你还需要使用 Cache 任务以确保在后续的管道运行中重新加载 Docker 缓存,并且需要设置手动交换步骤,其中新生成的缓存替换旧缓存。

1
在我的情况下,我必须使用buildx bake命令从构建的镜像中创建容器,@jidicula所建议的绝对是正确的。然而,我还必须添加--load标志将镜像加载到本地存储中,因为docker-container驱动程序会创建一个单独的环境(即您无法使用docker images ls命令查看镜像):docker buildx create --driver docker-container --usedocker buildx bake *--load*docker create --name=containerName image:tagdocker cp containerName:/<container-path>/ ./<host-path> - Krusty

3

看起来微软早就引入了Azure Devops的Pipeline Caching,可以缓存Docker层。请参阅此链接。


1
我认为这只缓存了最终的Docker镜像,而不是中间层,因此它实际上并没有加速Docker构建(Docker仍然会尝试重建中间层)。 - jidicula

1

您可以使用Docker@2拉取镜像,如果您有与ACR的服务连接,则无需运行login命令。此外,我不需要设置BUILDKIT_INLINE_CACHE,但如果它对您不起作用,则以下链接说明如何设置并使用它https://zws.im/‍。我的解决方案基于Kees Schollaart的解决方案:

- task: Docker@2
  displayName: "Pull image"
  inputs:
    command: pull
    containerRegistry: "$(ContainerRegistryName)"
    arguments: $(ACR_ADDRESS)/$(REPOSITORY):latest
- task: Docker@2   
  displayName: build   
  inputs:
    containerRegistry: '$(ContainerRegistryName)'
    repository: '$(REPOSITORY)'
    command: 'build'
    Dockerfile: './dockerfile '
    buildContext: '$(BUILDCONTEXT)'
    arguments: '--cache-from=$(ACR_ADDRESS)/$(REPOSITORY):latest' 
    tags: |
      $(Build.BuildNumber)
      latest
- task: Docker@2   
  displayName: "push"   
  inputs:
    command: push
    containerRegistry: "$(ContainerRegistryName)"
    repository: $(REPOSITORY) 
    tags: |
      $(Build.BuildNumber)
      latest

1

我曾经遇到过同样的问题。结果发现是任务“npm authenticate”每次插入新令牌破坏了缓存。我只需将静态的 .npmrc 文件放入 Pipeline > Library > SecureFiles 仓库中,一切都变得非常快速:

- task: DownloadSecureFile@1
  name: 'npmrc'
  displayName: 'Download of the npmrc authenticated'
  inputs:
    secureFile: '.npmrc'

- task: CopyFiles@2
  inputs:
    SourceFolder: $(Agent.TempDirectory)
    contents: ".npmrc"
    TargetFolder: $(Build.SourcesDirectory)/Code
    OverWrite: true
  displayName: 'Import of .npmrc'

- task: Docker@2
  displayName: Build an image
  inputs:
    command: build
    dockerfile: '$(Build.SourcesDirectory)/Dockerfile'
    tags: |
      $(tag)

唯一的缺点是个人访问令牌只有一年的有效期。所以你需要每年更换你的安全文件...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接