首先啟動一個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,目錄格式不一樣,因此你的配置要根據(jù)主機os類型而變化。而且你還要關注gitlab-runner服務可讀寫的目錄權限問題。wow,真是太復雜了。但是掛載到另一個容器,gitlab-runner幫你解決了所有的問題。只是犧牲了一點點效率。無非就是增加了壓縮和解壓緩存的時間。緩存在所屬的gitlab-runner(上述例子中所屬為:gr1)后續(xù)的所有pipeline可見。
工件:
artifacts:
paths:
- target/multi-renter.jar
工件和緩存類似,只不過其往往在一個pipeline中生存工件的后續(xù)job中可見。在web ui中也可見,但是在另一個pipeline中不可見。比如,我在mvn:package:這個job中編譯了一個jar文件,在編譯的下一個stage中想把這個jar復制到docker鏡像,就應該用工件去傳遞這個jar,而不是使用緩存。
構建docker image
構建docker鏡像,官方給了幾種方式
對于docker executor,每次都是啟動一個新的container構建,新的container是一個非常clear的環(huán)境,因此上次構建使用到的base image和構建的緩存都無法使用,所以閱讀下述方式時,重點關注如何加速構建速度,解決方式是否復雜以及帶來的問題。
kaniko
kaniko可以在不使用docker特權模式下構建docker鏡像,并且可以利用container registry加速構建。主要過程如下
- kaniko在--cache-dir目錄下查找base image緩存(即docker file中的from命令)這里需要注意,如果這里沒有命中,kaniko會去download image,但是不會寫入緩存,因此,即使你通過卷持久化了這個目錄,下次執(zhí)行依然不會命中。因此我們需要提前將鏡像緩存到--cache-dir,kanico關于cache base image的講解。我使用了一下warmer,發(fā)現(xiàn)按照官網(wǎng)提供的命令,不加-f,如果緩存沒命中,shell返回0,但實際報錯了,加上-v debug可以看到,如果緩存命中了,什么都不發(fā)生。加上-f,則每次都會強制pull鏡像覆蓋cache,消耗一定時間。這可能是一個bug。我們要做的就是,先使用warmer下載base image,然后把緩存的目錄掛載到kanico容器中使用(配置/srv/gitlab-runner/config/config.toml文件)。我只是想編譯而已,為何要我解決如此麻煩的緩存問題?另外注意一下,kanico的executor和warmer都有相同名稱的參數(shù),所以這里需要自己體會下其含義,不要搞混了。
- kaniko對run命令進行緩存,因此執(zhí)行run命令前,會到指定的遠程registry查看有沒有已經(jīng)緩存的層,命中的話就使用緩存
- 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
這種方式需要以特權方式執(zhí)行docker,帶來不安全的因素。同時,構建也是非常慢的。因此也需要使用緩存去加速構建。原理是:docker先從registry拉取上次構建的鏡像。然后構建的時候指定--cache-from=<last-image>,也就是說以上次的鏡像作為緩存構建。構建完畢后,上次鏡像,作為下次的構建緩存使用。但是需要注意的是,仍需要花費一部分時間在download image上。如果registry在內(nèi)網(wǎng),其實下載和上傳都是蠻快的,可以接受。
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中的緩存。當然,有利就有弊,此種方式的缺點有:
- 由于共享了主機的docker socket,所以docker命令都是向主機發(fā)出的命令,比如docker rm -f $(docker ps -a -q)會把主機上的所有docker容器刪除了,包括gitlab-runner容器。
- 并發(fā)工作可能無法正常執(zhí)行;創(chuàng)建具有特定名稱的容器,則它們可能會相互沖突。
- 將源倉庫中的文件和目錄共享到容器中可能無法正常工作,因為卷安裝是在主機而不是構建容器的上下文中完成的。
你必須時刻注意,你的docker命令是在主機執(zhí)行的,而不是容器內(nèi)部。 - 需要修改gitlab-runner的配置,掛載主機的docker socket,但是不同的host system的sock文件路徑不同,這目前只能在host上配置。
- 在k8s中,docker binding脫離了k8s的控制,是非常危險的行為。有的k8s集群通過名稱空間做環(huán)境隔離,想想一下,開發(fā)環(huán)境的docker命令刪除了線上環(huán)境的容器。
如何綁定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執(zhí)行器。