zoukankan      html  css  js  c++  java
  • 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地址

  • 相关阅读:
    webGL 光照
    Go语言入门之指针的使用
    Go语言入门之变量声明
    服务器开启防火墙
    Linux安全之密钥登录
    Mysql优化之my.cnf参数优化
    程序员工作法
    laravel中新增路由文件
    Mysql用户管理(远程连接、授权)
    Laravel通过Swoole提升性能
  • 原文地址:https://www.cnblogs.com/wh-blog/p/14858482.html
Copyright © 2011-2022 走看看