gitlab runner使用docker executor处理缓存 ,工件,构建docker镜像

首先启动一个gitlab runner服务,并注册一个docker executor,这是比较简单的,此处暂不赘述

本文主要讲述使用docker executor,如何处理缓存,工件,构建docker镜像的问题,如何尽可能在保证安全的前提下加速编译过程。

以java项目为例,配置ci/cd文件

stages:
  - package
  - build

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"

mvn:package:
  stage: package
  tags:
    - gr1
  image: maven:3.6.3-jdk-8
  #缓存mvn库
  cache:
    key: mvn_repo
    paths:
      - .m2/repository
  artifacts:
    paths:
    - target/multi-renter.jar
  script:
    - mvn package

缓存:

cache:
key:mvn_repo
paths:
- .m2/repository
这是指定一个缓存,在当前的gr1这个runner中的任何job中有效,缓存会保存在主机的另一个docker container(dockers ps -a 可以看到 gitlab runner helper),并最终挂在到主机的一个位置。为什么不直接挂载到主机上呢,那样效率不是更高吗?当然,直接挂载到主机上,效率更高,但是却让当前的job和主机的某个目录耦合,试想,如果其他人的job也往主机挂载,并且恰好挂载到和你一样的目录了呢?另外,不同的主机类型,比如windows和linux,目录格式不一样,因此你的配置要根据主机os类型而变化。而且你还要关注gitlab-runner服务可读写的目录权限问题。wow,真是太复杂了。但是挂载到另一个容器,gitlab-runner帮你解决了所有的问题。只是牺牲了一点点效率。无非就是增加了压缩和解压缓存的时间。缓存在所属的gitlab-runner(上述例子中所属为:gr1)后续的所有pipeline可见。

工件:

artifacts:
paths:
- target/multi-renter.jar
工件和缓存类似,只不过其往往在一个pipeline中生存工件的后续job中可见。在web ui中也可见,但是在另一个pipeline中不可见。比如,我在mvn:package:这个job中编译了一个jar文件,在编译的下一个stage中想把这个jar复制到docker镜像,就应该用工件去传递这个jar,而不是使用缓存。

构建docker image

构建docker镜像,官方给了几种方式
对于docker executor,每次都是启动一个新的container构建,新的container是一个非常clear的环境,因此上次构建使用到的base image和构建的缓存都无法使用,所以阅读下述方式时,重点关注如何加速构建速度,解决方式是否复杂以及带来的问题。

kaniko

kaniko可以在不使用docker特权模式下构建docker镜像,并且可以利用container registry加速构建。主要过程如下

  1. kaniko在--cache-dir目录下查找base image缓存(即docker file中的from命令)这里需要注意,如果这里没有命中,kaniko会去download image,但是不会写入缓存,因此,即使你通过卷持久化了这个目录,下次执行依然不会命中。因此我们需要提前将镜像缓存到--cache-dir,kanico关于cache base image的讲解。我使用了一下warmer,发现按照官网提供的命令,不加-f,如果缓存没命中,shell返回0,但实际报错了,加上-v debug可以看到,如果缓存命中了,什么都不发生。加上-f,则每次都会强制pull镜像覆盖cache,消耗一定时间。这可能是一个bug。我们要做的就是,先使用warmer下载base image,然后把缓存的目录挂载到kanico容器中使用(配置/srv/gitlab-runner/config/config.toml文件)。我只是想编译而已,为何要我解决如此麻烦的缓存问题?另外注意一下,kanico的executor和warmer都有相同名称的参数,所以这里需要自己体会下其含义,不要搞混了。
  2. kaniko对run命令进行缓存,因此执行run命令前,会到指定的远程registry查看有没有已经缓存的层,命中的话就使用缓存
  3. kaniko将构建好的image提交到registry
    这是个好东西,我简单试用了一下,官方给的示例多是k8s中用,如果不在k8s中用,如何缓存base image是一个问题,我暂时还没有在官方找到一个比较好的方式。
    kaniko拉取base image方式和docker pull不一样,测试使用warmer缓存java:8和使用docker pull java:8一个耗时不到一分钟,一个约3分钟。
docker:build:
  stage: build
  tags:
    - gr1
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  script:
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG --insecure

docker in docker

这种方式需要以特权方式执行docker,带来不安全的因素。同时,构建也是非常慢的。因此也需要使用缓存去加速构建。原理是:docker先从registry拉取上次构建的镜像。然后构建的时候指定--cache-from=,也就是说以上次的镜像作为缓存构建。构建完毕后,上次镜像,作为下次的构建缓存使用。但是需要注意的是,仍需要花费一部分时间在download image上。如果registry在内网,其实下载和上传都是蛮快的,可以接受。

docker:build:
  stage: build
  tags:
    - gr1
  image: docker:19.03.1
  services:
    - name: docker:19.03.1-dind
      command: ['--insecure-registry=your.registry.com:port']
  variables:
    # Use TLS https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#tls-enabled
    DOCKER_HOST: tcp://docker:2375
    DOCKER_TLS_CERTDIR: ""
    #DOCKER_TLS_CERTDIR: "/certs"
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker pull $CI_REGISTRY_IMAGE:latest || true
    - docker build --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:latest --build-arg JAR_FILE=target/*.jar .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - docker push $CI_REGISTRY_IMAGE:latest

DOCKER_HOST的配置参见docker dockerhub,主要注意一下证书如果配置了,端口号会是2376,否则是2375。
如果使用了不安全的registry,那么需要指定command: ['--insecure-registry=your.registry.com:port']

docker socket binding

由于此方式最终实际上是使用的host的docker daemon,因此,镜像和构建的缓存都是直接使用host中的缓存。当然,有利就有弊,此种方式的缺点有:

  1. 由于共享了主机的docker socket,所以docker命令都是向主机发出的命令,比如docker rm -f $(docker ps -a -q)会把主机上的所有docker容器删除了,包括gitlab-runner容器。
  2. 并发工作可能无法正常执行;创建具有特定名称的容器,则它们可能会相互冲突。
  3. 将源仓库中的文件和目录共享到容器中可能无法正常工作,因为卷安装是在主机而不是构建容器的上下文中完成的。
    你必须时刻注意,你的docker命令是在主机执行的,而不是容器内部。
  4. 需要修改gitlab-runner的配置,挂载主机的docker socket,但是不同的host system的sock文件路径不同,这目前只能在host上配置。
  5. 在k8s中,docker binding脱离了k8s的控制,是非常危险的行为。有的k8s集群通过名称空间做环境隔离,想想一下,开发环境的docker命令删除了线上环境的容器。
如何绑定docker.sock?

修改gitlab-runner服务的配置文件 /srv/gitlab-runner/config/config.toml
volumes = ["/cache","/var/run/docker.sock:/var/run/docker.sock"]

[[runners]]
  name = "gr1"
  url = "http://gitlab.lbl.com"
  token = "mJJWevdY42sJQ84syyN9"
  executor = "docker"
  [runners.custom_build_dir]
  [runners.docker]
    tls_verify = false
    image = "ruby:2.6"
    privileged = true
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache","/var/run/docker.sock:/var/run/docker.sock"]
    shm_size = 0
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]

ci/cd文件配置

docker:build:
  stage: build
  tags:
    - gr1
  image:
    name: docker:19.03.1
#  before_script:
    #登录到gitlab集成的注册表
#    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:latest --build-arg JAR_FILE=target/*.jar .
    #推送到gitlab集成的注册表
#    - docker push $CI_REGISTRY_IMAGE:latest

接下来,让我们看看,如果使用k8s执行器。

你可能感兴趣的:(gitlab runner使用docker executor处理缓存 ,工件,构建docker镜像)