以Spring boot项目为例。传统方式是本地生成jar包,FTP上传服务器,重启服务;若是是内网测试服,也能够在服务器上安装git,在服务器上编译打包。但这都须要人为干预,因而CI/CD
就出现了。html
CI/CD的工具备不少,最流行的当属jenkins
。不过以笔者为数很少的经验来看,做为后起之秀的gitlab更简单一点,也更灵活,不会像jenkins那样笨重。固然,二者的概念都是挺多的,没有师父,光靠本身入门都不容易。java
GitLab-CI/CD流程示例git
从左往右看,首先研发人员完成需求提交代码到 GitLab。GitLab 触发一次 Build,构建好服务,而后开始跑单元测试、集成测试。等待测试结果经过后,再由负责该项目的同事进行 CodeReview,灰度发布,正式部署到线上。redis
本文基于GitLab 13.7版本spring
Pipelines comprise:docker
Jobs are executed by runners
. Multiple jobs in the same stage are executed in parallel, if there are enough concurrent runners.shell
上述包含了GitLab-DevOps整个流程的全部环节,CI/CD只是其中的一部分。npm
能够安装在任意机子上,经过它能够[在一台机子上]注册多个runner
实例到gitlab服务器。每一个runner用于执行一个或多个具体任务(如build、test)。vim
runner有如下三类,可用范围从大到小segmentfault
Shared runners
are available to all groups and projects in a GitLab instance.Group runners
are available to all projects and subgroups in a group.Specific runners
are associated with specific projects. Typically, specific runners are used for one project at a time.咱们能够直接安装GitLab Runner到宿主机,也可使用docker方式安装。注意这两种方式会影响到后续.gitlab-ci.yml
中对pipline
的定义。好比要编译maven项目,若是executor
设为shell,那么若宿主机中安装有mvn
命令,前者能够在scripts中直接使用mvn,然后者并不能,只能经过定义Dockerfile,在其中定义搭建mvn环境到编译代码的整个流程。
采用docker方式安装的话,能够参考Docker搭建本身的Gitlab CI Runner
docker pull gitlab/gitlab-runner:latest
docker run -d --name inkscreen-api-runner --restart always \ -v /srv/gitlab-runner/config:/etc/gitlab-runner \ -v /var/run/docker.sock:/var/run/docker.sock \ gitlab/gitlab-runner:latest
注册runner实例
docker exec -it inkscreen-api-runner gitlab-runner register
会让咱们填一系列配置项,以下:
Enter the GitLab instance URL (for example, https://gitlab.com/): http://192.168.1.26:9980/ Enter the registration token: cJMXGJWx7qx9AmpSc6ee Enter a description for the runner: [a0debaaf80a9]: runner for InkScreen-API project Enter tags for the runner (comma-separated): InkScreenAPI Registering runner... succeeded runner=cJMXGJWx Enter an executor: docker-ssh, shell, docker-ssh+machine, kubernetes, custom, parallels, ssh, virtualbox, docker+machine, docker: docker Enter the default Docker image (for example, ruby:2.6): maven:3-jdk-8
完过后,咱们在gitlab->xxxProjct中就能找到该runner:
接下来,就能够定义项目构建流程了。项目的构建流程是由项目根目录的.gitlab-ci.yml
文件控制的。固然了,一个pipeline能够涉及到多个runner。
定义一个pipline,如下为示例
variables: DOCKER_TLS_CERTDIR: "/certs" # stage也能够自定义 stages: - build jar # - test - build and run image #job's name 能够随意取 buildJar: stage: build jar variables: # 若要使cache生效,须指定-Dmaven.repo.local MAVEN_OPTS: "-Dmaven.repo.local=.m2" cache: key: ${CI_COMMIT_REF_SLUG} paths: - .m2 only: - dev script: # package 已包含 test 步骤,因此流程中不须要另外配置test job - mvn clean package tags: - inkscreen_api artifacts: paths: - target/admin.jar expire_in: 3600 seconds deploy: stage: build and run image image: docker:stable services: - docker:dind only: - dev variables: IMAGE_NAME: newton/inkscreen-api:$CI_COMMIT_REF_NAME PORT: 38082 script: - docker build --build-arg JAR_PATH=target/admin.jar -t $IMAGE_NAME . - docker run -p $PORT:$PORT -d --name inkscreen-$CI_COMMIT_REF_NAME --env spring.redis.host=myredis $IMAGE_NAME tags: - inkscreen_api
cache
cache
经常使用在dacker-based job之间传递文件。好比项目依赖的公共jar包,jobA辛辛苦苦从网上down了下来,结果运行完了,jobA所在容器也跟着被移除,天然里面的全部文件都不存在了。后续其它job用到相同的jar包还要从新下载。一样的,pipline屡次执行,jobA本身每次也要从新下载。
为了解决这个问题,gitlab-ci采用了cache的方式。指定文件/目录,每次job结束前将其打包,放到/etc/gitlab-runner/config.toml
中对应的[runners.docker][volumes]指定的卷内,其它job(包括本身)运行前,对应的cache都会被加载并解压到容器内。
artifacts
artifacts
是job生成的中间产物,会以压缩包(.zip)的形式生成。它会自动上传到gitlab服务器,the artifacts will be downloaded and extracted in the context of later stages。因此它和cache很像,可是设计它们的初衷是不一样的。
Don't use caching for passing artifacts between stages, as it is designed to store runtime dependencies needed to compile the project:
cache
: For storing project dependencies
Caches are used to speed up runs of a given job in subsequent pipelines, by storing downloaded dependencies so that they don't have to be fetched from the internet again (like npm packages, Go vendor packages, etc.) While the cache could be configured to pass intermediate build results between stages, this should be done with artifacts instead.
artifacts
: Use for stage results that will be passed between stages.
Artifacts are files generated by a job which are stored and uploaded, and can then be fetched and used by jobs in later stages of the same pipeline. In other words, you can't create an artifact in job-A in stage-1, and then use this artifact in job-B in stage-1. This data will not be available in different pipelines, but is available to be downloaded from the UI.
The name artifacts sounds like it's only useful outside of the job, like for downloading a final image, but artifacts are also available in later stages within a pipeline.
另外,一样key的cache会被覆盖,而artifacts一旦生成就固定了,固然咱们能够设置expire_in
,过时删除之。
咱们定义一个最简单的pipline:第一步编译生成jar包,第二步将jar包导入docker镜像并运行,在某些环节还需加入代码review。由于最后咱们会以docker容器运行jar包,因此这里不建议docker-based runner/executor的形式,由于该形式致使Docker-in-Docker
的场景,带来可能的一些麻烦且难以解决的问题(好比内嵌容器如何关联外部服务以及对外提供服务)。因此咱们直接宿主机安装GitLab Runner。
若是必定要以docker-based形式,那么可参看使用GitLab CI和Docker自动部署SpringBoot应用。在该文中,并无在runner所在宿主机中运行容器,而是将生成的镜像发布到镜像仓库,再登陆目标服务器拉取镜像运行,因此不存在Docker-in-Docker的麻烦事。
# 1.Add the official GitLab repository curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh" | sudo bash # 2.Install the latest version of GitLab Runner export GITLAB_RUNNER_DISABLE_SKEL=true; sudo -E yum install gitlab-runner
注册runner
sudo gitlab-runner register -n \ --url http://192.168.1.26:9980/ \ --registration-token cJMXGJWx7qx9AmpSc6ee \ --executor shell \ --tag-list "inkscreen_hostrunner" \ --description "Host Runner for InkScreen"
Add the gitlab-runner user to the docker group:
sudo usermod -aG docker gitlab-runner
stages: - build jar - build and run image #job's name 能够随意取 buildJar: stage: build jar variables: # 默认是clone,改成fetch加快拉取速度(若本地无则会自动clone) GIT_STRATEGY: fetch only: - dev script: - > docker run -d --rm --name justforpackage-$CI_COMMIT_REF_NAME -v "$(pwd)":/build/inkscreen -v /inkscreen/maven/m2:/root/.m2 -w /build/inkscreen maven:3-jdk-8 mvn clean package - sleep 60 tags: - inkscreen_hostrunner artifacts: paths: - louwen-admin/target/louwen-admin.jar expire_in: 3600 seconds testDeploy: stage: build and run image only: - dev variables: # 不拉取代码 GIT_STRATEGY: none IMAGE_NAME: louwen/inkscreen-api:$CI_COMMIT_REF_NAME PORT: 38082 before_script: # 移除旧容器和镜像。这里为何要写成一行,下面有讲 - if [ docker ps | grep inkscreen-$CI_COMMIT_REF_NAME ]; then docker stop inkscreen-$CI_COMMIT_REF_NAME; docker rm inkscreen-$CI_COMMIT_REF_NAME; docker rmi $IMAGE_NAME; fi script: - docker build --build-arg JAR_PATH=louwen-admin/target/louwen-admin.jar -t $IMAGE_NAME . - > docker run -d --name inkscreen-$CI_COMMIT_REF_NAME -p $PORT:$PORT --network my_bridge --env spring.redis.host=myredis -v /inkscreen/inkscreen-api/logs/:/logs/ -v /inkscreen/inkscreen-api/louwen-admin/src/main/resources/:/configs/ $IMAGE_NAME tags: - inkscreen_hostrunner
注意build jar环节咱们sleep了60秒,是由于docker run并不会等待内部脚本执行完,而是启动后就直接返回了,此时jar包还没有生成,因此此处阻塞一段时间等待打包结束。正常应该写一段脚本循环判断jar包是否已生成,若生成或超时则跳出循环,此处做为演示简单sleep。
在testDeploy任务中,before_script被我写成了一行,最第一版本是:
before_script: # 若未找到记录,则该条命令会返回1,gitlab就直接报错返回了ERROR: Job failed: exit status 1 - docker ps | grep inkscreen-$CI_COMMIT_REF_NAME - > if [ $? -eq 0 ] then docker stop inkscreen-$CI_COMMIT_REF_NAME docker rm inkscreen-$CI_COMMIT_REF_NAME docker rmi $IMAGE_NAME fi
后改成
before_script: # 将检测语句直接做为条件内置,解决了上面的问题 - > if docker ps | grep inkscreen-$CI_COMMIT_REF_NAME then docker stop inkscreen-$CI_COMMIT_REF_NAME docker rm inkscreen-$CI_COMMIT_REF_NAME docker rmi $IMAGE_NAME fi
报错syntax error near unexpected token 'fi'
,估计是换行/回车格式的缘由。上述两个问题均可以经过单独建.sh文件的方式解决,我这里简单地将全部语句排成一行。
生成镜像天然少不了Dockerfile
FROM openjdk:8-jdk-oracle MAINTAINER louwen # 外部传入,主程序路径 ARG JAR_PATH ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 COPY $JAR_PATH /app.jar EXPOSE 38082 ENTRYPOINT ["java","-jar","/app.jar"]
题外话,其实咱们彻底能够将build jar环节也放在Dockerfile中,以下
# # build jar stage # FROM maven:3-jdk-8 AS MAVEN_BUILD COPY pom.xml /build/ COPY src /build/src/ WORKDIR /build/ RUN mvn clean package # FROM openjdk:8-jdk-oracle MAINTAINER louwen COPY --from=MAVEN_BUILD /build/target/*.jar /app.jar ENTRYPOINT ["java","-jar","/app.jar"]
目前较流行的代码检测工具是SonarQube,不过其社区版本对同一个代码仓库没法区分不一样分支,从而实现按代码的不一样分支显示对应分支的扫描结果。这里咱们使用Gitlab-CI的Code Quality stage
,其使用的是Codeclimate
,它是为代码质量分析平台提供的一个命令行接口工具,经过它能够在本机 Docker 容器中对要分析的代码执行质量分析,并生成分析报告。咱们熟知经常使用的代码质量检测工具例如 SonarQube、CheckStyle 等等,而 Codeclimate 接入了这些工具,并且支持咱们自定义检测工具。
按照官方说法,使用Code Quality
须要基于docker-based runner/executor,因此咱们另外使用docker方式安装GitLab-Runner并注册一个runner(参考上述概念
小节),executor选择docker。
include: - template: Code-Quality.gitlab-ci.yml # 如下配置参考网上一些资料,听说是官方示例,然而我没有在官方文档找到 code_quality: image: docker:stable variables: DOCKER_DRIVER: overlay2 # gitlab 13.6及以后版本支持 REPORT_FORMAT: html allow_failure: true services: - docker:dind script: # 镜像版本号格式参看 https://gitlab.com/gitlab-org/ci-cd/codequality/-/tree/master#versioning-and-release-cycle # - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') - docker run --net=host --env SOURCE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock "registry.gitlab.com/gitlab-org/security-products/codequality:${VERSION:-latest}" /code artifacts: paths: [ gl-code-quality-report.html ] tags: - InkScreenAPI
执行的时候可能会卡在拉取镜像环节。手动docker pull registry.gitlab.com/gitlab-org/security-products/codequality:latest
,发现各类超时。我开个阿里云香港ECS的抢占式实例(便宜)而后docker pull | save | load
将镜像文件迁移到公司测试服,仍是报Unable to find image 'registry.gitlab.com/gitlab-org/security-products/codequality:latest' locally
,不知如何将host中的镜像映射到docker:stable
中。看来仍是得kexue上网。
理论上,须要专人在合适的时候对提交的代码进行质量把关,通常这工做能够放在Merge Request
下进行。Merge Request的工做流程能够参看在团队中使用GitLab中的Merge Request工做模式
dial tcp: lookup docker on 192.168.1.1:53: no such host
错误。
This error occurs with docker-based gitlab runners such as the one we’re that are configured using a docker executor. The error message means that the inner docker container doesn’t have a connection to the host docker daemon.
解决:将/etc/gitlab-runner/config.toml
中对应的[runners.docker]节点设置privileged = true
,增长卷映射volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"]
或在.gitlab-ci.yml的job定义中增长services: - docker:dind
。
Cannot connect to the Docker daemon at tcp://docker:2375. Is the docker daemon running?
错误
解决:增长卷映射volumes = ["/certs/client", "/cache"]
,而后在.gitlab-ci.yml中增长变量DOCKER_TLS_CERTDIR: "/certs"
。
拉取代码时提示warning: failed to remove xxxx: Permission denied
简单粗暴地编辑/etc/passwd,将gitlab-runner帐号对应的uid:gid
改成0:0
(和root同样)。
Code Quality提示docker: Error response from daemon: Head https://registry.gitlab.com/v2/gitlab-org/security-products/codequality/manifests/13-7-stable: Get https://gitlab.com/jwt/auth?scope=repository%3Agitlab-org%2Fsecurity-products%2Fcodequality%3Apull&service=container_registry: dial tcp [2606:4700:90:0:f22e:fbec:5bed:a9b9]:443: connect: cannot assign requested address.
在scripts->docker run增长参数--net=host
在pipline流程执行过程当中,咱们但愿有任何风吹草动都能及时收到消息,邮件就是一个比较好的提醒方式。
vi /etc/gitlab/gitlab.rb
### GitLab email server settings ###! Docs: https://docs.gitlab.com/omnibus/settings/smtp.html ###! **Use smtp instead of sendmail/postfix.** gitlab_rails['smtp_enable'] = true gitlab_rails['smtp_address'] = "smtp.exmail.qq.com" gitlab_rails['smtp_port'] = 465 gitlab_rails['smtp_user_name'] = "xxxx@yyyy.com" gitlab_rails['smtp_password'] = "xxxxxxxx" gitlab_rails['smtp_domain'] = "exmail.qq.com" gitlab_rails['smtp_authentication'] = "login" gitlab_rails['smtp_enable_starttls_auto'] = true gitlab_rails['smtp_tls'] = true ### Email Settings gitlab_rails['gitlab_email_enabled'] = true ##! If your SMTP server does not like the default 'From: gitlab@gitlab.example.com' ##! can change the 'From' with this setting. ##! 要与上面的 smtp_user_name 保持一致 gitlab_rails['gitlab_email_from'] = 'xxxx@yyyy.com' # gitlab_rails['gitlab_email_display_name'] = 'Example' # gitlab_rails['gitlab_email_reply_to'] = 'noreply@example.com' # gitlab_rails['gitlab_email_subject_suffix'] = '' # gitlab_rails['gitlab_email_smime_enabled'] = false # gitlab_rails['gitlab_email_smime_key_file'] = '/etc/gitlab/ssl/gitlab_smime.key' # gitlab_rails['gitlab_email_smime_cert_file'] = '/etc/gitlab/ssl/gitlab_smime.crt' # gitlab_rails['gitlab_email_smime_ca_certs_file'] = '/etc/gitlab/ssl/gitlab_smime_cas.crt'
gitlab-ctl reconfigure
使配置生效
测试
gitlab-rails console irb(main):003:0> Notify.test_email('whatever@qq.com', 'Message Subject', 'Message Body').deliver_now
登陆whatever@qq.com查看受否收到信件。
jenkins + gitlab
若是使用jenkins做为CI/CD工具,代码由gitlab托管,那么它们之间的交互须要两个token:
mvn package、install、deploy都干了什么
由上可知:
alpine
Alpine Linux
是一个社区开发的面向安全应用的轻量级Linux发行版。不少镜像都会专门基于Alpine构建,大小会小不少。好比:
修改GitLab-ce域名
刚部署好的GitLab新建的项目ssh地址通常是个短连接如git@AKDJF3ld:xxx,有时候会不太好使,能够经过配置文件的修改,指向域名。
vim /opt/gitlab/embedded/service/gitlab-rails/config/gitlab.yml # host: 192.168.xx.xx # port: xxxx gitlab-ctl restart
当谈到 GitLab CI 的时候,咱们该聊些什么(上篇)
什么是devops,基于Gitlab从零开始搭建本身的持续集成流水线(Pipeline)
持续集成之.gitlab-ci.yml篇
Building Docker images with GitLab CI/CD
自动化 DevOps 使用 Codeclimate 执行代码质量分析
GitLab CI/CD
基于 Gitlab 的 Code Review 最佳实践