jenkins实现CD部分(SFTP+Nexus)

背景

1
2
3
4
5
6
客户有第三方供应商开发应用,所以不方便提供源码。
经商议,供应商交付制品为jar,我们运维团队负责CD部分进行部署即可。

需求:
1. 客户要求有存放制品的仓库,构建的jar包默认长期保存。
2. 供应商反馈提供sftp以供他们上传jar包

实现介绍

  • SFTP:提供上传下载jar包功能
  • Nexus:提供制品仓库功能
  • Jenkins:pipeline流水线,CD发布应用

配置

SFTP、Nexus、Jenkins安装部分省略,可参考网上资料。

Nexus
  • 创建仓库

    1
    2
    选择‘maven2(hosted)’类型的,其他配置按需设置。
    可考虑开启 Allow redeploy,这样可针对同一个版本重新上传制品
  • 创建子用户

    1
    权限划分,每个用户只对应相关仓库权限
  • 上传制品

    1
    一般上传有2种方式,nexus控制台上传、nexus api上传

Jenkins
  • 安装插件

    1
    2
    3
    4
    Build With Parameters:参数化构建,提供构建参数
    Nexus Artifact Uploader:上传制品到nexus仓库(利用api)
    Maven Artifact ChoiceListProvider (Nexus):在构建时提供列表形式,以供拉取制品
    Publish Over SSH:发布应用到目标服务器
  • General部分

新建任务,选择流水线

1
2
3
4
5
6
7
8
9
10
# 勾选参数化构建过程,选择 Extensible Choice
在出来的插件框中 Choice Provider,选择 Nexus3 Artifact Choice Parameter
Nexus Server URL:nexus仓库ip:port
Credentials:仓库子用户账户密码鉴权
RepositoryId:仓库id(可在nexus创建的仓库中查看)
GroupId:组id,一般建议按环境区分,如xx-prod、xx-uat
ArtifactId:制品id,一般建议按jar包功能类别区分
Packaging:jar

配置完成后,在右下角点击 List Files Now, 如配置正确且仓库已上传demo jar包,此时可列出jar包url路径

参数化构建

Nexus插件配置

列出对应过滤jar包

1
2
# 其余参数
选项参数,用于选择发布的目标服务器(如单台做灰度发布进选择对应服务器即可,正常发布选择 all 即可全部发布)

  • 流水线部分
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
# 流水线内容如下

String artifactURL = "${env.artifactUrl}".toString()

pipeline {
//运行在任意的可用节点上
agent any
stages {
stage('PullArtifact') {
steps("步骤名称:PullArtifact"){
echo '打印:PullArtifact'
println("The artifact url is: ${artifactURL}")
sh "wget ${ArtifactURL} && pwd && ls"
}
}
stage('DeployHost') {
steps {
echo '打印:DeployHost'
echo "${hostip}"
//sh 'jar_name=`echo "${ArtifactURL}" | awk -F \'/\' \'{print $7}\'`'
//echo "${jar_name}"
//jenkins服务器免密登录远程服务器
sh '''jar_name=`echo "${ArtifactURL}" | awk -F \'/\' \'{print $NF}\'`
deploy(){
IP=$1
scp -o StrictHostKeyChecking=no ${jar_name} root@${IP}:/data/
ssh root@${IP} "/bin/bash /data/deploy.sh deploy ${jar_name}"
}

if [ ${hostip} == "all" ];then
deploy 10.x.x.x
sleep 3
deploy 10.x.x.x
else
deploy ${hostip}
fi'''
}
}
stage('CheckService') {
steps {
echo '打印:CheckService'
sh '''jar_name=`echo "${ArtifactURL}" | awk -F \'/\' \'{print $NF}\'`
ps -ef|grep ${jar_name} | grep -v grep'''
}
}
}
post{
success {
script {
sh 'export TYPE=success;BUILD_TIMESTAMP="${BUILD_TIMESTAMP}";BUILD_NUMBER="${BUILD_NUMBER}";JOB_NAME="${JOB_NAME}";BUILD_URL="${BUILD_URL}console";sh /data/scripts/send_msg_weixin.sh'
}
}
failure {
script {
sh 'export TYPE=failure;BUILD_TIMESTAMP="${BUILD_TIMESTAMP}";BUILD_NUMBER="${BUILD_NUMBER}";JOB_NAME="${JOB_NAME}";BUILD_URL="${BUILD_URL}console";sh /data/scripts/send_msg_weixin.sh'
}
}
}
}
  • 脚本内容
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
# send_msg_weixin.sh,webhook方式发送告警至企业微信

#!/bin/sh
#替换成自己的群机器人key值
CHAT_WEBHOOK_KEY="25299927-xxxxx"
CHAT_CONTENT_TYPE='Content-Type: application/json'
#-o代表或的意思,成功或失败 CHAT_WEBHOOK_URL都是一样的WEBHOOK url
if [ _"${TYPE}" = _"success" -o _"${TYPE}" = _"failure" ]; then
CHAT_WEBHOOK_URL='https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key'
fi

if [ _"${CHAT_WEBHOOK_KEY}" = _"" ]; then
echo "please make sure CHAT_WEBHOOK_KEY has been exported as environment variable"
fi

echo "## send message for : ${TYPE}"
if [ _"${TYPE}" = _"success" ]; then
curl "${CHAT_WEBHOOK_URL}=${CHAT_WEBHOOK_KEY}" \
-H "${CHAT_CONTENT_TYPE}" \
-d '
{
"msgtype": "markdown",
"markdown": {
"content": "## 【<font color=\"warning\">Jenkins任务通知</font>】 \n> 构建BUMBER:<font color=\"comment\">'"${BUILD_NUMBER}"'</font>\n> 构建时间:<font color=\"comment\">'"${BUILD_TIMESTAMP}"'</font>\n> 任务名称:<font color=\"comment\">'"${JOB_NAME}"'</font>\n> 构建日志:<font color=\"comment\">[点击查看]('"${BUILD_URL}"')</font>\n> 构建状态: <font color = \"info\">**Success**</font>"
}
}'
elif [ _"${TYPE}" = _"failure" ]; then
curl "${CHAT_WEBHOOK_URL}=${CHAT_WEBHOOK_KEY}" \
-H "${CHAT_CONTENT_TYPE}" \
-d '
{
"msgtype": "markdown",
"markdown": {
"content": "## 【<font color=\"warning\">Jenkins任务通知</font>】 \n> 构建BUMBER:<font color=\"comment\">'"${BUILD_NUMBER}"'</font>\n> 构建时间:<font color=\"comment\">'"${BUILD_TIMESTAMP}"'</font>\n> 任务名称:<font color=\"comment\">'"${JOB_NAME}"'</font>\n> 构建日志:<font color=\"comment\">[点击查看]('"${BUILD_URL}"')</font>\n> 构建状态: <font color = \"warning\">**Failure**</font>"
}
}'
fi
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
# deploy.sh  部署脚本

#!/bin/bash
#参数
APP_NAME=app-api
#使用说明,用来提示输入参数
usage() {
echo "Usage: sh restart.sh [start|stop|restart|status]"
exit 1
}

#检查程序是否在运行
#grep -v反选匹配 awk指定文件某一行
is_exist(){
pid=`ps -ef|grep $APP_NAME |grep -v grep|awk '{print $2}'`
#如果不存在返回1,存在返回0
if [ -z "${pid}" ]; then
return 1
else
return 0
fi
}

#部署方法
deploy(){
stop
sleep 5
if [ $? -eq 0 ]; then
echo "${APP_NAME} is already running. pid=${pid}"
else
nohup java -jar -Xms128m -Xmx1024m $2 > ${APP_NAME}.log 2>&1 &
fi
}

#启动方法
start(){
is_exist
if [ $? -eq 0 ]; then
echo "${APP_NAME} is already running. pid=${pid}"
else
nohup java -jar -Xms128m -Xmx1024m $2 > ${APP_NAME}.log 2>&1 &
fi
}

#停止方法
stop(){
is_exist
if [ $? -eq "0" ]; then
kill -9 $pid
else
echo "${APP_NAME} is not running"
fi
}

#输出运行状态
status(){
is_exist
if [ $? -eq "0" ]; then
echo "${APP_NAME} is running. Pid is ${pid}"
else
echo "${APP_NAME} is NOT running."
fi
}

#重启
restart(){
stop
sleep 5
start
}

#根据输入参数,选择执行对应方法,不输入则执行使用说明
case "$1" in
"deploy")
deploy
;;
"start")
start
;;
"stop")
stop
;;
"status")
status
;;
"restart")
restart
;;
*)
usage
;;
esac

效果演示

  • build

  • 流水线

部署报错是没添加对应测试机SSH互信信息

-------------本文结束感谢您的阅读-------------
原创技术分享,感谢您的支持。