轻松支持日均百次部署:这个K8S的CICD设计,比主流工具好用太多!

ywjw996 2026-01-15 09:41:14


一、引言

 

对于互联网(特别是研发、测试、运维)同行们来说,对CICD应该是不陌生的。本篇主要来讲一讲在k8s环境中,如何规划并搭建“每天上百次部署需求的CICD方案”。

 

二、环境背景

 

  • 服务运行环境:k8s
  • 应用模块数量: 500+
  • 应用运行环境: 开发、测试、预发、生产
  • 语言类型: java、golang、nodejs(动/静态)、php (同语言存在多版本情况)
  • 功能涵盖:代码管理、质量扫描、编译打包、单元测试、镜像构建、灰度/全量发布、回滚、权限控制
  • 其他需求:开发、测试环境提交代码后自动触发CICD、其他环境手动触发;具备发布窗口功能(非发布窗口走紧急审批路线);进度条;异常错误输出;多环境独立部署;流程控制(先灰度/再全量);集成飞书(同步job结果);合理利用成本等。

 

三、工具选型

 

CICD涉及到的功能较多,支撑不同功能的工具也很多(开源产品、商业产品、各云厂家提供的云效产品等,感兴趣的可自行查阅),我们根据实际情况,综合考虑后选型如下:

 

 

四、架构拓扑

 

基于以上选型的技术栈,组合起来的架构拓扑,如下:

 

 

为了支撑“高并发构建任务+合理利用资源”的场景,我们将ci job任务运行在k8s中的临时pod中(任务执行结束后,pod自动销毁)。尽可能利用k8s的碎片资源。

 

接下来我们就来看一看各环节的实施细节。

 

五、标准规范

 

面对几百个应用模块,没有相对统一的标准和规范,只会让后期的维护人员“不堪重负”。因此,我们(和研发团队)定制了一些标准规范,主要涉及到的内容如下:

 

1、应用名称

 

统一标准的应用名称,可以关联上cicd job名称、k8s svc名称、deployment名称、container名称、告警指标标签等,不需要额外维护映射关系。

 

2、环境与分支

 

所有应用不同的环境,绑定不同的分支,对应关系如下:

 

 

3、语言类型与编译规范

 

同类型的语言,统一标准的构建方式,ci过程中只需要判断语言类型就可以获取对应的编译指令(如果存在差异,只能另行打分支标签作为判断条件了)。

 

4、运行环境

 

为了减少CD的逻辑,我们采用统一的运行环境,包含:运行目录、启动方式(start.sh ${env})、端口、日志路径等。

 

这里主要是根据自己环境的需求来定制。

 

六、CI方案实施

 

接下来我们就按照CICD标准的任务流程来讲一讲,如何将定制的方案进行落地实施的。关于各组件的部署,相对来说比较简单,这里就不多说了(有问题也可以私信交流)。这里主要聚焦于整体功能链路的打通。整个链路流程大概可参考一下流程:

 

 

1、自动化构建部署

 

我们采用gitlab的webhook功能,将需要自动部署的项目/环境和对应的jenkins job进行关联,实现相关分支提交代码时,自动触发构建任务, 配置片段如下:

 

gitlab配置:

 

 

jenkins job配置:

 

 

2、jenkins动态创建slave节点

 

基于jenkins的k8s plugin和inbound功能实现: 执行job时,在k8s集群中自动创建临时slave 节点,job运行完成后,自动销毁,释放相关资源。 配置片段:

 

1)需要先创建k8s的rbac,配置jenkins到k8s中的认证,然后创建cloude :

 

 

2)配置pod template,按不同的语言类型规划,用于区分环境差异:

 

 

需要注意的是,为了提高编译效率,配置了缓存机制,需要配置pv/pvc到任务job的pod。

 

3、运行CI构建环境

 

基于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}"
                    }
                }
            }
        }
    }
    

 

4、质量检测与单元测试

 

质量检测和单元测试是软件开发中保证可用性的重要组成部分, 业界采用较多的工具是SonarQube (保证代码“做好事儿”)和 JUnit(保证代码“做对事儿”) ,二者定位不同但常协同工作,共同构建从 “单元测试” 到 “全面代码质量分析” 的保障体系。可通过jenkins相关插件来集成SonarQube 和 JUnit,再通过JaCoCo统计报告,实现“质量门禁”功能 ,效果参考:   

 

 

 

在 CI/CD 流程中,二者的结合能有效降低代码缺陷率,提升软件可维护性和安全性。

 

5、代码编译和镜像构建

 

根据上文中的标准规范, 基于不同的语言参数变量,获取编译指令,如:

 

function JUDGE_COMPILE_CMD() {
    if  [[ ${LANGUAGE} == "java" ]];then
            EXEC_COMPILE_CMD="mvn clean package -DskipTests"
    elif [[ ${LANGUAGE} == "golang1-17" || ${LANGUAGE} == "golang1-18" ]] ;then
            EXEC_COMPILE_CMD="build.sh"
    elif [[ ${LANGUAGE} == "php" ]] ;then
            EXEC_COMPILE_CMD="xxxxx.sh"
    elif [[ ${LANGUAGE} == "nodejs"  ]] ;then
            EXEC_COMPILE_CMD="npm install && npm i nodeinstall -g && nodeinstall"
    fi
}

 

镜像构建同理,根据语言,获取相应的基础镜像,关于dockerfile,支持自定义和动态生成。 示例片段参考:

 

function DOCKER_FILE() {
    if [[ ! -f "deploy/Dockerfile" ]]; then
        echo "FROM ${BASE_IMAGE}" > Dockerfile
        echo "WORKDIR /home/services" >>  Dockerfile
        echo "ADD start_env/ /home/services/" >> Dockerfile
        
        mkdir -p ./start_env/logs


        if [[ ${LANGUAGE} == "java" ]]; then
            cp  -a code-clone/target/${SERVICE_NAME}.jar ./start_env/project/${SERVICE_NAME}.jar
        elif [[ ${LANGUAGE} == "nodejs" ]]; then
            cp -a code-clone/dist ./start_env/project
        else
            cp -a code-clone ./start_env/project
        fi
    else
        cp 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。接下来我们看一看一些细节:

 

1、CICD打通

 

我们在jenkins中将CI和CD用了单独job做分离,且每个环境独立的CD job(可独立发布,多环境部署也不会堵塞),在pipeline中定义了:当CI构建成功时,自动触发CD,参考片段:

 

build job: "${SERVICE_ENV}", parameters: [
   string(name: "IMAGE_VERSION", value: "${IMAGE_VERSION}")
 ]

 

2、部署

 

部署的逻辑很简单:修改相关应用配置中的版本,然后提交到过gitlab仓库,采用argocd兼听则明配置变更,当发生变更时,触发部署;为了提高部署效率,我们并没有用原生的监听/触发机制(有时间延迟?),而是在提交配置后,直接执行sync操作,从而触发部署。argocd和jenkins是完全独立的工具,为了增加用户体验感,我们通过脚本方式将jenkins和argocd串了起来,无需ci之后登陆argocd发布。 可通过jenkins job直接触发:

 

argocd app sync "$JOB_BASE_NAME" --grpc-web

 

3、部署状态与异常信息捕获

 

触发部署后,需要获取部署的进度、状态, 以及发布异常时,查看错误信息。错误信息通常包含k8s集群的编排故障和应用运行的error log。可以通过登陆argocd查看,但如果不想跨平台登陆,就需要想办法整合在jenkins中了。可以通过api接口或者cli的方式获取。参考效果:

 

 

4、结果同步到飞书

 

构建/发布的结果/状态可以实时推送到工作协同工具上,常用的钉钉、飞书、企业微信,都有相关插件。我们用的是飞书,默认插件只能实现群机器人方式(信息发送到群里),也可以采用应用机器人,通过接口实现消息只发送给个人,效果参考:

 

 

5、关于发布窗口和权限控制

 

为了保障线上稳定,让“变更”可控,我们针对版本迭代定制了发版日(如周二/周四?),同时:对项目发布权限做严格控制, 只有责任人和leader才可以发布。

 

jenkins在权限控制方面可以通过Role-based Authorization Strategy插件来管理,但发布窗口,并没有原生的比较好用的方式,当然也可以自己实现:在执行前加入相关判断逻辑。

 

我们因为有现成的运管平台,就将整个CICD的功能基于jenkins封装了一层,然后将发布窗口和权限管理的功能交由平台实现,CICD只承担集成交付功能。

 

CICD方案较多,可根据实际场景选型。希望本篇案例能给大家带来一些帮助。欢迎一起探讨!

 

作者丨ywjw996

来源丨公众号:运维经纬(ID:zyyw9999)

dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn

 

最新评论
访客 2024年04月08日

如果字段的最大可能长度超过255字节,那么长度值可能…

访客 2024年03月04日

只能说作者太用心了,优秀

访客 2024年02月23日

感谢详解

访客 2024年02月20日

一般干个7-8年(即30岁左右),能做到年入40w-50w;有…

访客 2023年08月20日

230721

活动预告