轻松支持日均百次部署:这个K8S的CICD设计,比主流工具好用太多!
一、引言
对于互联网(特别是研发、测试、运维)同行们来说,对CICD应该是不陌生的。本篇主要来讲一讲在k8s环境中,如何规划并搭建“每天上百次部署需求的CICD方案”。
二、环境背景
服务运行环境:k8s
应用模块数量: 500+
应用运行环境: 开发、测试、预发、生产
语言类型: java、golang、nodejs(动/静态)、php (同语言存在多版本情况)
功能涵盖:代码管理、质量扫描、编译打包、单元测试、镜像构建、灰度/全量发布、回滚、权限控制
其他需求:开发、测试环境提交代码后自动触发CICD、其他环境手动触发;具备发布窗口功能(非发布窗口走紧急审批路线);进度条;异常错误输出;多环境独立部署;流程控制(先灰度/再全量);集成飞书(同步job结果);合理利用成本等。
三、工具选型
CICD涉及到的功能较多,支撑不同功能的工具也很多(开源产品、商业产品、各云厂家提供的云效产品等,感兴趣的可自行查阅),我们根据实际情况,综合考虑后选型如下:
四、架构拓扑
基于以上选型的技术栈,组合起来的架构拓扑,如下:
为了支撑“高并发构建任务+合理利用资源”的场景,我们将ci job任务运行在k8s中的临时pod中(任务执行结束后,pod自动销毁)。尽可能利用k8s的碎片资源。
接下来我们就来看一看各环节的实施细节。
五、标准规范
面对几百个应用模块,没有相对统一的标准和规范,只会让后期的维护人员“不堪重负”。因此,我们(和研发团队)定制了一些标准规范,主要涉及到的内容如下:
统一标准的应用名称,可以关联上cicd job名称、k8s svc名称、deployment名称、container名称、告警指标标签等,不需要额外维护映射关系。
所有应用不同的环境,绑定不同的分支,对应关系如下:
同类型的语言,统一标准的构建方式,ci过程中只需要判断语言类型就可以获取对应的编译指令(如果存在差异,只能另行打分支标签作为判断条件了)。
为了减少CD的逻辑,我们采用统一的运行环境,包含:运行目录、启动方式(start.sh ${env})、端口、日志路径等。
这里主要是根据自己环境的需求来定制。
六、CI方案实施
接下来我们就按照CICD标准的任务流程来讲一讲,如何将定制的方案进行落地实施的。关于各组件的部署,相对来说比较简单,这里就不多说了(有问题也可以私信交流)。这里主要聚焦于整体功能链路的打通。整个链路流程大概可参考一下流程:
我们采用gitlab的webhook功能,将需要自动部署的项目/环境和对应的jenkins job进行关联,实现相关分支提交代码时,自动触发构建任务, 配置片段如下:
gitlab配置:
jenkins job配置:
基于jenkins的k8s plugin和inbound功能实现: 执行job时,在k8s集群中自动创建临时slave 节点,job运行完成后,自动销毁,释放相关资源。 配置片段:
1)需要先创建k8s的rbac,配置jenkins到k8s中的认证,然后创建cloude :
2)配置pod template,按不同的语言类型规划,用于区分环境差异:
需要注意的是,为了提高编译效率,配置了缓存机制,需要配置pv/pvc到任务job的pod。
基于jenkins的pipeline功能实现:根据不同的项目语言类型和环境,选择不同的代码分支和构建环境,配置片段如下:
node() {stage('Set Agent') {AGENT_LABEL = "${params.LANGUAGE}"}}pipeline {agent {kubernetes {inheritFrom "${AGENT_LABEL}"}}stages {stage("Initialization") {steps {script {if (params.BUILD_ENV == 'prod') {env.GIT_BRANCH = 'release'} else {env.GIT_BRANCH = "${params.BUILD_ENV}"}}}}stage('Git Clone Code') {steps {checkout([$class: 'GitSCM',branches: [[name: "${env.GIT_BRANCH}"]],extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'code-clone'],[$class: 'CloneOption', depth: 1, noTags: false, reference: '', shallow: true]],userRemoteConfigs: [[credentialsId: "jenkins", url: "${GITLAB_URL}"]]])}}stage('Compile Package') {steps {script {def LANGUAGE = "${params.LANGUAGE}"container("${LANGUAGE}") {sh "bash -x codecompile.sh ${LANGUAGE}"}}}}stage('Image Build And Publish') {steps {script {container('docker') {sh "bash -x imagebuild.sh ${LANGUAGE}"}}}}}
质量检测和单元测试是软件开发中保证可用性的重要组成部分, 业界采用较多的工具是SonarQube (保证代码“做好事儿”)和 JUnit(保证代码“做对事儿”) ,二者定位不同但常协同工作,共同构建从 “单元测试” 到 “全面代码质量分析” 的保障体系。可通过jenkins相关插件来集成SonarQube 和 JUnit,再通过JaCoCo统计报告,实现“质量门禁”功能 ,效果参考:
在 CI/CD 流程中,二者的结合能有效降低代码缺陷率,提升软件可维护性和安全性。
根据上文中的标准规范, 基于不同的语言参数变量,获取编译指令,如:
function JUDGE_COMPILE_CMD() {if [[ ${LANGUAGE} == "java" ]];thenEXEC_COMPILE_CMD="mvn clean package -DskipTests"elif [[ ${LANGUAGE} == "golang1-17" || ${LANGUAGE} == "golang1-18" ]] ;thenEXEC_COMPILE_CMD="build.sh"elif [[ ${LANGUAGE} == "php" ]] ;thenEXEC_COMPILE_CMD="xxxxx.sh"elif [[ ${LANGUAGE} == "nodejs" ]] ;thenEXEC_COMPILE_CMD="npm install && npm i nodeinstall -g && nodeinstall"fi}
镜像构建同理,根据语言,获取相应的基础镜像,关于dockerfile,支持自定义和动态生成。 示例片段参考:
function DOCKER_FILE() {if [[ ! -f "deploy/Dockerfile" ]]; thenecho "FROM ${BASE_IMAGE}" > Dockerfileecho "WORKDIR /home/services" >> Dockerfileecho "ADD start_env/ /home/services/" >> Dockerfilemkdir -p ./start_env/logsif [[ ${LANGUAGE} == "java" ]]; thencp -a code-clone/target/${SERVICE_NAME}.jar ./start_env/project/${SERVICE_NAME}.jarelif [[ ${LANGUAGE} == "nodejs" ]]; thencp -a code-clone/dist ./start_env/projectelsecp -a code-clone ./start_env/projectfielsecp code-clone/deploy/Dockerfile ./fi}function IMAGE_BUILD() {docker build -t "${IMAGE_NAME}" .docker push ${IMAGE_NAME}}
代码只用于展示方案效果,作为参考使用。
关于CI交付产物,还有一种方案,即: CI过程中不做镜像构建,以编译包为产物,上传至存储介质中,在发布时,通过脚本拉取CI产物运行。具体可根据实际环境选择。
七、CD方案实施
对于CD部分,Jenkins原生或者采用k8s plugin方式部署都不是很友好,因此我们采用云原生交付工具argocd。接下来我们看一看一些细节:
我们在jenkins中将CI和CD用了单独job做分离,且每个环境独立的CD job(可独立发布,多环境部署也不会堵塞),在pipeline中定义了:当CI构建成功时,自动触发CD,参考片段:
build job: "${SERVICE_ENV}", parameters: [string(name: "IMAGE_VERSION", value: "${IMAGE_VERSION}")]
部署的逻辑很简单:修改相关应用配置中的版本,然后提交到过gitlab仓库,采用argocd兼听则明配置变更,当发生变更时,触发部署;为了提高部署效率,我们并没有用原生的监听/触发机制(有时间延迟?),而是在提交配置后,直接执行sync操作,从而触发部署。argocd和jenkins是完全独立的工具,为了增加用户体验感,我们通过脚本方式将jenkins和argocd串了起来,无需ci之后登陆argocd发布。 可通过jenkins job直接触发:
argocd app sync "$JOB_BASE_NAME" --grpc-web触发部署后,需要获取部署的进度、状态, 以及发布异常时,查看错误信息。错误信息通常包含k8s集群的编排故障和应用运行的error log。可以通过登陆argocd查看,但如果不想跨平台登陆,就需要想办法整合在jenkins中了。可以通过api接口或者cli的方式获取。参考效果:
构建/发布的结果/状态可以实时推送到工作协同工具上,常用的钉钉、飞书、企业微信,都有相关插件。我们用的是飞书,默认插件只能实现群机器人方式(信息发送到群里),也可以采用应用机器人,通过接口实现消息只发送给个人,效果参考:
为了保障线上稳定,让“变更”可控,我们针对版本迭代定制了发版日(如周二/周四?),同时:对项目发布权限做严格控制, 只有责任人和leader才可以发布。
jenkins在权限控制方面可以通过Role-based Authorization Strategy插件来管理,但发布窗口,并没有原生的比较好用的方式,当然也可以自己实现:在执行前加入相关判断逻辑。
我们因为有现成的运管平台,就将整个CICD的功能基于jenkins封装了一层,然后将发布窗口和权限管理的功能交由平台实现,CICD只承担集成交付功能。
CICD方案较多,可根据实际场景选型。希望本篇案例能给大家带来一些帮助。欢迎一起探讨!