1、背景
公司有一个项目使用nacos作为服务注册和配置中心。老板希望项目更新升级过程尽量不影响用户使用,特申请一台服务器作负载均衡服务器。
服务器 | 部署内容 |
---|---|
172.16.0.14 | nginx、nacos、mysql、项目服务 |
172.16.0.13 | jenkins、项目服务 |
一共就两台服务器资源有限且项目的服务较多,故没有搭建harbor做镜像仓库。172.16.0.14作为jenkins slave执行代码获取、项目构建、部署。
2、jenkins配置
jenkin安装配置不作介绍,由于在pipeline执行过程环境变量是统一的,所以两台服务器上maven、jdk版本必须一致。
下面重点介绍slave配置:
2.1 slave新增节点
点击 manage jenkins(管理jenkins)–>manage nodes and clouds(节点管理)–>新增节点
输入节点名称,选中"Permanent Agent",点确定
2.2 配置slave节点
2.3服务器配置SSH免密登录
在172.16.0.13(master)执行命来 ssh-keygen,一路点enter生成SSH密钥。在用户的家目录下生成隐藏文件夹.ssh及密钥文件
[ .ssh]# ls
authorized_keys id_rsa id_rsa.pub known_hosts
公钥拷贝到authorized_keys文件
]# cat id_rsa.pub >> authorized_keys
]# chmod 600 authorized_keys
将shh密钥发给slave主机,方便从主机上免密登录
]# $ ssh-copy-id root@172.16.0.13
2.4 jenkins配置SSH凭据
点击manage jenkins(管理jenkins)–>manage credentials(凭据管理)–>添加凭据
类型选择:username with password
2.5启动slave节点
slave没有启动前此处有启动按钮,配置正确点启动即可启动slave节点
注:需要安装SSH插件,开始没有安装SSH插件。slave可以启动,能运行系统命令。但是执行git拉取代码报错,后来安装插件后搞定。具体哪些插件是必须的不记得了,当时找到疑似的都安装了
3、nacos服务
网关actuator,可以获取各种状态的参数
http://ip:port/actuator,此处ip:port是网关的地址
nacos服务下线,此处172.16.0.13:39001是具体某一个服务的地址,status=DOWN表示将该服务下线
curl -X ‘POST’ ‘http://172.16.0.13:39001/actuator/service-registry?status=DOWN’ -H ‘Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8’
转:微服务架构:Nacos本地缓存 PK 微服务优雅下线
获取服务状态,此处ip:port是nacos的地址
http://ip:port/nacos/v1/ns/instance/list?serviceName=wekb-print
nacos接口文档: https://nacos.io/zh-cn/docs/what-is-nacos.html
更新服务流程如下:
1、将服务A的第一个服务A1下线,更新;
2、将服务A的第二个服务A2下线,更新;
其中需要注意的是服务注册到nacos后需要时间一点时间才是健康状态。如果服务更新速度太快A1还没有恢复正常,此时A2下线会造成服务A无法对外提供服务。每个服务更新都需要计算好时间,可能更新A2时需要先sleep一小会。
4、项目pipeline
服务如果较多,需要使用ansible部署项目可以参考:jenkins pipeline harbor docker ansible-playbook部署maven项目实战
pipeline {
agent {
label "master" ##此处需要写节点agent,具体stage执行时用不同agent
}
tools { ##定义maven jdk环境变量
maven 'maven'
jdk 'JDK_1.8'
}
environment {
DOCKER_TAG=createVersion()
}
stages {
stage('master git pull'){
## 先在master节点执行任务,每个stage都指定agent
agent {
label "master"
}
steps {
sh 'pwd'
git branch: "${params.branch}" ,credentialsId: 'wekb', url: '你的项目git地址'
}
}
stage('master mvn install') {
agent {
label "master"
}
steps {
script {
try {
sh 'pwd'
## 微服务较多,有些微服务在子目录中,maven构建时需要指定父目录
if (params.DOCKER_NAME in ['blade-system', 'blade-user'] ){
sh "mvn clean install -pl blade-service/${params.DOCKER_NAME} -Dmaven.repo.local=/root/.m2/repository -Dmaven.test.skip=true -am"
}
else if (params.DOCKER_NAME in ['blade-resource', 'blade-xxljob-admin', 'blade-xxljob'] ){
sh "mvn clean install -pl blade-ops/${params.DOCKER_NAME} -Dmaven.repo.local=/root/.m2/repository -Dmaven.test.skip=true -am"
}
else {
sh "mvn clean install -pl ${params.DOCKER_NAME} -Dmaven.repo.local=/root/.m2/repository -Dmaven.test.skip=true -am"
}
currentBuild.result="SUCCESS"
} catch (e) {
currentBuild.result="FAILURE"
throw e
} finally {
}
}
}
}
stage('master image build & run container') {
agent {
label "master"
}
steps {
script {
try {
## 有些服务需要到子目录build镜像
if (params.DOCKER_NAME in ['blade-system', 'blade-user'] ){
sh "cd ./blade-service/${params.DOCKER_NAME} && docker build -t ${params.DOCKER_NAME}:${DOCKER_TAG} ."
}
else if (params.DOCKER_NAME in ['blade-resource', 'blade-xxljob-admin', 'blade-xxljob'] ){
sh "cd ./blade-ops/${params.DOCKER_NAME} && docker build -t ${params.DOCKER_NAME}:${DOCKER_TAG} ."
}
else {
sh "cd ./${params.DOCKER_NAME} && docker build -t ${params.DOCKER_NAME}:${DOCKER_TAG} ."
}
currentBuild.result="SUCCESS"
if (params.DOCKER_NAME=='blade-auth'){
## 更新服务前先将服务下线
sh " curl -X 'POST' 'http://172.16.0.13:8100/actuator/service-registry?status=DOWN' -H 'Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8' || true"
## 再删除服务的容器
sh "docker rm -f ${params.DOCKER_NAME}||true"
## 最后启动服务的容器
sh """docker run -p 8100:8100 -p 9106:9106 \
--name=${params.DOCKER_NAME} \
-v /data/config/${params.DOCKER_NAME}:/data/config/${params.DOCKER_NAME} \
-v /data/logs/${params.DOCKER_NAME}:/data/logs/${params.DOCKER_NAME} \
-v /etc/localtime:/etc/localtime:ro \
--network=host \
-e TZ=Asia/Shanghai \
-d ${params.DOCKER_NAME}:${DOCKER_TAG} \
--spring.profiles.active=test \
--logging.config=/data/config/${params.DOCKER_NAME}/logback-prod.xml
"""
}
}
catch (e) {
currentBuild.result="FAILURE"
throw e
}
finally {
sh "docker images | grep ${params.DOCKER_NAME} | sed -n \'2, \$p\' | awk \'{print \$3}\' | xargs docker rmi ||true"
}
}
}
}
stage('slave git pull'){
## 再在slave节点执行任务,每个stage都指定agent
agent {
label "qwin226"
}
steps {
sh 'pwd'
git branch: "${params.branch}" ,credentialsId: 'wekb', url: '你的项目git地址'
}
}
stage('slave mvn install') {
agent {
label "qwin226"
}
steps {
script {
try {
sh 'pwd'
if (params.DOCKER_NAME in ['blade-system', 'blade-user'] ){
sh "mvn clean install -pl blade-service/${params.DOCKER_NAME} -Dmaven.repo.local=/root/.m2/repository -Dmaven.test.skip=true -am"
}
else if (params.DOCKER_NAME in ['blade-resource', 'blade-xxljob-admin', 'blade-xxljob'] ){
sh "mvn clean install -pl blade-ops/${params.DOCKER_NAME} -Dmaven.repo.local=/root/.m2/repository -Dmaven.test.skip=true -am"
}
else {
sh "mvn clean install -pl ${params.DOCKER_NAME} -Dmaven.repo.local=/root/.m2/repository -Dmaven.test.skip=true -am"
}
currentBuild.result="SUCCESS"
} catch (e) {
currentBuild.result="FAILURE"
throw e
} finally {
}
}
}
}
stage('slave image build & run container') {
agent {
label "qwin226"
}
steps {
script {
try {
if (params.DOCKER_NAME in ['blade-system', 'blade-user'] ){
sh "cd ./blade-service/${params.DOCKER_NAME} && docker build -t ${params.DOCKER_NAME}:${DOCKER_TAG} ."
}
else if (params.DOCKER_NAME in ['blade-resource', 'blade-xxljob-admin', 'blade-xxljob'] ){
sh "cd ./blade-ops/${params.DOCKER_NAME} && docker build -t ${params.DOCKER_NAME}:${DOCKER_TAG} ."
}
else {
sh "cd ./${params.DOCKER_NAME} && docker build -t ${params.DOCKER_NAME}:${DOCKER_TAG} ."
}
currentBuild.result="SUCCESS"
if (params.DOCKER_NAME=='blade-auth'){
## 该服务更新速度快,需要先sleep10秒等master节点上的服务恢复健康再更新
sh "sleep 10s"
sh " curl -X 'POST' 'http://172.16.0.14:8100/actuator/service-registry?status=DOWN' -H 'Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8' || true"
sh "docker rm -f ${params.DOCKER_NAME}||true"
sh """docker run -p 8100:8100 -p 9106:9106 \
--name=${params.DOCKER_NAME} \
-v /data/config/${params.DOCKER_NAME}:/data/config/${params.DOCKER_NAME} \
-v /data/logs/${params.DOCKER_NAME}:/data/logs/${params.DOCKER_NAME} \
-v /etc/localtime:/etc/localtime:ro \
--network=host \
-e TZ=Asia/Shanghai \
-d ${params.DOCKER_NAME}:${DOCKER_TAG} \
--spring.profiles.active=test \
--logging.config=/data/config/${params.DOCKER_NAME}/logback-prod.xml
"""
}
}
catch (e) {
currentBuild.result="FAILURE"
throw e
}
finally {
sh "docker images | grep ${params.DOCKER_NAME} | sed -n \'2, \$p\' | awk \'{print \$3}\' | xargs docker rmi ||true"
}
}
}
}
}
}
def createVersion() {
// 定义一个版本号作为当次构建的版本
return new Date().format('yyyyMMddHHmmss') + "-${env.BUILD_ID}"
}
5、遗留问题
a、定时任务类服务未确认是否可以启两个,定时任务类服务先只启一个。
b、gateway是通过nginx转发,更新gateway服务如果想不影响用户使用,还需要修改nginx配置。gateway很少更新,到时需要手动修改nginx配置并reload。