Gitlab CI/CD 之 Gitlab Runner Docker Executor 緩存問題


定義一個流水線

在我們使用Gitlab的CICD的時候會定義一個Pipeline,Pipeline會由多個stage組成,stage整體是串行的,中間會存在並行任務。
如下是一個前端vue、后端.net的項目的自動化打包流水線

image: docker:20.10.5-dind

stages:
    - prebuild
    - build
    - test
    - publish-ui
    - publish-api
    - image

prebuild:
    image: node:15
    stage: prebuild
    tags:
        - builder
    only:
        changes:
            - app/package.json
    cache:
        key:
            files:
                - app/package.json
        paths:
            - app/node_modules/
    script:
        - cd app
        - npm install

build-ui:
    image: node:15
    stage: build
    tags:
        - builder
    only:
        changes:
            - app/**/*
    cache:
        key:
            files:
                - app/package.json
        policy: pull
        paths:
            - app/node_modules/
    script:
        - cd app
        - npm run build

build-api:
    image: dotnet/sdk:5.0
    stage: build
    only:
      changes:
        - api/**/*
    tags:
        - builder
    script:
        - cd api
        - dotnet build

test:
    image: dotnet/sdk:5.0
    stage: test
    only:
      changes:
        - api/**/*
    tags:
        - builder
    script: 
        - cd api
        - dotnet test

publish-ui:
    image: node:15
    stage: publish-ui
    tags:
        - builder
    only:
        refs:
            - main
    cache:
        - key: "$CI_COMMIT_REF_SLUG-ui"
          policy: push
          paths:
            - app/dist/
        - key:
            files:
                - app/package.json
          policy: pull
          paths:
            - app/node_modules/
    script:
        - cd app
        - npm run build

publish-api:
    image: dotnet/sdk:5.0
    stage: publish-api
    tags:
        - builder
    only:
        - main
    cache:
       key: "$CI_COMMIT_REF_SLUG-api"
       policy: push
       paths:
         - api/publish/
    script: 
        - dotnet publish -c Release -o api/publish

image:
    stage: image
    tags:
        - builder
    only:
        - main
    cache:
       - key: "$CI_COMMIT_REF_SLUG-ui"
         policy: pull
         paths:
            - app/dist/
       - key: "$CI_COMMIT_REF_SLUG-api"
         policy: pull
         paths:
            - api/publish/
    before_script:
        - docker login -u "$LOCAL_REGISTRY_USER" -p "$LOCAL_REGISTRY_PASSWORD" $LOCAL_REGISTRY
    script: 
        - docker build -f .gitlab/Dockerfile -t $LOCAL_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:latest .
        - docker push $LOCAL_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:latest
    after_script:
      - docker logout $LOCAL_REGISTRY

流水線中一共6個環節:prebuild、build、test、publish-ui、publish-api、image;其中build存在一個並行任務、其余都是串行

  • prebuild
    prebuild只針對vue項目,用於安裝npm的包,只有當package.json存在修改的時候才會執行,否則就使用緩存
  • build
    每當有人push代碼到服務器就會執行,檢查代碼是否能編譯通過,如果不通過流水線失敗,不能提mr
  • test
    針對.net項目的單元測試,如果不通過流水線失敗,不能提mr
  • publish-ui
    發布前端項目,只有當main分支上有更改的時候發生
  • publish-api
    發布后端項目,並把前端項目放在wwwroot下,只有當main分支上有更改的時候發生
  • image
    打包鏡像,只有當main分支上有更改的時候發生

引入緩存

我們知道Pipeline的每個Stage都是無狀態的,運行完成后,產生的中間文件就會被丟棄掉,為了得到上一個Stage產生的文件,就需要將文件保存到緩存中,以便下一個Stage可以直接哪來使用。
緩存的幾個屬性:

  • paths
    指定要緩存的文件或者文件夾,只能是本倉庫文件夾下的相對路徑,所以生成的中間文件也只能放在當前倉庫路徑下的相對路徑中,不能以放在/開頭的路徑中(如:/app等);
  • key
    每個緩存的鍵值,如果不指定就是default,那么整個倉庫就只有一份兒緩存(多個key就會有多個文件夾用來存放緩存文件),如果兩個Stage中都有使用不同的緩存,那么下一個Stage會覆蓋上一個Stage的緩存(一般情況下這樣也沒有任何問題,下一次Pipeline會先執行上一個Stage)。
  • policy
    緩存策略,分為pull、push、pull-push,
  1. pull表示當前Stage只會拉取緩存下來使用而不會對其進行改變;
  2. push表示當前Stage只會對緩存進行上傳
  3. pull-push表示當前Stage會先拉下緩存,結束后會再次上傳緩存
    默認策略是pull-push

緩存還可以全局定義(全局定義緩存與stages同一級即可),具有繼承特性,也可以禁用緩存。

// 此任務禁用緩存
job_name:
  cache: {}

帶來的問題

緩存解決了文件在不同Stage中的共享問題,同時也引入了一個並行任務問題。

問題描述

當一個倉庫中同時有兩個流水線、或者有並行Stage需要用到Cache的時候,Cache會有問題:要么找不到Cache、要么用的老的Cache。

出現問題的原因

通過研究發現runner的緩存文件存放在:/var/lib/docker/volumes/下以runner-{runnerid}-開頭的文件夾中,
每個項目的緩存存放方式:runner-{runnerid}-projects-{projectid}-concurrent-{num}-cache-3c3f060a0374fc8bc39395164f415a70|c33bcaa1fd2c77edfc3893b41966cea8
以3c3f060a0374fc8bc39395164f415a70結尾的文件夾中存放的就是緩存文件,以c33bcaa1fd2c77edfc3893b41966cea8結尾的文件夾中存放的是代碼源文件。
當任務出現並行的時候runner會創建多個Pipeline實例文件夾concurrent-0、concurrent-1...每個文件夾中保存當前並行實例的緩存數據,且每個job的並行id是不固定的;
如下兩個並行Pipeline A、B,有5個Stage,並行執行會產生10個job:

A:1-2-3-4-5
B:1-2-3-4-5
  1. 第一種情況
    假如3、4Stage需要用到緩存,那么可能會出現什么情況?
    當A3在執行的時候緩存文件夾是concurrent-0、B3是concurrent-1,兩個任務同時完成;
    當A4在執行的時候緩存文件夾是concurrent-1、B4是concurrent-0,這樣兩個緩存就出現了交叉,出現嚴重問題。
  2. 第二種情況
    假如3、4Stage需要用到緩存,且3是一個並行任務(pub-ui、pub-api)
    那么就可能會同時出現4個並行實例,concurrent-0、concurrent-1、concurrent-2、concurrent-3;
    假如4需要3的兩個緩存,那么4要么永遠都拿不到3中的其中一個緩存,要么拿到老的緩存。
    這就造成了很驗證的緩存錯亂的問題。

如何解決此問題

  1. 從根本上解決
    上分布式緩存(s3, gcs, azure.),這個沒實踐過官方反正這么說的;
  2. 治標不治本
    同一個項目中並行的job不要用緩存、用到緩存的Stage走串行、不要同時發生多個使用到緩存的Pipeline實例。

環境說明:
以上問題的對應的gitlab-runner版本為v13.10.0
參考鏈接:
官方cache文檔地址
官方問題issue地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM