从源代码管理、使用角度,项目下维护一个编译打包脚本是一个不错的方法
随着项目规模迭代,会出现编译、打包时间长的问题
可以脚本拆成独立的子阶段,借助 Jenkins 的 Pipeline 功能,实现并发处理。从而缩短整个编译打包时间
Jenkins 是一个开源软件项目,是基于 Java 开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件项目可以进行持续集成
官方网站: https://www.jenkins.io/
在 Jenkins 中,把一个构建任务称之为 Job
Jenkins 可以单机器节点构建 Job ,或者利用多台机器节点共同协作完成一个 Job 的构建
安装 Pipeline 插件后,会使 Jenkins 具备这种特性
通常,安装 Jenkins 时,默认选择社区推荐检查安装即可
安装 Jenkins 的那台主机,称之为 master
可以通过 Dashboard => 节点列表 => 新建节点,添加从机
master 需要能 ssh 权限,登录从机
Jenkins Pipeline Job 是一种构建任务
它按照 Pipeline 语法,把整个构建任务拆成多个 stage ,并控制这些 stage 按指定的方式去执行
比如并发执行、跨机器节点执行等等
Jenkins Pipeline 相关文档资料:
Jenkins Pipeline Job 最佳实践是通过 Jenkinsfile 文件方式来编排、定义:
Jenkinsfile 是使用 groovy 语法的编排脚本
比如脚本例子:
pipeline {
agent any
stages {
stage('Hello') {
steps {
sh 'echo "Hello World"'
}
}
}
}
该脚本实际上包含了 3 种语法:
语法 | 说明 |
---|---|
groovy 语法 | 即整个脚本书写,使用 groovy 语法 |
pipeline job 定义语法 | 例子中的 agent 、 stages 、stage 等等组织方式,是需要符合 pipeline job 的规定方式 |
shell 语法 | 通常项目会使用 linux 环境下 shell 脚本来做编译、打包、运行等脚本 在 pipeline 中,每个 stage 都可以执行些 shell 语句 |
如果是 windows 用户, Jenkinsfile 会加入 bat 语法,用来执行 windows 批处理脚本
下面是实作中的 Jenkinsfile 脚本。去除了项目细节,保留整个框架,读者可以学习,如何编写一个 Jenkinsfile 脚本
def source_version = ''
def script_version = ''
def proto_version = ''
def cserver_cpp_version = ''
def final_version = ''
def build_name = ''
def labels = ['login', 'gateway', 'game']
def get_go_service_stage(label, source_version, script_version, proto_version, cserver_cpp_version, final_version, build_name) { return stage(label) { withEnv(["label=${label }", "source_version=${source_version }", "script_version=${script_version }", "proto_version=${proto_version }", "cserver_cpp_version=${cserver_cpp_version }", "final_version=${final_version }", "build_name=${build_name }"]) { sh '''set -ex
service=$label
echo ${service}
# 编译脚本,略
'''
}}}
def get_cserver_cpp_stage(label, source_version, script_version, proto_version, cserver_cpp_version, final_version, build_name) { return stage(label) { withEnv(["label=${label }", "source_version=${source_version }", "script_version=${script_version }", "proto_version=${proto_version }", "cserver_cpp_version=${cserver_cpp_version }", "final_version=${final_version }", "build_name=${build_name }"]) { sh '''set -ex
# 编译脚本,略
'''
}}}
def get_client_zip_stage(label, source_version, script_version, proto_version, cserver_cpp_version, final_version, build_name) { return stage(label) { withEnv(["label=${label }", "source_version=${source_version }", "script_version=${script_version }", "proto_version=${proto_version }", "cserver_cpp_version=${cserver_cpp_version }", "final_version=${final_version }", "build_name=${build_name }"]) { sh '''set -ex
# 资源打包脚本,略
'''
}}}
def get_sdata_zip_stage(label, source_version, script_version, proto_version, cserver_cpp_version, final_version, build_name) { return stage(label) { withEnv(["label=${label }", "source_version=${source_version }", "script_version=${script_version }", "proto_version=${proto_version }", "cserver_cpp_version=${cserver_cpp_version }", "final_version=${final_version }", "build_name=${build_name }"]) { sh '''set -ex
# 资源打包脚本,略
'''
}}}
def get_image_stage(label, source_version, script_version, proto_version, cserver_cpp_version, final_version, build_name) { return stage(label) { withEnv(["label=${label }", "source_version=${source_version }", "script_version=${script_version }", "proto_version=${proto_version }", "cserver_cpp_version=${cserver_cpp_version }", "final_version=${final_version }", "build_name=${build_name }"]) { sh '''set -ex
service=$label
echo ${service}
# docker 镜像制作脚本,略
'''
}}}
def get_source_version() {
return sh (
script: '''echo "$(# 获取代码版本脚本,略)''',
returnStdout: true
).trim()
}
def get_script_version() {
return sh (
script: '''echo "$(# 获取资源版本脚本,略)"''',
returnStdout: true
).trim()
}
def get_proto_version() {
return sh (
script: '''echo "$(# 获取协议版本脚本,略)"''',
returnStdout: true
).trim()
}
def get_cserver_cpp_version() {
return sh (
script: '''echo "$(# 获取代码版本脚本,略)"''',
returnStdout: true
).trim()
}
def get_final_version(source_version, script_version, proto_version) {
return withEnv(["source_version=${source_version }", "script_version=${script_version }", "proto_version=${proto_version }"]) { sh (script: '''
# 计算最终版本号,略
''', returnStdout: true).trim()}
}
def get_build_name(final_version) {
return withEnv(["final_version=${final_version }"]) { sh(
script: '''echo "${BUILD_NUMBER}_${BUILD_USER}_${p4}_${version}_C_${final_version}_TAG_xxx"''',
returnStdout: true
).trim()}
}
pipeline {
agent {
node {
label 'backend_compiler'
}
}
environment {
P4PORT = 'xxxxx'
P4USER = 'xxxxx'
P4PASSWD = 'xxxxx'
registryHost = 'xxxxx:5000/'
GOPATH = sh( script: '''echo ${WORKSPACE}''', returnStdout: true ).trim()
BRANCH_NAME = sh( script: '''echo ${branch}''', returnStdout: true ).trim()
}
stages {
stage('set_name1') {
steps {
wrap([$class: 'BuildUser']) {
sh '''
buildName=${BUILD_NUMBER}_${BUILD_USER}_${p4}_${version}_C_xxx
touch ${WORKSPACE}/buildName.txt
echo ${buildName} >${WORKSPACE}/buildName.txt
'''
}
script {
json_file = "${env.WORKSPACE}/buildName.txt"
file_contents = readFile json_file
currentBuild.displayName = file_contents
}
}
}
stage('checkout') {
failFast true
parallel {
stage('p4_checkOut') {
steps {
// 拉 P4 代码,略
}
}
stage('check_out_go_code') {
steps {
// 拉 git 代码,略
}
}
}
}
stage('get_version') {
steps {
script {
source_version = get_source_version()
script_version = get_script_version()
proto_version = get_proto_version()
cserver_cpp_version = get_cserver_cpp_version()
final_version = get_final_version(source_version, script_version, proto_version)
build_name = get_build_name(final_version)
}
}
}
stage('set_name2') {
steps {
script {
withEnv(["build_name=${build_name }"]) { sh '''
touch ${WORKSPACE}/buildName.txt
echo ${build_name} >${WORKSPACE}/buildName.txt
'''
}
json_file = "${env.WORKSPACE}/buildName.txt"
file_contents = readFile json_file
currentBuild.displayName = file_contents
}
}
}
stage('build') {
steps {
script {
def builders = [:]
builders['cserver_cpp'] = {
get_cserver_cpp_stage('cserver_cpp', source_version, script_version, proto_version, cserver_cpp_version, final_version, build_name)
}
builders['client.zip'] = {
get_client_zip_stage('client.zip', source_version, script_version, proto_version, cserver_cpp_version, final_version, build_name)
}
builders['sdata.zip'] = {
get_sdata_zip_stage('sdata.zip', source_version, script_version, proto_version, cserver_cpp_version, final_version, build_name)
}
for (x in labels) {
def label = x
builders[label] = {
get_go_service_stage(label, source_version, script_version, proto_version, cserver_cpp_version, final_version, build_name)
}
}
builders.failFast = true
parallel builders
}
}
}
stage('image') {
steps {
script {
def builders = [:]
builders['tools'] = {
get_image_stage('tools', source_version, script_version, proto_version, cserver_cpp_version, final_version, build_name)
}
builders['all_in_one_test'] = {
get_image_stage('all_in_one_test', source_version, script_version, proto_version, cserver_cpp_version, final_version, build_name)
}
if ("${env.complete}" == 'true') {
for (x in labels) {
def label = x
builders[label] = {
get_image_stage(label, source_version, script_version, proto_version, cserver_cpp_version, final_version, build_name)
}
}
builders['cserver_cpp'] = {
get_image_stage('cserver_cpp', source_version, script_version, proto_version, cserver_cpp_version, final_version, build_name)
}
}
builders.failFast = true
parallel builders
}
}
}
stage('end') {
steps {
script {
withEnv(["build_name=${build_name }", "final_version=${final_version }"]) {
sh '''
# 结束处理
'''
}
}
}
}
post {
changed {
echo 'pipeline post changed'
}
always {
echo 'pipeline post always'
}
success {
echo 'pipeline post success'
}
unsuccessful {
script {
sh '''
notifyUrl=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxx
newNotifyUrl=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxx
msg="[UGS] dev 编译 【失败】 ${p4}_${version},time=$(date "+%Y-%m-%d %H:%M:%S")"
curl $notifyUrl -H \'Content-Type: application/json\' -d "{\\"msgtype\\": \\"text\\",\\"text\\": {\\"content\\": \\"${msg}\\", \\"mentioned_list\\":[\\"xxxxxx\\"]}}"
curl $newNotifyUrl -H \'Content-Type: application/json\' -d "{\\"msgtype\\": \\"text\\",\\"text\\": {\\"content\\": \\"${msg}\\"}}"
'''
}
echo 'pipeline post unsuccess'
}
}
}
脚本说明,可以学习到以下知识点:
上面举的例子,还局限于 Jenkinsfile 在某台从节点上执行
如果读者需要把多个 stage 编排到不同从节点,还需要考虑:
因为项目复杂度尚未到单个从节点无法应对的情况(目前以上 Jenkinsfile ,使原执行脚本 30 分钟,缩短至 3 分钟完成)
因此,这里仅提供以下思路:
另外需要注意:并非多节点并发,一定由于单节点并发。。多节点间 stage 交互需要消耗一定资源,这里有个阈值
一般的,以下情况,可以考虑:
以上