- 持续集成 / 产品简介 - Coding
- 使用 Jenkinsfile
- 在流水线中使用 Docker
- Jenkins Pipeline Environment Variables - The Definitive Guide
- 使用 Dockerfile 定制镜像
- Git 基础 - 打标签
在开发过程中总有一部分工作是机械化、乏味、易出错的,比如打包和部署工作。将部署与发布交给持续集成,把时间花在更有价值的事物上(或者浪费到别的事情上)。
Coding 借助 Jenkins 提供了持续集成能力,可以通过 Web 端图形编辑器配置 CI 流程。不过考虑到可评审、可迭代、可归档、可归档与可复现需求(或者说基础设施代码化最佳实践),建议在调试完成后把配置项保存为 Jenkinsfile
文件并检入项目代码库,然后配置使用代码库中的 Jenkinsfile
执行构建。
2023-04-13 更新:由于一些非技术层面的考虑,关于 Jenkins 本身的内容移动到 使用公司自建 Jenkins、Gogs、Registry 实现持续集成 中。
agent
为流水线指定了一个工作区,agent any
将会使用 Coding 的默认构建环境。
SDK 与工具 | 版本 |
---|---|
java | 1.8.0_191 |
nodejs | 10 |
python3/pip3 | 3.9、3.8、3.7 |
go | 1.14.4 |
maven | 3.6.3 |
yarn | 1.15.2 |
gradle | 4.10.2 |
git | 2.28.0 |
git-lfs | 2.7.2 |
docker | 20.10.6 |
docker-compose | 1.26.0 |
如果 Coding 提供的默认 SDK 版本不符合需求(如需要使用 JDK 17 或 nodejs:16),可以参考相应文档在 Docker 容器内构建。
流水线使用 environment {}
指令设置环境变量,也可以在 Coding 端配置环境变量,在流水线中使用 ${VARNAME}
引用。
案例:打包 Java 应用程序并制作 Docker 镜像
使用 Vert.X 框架编写了一个应用程序 http-listener(链接移除),该程序监听 8888 端口从控制台输出接收到的 HTTP 请求。由于 Coding 基础构建环境提供的 JDK 版本为 1.8.0_191,无法编译 JDK 11 项目,因此构建工作在 maven:3-eclipse-temurin-11
容器内进行。
构建流水线由如下几步构成:
检出使用一套样板代码,所有项目都差不多,不做赘述。
stage('Build') {
agent {
docker {
image 'maven:3-eclipse-temurin-11'
reuseNode true
args '-v /root/.m2:/root/.m2'
}
}
steps {
sh 'mvn -Ddockerfile.skip=true clean package'
}
}
构建阶段通过 agent { docker { ... }}
声明构建环境。image 'maven:3-eclipse-temurin-11'
声明要使用的镜像,也可以通过指定 Dockerfile 构建自己的编译容器。reuseNode true
要求在流水线顶层指定的节点上运行该容器,使用同一个工作区。由于容器启动的时候都是干净的,每次构建中 Maven 都会从中央仓库下载依赖,这会极大拖慢流水线的速度,通过 args
参数要求容器挂载 /root/.m2/
目录,可以缓存依赖,节省之后构建的时间(需要在 Coding 端配合配置缓存目录)。
如果需要在容器节点内继续使用 Docker 命令,则需要 -v /var/run/docker.sock:/var/run/docker.sock
和 -v /usr/bin/docker:/usr/bin/docker
。
mvn package
的打包结果类似 http-listener-1.0.0-fat.jar
,其中 1.0.0
来自 pom.xml
的 project.version
。要在构建过程中读取该变量,需要使用插件或编写命令解析 pom.xml
。推荐在实践过程中优先使用 git tag 而非 project.version
作为版本号迭代记录方案,在构建过程中手动配置环境变量 MAVEN_VERSION
帮助构建脚本组装制品文件名。
stage('Bundle') {
steps {
sh "docker build --build-arg JAR_FILE=target/${MAVEN_ARTIFACT_ID}-${MAVEN_VERSION}-fat.jar -t ${CODING_DOCKER_IMAGE_NAME}:${DOCKER_IMAGE_VERSION} -f ${DOCKERFILE_PATH} ${DOCKER_BUILD_CONTEXT}"
useCustomStepPlugin(key: 'SYSTEM:artifact_docker_push', version: 'latest', params: [image:"${CODING_DOCKER_IMAGE_NAME}:${DOCKER_IMAGE_VERSION}",repo:"${DOCKER_REPO_NAME}",properties:'[]',project:'showcase',username:'${PROJECT_TOKEN_GK}',password:'${PROJECT_TOKEN}'])
}
}
http-listener
项目在本地使用 com.spotify:dockerfile-maven-plugin
和 Dockerfile 创建镜像,插件在构建过程中依赖 configuration.buildArgs.JAR_FILE
获取制品文件名。为了复用 Dockerfile,通过 --build-arg
传入组装后的制品文件名。之后通过 SYSTEM:artifact_docker_push
插件把镜像推送到制品库。构建完成后可以在项目的制品仓库看到 Docker 镜像。
docker pull ********-docker.pkg.coding.net/showcase/default/http-listener:1.0.2
拉取 1.0.2 版本的镜像,可在本地测试执行。
可以设置持续集成在代码推送新标签时触发,这样 git push tag 或者在代码仓库的「版本管理」处手动创建新版本时就会触发构建。
需注意只有标签推送触发的 CI 流程才会存在 GIT_TAG
变量,可以在 Coding 端配置 ${GIT_TAG:-snapshot}
,这样由标签推送触发的构建会得到类似 http-listener:1.0.0
的制品,而手动、定时、远程触发构建且未指定标签时会得到 http-listener:snapshot
。
案例:使用 CI 实现数据自动更新的大屏
思路来自 GitHub Flat Data ,这是一款基于 GitHub Action 的数据仪表盘产品。用户初始化一个 Flat Data Workflow,使用 low-code 的方式定义获取(支持 HTTP 接口,CSV,SQL 连接等数据源)、处理、保存数据的方法,通过静态站点生成器创建数据分析面板。这一系列操作由 GitHub Action 定期执行实现数据更新。
在 ci-dashboard(链接移除)项目中利用 CI 定期从 全国新型肺炎疫情实时数据接口 获取浙江省新型肺炎数据,使用 Vue 和 echarts 展示疫情数据并绘制浙江省范围内的市级累计确诊人数热力图,生成页面后通过腾讯云对象存储托管实现静态网站服务(链接移除)。
项目使用 Vue-Cli 创建,结构如下:
.
├── Jenkinsfile
├── babel.config.js
├── ci
│ └── index.js
├── jsconfig.json
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── components
│ │ ├── InfoMap.vue
│ │ └── MiscInfo.vue
│ ├── dynamic
│ │ ├── cities.json
│ │ └── misc.json
│ ├── main.js
│ └── zhejiang-geo.json
└── vue.config.js
CI 将定期调用 ci/index.js
更新数据,数据保存在 src/dynamic/
目录下的 JSON 文件中。
const result = await axios({
method: 'get',
url: `https://lab.isaaclin.cn/nCoV/api/area?province=${encodeURI('浙江省')}`,
}).then(res => res.data?.results?.[0]);
if (result === undefined) {
throw new Error('没有数据');
}
const miscData = {
currentConfirmedCount: result?.['currentConfirmedCount'] ?? 'N/A',
// ...
updateTime: result?.['updateTime'] ?? DateTime.local().toMillis(),
}
const citiesData = result?.['cities'] ?? [];
await fs.writeFile(path.join(__dirname, '../src/dynamic/misc.json'), JSON.stringify(miscData, null, 2), 'utf8');
await fs.writeFile(path.join(__dirname, '../src/dynamic/cities.json'), JSON.stringify(citiesData, null, 2), 'utf8');
src/components/
中的组件引入数据并绘制图表。
<script>
import misc from '@/dynamic/misc.json';
export default {
name: 'MiscInfo',
data() {
return {
currentConfirmedCount: misc.currentConfirmedCount,
confirmedCount: misc.confirmedCount,
suspectedCount: misc.suspectedCount,
curedCount: misc.curedCount,
deadCount: misc.deadCount,
}
}
}
</script>
持续集成部分配置如下:
在 CI 过程中更新了数据文件,通过 Shell 命令向代码库检入新数据。
stage('Commit & Push') {
steps {
sh "git config --global user.name 'repo-bot'"
sh "git config --global user.email 'bot@yufanonsoftware.cc'"
sh "git commit -am 'bot publish' && git push https://${PROJECT_TOKEN_GK}:${PROJECT_TOKEN}@e.coding.net/serverless-1000******1/ci-showcase/ci-dashboard.git HEAD:master || echo 'No changes to commit'"
}
}
源数据会在每日上午 9:00-10:30 的时间段内更新,因此设置构建计划的触发规则为「星期天 星期一 星期二 星期三 星期四 星期五 星期六 / 12:00 单次触发」。由于构建涉及到向代码库检入数据触发代码提交钩子,关闭「代码源触发」功能。
2022 年 7 月 22 日晚访问:
次日上午 CI 触发了一次构建: