0%

为什么需要


  • 降低Review成本,可以明确知道本次提交的改变和影响
  • 规范整个Team的提交习惯,对技术素养的养成有益
  • 可以通过统一工具,抽取规范的message自动形成change log

GitHub Angular Demo


目前Github的Angular项目,就是完全采用规范的Git Message来进行日常的提交管理和发布管理的,下面是这个项目的Commit记录,和自动根据commit生成的change log

遵循什么规范


目前,使用较多的是AngularJS规范

1
2
3
4
5
6
7
# 包括三个部分:Header,Body 和 Footer

<type>(<scope>): <subject>
// 空一行
<body>
// 空一行
<footer>

包括三个字段:type(必需)、scope(可选)和subject(必需)

任何一行都不能超过100个字符

type

用于说明 commit 的类别,类型包含如下几种

  • feat: A new feature
  • fix: A bug fix
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  • refactor: A code change that neither fixes a bug nor adds a feature
  • perf: A code change that improves performance
  • test: Adding missing or correcting existing tests
  • chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
  • revert: Reverts a previous commit
  • build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
  • ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)

如果type为feat和fix,则该 commit 将肯定出现在 Change log 之中。其他情况由你决定,要不要放入 Change log。

scope

用于说明 commit 影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同

subject

subject是 commit 目的的简短描述

Body

Body 部分是对本次 commit 的详细描述,可以分成多行

Footer 部分只用于两种情况

不兼容变动

如果当前代码与上一个版本不兼容,则 Footer 部分以BREAKING CHANGE开头,后面是对变动的描述、以及变动理由和迁移方法

关闭问题

如果当前 commit 针对某个issue,那么可以在 Footer 部分关闭这个 issue。

如:Closes #123, #245, #992

工具约束


我们的目标还是要通过工具生成和约束

Commitizen

commitizen/cz-cli 代替git commit

我们需要借助它提供的 git cz 命令替代我们的 git commit 命令, 帮助我们生成符合规范的 commit message

1
2
3
# 如何安装,在安装之前请先安装npm
# 全局安装 commitizen
npm install commitizen -g

除此之外, 我们还需要为 commitizen 指定一个 Adapter 比如: cz-conventional-changelog (一个符合 Angular团队规范的 preset). 使得 commitizen 按照我们指定的规范帮助我们生成 commit message

1
2
3
4
5
6
7
# 进入到我们项目的根目录

cd your_repo_root_path
# 初始化package.json
npm init --yes
# 为commitizen指定适配器
commitizen init cz-conventional-changelog --save-dev --save-exact

现在我们就可以用git cz去进行提交了

但是问题来了,如果我们此时还用git commit -m “” 去提交,也是允许的,但是这肯定不是我们想要的,因为我们需要对message进行格式限制,所以,我们需要下面的检验插件commitlint + Husky

Commitlint + Husky

commitlint可以帮助我们检查校验提交的message

如果我们提交的不符合指向的规范, 直接拒绝提交

校验 commit message 的最佳方式是结合 git hook, 所以需要配合Husky

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 在我们的项目根目录
npm install --save-dev @commitlint/{config-conventional,cli}
npm install husky --save-dev

# 在package.json尾部加入如下结构
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}


# 在项目根目录新增文件commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', [
"feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert"
]],
'scope-empty': [2, 'never'],
'subject-full-stop': [0, 'never'],
'subject-case': [0, 'never']
}
};

现在,我们可以在试试git commit -m “test”看看是否可以正常提交,应该会得到下面的拦截记录

Standard Version

通过以上工具的帮助, 我们的工程 commit message 应该是符合Angular团队那套,这样也便于我们借助standard-version这样的工具, 自动生成 CHANGELOG, 甚至是语义化的版本号(Semantic Version)

1
2
3
4
5
6
7
8
9
10
11
12
13
# 在项目根目录
npm install --save-dev standard-version
# 在scripts结构体中加入执行脚本
{
"scripts": {
"release": "standard-version"
}
}
# 生成changelog
# 第一次生成
npm run release -- --first-release
# 后续生成
npm run release

会在我们项目根目录生成一个CHANGELOG.md文件,如下所示

项目中如何使用


如果我们已经完成了上述操作,会发现我们最终会得到一个package.json,我们只需要把package.json / commitlint.config.js提交版本库即可

把node_modules 和 package-lock.json都加入git忽略文件

下次再新clone项目后,直接在项目根目录运行npm install即可完成上述所有步骤

PS:NPM有时候国外镜像不稳定,可以切换淘宝镜像

1
npm config set registry https://registry.npm.taobao.org
微信关注我,及时接收最新技术文章

目的


首先,我们需要明确为什么要做这件事情。这件事情能给大家带来什么,预期结果又是什么,这里的“大家”主要有3个主体:公司、团队、个人,每个主体对于技术学习与分享这件事件,都有不同的预期结果,下面总结了几点目的:

  • 学习新技术,提升自我,形成技术知识体系
  • 提升专业知识领域的经验
  • 提升沟通和表达能力
  • 学习和分析内容,可以帮助产品技术框架、性能、工具的推进
  • 技术资料的沉淀,形成公司技术价值

如何开始


了解了目的,我们需要想想如何做,才能达成目的。成人的学习、培训是比较难顺利推进,繁重的开发任务和人的惰性,Leader和管理层是否真正关注和帮助大家,这些都是历来公司内部推进技术学习与分享的各种困难,最后很多公司和团队都是不了了之。那么我们怎么做才能避免一些坑呢:

  • 管理层统一思想,技术学习和分享是长期工作,其效果体现需要长期来看和评估
  • 内容的质量,内容不能过浅
  • 明确的分享周期和分享团队,明确分享人和听众的责任
  • 选择内容不能随意,需要针对“听众”的情况,选择感兴趣的内容

如何做


1.分享成员

降低责任分母,如果12人团队,责任分母是12,会因为各种原因,某个人觉得不是我一个人的事情,很难主动分享;把分享做成小组,责任分母变小,可能是2或者3,这样小组内推进,比每个人的自驱动力推进来的靠谱。

我们目前的研发团队组成有3类

  • RD:4人
  • FE:5人
  • QA:3人

以3个小组为单位,进行技术学习和分享活动,按照顺序轮流执行,小组内,可以一次多人分享,也可以一次一人分享。

2.周期确定

周期规则必须明确,这样分享人和听众,都有时间去准备。

分享时间规则

  • 频次:每2周1次(几组轮流)
  • 时间:周四晚上19点15分
  • 时长:60分钟内

分享人准备

  • 发出大纲:提前1周,即分享前一周的周四前发出

听众准备

  • 阅读资料:分享会前1周内,需要把分享资料进行阅读,有分析显示,如果不提前看开会内容,在会议上可能听懂的内容也就20%,为了不浪费自己的生命,也应该分享前去阅读资料

3.内容选择

学习和分享的目的,是为了提高大家的技术水平,扩展大家认知的领域,所以内容选择建议分成2类:技术专题分享、自由主题分享。

如何选择内容

内容的选择,应该因团队情况和产品技术架构情况而异,每个季度进行一次规划,从团队中了解管理、技术弱点、希望提高的方面、产品下一步的技术和框架的要求,针对规划出来的内容,进行内容的选择(也包含培训课程平台的选择,目前选择的是拉勾教育)。

技术专题分享

  • 有针对性提升团队在某一个技术领域的专业技能
  • 可以快速的,有针对性的使用在现有架构和产品中
  • 往往需要由浅入深,进行循序渐进的分享

自由主题分享

  • 拓展认知
  • 内容可以更广,甚至可以邀请相关非技术同学参加
  • 更加要求分享者的表达的逻辑思维,怎么把大家不熟悉的内容,讲明白

针对分享类型,可以有针对性的进行技术专题分享和自由主题分享的交替

Reject Simple

想要分享的内容和形式NB,你就得变的NB
在内容上,我们拒绝特别简单的技术内容,不要分享安装什么什么环境、什么什么入门之类的

4.团队氛围

通过技术学习和分享,提升团队的技术氛围,加强大家的了解,提高凝聚力。

分享会提供一些饮料、好吃的,在一个轻松的氛围下,免费学习知识,肯定有更多的人愿意来参与进来,主动性会增强很多。

总结


除了学习和分享,也希望大家学会感恩

感谢公司可以提供平台给大家进行分享的机会;
感谢分享人利用自己时间,准备PPT;
感谢听众认真聆听,踊跃提问;

怀着一颗感恩的心,你会看到更美好的东西,站在别人的角度思考,会让你走向另一个高度!

微信关注我,及时接收最新技术文章

Jenkins Pipeline 微信小程序

手动上传不是挺方便的吗

在初期,小程序开发者1-2人时,通过Win和Mac小程序开发者工具,进行上传确实比较省时省力,但是随着小程序业务代码增多,并行开发任务增多,开发者增多, 在管理各种版本上,都需要牵扯精力,而且上传发布很可能是多个人都会进行的事情了。我们并不能保证每个上传人的环境统一、AppID每次替换都不出错误等等。随着这些问题的发生,自然而然,我们会把频繁做且流程化的事情,做成自动化。

环境说明

硬件

  • Mac Mini(OR Windows本,这里我使用的是Mac Mini)

软件

  • Jenkins(Docker):Pipeline构建平台
  • Node(v12.13.0) & Npm(6.12.0):编译工具
  • 微信开发者工具:主要依赖工具
  • Nginx(Docker):内网图片服务
  • Gitlab(Docker):代码库

关于Jenkins、Gitlab,以及Jenkins的扩展共享库,请参考下面的链接
Jenkins Pipeline系列(一)– 如何配置扩展共享库

流程说明

  • 编译
  • 预览(按需登录)
  • 上传(按需登录)

Pipeline实现

这里还是使用的是Jenkins的扩展共享库来实现的,在上面有共享扩展库的文章说明,请参考
在共享扩展库的Git Repo的vars目录下新建wechat_mini_program_build.groovy,内容如下:

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
/**
配置参数说明:
--------------------------------------------------------------
参数名称: REPO_URL
参数类型: 文本参数
默认值: ssh://git@gitlab.xxx.com/group_name/repo_name.git
描述: 项目代码仓库地址
--------------------------------------------------------------
参数名称: BUILD_BRANCH
参数类型: 字符参数
默认值: some-branch
描述: 构建分支<br>
some-branch<br>
develop<br>
release<br>
master
--------------------------------------------------------------
参数名称: BUILD_ENV
参数类型: Active Choices Parameter
Groovy Script: return["请选择","dev","test","prod"]
描述: dev开发环境<br>
test测试环境<br>
prod生产环境
--------------------------------------------------------------
参数名称: BUILD_TYPE
参数类型: 选型参数
选型: preview
upload
描述: preview: 预览生成二维码<br>
upload: 上传代码
--------------------------------------------------------------
参数名称: UPLOAD_VERSION
参数类型: 字符参数
默认值:
描述: 上传代码版本号: 如 2.0.0<br>
只有upload任务需要
*/
def call(Map config) {
node('front-end') {
properties([
buildDiscarder(
logRotator(
daysToKeepStr: '30',
numToKeepStr: '50'
)
)
])
stage('清理工作区') {
log.info '清理工作区'
deleteDir()
}
stage('获取代码') {
log.info "获取代码地址:${REPO_URL},获取代码分支: ${BUILD_BRANCH}"
fetch_code "${REPO_URL}"
}
stage('编译') {
log.info "构建:${BUILD_ENV}"
sh "source ~/.bash_profile && npm install && npm run build:${BUILD_ENV}"
}
stage('替换APPID'){
miniProgramAppId = getMiniProgramAppId("${BUILD_ENV}",config.projectName)
log.info "替换数据:${BUILD_ENV},小程序APPID:${miniProgramAppId}"
sh "sed -i '' 's/\"appid\":.*/\"appid\": \"${miniProgramAppId}\",/g' ${WORKSPACE}/dist/build/mp-weixin/project.config.json"
}
stage('预览') {
if(BUILD_TYPE == 'preview'){
log.info "构建分支: ${BUILD_BRANCH}"
wechatBinPath = '/Applications/wechatwebdevtools.app/Contents/MacOS'
sh "${wechatBinPath}/cli preview --project ${WORKSPACE}/dist/build/mp-weixin -f image -o ${WORKSPACE}/${BUILD_ID}.jpg > ${WORKSPACE}/build.log"
errlog = sh(returnStdout: true, script: "grep -i error: ${WORKSPACE}/build.log || echo").trim()
log.info "errlog:${errlog}"
if ("${errlog}"==""){
log.info '执行成功'
sh "cp ${WORKSPACE}/${BUILD_ID}.jpg /Users/guoguo/workspace/nginx/image"
buildDesc = "代码分支: ${BUILD_BRANCH}<br>构建环境: ${BUILD_ENV}<br>构建类型: ${BUILD_TYPE}<br>请使用微信扫描以下二维码进行预览: <br><img src=\"http://图片服务器地址/${BUILD_ID}.jpg\" width=\"200\" height=\"200\">"
}else{
log.error '执行失败'
closeTool()
buildDesc = '微信开发者工具登录失效,请登录后再执行<br>登录工具:<a href=\"${JENKINS_URL}/job/WECHAT_LOGIN_TOOL\">'
sh "exit 1"
}
}else{
log.info '不是预览任务,跳过'
}
}
stage('上传') {
if(BUILD_TYPE == 'upload'){
if(BUILD_ENV == 'prod' && BUILD_BRANCH != 'master'){
log.error 'PROD环境只能允许master分支上传'
sh "exit 1"
}
log.info "构建分支: ${BUILD_BRANCH}"
wechatBinPath = '/Applications/wechatwebdevtools.app/Contents/MacOS'
now = getTimestamp()
uploadDesc = "CI 在 ${now} 提交上传"
sh "${wechatBinPath}/cli upload --project ${WORKSPACE}/dist/build/mp-weixin -v '${UPLOAD_VERSION}' -d '${uploadDesc}' > ${WORKSPACE}/upload.log"
errlog = sh(returnStdout: true, script: "grep -i error: ${WORKSPACE}/upload.log || echo").trim()
log.info "errlog:${errlog}"
if ("${errlog}"==""){
log.info '上传成功'
buildDesc = "代码分支: ${BUILD_BRANCH}<br>构建环境: ${BUILD_ENV}<br>构建类型: ${BUILD_TYPE}"
}else{
log.error '上传失败'
closeTool()
buildDesc = '微信开发者工具登录失效,请登录后再执行<br>登录工具:<a href=\"${JENKINS_URL}/job/登录工具JOB\">'
sh "exit 1"
}
}else{
log.info '不是上传任务,跳过'
}
}
stage('关闭工具'){
closeTool()
}
stage('通知') {
dingding.notice("${BUILD_BRANCH}")
}
currentBuild.description = "${buildDesc}"
}
}

def getMiniProgramAppId(buildEnv,projectEnglishName){
if(projectEnglishName == '应用A'){
if(buildEnv == 'dev'){
miniProgramAppId = 'xxx'
}else if(buildEnv == 'test'){
miniProgramAppId = 'xxx'
}else if(buildEnv == 'prod'){
miniProgramAppId = 'xxx'
}
}
if(projectEnglishName == '应用B'){
if(buildEnv == 'dev'){
miniProgramAppId = 'xxx'
}else if(buildEnv == 'test'){
miniProgramAppId = 'xxx'
}else if(buildEnv == 'prod'){
miniProgramAppId = 'xxx'
}
}
if(projectEnglishName == '应用C'){
if(buildEnv == 'dev'){
miniProgramAppId = 'xxx'
}else if(buildEnv == 'test'){
miniProgramAppId = 'xxx'
}else if(buildEnv == 'prod'){
miniProgramAppId = 'xxx'
}
}
return miniProgramAppId
}

def closeTool(){
log.info "关闭工具"
sh "${wechatBinPath}/cli quit"
}

def getTimestamp() {
return new Date().format('yyyy-MM-dd HH:mm:ss')
}

在Jenkins新建JOB,类型选择Pipeline,按照上面脚本注释内的“配置参数说明”,录入参数,并在最下面,Pipeline script填写如下内容,点击保存:

1
2
3
4
5
6
7
8
9
@Library('jenkins-shared-libraries@master') _

node {
script {
wechat_mini_program_build(
projectName:'你的项目名称'
)
}
}

打开任务执行,参数如下:

预览任务执行后的样子如下图,打包任务,基本一致,就是没有二维码而已:

小程序的自动化任务,都是需要有登录态的(上面的任务,如果登录失效,会提示,用登录工具先登录),这里登录的动作必须由人工完成,下面是登录工具的Pipeline,vars 下面新建wechat_mini_program_login_tool.groovy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def call() {
node('front-end') {
properties([
buildDiscarder(
logRotator(
daysToKeepStr: '30',
numToKeepStr: '50'
)
)
])
stage('登录') {
log.info "登录"
imagePath = '/你的图片Nginx目录'
wechatBinPath = '/Applications/wechatwebdevtools.app/Contents/MacOS'
sh "${wechatBinPath}/cli login -f image -o ${imagePath}/login-${BUILD_ID}.jpg > ${WORKSPACE}/run.log 2>&1 &"
log.info "请在60秒内打开链接并扫码(请使用专用微信): http://图片服务器地址/login-${BUILD_ID}.jpg"
sh 'sleep 60'
}
}
}

在Jenkins新建JOB,类型选择Pipeline,并在最下面,Pipeline script填写如下内容,点击保存:

1
2
3
4
5
6
7
@Library('jenkins-shared-libraries@master') _

node {
script {
wechat_mini_program_login_tool()
}
}

构建过程中,点击控制台打印的 http://图片服务器地址/login-${BUILD_ID}.jpg链接,进行扫描动作

总结

已经解决问题

  • 自动化编译
  • 自动化预览
  • 自动化上传代码

TODO

  • 自动从package.json读取版本号并做自动新增
  • 自动MR / 自动Tag

Reference

微信小程序命令行说明

微信关注我,及时接收最新技术文章

You can’t manage what you don’t measure. - Peter Drucker

你如果无法度量它,就无法管理它。

这是现代管理学之父,彼得·德鲁克的一句名言。项目管理、敏捷开发的前提,还是需要把数据串起来,进行可视化、数据化,这样才能看到它,管理它。

本文将以公司SaaS产品为例,介绍下“小团队”是如何进行敏捷研发的落地的。

为什么要实施


  • 需求的进展不透明,不知道现在到哪里了
  • 需求延期发布成为了家常便饭,不知道什么时候会发布上线
  • 需求发布上线后,心里总是忐忑不安,不知道什么时候会出现问题和故障
  • 团队沟通成本太高,经常性出现RD、FE、QA、PM信息不一致
  • 需求插入随意、频繁,不计成本
  • 不清楚,研发团队的工作量,是正常、超负荷、还是有人不饱和

在互联网初创公司里,需求和有限的资源,永远是矛盾命题,如何在矛盾中寻找平衡,把有限的资源专注于符合公司战略的需求,保持团队的节奏和良好的情绪,就是要实施敏捷管理的痛点,也是我们为什么要实施,敏捷管理也可以很好的回答上面出现的各种问题,给出答案。

使用的工具


下面是我们所使用的工具,Confluence主要是知识库和文档的汇集,JIRA是项目管理工具和BUG管理工具,下面是之前写的如何搭建这些工具的文章,大家可以参考

✔️Atlassian Confluence

创业公司基础设施如何搭建(三) – Confluence(Docker版本)

✔️Atlassian Jira

创业公司基础设施如何搭建(四) – Jira(Docker版本)

如何做好这件事情


需求评审 ➡️ 设计评审 ➡️ 研发实现 ➡️ 测试 ➡️ 验收 ➡️ 发布 ➡️ 后评估

为了让产品和研发过程可视化,更加可控,信息互通,我们采用4个看板模型进行敏捷管理实践,看板名称和功能如下:

公开需求看板(Kanban Board)


通过「看板」建立一个公开需求池,向跨部门成员广泛收集需求,一切市场反馈及时传递到位。看板类型为Kanban Board。

需求看板(Kanban Board)


为需求生命周期搭建流程,按「Backlog - 评审 - 排期 - 设计 - 开发 - 发布」设立多个阶段,需求流转可视化。

任务效能看板(Scrum Board)


为需求预设好发版时间,所有人都可以及时预知逾期风险;产品、开发和需求提出者随时发起沟通,及时同步需求变化或者开发进展。

BUG看板(Kanban Board)


通过看板查询,迭代中的各种类型的BUG数量情况,清楚明了。

公开需求管理


公司属于教育类SaaS,其公开需求主要来源有下面几类:

  • 重要客户(学校)
  • 用户日常使用反馈(教师、学生、家长)
  • 销售渠道

甄别和过滤伪需求和次要或者不符合战略的需求,在这里进行,但是“业务方”提出的众多的需求如何管理,也是一件头疼的事情,这里主要流程发生有下列几种:

  • 用户使用体验 ➡️ 客户成功同学 ➡️ 记录问题 ➡️ 反馈处理结果
  • 大客户需求 ➡️ 客户成功同学 ➡️ 记录问题 ➡️ 反馈处理结果

客户成功同学、销售同学或者其他干系人,都可以在这个看板内,提交原始需求问题,产品同学会过滤、调研,转化为产品需求,到产品需求池内,下面是**公开需求看板**,卡片的内容主要包含了:需求描述、问题类型、解决状态、经办人

  • 判断价值很低或者肯定不会做的需求,直接拖到已完成
  • 判断有一定价值或需要在分析的需求,拖到调研讨论,最终确定后,再拖到已完成

产品研发需求管理


需求分类


产品研发内部,我们把需求分成2类:

  • 产品需求:PM提出的迭代、紧急、日常类需求
  • 技术需求:研发内部为了稳定性、扩展性、维护性而进行的技术重构类需求

需求等级


古语云:师出有名,需求的提出也是如此,为了让研发同学知道需求的重要和紧急程度,需求等级划分是特别需要的一件事情。

  • 产品需求等级划分

    P0:紧急任务,必须穷尽所能,最短时间完成;可以调人支援,可以停止其他项目,需要加班

    P1:非常重要任务,有Deadline,并且不可以Delay;如遇到P0,那么就需要加班保证P1的Deadline

    P2:重要、有影响力的任务,有Deadline,如遇到P0和P1,可以顺延(应该是大部分任务)

    P3:锦上添花的正常任务,优先级最低

  • 技术需求等级划分

    T0:重大性能和漏洞,需要加班加点进行修复

    T1:扩展性和性能风险问题,一般是单独任务进行修复

    T2:设计或者一般性能缺陷,一般是随着迭代进行相关改进

产品需求管理(需求看板)


PM和研发同学,通过PRD的方式进行沟通和交流,研发同学最终也是通过PRD进行开发、测试工作,所以第一步是需要创建PRD,PRD的管理方式采用相对灵活的方式,PM写PRD的工具有的是蓝湖,有的墨刀,我们这里为了统一归档,在Confluence做了归档的统一管理(PRD的详细链接可以是任何工具的链接), 在Confluence创建时选择模板创建,见下图:

主要包含了

  • 背景描述

  • 业务目标的描述

  • 需求链接和功能列表(即Story)

    需求看板的泳道有P0、P1、P1以下、技术需求的4个泳道,为了便于搜索,在快捷搜索列加入了常用的搜索关键词,卡片主要包含:需求等级、到期日、需求负责人

技术需求管理(需求看板)

类似数据结构的变更、技术架构的改进,比如:更换配置中心为Apollo,这类需要简称技术需求,其数据显示和看板功能,和产品需求基本一致,也显示在 需求看板 内,看板如下:

技术任务管理(任务效能看板)

这个阶段,主要是从需求阶段进入到了研发阶段,这个阶段主要包含如下类型的任务:

  • 开发任务:RD、FE
  • 开发自测:RD、FE
  • 测试用例编写:QA
  • 测试用例执行:QA

技术任务类型的问题,主要来源于2个方面

  • 产品需求
  • 技术需求

对于此类任务管理,我们使用的看板是 任务效能看板。在开始之前,我们需要在Backlog内,拖动需要进行迭代的技术需求或产品需求,如下图:

然后,以产品需求和技术需求为父任务,在 需求看板内,创建子任务,界面如下:

创建好后,可以查看父子任务详情,并有工作量体现

点击开始Sprint,并设置好时间,如下图:

RD & QA & FE,在每天下班前,填写其任务的工作量即可达到任务工作量跟踪的效果,如下图:

测试BUG管理(BUG看板)


在Sprint中产生的BUG都会显示在 BUG看板中,工作流主要是打开 ➡️ 处理中 ➡️ 已解决 ➡️ 已关闭,如下图:

我们所采用的BUG类的问题类型有以下几种:

  • 功能错误

  • 界面优化

  • 功能优化

  • 性能问题

  • 安全相关

    在这个迭代结束后,测试人员,会根据BUG的统计报告数据,分析得出本次迭代的测试报告,测试报告,我们在Confluence创建了统一模板,主要内容如下:

小结


需求和效能的生命周期管理,这里仅仅是按照目前产品和团队的需求和阶段,规定了一些适合我们的方法,这个周期管理,还是需要随着人员和阶段的不同而进行不断的改造和演进的,下面是我们在JIRA和Confluence使用的一些核心流程和方法:

  • 4个看板

    公开需求看板:处理市场、销售前端部门提出的“大客户需求”和“用户使用体验问题”

    需求看板:主要是管理技术需求和产品需求

    任务效能看板:主要是管理开发阶段,RD & FE & QA的任务和工作量,跟踪其任务合理性

    BUG看板:主要是管理迭代内产生的5类BUG问题,功能优化 & 功能错误 & 界面优化 & 性能问题 & 安全相关

  • 2个模板

    产品需求模板:产品需求的管理

    测试报告模块:迭代后,针对BUG和其他问题,进行的测试相关的总结

    目前,敏捷相关的管理,这个阶段也仅仅是做了一小部分,还有很多实践方法,在后续的变化中,会加入和实施

    敏捷管理是为了快速、稳定的交付产品而服务的,切忌不要为了追求敏捷工具的使用而耽误了实际要达成的目标。

微信关注我,及时接收最新技术文章

Jenkins Pipeline Maven Project

用代码的方式,管理日常的构建

Maven Project构建的阶段(简单流程)

  1. 清理工作区
  2. 获取code
  3. 加载CI配置文件
  4. 打包
  5. 工件归档
  6. 部署或其他操作
  7. 通知构建结果(这里用的是钉钉)

上面仅仅是针对一般类型项目进行的构建阶段说明,可以针对自己的要求进行增减(还可以包含docker镜像的打包、推送私服、git tag等等)

Maven Demo Project

1
2
3
4
5
6
7
8
9
10
11
12
.
├── README.md
├── app # 主要的code存放module
├── assembly # 打包module
├── commitlint.config.js # git commit message插件配置
├── config # 相关yml properties配置module
├── jenkins.groovy # Jenkins Pipeline调用的配置文件
├── maven # mvnw
├── mvnw
├── mvnw.bat
├── package.json # git commit 依赖包相关
└── pom.xml

上图是标准的Maven多module项目结构,其中有一个jenkins.groovy是和Jenkins Pipeline相关的

1
2
3
4
5
6
7
8
//打包配置文件,配置打包相关信息,CI打包的时候会加载这个文件

// 项目名称
env.PROJECT_CHINESE_NAME = "michael的测试项目"
// 英文名字,用于产物命名,不要随意修改
env.PROJECT_ENGLISH_NAME = "michael-jenkins-pipeline-demo"
// 归档文件正则,如:*.jar
env.ARCH_FILE_PATTERN = "assembly/target/michael-jenkins-pipeline-demo-deploy.zip"

Pipeline Scripts

辅助脚本

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
# 在vars新建fetch_code.groovy
# BUILD_BRANCH为输入参数

def call(GIT_URL) {
git branch: "${BUILD_BRANCH}",
credentialsId: '你的凭证',
url: "${GIT_URL}"
}

# 在vars新建log.groovy

def info(message) {
echo "INFO: ${message}"
}

def warning(message) {
echo "WARNING: ${message}"
}

def error(message) {
echo "ERROR: ${message}"
}

# 在vars新建dingding.groovy
def notice(branch) {
duration = currentBuild.duration / 1000
statusMessage = ''
buildStatusMessage = currentBuild.currentResult
if(buildStatusMessage == 'SUCCESS') {
statusMessage="**<font color=red>成功</font>**"
}else {
statusMessage="**<font color=red>失败</font>**"
}
dingtalk (
robot: '机器人code',
type: 'MARKDOWN',
atAll: false,
title: "${JOB_NAME} BUILD",
text:[
"# 【${JOB_NAME}】构建完成",
"",
"---",
"构建结果: ${statusMessage}",
"> [查看控制台](${BUILD_URL})",
"- 构建分支: ${branch}",
"- 构建用时: ${duration} 秒"
]
)
}

构建脚本

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
/* Config {
deployPath: 部署路径
}
*/
def call(Map config) {
node('java') {
properties([
buildDiscarder(
logRotator(
daysToKeepStr: '30',
numToKeepStr: '50'
)
)
])
stage('清理工作区') {
log.info '清理工作区'
deleteDir()
}
stage('获取代码') {
log.info "获取代码地址:${REPO_URL},获取代码分支: ${BUILD_BRANCH}"
fetch_code "${REPO_URL}"
}
stage('加载CI配置文件') {
log.info "加载CI配置文件"
jenkinsConfigFile="./jenkins.groovy"
if (fileExists("${jenkinsConfigFile}")) {
load "${jenkinsConfigFile}"
log.info "找到打包参数文件${jenkinsConfigFile},加载成功"
} else {
log.error "${jenkinsConfigFile}不存在,请在项目${jenkinsConfigFile}里面配置打包参数"
sh "exit 1"
}
}
stage('构建') {
log.info '构建'
sh "mvn wrapper:wrapper && ./mvnw clean package -Dmaven.test.skip=true"
}
stage('工件归档') {
log.info '工件归档'
archive_file = "${ARCH_FILE_PATTERN}"
echo "Archive ${archive_file}"
archiveArtifacts artifacts: "${archive_file}", fingerprint: true
}
stage('部署 OR 其他操作') {
log.info "部署分支: ${BUILD_BRANCH}"
// 进行对象的部署获取其他操作
currentBuild.description = "代码分支: ${BUILD_BRANCH}<br>"
}
stage('通知') {
dingding.notice("${BUILD_BRANCH}")
}
}
}
微信关注我,及时接收最新技术文章

什么是扩展共享库

在工作中,我们可能会有很多Maven项目的打包部署,还有Android、iOS等等的打包任务,虽然项目不同,但是打包、部署等等流程有很多相似之处,扩展库共享库允许我们抽象出很多公共部分,这样后续在新项目配置Pipeline的时候,会非常简便

准备

  • Jenkins:安装建议参考Jenkins安装
  • Gitlab:安装建议参考Gitlab安装
    为了方便,我这里都选择Docker安装,请大家自行配置Gitlab和Jenkins相关参数,比如Gitlab的SSH Key,Jenkins的凭证,这里不做赘述

扩展共享库配置

创建Gitlab Repo

  • 如图,我创建了名字是jenkins-shared-libraries的Git Repo

目录说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(root)
+- src # Groovy 源码文件
| +- com
| +- michael
| +- Bar.groovy # for org.foo.Bar class,主要是类库相关
+- vars # 变量和公共过程
| +- foo.groovy # 全局'foo'变量
| +- foo.txt # 'foo' 变量的帮助文档
+- resources # 资源文件 (external libraries only)
| +- com
| +- michael
| +- bar.json # static helper data for com.michael.Bar
| +- bar.shell # shell scripts
| +- package.json # 主要是Git commit message 插件
| +- commitlint.config.js # 主要是Git commit message 插件

配置Jenkins扩展共享库

  • 依次点击 系统管理 > 系统配置,找到Global Pipeline Libraries,如下图

编写Pipeline Demo

在git的jenkins-shared-libraries中的vars下面,新建log.groovy文件,并提交

1
2
3
4
5
6
7
8
9
10
11
def info(message) {
echo "INFO: ${message}"
}

def warning(message) {
echo "WARNING: ${message}"
}

def error(message) {
echo "ERROR: ${message}"
}

执行

  • 新建JOB
  • 执行JOB
    在Pipeline中引用共享库,并使用log.info的方法
  • 执行结果

PS:这里仅仅是最简单的Hello world的扩展库介绍,让大家有个感性认识,后面的文章,会针对Maven、Npm这些常见的打包部署方式的项目,进行Pipeline的扩展共享库的实践介绍。

Reference

Jenkins安装
Gitlab安装
Jenkins Shared-Libraries

微信关注我,及时接收最新技术文章

集中式身份管理

Crowd从字面意思看就知道啥意思了,可以管理多种用户目录,如Active Directory、LDAP、OpenLDAP、Microsoft Azure AD等,单点登录 (SSO),无缝集成 Jira、Confluence、Jenkins等等常用DevOps工具系统。

工具包准备

安装启动方式

需要准备

  • Docker

由于官方已经有镜像,我们在官方镜像上面加入自定义的内容即可,官方3.2.1镜像Repo,我们需要的是红框的2个文件,如下(Repo托管是bitbucket):

我在本地Mac上面建立了一个Docker Build目录,包含下面几个文件

  • Dockerfile(上面的官方repo下载)
  • atlassian-crowd-3.2.1.tar.gz(官方二进制包,主要是Dockerfile里面的Curl自动下载的速度实在是慢的可以,我在提前下载好,COPY进去,这块不是非必须用这个包,可以依赖Dockerfile的Curl到官方下载安装包)
  • mysql-connector-java-5.1.45.jar(Mysql官网下载一个)
  • entrypoint.sh(上面的官方repo下载)
    修改Dockerfile
    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
    FROM openjdk:8-jdk-alpine
    MAINTAINER Dave Chevell

    ENV RUN_USER daemon
    ENV RUN_GROUP daemon

    # https://confluence.atlassian.com/crowd/important-directories-and-files-78676537.html
    ENV CROWD_HOME /var/atlassian/application-data/crowd
    ENV CROWD_INSTALL_DIR /opt/atlassian/crowd

    VOLUME ["${CROWD_HOME}"]

    # Expose HTTP port
    EXPOSE 8095

    WORKDIR $CROWD_HOME

    CMD ["/entrypoint.sh", "-fg"]
    ENTRYPOINT ["/sbin/tini", "--"]

    RUN apk add --no-cache wget curl openssh bash procps openssl perl ttf-dejavu tini

    COPY entrypoint.sh /entrypoint.sh

    ARG CROWD_VERSION=3.2.1

    COPY atlassian-crowd-${CROWD_VERSION}.tar.gz /tmp/atlassian-crowd-${CROWD_VERSION}.tar.gz
    COPY mysql-connector-java-5.1.45.jar /tmp/mysql-connector-java-5.1.45.jar

    RUN mkdir -p ${CROWD_INSTALL_DIR} \
    && tar -xzvf /tmp/atlassian-crowd-${CROWD_VERSION}.tar.gz -C ${CROWD_INSTALL_DIR} --strip-components=1 --no-same-owner \
    && mv /tmp/mysql-connector-java-5.1.45.jar ${CROWD_INSTALL_DIR}/crowd-webapp/WEB-INF/lib \
    && rm -rf /tmp/atlassian-crowd-${CROWD_VERSION}.tar.gz \
    && chown -R ${RUN_USER}:${RUN_GROUP} ${CROWD_INSTALL_DIR}/ \
    && sed -i -e 's/-Xms\([0-9]\+[kmg]\) -Xmx\([0-9]\+[kmg]\)/-Xms\${JVM_MINIMUM_MEMORY:=\1} -Xmx\${JVM_MAXIMUM_MEMORY:=\2} \${JVM_SUPPORT_RECOMMENDED_ARGS} -Dcrowd.home=\${CROWD_HOME}/g' ${CROWD_INSTALL_DIR}/apache-tomcat/bin/setenv.sh \
    && sed -i -e 's/port="8095"/port="8095" secure="${catalinaConnectorSecure}" scheme="${catalinaConnectorScheme}" proxyName="${catalinaConnectorProxyName}" proxyPort="${catalinaConnectorProxyPort}"/' ${CROWD_INSTALL_DIR}/apache-tomcat/conf/server.xml

    执行build
    1
    2
    cd /your-docker-build-path
    docker build -t michael/atlassian-crowd:3.2.1 .
    启动镜像(配套如果使用MYSQL,建议Docker Compose启动Jira + MYSQL)
    1
    2
    3
    4
    5
    6
    docker run -d --rm --name="crowd" \
    -v /your-path/crowd:/var/atlassian/application-data/crowd \
    -p 8095:8095 \
    -e "JVM_MINIMUM_MEMORY=384m"
    -e "JVM_MAXIMUM_MEMORY=768m"
    michael/atlassian-crowd:3.2.1
微信关注我,及时接收最新技术文章

项目与事务跟踪工具

Jira是做需求、Bug、任务等等管理的系统,工作流支持全部自定义,自由度极强,下面介绍下Jira的安装流程

为什么是Jira

  • 缺陷跟踪
  • 需求收集
  • 流程审批
  • 任务跟踪
  • 项目跟踪和敏捷管理
  • 完全自定义的workflow

工具包准备

安装和启动

这里介绍2种安装和启动方式

  • 传统安装启动方式
  • Docker安装启动方式(推荐)

传统安装和启动

需要准备

  • CentOS 7.x
  • JDK 1.8(配置好JAVA_HOME)
  • MYSQL 5.7.X(二进制安装或者Docker镜像)

一顿操作猛如虎。。。

1
2
3
4
5
6
7
8
9
10
11
12
## 解压安装包
cd your-path
tar -xvf atlassian-jira-software-7.10.0.tar.gz
mv /your-path/atlassian-jira-software-7.10.0-standalone/ /your-path/atlassian-jira-7.10.0
## 配置环境变量JIRA_HOME
vim ~/.zshrc #看大家用的什么系统,环境文件有所不同
export JIRA_HOME=/tmp/jira-home
## mysql的驱动Jar去官网下载一个适合5.7的即可
cp mysql-connector-java-5.1.45.jar /your-path/atlassian-jira-7.10.0/atlassian-jira/WEB-INF/lib
## 启动
/your-path/atlassian-jira-7.10.0/bin/start-jira.sh
# 默认是8080端口,在浏览器打开http://localhost:8080

Docker安装启动方式

需要准备

  • Docker

由于官方已经有镜像,我们在官方镜像上面加入自定义的内容即可,官方的7.10.0的Dockerfile的Repo地址,我们需要的是红框的2个文件,如下:

我在本地Mac上面建立了一个Docker Build目录,包含下面几个文件

  • Dockerfile(上面的官方repo下载)
  • atlassian-jira-software-7.10.0.tar.gz(官方二进制包,主要是Dockerfile里面的Curl自动下载的速度实在是慢的可以,我在提前下载好,COPY进去,这块不是非必须用这个包,可以依赖Dockerfile的Curl到官方下载安装包)
  • docker-entrypoint.sh(上面的官方repo下载)
    修改Dockerfile
    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
    FROM openjdk:8-alpine

    # Configuration variables.
    ENV JIRA_HOME /var/atlassian/jira
    ENV JIRA_INSTALL /opt/atlassian/jira
    ENV JIRA_VERSION 7.10.0

    # [新增]
    COPY atlassian-jira-software-${JIRA_VERSION}.tar.gz /tmp/atlassian-jira-software-${JIRA_VERSION}.tar.gz
    # [新增]
    RUN mkdir -p ${JIRA_INSTALL}
    RUN tar -xzvf /tmp/atlassian-jira-software-${JIRA_VERSION}.tar.gz -C ${JIRA_INSTALL} --strip-components=1 --no-same-owner \
    && rm -rf /tmp/atlassian-jira-software-${JIRA_VERSION}.tar.gz

    # Install Atlassian JIRA and helper tools and setup initial home
    # directory structure.
    RUN set -x \
    && apk add --no-cache curl xmlstarlet bash ttf-dejavu libc6-compat \
    && mkdir -p "${JIRA_HOME}" \
    && mkdir -p "${JIRA_HOME}/caches/indexes" \
    && chmod -R 700 "${JIRA_HOME}" \
    && chown -R daemon:daemon "${JIRA_HOME}" \
    && mkdir -p "${JIRA_INSTALL}/conf/Catalina" \
    && curl -Ls "https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.38.tar.gz" | tar -xz --directory "${JIRA_INSTALL}/lib" --strip-components=1 --no-same-owner "mysql-connector-java-5.1.38/mysql-connector-java-5.1.38-bin.jar" \
    && rm -f "${JIRA_INSTALL}/lib/postgresql-9.4.1212.jar" \ # [修改] 之前的要删除的jar发现lib没有
    && curl -Ls "https://jdbc.postgresql.org/download/postgresql-42.2.1.jar" -o "${JIRA_INSTALL}/lib/postgresql-42.2.1.jar" \
    && chmod -R 700 "${JIRA_INSTALL}/conf" \
    && chmod -R 700 "${JIRA_INSTALL}/logs" \
    && chmod -R 700 "${JIRA_INSTALL}/temp" \
    && chmod -R 700 "${JIRA_INSTALL}/work" \
    && chown -R daemon:daemon "${JIRA_INSTALL}/conf" \
    && chown -R daemon:daemon "${JIRA_INSTALL}/logs" \
    && chown -R daemon:daemon "${JIRA_INSTALL}/temp" \
    && chown -R daemon:daemon "${JIRA_INSTALL}/work" \
    && sed --in-place "s/java version/openjdk version/g" "${JIRA_INSTALL}/bin/check-java.sh" \
    && echo -e "\njira.home=$JIRA_HOME" >> "${JIRA_INSTALL}/atlassian-jira/WEB-INF/classes/jira-application.properties" \
    && touch -d "@0" "${JIRA_INSTALL}/conf/server.xml"

    # Use the default unprivileged account. This could be considered bad practice
    # on systems where multiple processes end up being executed by 'daemon' but
    # here we only ever run one process anyway.
    USER daemon:daemon

    # Expose default HTTP connector port.
    EXPOSE 8080

    # Set volume mount points for installation and home directory. Changes to the
    # home directory needs to be persisted as well as parts of the installation
    # directory due to eg. logs.
    VOLUME ["/var/atlassian/jira", "/opt/atlassian/jira/logs"]

    # Set the default working directory as the installation directory.
    WORKDIR /var/atlassian/jira

    COPY "docker-entrypoint.sh" "/"
    ENTRYPOINT ["/docker-entrypoint.sh"]

    # Run Atlassian JIRA as a foreground process by default.
    CMD ["/opt/atlassian/jira/bin/start-jira.sh", "-fg"]
    执行build
    1
    2
    cd /your-docker-build-path
    docker build -t michael/atlassian-jira:7.10.0 .
    启动镜像(配套如果使用MYSQL,建议Docker Compose启动Jira + MYSQL)
    1
    2
    3
    4
    docker run -d --rm --name=jira -p 8080:8080 \
    -v /Users/liuyang/CodeMonkey/workspace/docker-workspace/jira/home:/var/atlassian/jira \
    -v /Users/liuyang/CodeMonkey/workspace/docker-workspace/jira/logs:/opt/atlassian/jira/logs \
    michael/atlassian-jira:7.10.0
微信关注我,及时接收最新技术文章

知识库

DevOps、敏捷管理,对于整体流程和团队,2个工具是最不能少的,而且使用频率非常高,知识库 + 问题跟踪,我用的是Confluence + Jira,工欲善其事必先利其器,我们先需要把工具选择和准备好,再开始后面的事情,下面介绍下Confluence的安装流程

为什么是Confluence

  • 丰富的知识库相关的模板
  • 在线协作编辑
  • 每个人的知识可以分享给全公司的人
  • 评论功能和提醒功能完美结合,随时随地得到最新知识库更新
  • 和JIRA结合完美

工具包准备

安装和启动

这里介绍2种安装和启动方式

  • 传统安装启动方式
  • Docker安装启动方式(推荐)

传统安装和启动

需要准备

  • CentOS 7.x
  • JDK 1.8(配置好JAVA_HOME)
  • MYSQL 5.7.X(二进制安装或者Docker镜像)

一顿操作猛如虎。。。

1
2
3
4
5
6
7
8
9
10
11
## 解压安装包
cd your-path
tar -xvf atlassian-confluence-6.9.0.tar.gz
## 指定工作目录
vi /your-path/atlassian-confluence-6.9.0/confluence/WEB-INF/classes/confluence-init.properties
# 在文件中加入 confluence.home=/your-home-path/
## mysql的驱动Jar去官网下载一个适合5.7的即可
cp mysql-connector-java-5.1.45.jar /your-path/atlassian-confluence-6.9.0/confluence/WEB-INF/lib
## 启动
/your-path/atlassian-confluence-6.9.0/bin/start-confluence.sh
# 默认是8090端口,在浏览器打开http://localhost:8090

Docker安装启动方式

需要准备

  • Docker

由于官方已经有镜像,我们在官方镜像上面加入自定义的内容即可,官方的6.9.0的Dockerfile的Repo地址,我们需要的是红框的2个文件,如下:

我在本地Mac上面建立了一个Docker Build目录,包含下面几个文件

  • Dockerfile(上面的官方repo下载)
  • atlassian-confluence-6.9.0.tar.gz(官方二进制包,主要是Dockerfile里面的Curl自动下载的速度实在是慢的可以,我在提前下载好,COPY进去,这块不是非必须用这个包,可以依赖Dockerfile的Curl到官方下载安装包)
  • docker-entrypoint.sh(上面的官方repo下载)
    修改Dockerfile
    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
    FROM openjdk:8-alpine

    # Setup useful environment variables
    ENV CONF_HOME /var/atlassian/confluence
    ENV CONF_INSTALL /opt/atlassian/confluence
    ENV CONF_VERSION 6.9.0

    ENV JAVA_CACERTS $JAVA_HOME/jre/lib/security/cacerts
    ENV CERTIFICATE $CONF_HOME/certificate

    # [新增] 把build目录的必要文件复制到镜像内
    COPY atlassian-confluence-${CONF_VERSION}.tar.gz /tmp/atlassian-confluence-${CONF_VERSION}.tar.gz

    # [新增] 创建Install目录,解压安装包,替换工具Jar包,删除安装包压缩文件
    RUN mkdir -p ${CONF_INSTALL}
    RUN tar -xzvf /tmp/atlassian-confluence-${CONF_VERSION}.tar.gz -C ${CONF_INSTALL} --strip-components=1 --no-same-owner \
    && rm -rf /tmp/atlassian-confluence-${CONF_VERSION}.tar.gz

    # Install Atlassian Confluence and helper tools and setup initial home
    # directory structure.
    RUN set -x \
    && apk --no-cache add curl xmlstarlet bash ttf-dejavu libc6-compat gcompat \
    && mkdir -p "${CONF_HOME}" \
    && chmod -R 700 "${CONF_HOME}" \
    && chown daemon:daemon "${CONF_HOME}" \
    && mkdir -p "${CONF_INSTALL}/conf" \
    && curl -Ls "https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.44.tar.gz" | tar -xz --directory "${CONF_INSTALL}/confluence/WEB-INF/lib" --strip-components=1 --no-same-owner "mysql-connector-java-5.1.44/mysql-connector-java-5.1.44-bin.jar" \
    && chmod -R 700 "${CONF_INSTALL}/conf" \
    && chmod -R 700 "${CONF_INSTALL}/temp" \
    && chmod -R 700 "${CONF_INSTALL}/logs" \
    && chmod -R 700 "${CONF_INSTALL}/work" \
    && chown -R daemon:daemon "${CONF_INSTALL}/conf" \
    && chown -R daemon:daemon "${CONF_INSTALL}/temp" \
    && chown -R daemon:daemon "${CONF_INSTALL}/logs" \
    && chown -R daemon:daemon "${CONF_INSTALL}/work" \
    && echo -e "\nconfluence.home=$CONF_HOME" >> "${CONF_INSTALL}/confluence/WEB-INF/classes/confluence-init.properties" \
    && xmlstarlet ed --inplace \
    --delete "Server/@debug" \
    --delete "Server/Service/Connector/@debug" \
    --delete "Server/Service/Connector/@useURIValidationHack" \
    --delete "Server/Service/Connector/@minProcessors" \
    --delete "Server/Service/Connector/@maxProcessors" \
    --delete "Server/Service/Engine/@debug" \
    --delete "Server/Service/Engine/Host/@debug" \
    --delete "Server/Service/Engine/Host/Context/@debug" \
    "${CONF_INSTALL}/conf/server.xml" \
    && touch -d "@0" "${CONF_INSTALL}/conf/server.xml" \
    && chown daemon:daemon "${JAVA_CACERTS}"

    # Use the default unprivileged account. This could be considered bad practice
    # on systems where multiple processes end up being executed by 'daemon' but
    # here we only ever run one process anyway.
    USER daemon:daemon

    # Expose default HTTP connector port.
    EXPOSE 8090 8091

    # Set volume mount points for installation and home directory. Changes to the
    # home directory needs to be persisted as well as parts of the installation
    # directory due to eg. logs.
    VOLUME ["/var/atlassian/confluence", "/opt/atlassian/confluence/logs"]

    # Set the default working directory as the Confluence home directory.
    WORKDIR /var/atlassian/confluence

    COPY docker-entrypoint.sh /
    ENTRYPOINT ["/docker-entrypoint.sh"]

    # Run Atlassian Confluence as a foreground process by default.
    CMD ["/opt/atlassian/confluence/bin/start-confluence.sh", "-fg"]

    执行build
    1
    2
    cd /your-docker-build-path
    docker build -t michael/atlassian-confluence:6.9.0 .
    启动镜像(配套如果使用MYSQL,建议Docker Compose启动Confluence + MYSQL)
    1
    2
    3
    4
    docker run -d --rm --name=wiki -p 8090:8090 -p 8091:8091 \
    -v /your-confluence-path/home:/var/atlassian/confluence \
    -v /your-confluence-path/logs:/opt/atlassian/confluence/logs \
    michael/atlassian-confluence:6.9.0
微信关注我,及时接收最新技术文章

CentOS 7.x / Docker CE

安装docker

1
2
3
4
5
6
7
8
yum install -y yum-utils device-mapper-persistent-data lvm2
# repo建议使用阿里云的,官方的docker,国内连接起来不稳定
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum clean all # 清理yum
yum makecache fast # 生成yum repo缓存
yum install docker-ce # 安装docker社区版本
systemctl enable docker # 设置为开机启动
systemctl start docker # 启动docker服务

设置镜像加速器

1
2
3
4
vim /etc/systemd/system/multi-user.target.wants/docker.service
# registry-mirror用的是阿里云的Hub加速器,可以在https://dev.aliyun.com/search.html申请自己的专属加速器地址
# 找到ExecStart=这行,添加 --registry-mirror=https://xxxx.mirror.aliyuncs.com #使用阿里云加速
ExecStart=/usr/bin/dockerd --registry-mirror=https://xxxx.mirror.aliyuncs.com

配置docker私服地址(可选)

1
2
3
vim /etc/systemd/system/multi-user.target.wants/docker.service
# 找到ExecStart=这行,添加 --insecure-registry=hub.xxx.com
ExecStart=/usr/bin/dockerd --registry-mirror=https://xxxx.mirror.aliyuncs.com --insecure-registry=hub.xxx.com

自定义docker存储目录(可选)

1
2
3
vim /etc/systemd/system/multi-user.target.wants/docker.service
# 找到ExecStart=这行,添加 --graph /opt1/docker
ExecStart=/usr/bin/dockerd --registry-mirror=https://xxxx.mirror.aliyuncs.com --insecure-registry=hub.xxx.com --graph /opt1/docker

重启Docker

1
2
systemctl daemon-reload # 重新加载daemon
systemctl restart docker # 重启docker

参考资料

微信关注我,及时接收最新技术文章