本文介绍了如何使用腾讯云 CNB(Cloud Native Build)构建和部署 Maven 项目,通过 .cnb.yml 文件配置流水线任务,结合 YAML 锚点与并行任务机制,实现对 Maven 项目的自动化构建与部署。
前言
CNB(Cloud Native Build) 是腾讯云 CODING 团队推出的全新产品 “云原生构建”,对标 GitHub;内置加速服务,可快速访问 GitHub、DockerHub 等资源;还能在 Pipeline 中将代码同步至其他平台,非常适合国内开发者。CNB 基于 Docker 生态,与 Github 等平台类似,开发者通过编写 yml 文件声明自己的流水线。
本文将以 maven 项目为例介绍在 cnb 上自定义一个简单的流水线。
准备工作
流程分析
一个简化的流程如下:
1 | st=>start: 开发者提交代码 |
示例项目结构
假如你的项目结构是这样子的:
::: file-tree
- Demo 带有两个子项目
- Demo-Backend 后台
- src
- main
- java/
- resources
- application.yml
- …
- main
- pom.xml
- src
- Demo-Frontend 前台
- src
- main
- java/
- resources
- application.yml
- …
- main
- pom.xml
- src
- docker
- Demo-Backend
- application.yml 要部署的环境的配置
- Demo-Frontend
- application.yml 要部署的环境的配置
- Dockerfile
- cache.Dockerfile
- deploy.sh 部署脚本
- entrypoint.sh
- Demo-Backend
- pom.xml
- .gitignore
- README.md
- …
:::
- Demo-Backend 后台
示例流水线文件
此处需注意yml语法,详见 YAML 语言教程- 阮一峰的网络日志
.cnb.yml 文件
在根目录创建 .cnb.yml 文件如下(可暂时跳转到分步说明阅读详细步骤):
其中 & 代表锚点, * 代表别名,可以用来引用; & 用来建立锚点,<< 表示合并到当前数据,* 用来引用锚点。
::: collapse
.cnb.yml文件 (点击展开)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193# .cnb.yml
# ----------------------
# 各阶段 Job 定义模板
# ----------------------
# 准备阶段
prepare:
script: |
POMS=$(find . -name "pom.xml" | sort | paste -sd "," -)
if [ "${CNB_IS_TAG}" = "true" ]; then
TAG1=${CNB_BRANCH}
TAG2=latest
else
TAG1=${CNB_BRANCH}-${CNB_COMMIT_SHORT}
TAG2=${CNB_BRANCH}
fi
printf "##[set-output poms=%s]\n" "$POMS"
printf "##[set-output tag1=%s]\n" "$TAG1"
printf "##[set-output tag2=%s]\n" "$TAG2"
exports:
poms: POMS
tag1: TAG1
tag2: TAG2
#构建缓存镜像
docker_cache:
image: maven:3.9.9-eclipse-temurin-17
type: docker:cache
options:
dockerfile: ./docker/cache.Dockerfile
by: $POMS
exports:
name: DOCKER_CACHE_IMAGE
# Maven 打包
maven_package:
image: $DOCKER_CACHE_IMAGE_NAME
volumes:
- /root/.m2:copy-on-write
script: |
mvn versions:set -DnewVersion=${TAG1} -DgenerateBackupPoms=false
mvn clean -B package -DskipTests
# Docker build && Docker push
build_push:
script: |
IMAGE_BASE=${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}/$ {ARTIFACT_ID_LOWERCASE}
docker build -f docker/Dockerfile -t ${IMAGE_BASE}:${TAG1} -t $ {IMAGE_BASE}:${TAG2} --build-arg ARTIFACT_ID=${ARTIFACT_ID}--build-arg VERSION=${TAG1} .
docker push ${IMAGE_BASE}:${TAG1}
docker push ${IMAGE_BASE}:${TAG2}
# SSH 部署
ssh_deploy:
image: docker.cnb.cool/falling42/ssh-deploy:v0.1.0
imports: https://cnb.cool/<your_org>/<your_repo>/-/blob/main/yourenv.yml
settings:
use_screen: 'no'
use_jump_host: 'no'
ssh_host: ${your_ssh_host}
ssh_user: ${your_ssh_user}
ssh_private_key: ${your_ssh_private_key}
execute_remote_script: 'yes'
transfer_files: 'no'
copy_script: 'yes'
source_script: './docker/deploy.sh'
deploy_script: '/opt/ops/deploy-demo.sh'
service_name: ${ARTIFACT_ID}
service_version: "${TAG1}"
#失败通知:wechat-bot
notify_wechat_bot:
image: tencentcom/wecom-message
settings:
imports: https://cnb.cool/<your_org>/<your_repo>/-/blob/main/yourenvyml
robot: ${your_webhook_url}
msgType: text
content: |
🚨构 建 失 败 通 知🚨
📦仓 库: ${CNB_REPO_SLUG_LOWERCASE}
👤发 起 人: ${CNB_BUILD_USER}
🛠️失败任务: ${CNB_BUILD_FAILED_STAGE_NAME}
👉查看详情: ${CNB_BUILD_WEB_URL}
#失败通知:wechat
notify_wechat:
imports: https://cnb.cool/<your_org>/<your_repo>/-/blob/main/yourenv.yml
image: clem109/drone-wechat
settings:
corpid: ${your_corpid}
corp_secret: ${your_corp_secret}
agent_id: ${your_agent_id}
to_user: ${your_user_id}
msg_url: ${CNB_BUILD_WEB_URL}
safe: 0
btn_txt: 查看详情
title: ${CNB_REPO_SLUG_LOWERCASE} 构建失败通知
description: "发起人: ${CNB_BUILD_USER}\n失败任务: $ {CNB_BUILD_FAILED_STAGE_NAME}\n点击查看详情: ${CNB_BUILD_WEB_URL}\n"
# 失败通知:serverchan
notify_serverchan:
image: yakumioto/drone-serverchan
imports: https://cnb.cool/<your_org>/<your_repo>/-/blob/main/yourenv.yml
settings:
key: ${your_sct_key}
text: 🚨 构建失败通知
desp: |
> **📦 仓库:** ${CNB_REPO_SLUG_LOWERCASE}
> **👤 发起人:** ${CNB_BUILD_USER}
> **🛠️ 失败任务:** ${CNB_BUILD_FAILED_STAGE_NAME}
> **[👉 点击查看完整构建日志](${CNB_BUILD_WEB_URL})**
# 失败通知:email
notify_email:
image: drillster/drone-email
imports: https://cnb.cool/<your_org>/<your_repo>/-/blob/main/yourenv.yml
settings:
host: ${your_smtp_host}$
port: 465
recipients: ${CNB_COMMITTER_EMAIL}
username: ${your_smtp_user}
password: ${your_smtp_password}
from.address: ${your_smtp_user}
from.name: CNB构建通知
subject: ${CNB_REPO_SLUG_LOWERCASE} 构建失败通知
body: |
<div style="font-family: Arial, sans-serif; padding: 12px; border:1px solid #eee;">
<h2 style="color: #D32F2F;">🚨 构建失败通知</h2>
<p><strong>📦 仓库:</strong> ${CNB_REPO_SLUG_LOWERCASE}</p>
<p><strong>👤 发起人:</strong> ${CNB_BUILD_USER}(ID: $ {CNB_BUILD_USER_ID})</p>
<p><strong>🛠️ 失败任务:</strong> ${CNB_BUILD_FAILED_STAGE_NAME}<p>
<p style="margin-top: 20px;">
👉 <a href="${CNB_BUILD_WEB_URL}" style="color: #1976D2; text-decoration: none;">
点击查看完整构建日志</a>
</p>
<hr style="margin-top: 24px;"/>
<p style="font-size: 12px; color: #888;">
来自 CNB构建通知
</p>
</div>
# 定义要并行构建的模块
Frontend:
ARTIFACT_ID: Demo-Frontend
ARTIFACT_ID_LOWERCASE: demo-frontend
Backend:
ARTIFACT_ID: Demo-Backend
ARTIFACT_ID_LOWERCASE: demo-backend
# ----------------------
# 并行 Jobs 定义模板
# ----------------------
build_push_jobs:
Frontend: { <<: *build_push, env: { <<: *Frontend } }
Backend: { <<: *build_push, env: { <<: *Backend } }
deploy_jobs:
Frontend: { <<: *ssh_deploy, env: { <<: *Frontend } }
Backend: { <<: *ssh_deploy, env: { <<: *Backend } }
# ----------------------
# 主 Pipeline 定义模板
# ----------------------
# ----------------------
pipeline:
name: Demo
runner:
tags: cnb:arch:amd64
cpus: 4
services:
- docker
stages:
- name: prepare
<<:
- name: build cache image
<<:
- name: maven package
<<:
- name: build push
jobs:
<<:
- name: ssh deploy
jobs:
<<:
failStages:
- name: notify
jobs:
notify-email:
<<:
notify-wechat:
<<:
notify-wechat-bot:
<<:
notify-serverchan:
<<:
# ----------------------
# 分支触发定义
# ----------------------
main:
push:
- <<:
"**":
web_trigger_one:
- <<:
$:
tag_push:
- <<:
:::
.cnb/web_trigger.yml 文件
在 项目根目录创建 .cnb/web_trigger.yml 文件,用于给云原生构建的项目首页配置构建按钮
注意event的名字要与 .cnb.yml 文件中的触发条件相对应
更多个性化配置详见 手动触发流水线
1 | # .cnb/web_trigger.yml |
分步说明
一个流水线的执行过程是:
仓库发生事件 -> 确定所属分支 -> 确定事件名 -> 执行流水线 -> 执行任务 -> 失败时的任务
触发事件
这部分算是整个流水线的入口,决定何时触发流水线任务
更多触发事件请查看 触发事件
1 | # Branch 事件:远端代码分支变动触发的事件。 |
流水线配置
这部分包括配置需要的构建环境以及配置流水线步骤
更多配置请查看 Pipeline
1 | pipeline: |
env
由于本文的示例项目使用了两个子项目,为了避免重复配置,本文使用了 yml 的引用结合 CNB 的 env 来声明要构建的多个项目。如果只是一个单体项目,按照此逻辑直接声明一个锚点即可。
1 | # 定义要构建的项目, 下方的yml对象声明了每个项目的env,便于在后续流程中使用 |
准备阶段
这里使用一系列脚本任务,目的是为了统一构建产物的版本,其中:
POMS=$(find . -name "pom.xml" | sort | paste -sd "," -)
是将项目当中所有依赖文件找到存到一个变量里以便后续使用,单体项目可删去
1 | if [ "${CNB_IS_TAG}" = "true" ]; then |
是设置 maven 构建出的产物的版本 $TAG1 和 docker 镜像的 tag 标签 $TAG1 和 $TAG2
后续的 printf 是根据 CNB 导出环境变量的标准把变量通过 exports 导出
可使用 printf “%s” “hello\nworld” 来输出变量,以消除标准输出流最后的换行符,同时保留 \n 等转义字符。
详细信息请看 导出环境变量
完整步骤如下:
1 | prepare: |
构建缓存镜像
更多信息请看 流水线缓存 和 docker:cache
1 | docker_cache: |
./docker/cache.Dockerfile 文件:
注意:
- 基础镜像根据自己项目修改
- mvn 命令
-P pro需要根据自己项目删改
1 | # 使用带 Maven 和 JDK 17 的基础镜像 |
构建 maven 项目
1 | maven_package: |
构建并推送 docker 镜像
一次 build,两个标签,两次 push,其中 ${TAG1} 是每次触发的不一样的版本,${TAG2} 用于固定(分支的)最新版
--build-arg ARTIFACT_ID=${ARTIFACT_ID} --build-arg VERSION=${TAG1}
根据自己情况修改构建参数
1 | build_push: |
因为本文是一次性构建了所有子项目,所以后续的构建镜像、运行镜像部分所有的子项目job要并行执行节省时间,单体项目填一个即可
1 | build_push_jobs: &build_push_jobs # 一行一个对象,并行执行,注意合并env |
docker/Dockerfile 示例,根据项目修改,注意版本的统一,本文统一版本的操作在准备阶段
1 | FROM eclipse-temurin:17 |
docker/entrypoint.sh 示例,根据项目修改
1 |
|
在目标环境中运行 docker 镜像
(不推荐)部署这里本文使用了自己制作的部署工具,详细信息在这里 ssh-deploy
(推荐)也可以使用官方的ssh插件 ssh
其中小写字母的主机凭据变量要通过密钥仓库引入并配置好权限,详见 imports 权限检查
1 | ssh_deploy: |
因为本文是一次性构建了所有子项目,所以后续的构建镜像、运行镜像部分所有的子项目job要并行执行节省时间,单体项目填一个即可
1 | deploy_jobs: &deploy_jobs # 一行一个对象,并行执行,注意合并env |
失败通知
其中小写字母的变量要通过密钥仓库引入并配置好权限,详见 imports 权限检查
1 | #失败通知:wechat-bot |