使用GitLab+Jenkins实现CI—CD?
一、今天想干啥?
今天我们来聊聊如何快速落地GitLab + Jenkins + Harbor 工具链。
请注意这里的关键词:快速(有多快呢?我希望这个时间是5分钟。)
我知道你想要一条闪闪亮的工具链来支撑你的应用 CICD 流程,你想要最佳实践,你想要既灵活又简单还易维护,你有一肚子的既要,又要,还要……
行,今天我就给你一个既有,又有,还有的《GitLab + Jenkins + Harbor 落地方案》。
二、今天干点啥?
今天我们要搭建一条怎样的工具链呢?且看效果图:
- 首先我们需要完成 GitLab、Jenkins 和 Harbor 三个工具的部署;
- 接着我们需要在 GitLab 上创建一个代码库,并且在 Jenkins 上创建相应的流水线,这个流程最好也自动化(确实可以自动化);
- 然后适当地配置这三个工具,实现如下 CI 流程:
- 当用户推送代码到 GitLab,也就是 GitLab 上相应代码库产生 push 或者 merge 事件的时候,这个事件能够自动触发 Jenkins 上的流水线执行;
- Jenkins 上流水线执行的结果能够回显到 GitLab;
- Jenkins 上完成了编译、构建等等流程后,最终制品是一个容器镜像,这个镜像可以被推送到 Harbor 上。
三、今天怎么干?
我准备使用云原生的方式来部署这三个工具,原因不赘述。
当然我也知道多数情况下你并不需要考虑 GitLab 如何部署,因为95% 的概率你们公司已经有可用的 GitLab 了,或者你们考虑使用 SaaS 版的 GitLab。外加 Kubernetes 上部署 GitLab 的复杂度不低,运维成本高,所以,GitLab 的高可用部署不是本文重点,我们把重点放在如何部署和配置好 Jenkins + Harbor,然后对接 GitLab,走通一个 CI 流程。
综上,今天我准备 sale 的部署模式是:
- GitLab:Docker
- Jenkins:Helm(Kubernetes)
- Harbor:Helm(Kubernetes)
3.1、常规打法
如果按照常理出牌,这时候我们应该是翻阅三个工具的官网,学习部署流程和配置步骤,然后总结最佳实践,一步步试错,一步步改进……
听起来就复杂。
这个流程不应该让所有人都重头体验一遍,被折磨一遍。假如有人已经研究了一遍这些工具的部署模式,并且将这个流程代码化,做一个工具出来,并且开源免费,让大家开箱即用,那该多好!
3.2、不走寻常路
没错,你已经猜到了,我不打算按常理出牌,我要找一个能够管理 DevOps 工具链的工具!
有这种工具?还真有!
DevStream就干这事。DevStream 是啥?一句话:一个 DevOps工具链管理器。
我们看下 DevStream 如何完成这三个工具的落地:
DevStream 官网里有这么一个图。所以,这个花里胡哨的DevStream 做了啥?
从上面的流程图,结合官方文档和源码,大致我可以猜到它的工作流和原理:
- DevStream 首先将 GitLab、Jenkins、Harbor 等工具的部署流程代码化,通过插件的形式支持这些工具的安装部署;
- 工具部署完成后,DevStream 会从 SCM(GitHub 或者 GitLab 都可以)下载一个项目脚手架模板,模板源码在这里;这个模板支持高度自定义,本质就是将一些需要自定义的内容抽离成变量,供用户自由渲染,然后批量生产项目脚手架;
- 接着 DevStream 根据用户给定的配置文件渲染模板库,然后将其上传到 SCM(GitHub 或者 GitLab 都可以);
- 然后 DevStream 会配置 Jenkins,安装一些必要的插件等,用户支持最终的 Pipeline 顺利执行;
- DevStream 期望 Pipeline 配置通过 Jenkinsfile 来定义,这个 Jenkinsfile 也是通过模板的方式保存,可以灵活渲染。比如官网示例中 Jenkinsfile 模板保存在这里;DevStream 执行的时候会下载这个 Jenkinsfile 模板(当然,这个模板也支持自定义,支持放到 GitLab 或者其他任何 web 服务器上),下载后渲染用户自定义变量,然后将其写入刚才创建的项目脚手架对应的代码库里;
- 接着 DevStream 就可以调用 Jenkins api,完成 Pipeline 创建了。没错,创建 Pipeline 的时候,需要的 Jenkinsfile、项目地址等信息都有了,所以这里的 Pipeline 配置很轻量;
- 最后 DevStream 还需要调用 GitLab api 完成webhook的创建,这样 SCM(GitHub 或者 GitLab)上的事件(push、merge 等)才能顺利通知到 Jenkins,从而触发 Pipeline 执行。
到这里 DevStream 基本就打完收工了,这时候如果你往这个代码库里的主分支 push 了一个commit,GitLab 就会直接触发 Jenkins 上流水线运行;进而 Jenkins 上的流水线执行状态也会直接回显到 GitLab 上;当然,Jenkins 里构建的产物,比如 Docker container image(s) 也会被 push 到 Harbor(没错,这个过程是定义在 Jenkinsfile 里的,你可以灵活修改;同时 Harbor 也不一定非得是 Harbor,你可以直接改成其他镜像仓库的地址,从而让 Jenkins 对接到云厂商提供的镜像仓库服务里也完全 OK)。
四、开干吧!
考虑到插件的依赖顺序,外加 Jenkins、GitLab、Harbor 等工具的部署属于"基础设施",几乎只需要执行一次, 而 Repo Scaffolding 和 Jenkins Pipeline 的创建属于"配置"过程,可能要执行多次(比如不断新增 Repo 和 Pipeline 等), 所以我们分2步来完成这条工具链的搭建过程。
4.1、工具链部署
先下载一个 DevStream 的 CLI,参考这个文档。有了 dtm 之后,我们就该着手准备配置文件了(下面配置保存到 config.yaml 里):
---varFile:""If not empty, use the specified external variables config filetoolFile:""If not empty, use the specified external tools config filepluginDir:""If empty, use the default value: ~/.devstream/plugins, or use -d flag to specify a directorystate:state config, backend can be local, s3 or k8sbackend:localoptions:stateFile:devstream-1.state---tools:-name:gitlab-ce-dockerinstanceID:defaultdependsOn:[]options:hostname:gitlab.example.comgitlabHome:/srv/gitlabsshPort:30022httpPort:30080httpsPort:30443rmDataAfterDelete:falseimageTag:"rc"-name:jenkinsinstanceID:defaultdependsOn:[]options:repo:name:jenkinsurl:https://charts.jenkins.iochart:chartPath:""chartName:jenkins/jenkinsnamespace:jenkinswait:truetimeout:5mupgradeCRDs:truevaluesYaml:|serviceAccount:create: truename: jenkinscontroller:adminUser: "admin"adminPassword: "changeme"ingress:enabled: truehostName: jenkins.example.cominstallPlugins:- kubernetes:3600.v144b_cd192ca_a_- workflow-aggregator:581.v0c46fa_697ffd- git:4.11.3- configuration-as-code:1512.vb_79d418d5fc8additionalPlugins:install "GitHub Pull Request Builder" plugin, see https://plugins.jenkins.io/ghprb/ for more details- ghprbinstall "OWASP Markup Formatter" plugin, see https://plugins.jenkins.io/antisamy-markup-formatter/ for more details- antisamy-markup-formatterEnable HTML parsing using OWASP Markup Formatter Plugin (antisamy-markup-formatter), useful with ghprb plugin.enableRawHtmlMarkupFormatter: trueJenkins Configuraction as Code, refer to https://plugins.jenkins.io/configuration-as-code/ for more detailsnotice: All configuration files that are discovered MUST be supplementary. They cannot overwrite each others configuration values. This creates a conflict and raises a ConfiguratorException.JCasC:defaultConfig: true-name:harborinstanceID:defaultdependsOn:[]options:chart:valuesYaml:|externalURL: http://harbor.example.comexpose:type: ingresstls:enabled: falseingress:hosts:core: harbor.example.comchartmuseum:enabled: falsenotary:enabled: falsetrivy:enabled: falsepersistence:persistentVolumeClaim:registry:storageClass: ""accessMode: ReadWriteOncesize: 5Gijobservice:storageClass: ""accessMode: ReadWriteOncesize: 1Gidatabase:storageClass: ""accessMode: ReadWriteOncesize: 1Giredis:storageClass: ""accessMode: ReadWriteOncesize: 1Gi
这里的配置项并不难看懂,推荐大伙执行后面的命令前先仔细看一遍这个配置文件,按需调整。比如里面配置了几个工具的域名啥的,这些都可以改。
然后就可以开始初始化了(主要是插件下载):
dtm init -f config.yaml
然后执行 apply 开始部署:
dtm apply -f config.yaml -y
这时候你会看到和谐的日志:
2022-10-08 09:43:13 ℹ[INFO]Apply started.
2022-10-08 09:43:13 ℹ[INFO]Using dir to store plugins.
2022-10-08 09:43:13 ℹ[INFO]Usinglocalbackend. State file: devstream-1.state.
2022-10-08 09:43:13 ℹ[INFO]Tool(gitlab-ce-docker/default)found in config but doesnt exist in the state, will be created.2022-10-08 09:43:13 ℹ [INFO] Tool (jenkins/default) found in config but doesnt exist in the state, will be created.
2022-10-08 09:43:13 ℹ[INFO]Tool(harbor/default)found in config but doesnt exist in the state, will be created.2022-10-08 09:43:13 ℹ [INFO] Start executing the plan.2022-10-08 09:43:13 ℹ [INFO] Changes count: 3.2022-10-08 09:43:13 ℹ [INFO] -------------------- [ Processing progress: 1/3. ] --------------------2022-10-08 09:43:13 ℹ [INFO] Processing: (gitlab-ce-docker/default) -> Create ...2022-10-08 09:43:13 ℹ [INFO] Cmd: docker image ls gitlab/gitlab-ce:rc -q.2022-10-08 09:43:13 ℹ [INFO] Running container as the name 2022-10-08 09:43:13 ℹ [INFO] Cmd: docker run --detach --hostname gitlab.example.com --publish 30022:22 --publish 30080:80 --publish 30443:443 --name gitlab --restart always --volume /srv/gitlab/config:/etc/gitlab --volume /srv/gitlab/data:/var/opt/gitlab --volume /srv/gitlab/logs:/var/log/gitlab gitlab/gitlab-ce:rc.Stdout: 53e30ad85faf7e9d6d18764450bb8458db46b388b690b7c8b7a7cc6d0deb283a2022-10-08 09:43:14 ℹ [INFO] Cmd: docker inspect --format={{json .Mounts}}gitlab.2022-10-08 09:43:14 ℹ [INFO] GitLab access URL: http://gitlab.example.com:300802022-10-08 09:43:14 ℹ [INFO] GitLab initial root password: execute the command -> docker exec -it gitlab grepPassword:/etc/gitlab/initial_root_password
2022-10-08 09:43:14 ✔[SUCCESS]Tool(gitlab-ce-docker/default)Createdone.
2022-10-08 09:43:14 ℹ[INFO]--------------------[Processing progress: 2/3.]--------------------
2022-10-08 09:43:14 ℹ[INFO]Processing:(jenkins/default)-> Create ...
2022-10-08 09:43:15 ℹ[INFO]Creating or updating helm chart ...
2022/10/08 09:43:16 creating13resource(s)2022/10/08 09:43:16 beginningwaitfor13resources with timeout of 5m0s
2022/10/08 09:43:16 StatefulSet is not ready: jenkins/jenkins.0out of1expected pods are ready
...
2022/10/08 09:44:18 StatefulSet is not ready: jenkins/jenkins.0out of1expected pods are ready
2022/10/08 09:44:20 release installed successfully: jenkins/jenkins-4.1.17
2022-10-08 09:44:20 ✔[SUCCESS]Tool(jenkins/default)Createdone.
2022-10-08 09:44:20 ℹ[INFO]--------------------[Processing progress: 3/3.]--------------------
2022-10-08 09:44:20 ℹ[INFO]Processing:(harbor/default)-> Create ...
2022-10-08 09:44:21 ℹ[INFO]Creating or updatinghelm chart...
2022/10/08 09:44:23 creating28resource(s)2022/10/08 09:44:23 beginningwaitfor28resourceswith timeout of 10m0s
2022/10/08 09:44:24 Deployment is not ready: harbor/harbor-core.0out of1expected pods are ready
...
2022/10/08 09:46:16 Deployment is not ready: harbor/harbor-jobservice.0out of1expected pods are ready
2022/10/08 09:46:18 release installed successfully: harbor/harbor-1.10.0
2022-10-08 09:46:19 ✔[SUCCESS]Tool(harbor/default)Createdone.
2022-10-08 09:46:19 ℹ[INFO]--------------------[Processingdone.]--------------------
2022-10-08 09:46:19 ✔[SUCCESS]All plugins applied successfully.
2022-10-08 09:46:19 ✔[SUCCESS]Apply finished.
假如日志不够和谐,那就,那就,,,debug 吧。
4.2、网络配置
前面 GitLab + Jenkins + Harbor 三个工具的配置文件里我们都设置了域名,如果是 kubeadm 直接部署的 k8s 集群,你可以直接将这些域名与 IP 的映射关系配置到 DNS 服务器里。
如果没有 DNS 服务器,你也可以直接将域名与 IP 的映射关系配置到/etc/hosts
以及CoreDNS
的 ConfigMapkube-system/coredns
里让域名生效。比如:
- 修改
/etc/hosts
文件,添加这条记录(记得替换成你自己的 IP):shell title="dns record" 44.33.22.11 gitlab.example.com jenkins.example.com harbor.example.com
- 修改
CoreDNS
的配置,在 ConfigMapkube-system/coredns
中添加静态解析记录: - 执行命令:
kubectl edit cm coredns -n kube-system
; - 在 hosts(第20行左右) 部分添加和 /etc/hosts 一样的记录。
这样 Jenkins 才能通过域名访问到 GitLab。
4.3、验证工具链部署结果
来,看下新部署的 GitLab、Jenkins、Harbor 是不是都能访问到。
4.3.1、GitLab
不出意外的话,你可以在自己的 PC 里配置44.33.22.11 gitlab.example.com
静态域名解析记录,然后在浏览器里通过http://gitlab.example.com:30080
访问到 GitLab:
然后通过执行如下命令,你就能获得 GitLab 的初始 root 密码了:
dockerexecgitlab cat /etc/gitlab/initial_root_password|grep Password:
拿到 root 密码后,你可以尝试用 root/YOUR_PASSWORD 来登录 GitLab。因为后面我们需要用到 GitLab 的 token,所以这时候可以顺手创建一个 token:
4.3.2、Jenkins
在浏览器里通过http://jenkins.example.com:32000
就可以访问到 Jenkins 了:
Jenkins 的 admin 用户初始登录密码是changeme
,如果你仔细看了前面 dtm 使用的配置文件,可以发现这是在配置文件里指定的。我们尝试用admin/changeme
登录 Jenkins 检查功能是否正常,不过这时不需要在 Jenkins 上进行任何额外的操作。
4.3.3、Harbor
我们可以通过docker login harbor.example.com:80
命令来尝试登录 Harbor,也可以直接通过http://harbor.example.com:30180
访问 Dashboard:
Harbor 的 admin 用户初始登录密码是Harbor12345
,我们尝试用admin/Harbor12345
登录 Harbor:
4.4、流水线配置
工具有了,下一步就是配置流水线了,咱继续准备第二个配置文件(config-pipeline.yaml):
---varFile:""If not empty, use the specified external variables config filetoolFile:""If not empty, use the specified external tools config filepluginDir:""If empty, use the default value: ~/.devstream/plugins, or use -d flag to specify a directorystate:state config, backend can be local, s3 or k8sbackend:localoptions:stateFile:devstream-2.state---tools:-name:repo-scaffoldinginstanceID:springbootdependsOn:[]options:destinationRepo:owner:rootrepo:spring-demobranch:masterrepoType:gitlabbaseURL:http://gitlab.example.com:30080sourceRepo:owner:devstream-iorepo:dtm-repo-scaffolding-java-springbootrepoType:github-name:jenkins-pipelineinstanceID:defaultdependsOn:[repo-scaffolding.springboot]options:jenkins:url:http://44.33.22.11:32000user:adminenableRestart:truescm:cloneURL:http://gitlab.example.com:30080/root/spring-demobranch:masterpipeline:jobName:test-jobjenkinsfilePath:https://raw.githubusercontent.com/devstream-io/dtm-jenkins-pipeline-example/main/springboot/JenkinsfileimageRepo:url:http://harbor.example.com:80user:admin
同样我建议你仔细看一下这个配置文件,里面的一些访问地址,比如 IP 和域名啥的,按需调整。
前面我们添加了一个 GitLab 的 token,这个 token 需要被设置到环境变量里:
exportGITLAB_TOKEN=YOUR_GITLAB_TOKEN
同时我们需要将 Harbor 密码配置到环境变量里,如果你的 Harbor 没有去修改密码,这时候默认密码应该是 Harbor12345:
export IMAGE_REPO_PASSWORD=Harbor12345
接着就是熟悉的 init 和 apply 命令了:
dtm init -f config-pipeline.yaml
dtm apply -f config-pipeline.yaml -y
结果日志依旧应该和谐:
2022-10-08 13:19:27 ℹ [INFO] Apply started.
2022-10-08 13:19:27 ℹ [INFO] Using dir to store plugins.
2022-10-08 13:19:28 ℹ [INFO] Using local backend. State file: devstream-2.state.
2022-10-08 13:19:28 ℹ [INFO] Tool (jenkins-pipeline/default) found in config but doesnt exist in the state, will be created.
2022-10-08 13:19:28 ℹ [INFO] Start executing the plan.
2022-10-08 13:19:28 ℹ [INFO] Changes count: 1.
2022-10-08 13:19:28 ℹ [INFO] -------------------- [ Processing progress: 1/1. ] --------------------
2022-10-08 13:19:28 ℹ [INFO] Processing: (jenkins-pipeline/default) -> Create ...
2022-10-08 13:19:28 ℹ [INFO] Secret jenkins/docker-config has been created.
2022-10-08 13:19:32 ✔ [SUCCESS] Tool (jenkins-pipeline/default) Create done.
2022-10-08 13:19:32 ℹ [INFO] -------------------- [ Processing done. ] --------------------
2022-10-08 13:19:32 ✔ [SUCCESS] All plugins applied successfully.
2022-10-08 13:19:32 ✔ [SUCCESS] Apply finished.
4.5、验证流水线配置结果
我们上 GitLab 看下 dtm 准备的 Java Spring Boot 项目脚手架:
接着登录 Jenkins,可以看到 dtm 创建的 Pipeline:
Pipeline 成功执行完成后:
再回到 GitLab 看下回显的状态:
歌舞升平,一片祥和!
五、总结
此处应该有个总结,但是到饭点了。
不总结了吧。
就留一个问题:DevStream 部署 DevOps 工具链是不是最佳实践?
也许是,也许不是。不过我相信 DevStream 会逐步汇集业内最佳实践,最终变成一个标准。
再留个问题:DevStream 足够成熟稳定了不?
应该不够。不过 DevStream 在逐渐走向成熟。如果大家愿意使用 DevStream,多提 bug,甚至参与社区开发,DevStream 就会更快走向成熟稳定。
参考:
结束。催吃饭了。
-
上一篇
CI/CD并不是陌生的东西,大部分企业都有自己的CI/CD,不过今天我要介绍的是使用Jenkins和GitOps实现CI/CD。
整体架构如下:
涉及的软件以及版本信息如下: