0%

DevOps实战

前言

​ 本文介绍使用Jenkins部署项目,使用gitea、registry配合,分别用作代码管理、镜像存储仓库

  • 注:一般公司的发布方式有3种

    1. 直接用jar包发布(在开发环境构建jar包,将jar包上传到目标服务器,并在该服务器运行jar包)

    2. 使用docker发布(利用Jenkins从gitea拉取对应分支代码,构建jar包,将jar包和dockerfile构建为docker镜像,然后将该镜像上传的目标服务器,在目标服务器用docker运行该镜像)

    3. 使用k8s发布(利用Jenkins从gitea拉取对应分支代码,构建jar包,将jar包和dockerfile构建为docker镜像,然后将该镜像上传到私有镜像仓库(registry、harbor等),然后由k8s从私有镜像仓库拉取docker镜像,并进行部署(k8s一般进行集群部署,将镜像部署到多台服务器))

    4. 本文采取循序渐进的方式,先讲述如何实现docker部署方式(第二种方式),再慢慢补充k8s的部署方式

    5. 一般初创企业、中小企业使用第二种方式已经能够满足需求

发布流程(docker方式)

设备拓扑(docker方式)

  • 注:我用gitea代替了gitlab,因为gitea的对设备的性能要求更小一些;另外我将gitea和Jenkins都部署在本地(Win11)的DockerDesktop中,因为网络的问题(我在虚拟机中使用macvlan,但是Jenkins很难访问外网),我利用frp转接(Jenkins访问云服务器,云服务利用frp将访问请求转发到本机的gitea),但是原理是一样的,我们开发后将代码推送到gitea服务,Jenkins从gitea拉取代码,编译打包后将jar包推送到需要部署的目标服务器,将jar包和Dockerfile构建为docker镜像,再启动服务

基础服务安装部署

gitea服务安装部署

  1. 在适当的地方建立gitea文件夹

  2. 在gitea文件夹中建立docker-compose.yml文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    version: '3'

    services:
    gitea:
    image: gitea/gitea:1.22.2 # 使用最新的 Gitea 镜像
    container_name: gitea
    restart: always
    environment:
    # USER_UID和USER_GID是用来给用户授权的,在centos系统中可以通过命令“id”获取;当你是root用户时,这两个参数可以省略
    - USER_UID=1000
    - USER_GID=1000
    ports:
    - "3030:3000" # Web界面端口
    - "2222:22" # SSH 端口
    volumes:
    - ./data:/data # 持久化 Gitea 数据
    - /etc/timezone:/etc/timezone:ro
    - /etc/localtime:/etc/localtime:ro
  3. 运行命令安装并运行gitea

    1
    docker-compose up -d
  4. 运行命令查看gitea是否成功启动

    1
    docker ps -a
  5. 访问http://localhost:3030(用frp转接后可以从外网访问,http://{云服务器公网ip}:{frp配置的外网接口}), 设置用户名密码后就可以像使用github、gitee一样使用这个git服务器

  6. 创建一个仓库,将本地代码推送到服务器

    使用git命令查看本地git远程连接

    1
    git remote -v

    到新建仓库获取仓库URL(这里建议使用frp转接后的公网url,remote默认名称为origin),使用git命令添加一条remote链接

    1
    git remote add remote名称 仓库URL

    现在可以使用git push命令将本地代码推送到搭建好的gitea服务器

    1
    git push remote名称 仓库分支名称

Jenkins服务安装部署

  1. 在适当的地方创建Jenkins文件夹
  2. 在Jenkins文件夹中创建docker-compose.yml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
version: '3.6'

services:
jenkins:
image: jenkins/jenkins:lts # 使用长支持版本
container_name: jenkins
restart: on-failure # 如果是发生错误导致服务停止,docker会重启服务
privileged: true # 设置更高权限
user: root
environment:
- JAVA_OPTS=-Xmx1024m # 设置Jenkins Java虚拟机的内存限制
ports:
- "8888:8080" # 将Jenkins的8080端口映射到宿主机
- "50000:50000" # Jenkins的Jenkins agents连接端口
volumes:
- ./jenkins_home:/var/jenkins_home
- ./tool:/tool # 这个文件夹用于挂载JAVA_HOME和MAVEN_HOME
  1. 运行命令安装并启动Jenkins
1
docker-compose up -d
  1. 验证Jenkins已经启动
1
docker ps -a
  1. 查看初始密码
1
cat /jenkins_home/secrets/initialAdminPassword
  1. 修改’/jenkins_home/hudson.model.UpdateCenter.xml’文件中的url参数为国内的镜像源(如清华镜像源)
1
2
3
4
5
6
7
<?xml version='1.1' encoding='UTF-8'?>
<sites>
<site>
<id>default</id>
<url>https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json</url>
</site>
</sites>
  1. 设置账号密码后进入并额外安装两款插件‘Git Parameter’、‘Publish Over SSH’

  2. 将linux版的jdk和maven下载并解压到tool文件夹

  3. 进入Jenkins页面(http://localhost:8888),进入‘系统管理’-‘全局工具配置(tool)’,点击‘JDK安装’和‘MAVEN安装’,名称可以自己取,路径则填写‘/tool/jdk’和’/tool/maven‘

  • 注意:路径名称可以不固定的,但是填写的路径要一直到’bin‘目录的上一级;解压文件时windows系统要用管理员模式解压;
  1. 在tool下新建repo目录,进入刚才的’/tool/maven/conf’文件夹,修改settings.xml文件中的’localRepository’的值为’/tool/repo‘,修改’mirror‘的值为阿里云镜像
1
2
3
4
5
6
7
8
<localRepository>/tool/repo</localRepository>

<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>

registry服务安装部署

  1. 在适当位置新建’registry‘文件夹,在其中新建’docker-compose.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
services:
registry:
privileged: true #这里把权限配置成true
image: registry #镜像来源
#restart: on-failure #开机后自己启动
restart: no
container_name: registry #自定义服务名
ports: #暴露端口
- 5000:5000
volumes: #挂载目录配置
- ./data:/var/lib/registry
- ./certs:/certs
networks:
- registry-net
registry-web:
image: konradkleine/docker-registry-frontend:v2
#restart: on-failure
restart: no
ports:
- 8035:80
environment:
# 这里的ip可以配置容器名称’registry‘,因为’registry‘和'registry-web'处于同一个网络’registry-net‘中,但是我为了统一,配置的是代理云服务器的ip
- ENV_DOCKER_REGISTRY_HOST=139.155.229.99
- ENV_DOCKER_REGISTRY_PORT=5000
networks:
- registry-net

networks:
registry-net:
driver: bridge

1
2
docker compose up -d
docker ps -a
  1. 修改’/etc/docker/daemon.json’文件,添加连接项后重启docker
1
2
3
{
"insecure-registries": ["139.155.229.99:5000"]
}
1
2
systemctl daemon-reload
systemctl restart docker

​ 如果是使用docker-desktop,则在’设置‘-’docker enigine‘中添加连接项,然后点击‘Apply & restart’

  • 注:这里设置的‘insecure-registries’是docker访问registry服务器的链接;在生产环境中一般会使用https和SSL证书来保证安全
  1. 使用方式

(1)为镜像打标签

1
2
3
docker tag 镜像名称 registry-ip:port/组织名称/镜像名称
# 例:
docker tag mysql:8.0 192.168.144.140:5000/qiuli/mysql:8.0

(2)上传镜像到registry仓库

1
2
3
docker push registry-ip:port/组织名称/镜像名称
# 例
docker push 192.168.144.140:5000/qiuli/mysql:8.0

(3)访问registry-web可以查看到上传的镜像

(4)拉取镜像

​ 进入镜像页面,使用命令拉取镜像即可

1
docker pull 139.155.229.99:5000/qiuli/hello-world:1.0.0

部署单体项目

使用Jenkins部署Java项目

新建目标服务器

​ 从Jenkins主页进入’系统管理‘-’系统配置‘-’Publish Over SSH‘-‘SSH Servers’,添加‘SSH Server’;自定义name,hostname为目标服务器ip,username为目标服务器用户名(默认为’root‘),Remote Directory是发布路径(即将推送来的jar包放在哪里,这里用‘/usr/local’);点击’高级‘,选择’use password‘,然后填写密码;点击’Test Configuration‘,如果显示‘success’则点击’应用‘-’保存‘

新建部署任务

点击’新建任务‘,输入任务名称,先选择’构建一个自由风格的软件项目‘,点击’确定‘

在’源码管理‘中选择’Git‘,填入gitea的仓库URL,选择需要部署的分支

  • 注意:这里如果仓库有权限控制则需要输入账号密码

在’Build Steps‘中点击’增加构建步骤‘,选择’执行Shell‘,添加命令

1
sh /tool/maven/bin/mvn clean package -Dmaven.test.skip=true

  • 注意:因为我们没有在容器内添加全局环境变量,所以这里没有办法使用’mvn‘,而必须使用包含具体执行目录的’’/tool/maven/bin/mvn’,‘/tool/maven’是我们安装是yml文件中配置的挂载目录

点击’应用‘-’保存‘回到任务界面,点击’立即构建‘,等待其构建,在’控制台输出‘中可以看到日志

构建完毕后进入docker-compose.yml文件中挂载的’jenkins_home‘文件夹,在’workspace‘-’项目名称‘-’target‘中可以看到编译打包完毕的jar包

添加构建脚本

​ 这里我们需要更进一步,将打包好的jar包推送到目标服务器并编写shell脚本运行它

​ 进入该项目配置页面,来到’构建后操作‘-’增加构建后操作步骤‘-’Send build artifacts over SSH‘;

​ 选择刚才配置的云服务器,在’Transfer Set‘-’Source files‘中填写’target/*.jar‘(即打包完毕的jar包的本地路径);在’Exec command‘中填写在目标服务器中将要执行的部署脚本:

1
2
3
4
# 首先关闭原先在运行中的java程序(如果有的话)
pkill java
# 运行java程序并且在运行结束1s后退出
nohup java -jar /usr/local/target/jenkins-deploy-demo-1.0-SNAPSHOT.jar & sleep 1

​ 再选择’高级‘,点击’Exec in pty‘;点击’应用‘-’保存‘

构建并部署

​ 来到项目界面,再次点击‘立即构建’,等待构建完成查看日志结尾显示‘success’,来到目标服务器,可以查看‘nohup.out’文件,里面是项目的启动日志;这时可以确认项目启动成功

​ 从浏览器访问’http://目标服务器ip:项目端口port/‘,就可以看到’hello world‘页面

使用Jenkins部署Java项目(构建docker镜像并运行容器)

修改代码

​ 在Java项目中新建’/docker/Dockerfile’,并将修改后的代码推送到gitea服务器

1
2
3
4
5
6
7
8
9
10
11
12
# 使用openjdk8的镜像
FROM openjdk:8-jre

# 设置工作目录
WORKDIR /build

# 将jar包复制到容器中
#(注意,这里因为我们在第3步设置了消除多余前缀目录,所以jar包直接放在目标服务器中设置的‘Remote Directory’中,前面不会再有‘target’)
COPY ./jenkins-deploy-demo-1.0-SNAPSHOT.jar ./jenkins-deploy-demo.jar

# 运行jar包
ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS jenkins-deploy-demo.jar $PARAMS"]

​ 使用git推送到gitea服务器,这里不再赘述

新建目标服务器

​ 按照‘使用Jenkins部署Java项目’中的步骤创建即可,这里’Remote Directory’设置为‘/usr/local/jenkins-deploy-demo’,也可以使用已经创建好的目标服务器进行修改

新建部署任务

​ 按照‘使用Jenkins部署Java项目’中的步骤创建即可,也可以使用原来已经创建好的任务进行修改

重写构建脚本并推送文件到目标服务器

​ 这里我使用原来的部署任务进行修改,在‘构建后操作’-’Send build artifacts over SSH‘,’Transfers’-‘Transfer Set’中添加‘Remove prefix’的值为‘target’,并在‘Exec command’中删除原来的构建脚本

  • 注:‘Remove prefix’的意思是移除文件前缀,目的是将多余的文件夹去除,让上传的文件保存到同一个目录下;

​ 再添加一项‘Transfer Set’,‘Source files’中添加‘docker/*’,‘Remove prefix’中添加‘docker’(去除前缀目录),’Exec command’中添加构建脚本:

1
2
3
4
5
6
# 首先从 '/usr/local/project/jenkins-deploy-demo' 目录中的 Dockerfile 构建一个名为 'qiuli/jenkins-deploy-demo:1.0' 的镜像
docker build -t qiuli/jenkins-deploy-demo:1.0 /usr/local/project/jenkins-deploy-demo
# 如果有一个名为 jenkins-deploy-demo 的容器正在运行,将其强制删除
docker rm -f jenkins-deploy-demo
# 运行一个新的容器,使用 qiuli/jenkins-deploy-demo:1.0 镜像,容器将在后台运行,并且将 10001 端口映射到宿主机的 10001 端口
docker run -d -p 10001:10001 --name=jenkins-deploy-demo qiuli/jenkins-deploy-demo:1.0

  • 注:这里因为我重新设置了‘SSH Server’-‘Remote Directory’为‘/usr/local/project/jenkins-deploy-demo’,所以上传的两个文件保存在了‘/usr/local/project/jenkins-deploy-demo’中

构建并部署

​ 点击‘应用’-‘保存’后回到项目界面,点击‘立即构建’(这里如果报错,可以到目标服务器,将构建脚本一步步运行,查看报错);

​ 构建成功后来到目标服务器,输入“docker images”、“docker ps -a”可以看到构建后的镜像和运行的容器

根据运行的docker容器的端口号,从浏览器访问“http://124.71.82.231:10001/demo/helloworld”,成功返回信息

使用Jenkins流水线部署Java项目(Jenkins-pipeline)

Jenkins流水线任务示例

新建Jenkins流水线任务

填写简单的pipeline脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pipeline {
agent any

# 执行阶段
stages {
# 阶段1,拉取代码
stage('拉取代码') {
# 执行步骤
steps {
echo '拉取成功'
}
}
# 阶段2,执行构建
stage('执行构建') {
# 执行步骤
steps {
echo '构建完成'
}
}
}
}

取消勾选‘使用 Groovy 沙盒’,点击‘Approve script’(审批脚本)

看到‘The script is already approved’(脚本已经审批),再点击‘应用’-‘保存’

来到该流水线任务页面,点击‘立即构建’,可以看到执行成功

  • 注:可以在Jenkins中安装’Blue Ocean’插件,为流水线提供了更多功能
流水线语法
  • pipeline语法结构:
1
2
3
4
5
pipeline:整条流水线
agent:指定执行器
stages:所有阶段
stage:某一阶段,可有多个
steps:阶段内的每一步,可执行命令
  • pipeline语法基础框架:

    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
    pipeline {
    agent any

    stages {
    stage('拉取代码') {
    steps {
    echo '拉取代码完成'
    }
    }
    stage('执行构建') {
    steps {
    echo '执行构建完成'
    }
    }
    }

    post {
    always {
    echo "完成"
    }

    failure {
    echo "失败"
    }
    }
    }
    • post 流水线完成后可执行的任务

      1
      2
      3
      4
      5
      6
      always 无论流水线或者阶段的完成状态。
      changed 只有当流水线或者阶段完成状态与之前不同时。
      failure 只有当流水线或者阶段状态为"failure"运行。
      success 只有当流水线或者阶段状态为"success"运行。
      unstable 只有当流水线或者阶段状态为"unstable"运行。例如:测试失败。
      aborted 只有当流水线或者阶段状态为"aborted "运行。例如:手动取消。
    • agent 指定执行节点

      label 指定运行job的节点标签;any 不指定,由Jenkins分配

      1
      2
      3
      4
      5
      6
      7
      8
      9
      pipeline {
      agent {
      node {
      label "jenkins-02"
      }

      }
      。。。
      }
    • 多行sh命令

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      sh """
      命令1
      命令2
      """

      例:
      sh """
      cd /root
      mvn clean package
      """
使用流水线部署Java项目

(1)新建流水线任务

​ 见前文

(2)代码生成器的使用方式

  • 编写复杂脚本可以使用流水线语法片段生成器

(3)’拉取代码‘片段

(4)’执行构建‘片段

1
mvn clean package -Dmaven.test.skip=true

(5)’清理目标服务器前次部署时创建的容器和镜像‘片段

1
2
3
4
5
# 需要执行的清理脚本
rm -rf *
docker stop jenkins-deploy-demo
docker rm -f jenkins-deploy-demo
docker rmi -f qiuli/jenkins-deploy-demo:1.0

(6)’推送到目标服务器、生成镜像并运行容器‘片段

1
2
3
# 生成镜像及运行容器脚本
docker build -t qiuli/jenkins-deploy-demo:1.0 /usr/local/project/jenkins-deploy-demo
docker run -d -p 10001:10001 --name=jenkins-deploy-demo qiuli/jenkins-deploy-demo:1.0

(7)全部脚本如下

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
pipeline {
agent any

// 导入maven,下文就可以使用’mvn‘构建jar包(“maven”的名称与全局工具配置(tool)-maven安装中的一致)
tools {
maven "maven"
}

stages {
stage('拉取代码') {
steps {
git 'http://139.155.229.99:3030/lkd7736241/Jenkins-deploy-demo.git'
echo '拉取成功'
}
}

stage('执行构建') {
steps {
// 测试mvn命令能否正确运行
// sh "mvn --version"

sh "mvn clean package -Dmaven.test.skip=true"
echo '构建完成'
}
}

stage('清理目标服务器前次部署时创建的容器和镜像') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'target-huaweiyun-jenkinsdeploydemo', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''rm -rf *
docker stop jenkins-deploy-demo
docker rm -f jenkins-deploy-demo
docker rmi -f qiuli/jenkins-deploy-demo:1.0''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
echo '清理完成'
}
}

stage('推送到目标服务器、生成镜像并运行容器') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'target-huaweiyun-jenkinsdeploydemo', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: 'jenkins-deploy-demo/target', sourceFiles: 'jenkins-deploy-demo/target/*.jar'), sshTransfer(cleanRemote: false, excludes: '', execCommand: '''docker build -t qiuli/jenkins-deploy-demo:1.0 /usr/local/project/jenkins-deploy-demo
docker run -d -p 10001:10001 --name=jenkins-deploy-demo qiuli/jenkins-deploy-demo:1.0''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: 'jenkins-deploy-demo/docker', sourceFiles: 'jenkins-deploy-demo/docker/*')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
echo '部署完成'
}
}
}
}

(8)将脚本粘贴并审批后,点击’应用‘-’保存‘,来到该流水线项目的页面,点击’立即构建‘

使用多分支流水线部署Java项目

(1)在Java项目中创建一个新的分支test,修改后推送到gitea代码仓库

1
2
git checkout -b test
git push origin test

(2)在两个分支中新建Jenkinsfile文件

该文件中即是构建脚本;本示例项目中,master分支可以直接使用上文中的构建脚本,test分支需要调整的地方是:a.git拉取代码的语句 b.目标服务器的ip、端口、目录等(例如测试服务器和生产服务是分开的,那就需要更新目标服务器脚本片段,这不是必须,根据实际情况调整);脚本还是由脚本片段生成器生成

1
2
# git片段
git branch: 'test', url: 'http://139.155.229.99:3030/lkd7736241/Jenkins-deploy-demo.git'

​ master分支构建脚本全文:

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
pipeline {
agent any

// 导入maven,下文就可以使用’mvn‘构建jar包(“maven”的名称与全局工具配置(tool)-maven安装中的一致)
tools {
maven "maven"
}

stages {
stage('拉取master分支代码') {
steps {
git 'http://139.155.229.99:3030/lkd7736241/Jenkins-deploy-demo.git'
echo '拉取成功'
}
}

stage('执行构建') {
steps {
// 测试mvn命令能否正确运行
// sh "mvn --version"

sh "mvn clean package -Dmaven.test.skip=true"
echo '构建完成'
}
}

stage('清理目标服务器前次部署时创建的容器和镜像') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'target-huaweiyun-jenkinsdeploydemo', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''rm -rf *
docker stop jenkins-deploy-demo
docker rm -f jenkins-deploy-demo
docker rmi -f qiuli/jenkins-deploy-demo:1.0''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
echo '清理完成'
}
}

stage('推送到目标服务器、生成镜像并运行容器') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'target-huaweiyun-jenkinsdeploydemo', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: 'jenkins-deploy-demo/target', sourceFiles: 'jenkins-deploy-demo/target/*.jar'), sshTransfer(cleanRemote: false, excludes: '', execCommand: '''docker build -t qiuli/jenkins-deploy-demo:1.0 /usr/local/project/jenkins-deploy-demo
docker run -d -p 10001:10001 --name=jenkins-deploy-demo qiuli/jenkins-deploy-demo:1.0''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: 'jenkins-deploy-demo/docker', sourceFiles: 'jenkins-deploy-demo/docker/*')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
echo '部署完成'
}
}
}
}

​ test分支构建脚本全文:

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
pipeline {
agent any

// 导入maven,下文就可以使用’mvn‘构建jar包(“maven”的名称与全局工具配置(tool)-maven安装中的一致)
tools {
maven "maven"
}

stages {
stage('拉取test分支代码') {
steps {
git branch: 'test', url: 'http://139.155.229.99:3030/lkd7736241/Jenkins-deploy-demo.git'
echo '拉取成功'
}
}

stage('执行构建') {
steps {
// 测试mvn命令能否正确运行
// sh "mvn --version"

sh "mvn clean package -Dmaven.test.skip=true"
echo '构建完成'
}
}

stage('清理目标服务器前次部署时创建的容器和镜像') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'target-huaweiyun-jenkinsdeploydemo', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''rm -rf *
docker stop jenkins-deploy-demo
docker rm -f jenkins-deploy-demo
docker rmi -f qiuli/jenkins-deploy-demo:1.0''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
echo '清理完成'
}
}

stage('推送到目标服务器、生成镜像并运行容器') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'target-huaweiyun-jenkinsdeploydemo', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: 'jenkins-deploy-demo/target', sourceFiles: 'jenkins-deploy-demo/target/*.jar'), sshTransfer(cleanRemote: false, excludes: '', execCommand: '''docker build -t qiuli/jenkins-deploy-demo:1.0 /usr/local/project/jenkins-deploy-demo
docker run -d -p 10001:10001 --name=jenkins-deploy-demo qiuli/jenkins-deploy-demo:1.0''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: 'jenkins-deploy-demo/docker', sourceFiles: 'jenkins-deploy-demo/docker/*')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
echo '部署完成'
}
}
}
}

(3)新建多分支流水线任务(需要安装multi-pipeline插件),添加git仓库后保存

(4)来到该项目页面,点击’立刻扫描 多分支流水线‘即可执行;点击’状态‘,再点击右手边构建符号即可以对某分支进行单独执行

部署集群项目(K8S方式部署)

参考文章kubernetes集群部署kubernetes部署Nginx集群kubernetes部署Redis集群

// TODO

项目部署实战

部署hades项目

Jenkins自由风格部署
将代码推送到gitea服务器
1
git push gitea deploy
新建目标服务器

即服务将被部署的服务器,见‘使用Jenkins部署Java项目’-’新建目标服务器‘

image-20250505105253024

新建自由风格任务

image-20250505104943172

填写相关配置

配置代码源(gitea服务器地址)、构建步骤(Maven构建)、构建后操作(将Dockerfile文件和jar包推送到目标服务器,然后使用docker构建镜像、运行容器)

配置代码源(gitea服务器)

构建步骤(Maven构建)
1
sh /tool/maven/bin/mvn clean package -Dmaven.test.skip=true

构建后操作(将jar包推送到目标服务器)

构建后操作(将Dockerfile文件推送到目标服务器,然后使用docker构建镜像、运行容器)
1
2
3
docker build -t qiuli/hades-web:1.0 /usr/local/project/hades-qiuli
docker rm -f hades-web
docker run -d -p 7890:7890 --name=hades-web qiuli/hades-web:1.0

注:如果目标服务器是云服务器,记得打开端口(见’虚拟机的初始准备‘-’网络设置‘)

Jenkins多分支流水线部署
新建目标服务器

image-20250505105535578

新建多分支流水线任务

image-20250507104935016

配置分支源

点击‘配置’-‘分支源’-‘git’,填写代码仓库的地址

image-20250507105834153

生成Jenkinsfile文件

进入项目的流水线语法页面,选择‘sshPublisher’,填写各项配置

image-20250507105158815

注:在需要进行部署的代码分支新建文件Jenkinsfile,该文件中包含部署脚本,使用Jenkins的“流水线语法

生成脚本片段,本次部署脚本共4个片段

(1)片段一:拉取deploy分支代码

1
git branch: 'deploy', url: 'http://139.155.229.99:3030/lkd7736241/hades-qiuli.git'

(2)片段二:利用maven构建jar包

1
sh "mvn clean package -Dmaven.test.skip=true"

image-20250507145345470

(3)片段三:清理目标服务器前次部署时创建的容器和镜像

1
2
3
4
rm -rf *
docker stop hades-web
docker rm -f hades-web
docker rmi -f qiuli/hades-web:1.0

1
2
3
4
5
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'huaweiyun-hades-qiuli', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''rm -rf *
docker stop hades-web
docker rm -f hades-web
docker rmi -f qiuli/hades-web:1.0''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])

(4)片段四:推送文件到目标服务器、生成镜像并运行容器

1
2
docker build -t qiuli/hades-web:1.0 /usr/local/project/hades-qiuli
docker run -d -p 7890:7890 --name=hades-web qiuli/hades-web:1.0

1
2
sshPublisher(publishers: [sshPublisherDesc(configName: 'huaweiyun-hades-qiuli', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: 'hades-qiuli/target', sourceFiles: 'hades-qiuli/target/*.jar'), sshTransfer(cleanRemote: false, excludes: '', execCommand: '''docker build -t qiuli/hades-web:1.0 /usr/local/project/hades-qiuli
docker run -d -p 7890:7890 --name=hades-web qiuli/hades-web:1.0''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: 'hades-qiuli/docker', sourceFiles: 'hades-qiuli/docker/*')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])

(5)将4个片段粘贴到流水线部署脚本中,本次部署脚本如下:

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
pipeline {
agent any

// 导入maven,下文就可以使用’mvn‘构建jar包(“maven”的名称与全局工具配置(tool)-maven安装中的一致)
tools {
maven "maven"
}

stages {
stage('拉取deploy分支代码') {
steps {
git branch: 'deploy', url: 'http://139.155.229.99:3030/lkd7736241/hades-qiuli.git'
echo '拉取成功'
}
}

stage('构建jar包') {
steps {
// 测试mvn命令能否正确运行
// sh "mvn --version"

sh "mvn clean package -Dmaven.test.skip=true"
echo '构建完成'
}
}

stage('清理目标服务器前次部署时创建的容器和镜像') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'huaweiyun-hades-qiuli', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''rm -rf *
docker stop hades-web
docker rm -f hades-web
docker rmi -f qiuli/hades-web:1.0''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
echo '清理完成'
}
}

stage('推送文件到目标服务器、生成镜像并运行容器') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'huaweiyun-hades-qiuli', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: 'hades-qiuli/target', sourceFiles: 'hades-qiuli/target/*.jar'), sshTransfer(cleanRemote: false, excludes: '', execCommand: '''docker build -t qiuli/hades-web:1.0 /usr/local/project/hades-qiuli
docker run -d -p 7890:7890 --name=hades-web qiuli/hades-web:1.0''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: 'hades-qiuli/docker', sourceFiles: 'hades-qiuli/docker/*')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
echo '部署完成'
}
}
}
}
将Jenkinsfile文件推送动到对应分支,启动部署

将编辑好的部署脚本推送到deploy分支,在Jenkins对应项目页面点击’立即扫描多分支流水线‘,则会扫描出含有Jenkinsfile的分支,点击启动按钮就可以进行部署

部署hades-admin项目

前置安装的服务:node、npm、git、pm2

使用git克隆hades-admin代码到目标服务器适当的文件夹

1
2
# 注意要调整backend_url(请求后端的url)
git clone -b deploy http://139.155.229.99:3030/lkd7736241/hades-admin.git

进入hades-admin文件夹,使用npm安装依赖

1
npm i

使用pm2启动hades-admin服务、查看服务状态

1
2
pm2 start hades-server.js
pm2 status
  • 注:hades-server.js为安装脚本
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
const express = require('express');
const http = require('http');
const path = require('path');
const reload = require('reload');
const bodyParser = require('body-parser');
const logger = require('morgan');

const app = express();

app.set('port', process.env.PORT || 3100);
app.use(logger('deploy'));
app.use(bodyParser.json()); // Parses json, multi-part (file), url-encoded

app.use('/public', express.static('public'));
app.use('/pages', express.static('pages'));
app.use('/sdk', express.static('sdk'));



app.get('/*', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});

const server = http.createServer(app);

// Reload code here
reload(app)
.then(function (reloadReturned) {
// reloadReturned is documented in the returns API in the README

// Reload started, start web server
server.listen(app.get('port'), function () {
console.log(
// 我使用腾讯云服务器做内网穿透,因为需要提示从外网访问,这里使用腾讯云的ip
'Web server listening on port http://ip:' + app.get('port')
);
});
})
.catch(function (err) {
console.error(
'Reload could not start, could not start server/sample app',
err
);
});

部署austin项目

Jenkins自由风格部署
修改代码并上传到gitea服务器
  • 注:本次在deploy分支部署,修改的是配置文件application.yml和application-deploy.yml,主要修改ip端口号等;另外新增Dockerfile文件用于构建docker镜像

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 使用openjdk8的镜像
    FROM openjdk:8-jre

    ENV PARAMS="--spring.profiles.active=deploy"

    # 设置工作目录
    WORKDIR /build

    # 将jar包复制到容器中
    COPY ./austin-web-0.0.1-SNAPSHOT.jar ./austin.jar

    # 运行jar包
    ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS austin.jar $PARAMS"]
    Dockerfile
新建目标服务器

​ - 注:目标服务器确定项目部署的服务器、文件夹等,记得在高级中配置服务器ssh登录密码

1
2
3
4
5
6
7
8
# Name
huaweiyun-austin-qiuli
# Hostname
124.71.82.231
# Username
root
# Remote Directory
/usr/local/project/austin-qiuli

新建自由风格服务
1
austin-qiuli-freestyle
填写相关配置

(1)配置代码源(gitea仓库地址)

1
2
3
4
# Repository URL
http://139.155.229.99:3030/lkd7736241/austin_qiuli.git
# 指定分支
*/deploy

(2)配置构架操作(构建jar包)

​ “增加构建步骤”-“执行Shell”

1
sh /tool/maven/bin/mvn clean package -Dmaven.test.skip=true
  • 注:jdk和Maven已经配置够了,见“基础服务安装部署”-“Jenkins服务安装部署”

(3)配置构建后操作

将Dockerfile文件和jar包推送到目标服务器,停止之前部署的服务,清理之前的容器、镜像,然后使用docker构建镜像、运行容器

1
2
3
4
# Source files
austin-web/target/*.jar
# Remove prefix
austin-web/target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
rm -rf *.jar
rm -rf Dockerfile

docker stop austin-qiuli
docker rm -f austin-qiuli
docker rmi -f qiuli/austin-qiuli:1.0

docker build -t qiuli/austin-qiuli:1.0 /usr/local/project/austin-qiuli

mkdir /usr/local/project/austin-qiuli/logs
mkdir /usr/local/project/austin-qiuli/mailinglist

docker run -d -p 8899:8899 -p 6666:6666 \
-v /usr/local/project/austin-qiuli/logs:/build/logs \
-v /usr/local/project/austin-qiuli/mailinglist:/home/austin/mailinglist \
--name=austin-qiuli \
-e SET_CONTAINER_TIMEZONE=true \
-e CONTAINER_TIMEZONE=Asia/Shanghai \
-e TZ=Asia/Shanghai \
qiuli/austin-qiuli:1.0

  • 注意:将项目打包为docker镜像并在docker中运行,需要将所有涉及到的接口映射到主机(例如作为xxl-job执行器需要暴露单独的端口6666);
  • 要想项目运行的时间正常,需要设置时区的环境变量:
1
2
3
-e SET_CONTAINER_TIMEZONE=true
-e CONTAINER_TIMEZONE=Asia/Shanghai
-e TZ=Asia/Shanghai
Jenkins多分支流水线部署
新建目标服务器

image-20250507135447552

新建多分支流水线任务

image-20250507130033218

配置分支源

点击‘配置’-‘分支源’-‘git’,填写代码仓库的地址

image-20250507141810765

生成Jenkinsfile文件

进入项目的流水线语法页面,选择‘sshPublisher’,填写各项配置

image-20250507143020998

注:在需要进行部署的代码分支新建文件Jenkinsfile,该文件中包含部署脚本,使用Jenkins的“流水线语法

生成脚本片段,本次部署脚本共4个片段

(1)片段一:拉取deploy分支代码

image-20250507145504188

1
git branch: 'deploy', url: 'http://139.155.229.99:3030/lkd7736241/austin_qiuli.git'

(2)片段二:利用maven构建jar包

1
sh /tool/maven/bin/mvn clean package -Dmaven.test.skip=true

image-20250507152711236

1
sh 'sh /tool/maven/bin/mvn clean package -Dmaven.test.skip=true'

(3)片段三:清理目标服务器前次部署时创建的容器和镜像

1
2
3
4
5
6
rm -rf *.jar
rm -rf Dockerfile

docker stop austin-qiuli
docker rm -f austin-qiuli
docker rmi -f qiuli/austin-qiuli:1.0

image-20250507150939903

image-20250507151003124

1
2
3
4
5
6
sshPublisher(publishers: [sshPublisherDesc(configName: 'centos7-05-austin-qiuli', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''rm -rf *.jar
rm -rf Dockerfile

docker stop austin-qiuli
docker rm -f austin-qiuli
docker rmi -f qiuli/austin-qiuli:1.0''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: 'austin-web/target', sourceFiles: 'austin-web/target/*.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])

(4)片段四:推送文件到目标服务器、生成镜像并运行容器

1
2
3
4
5
6
7
8
9
10
11
12
13
docker build -t qiuli/austin-qiuli:1.0 /usr/local/project/austin-qiuli

mkdir /usr/local/project/austin-qiuli/logs
mkdir /usr/local/project/austin-qiuli/mailinglist

docker run -d -p 8899:8899 -p 6666:6666 \
-v /usr/local/project/austin-qiuli/logs:/build/logs \
-v /usr/local/project/austin-qiuli/mailinglist:/home/austin/mailinglist \
--name=austin-qiuli \
-e SET_CONTAINER_TIMEZONE=true \
-e CONTAINER_TIMEZONE=Asia/Shanghai \
-e TZ=Asia/Shanghai \
qiuli/austin-qiuli:1.0
1
2
3
4
5
6
7
8
9
10
11
12
13
sshPublisher(publishers: [sshPublisherDesc(configName: 'centos7-05-austin-qiuli', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''docker build -t qiuli/austin-qiuli:1.0 /usr/local/project/austin-qiuli

mkdir /usr/local/project/austin-qiuli/logs
mkdir /usr/local/project/austin-qiuli/mailinglist

docker run -d -p 8899:8899 -p 6666:6666 \\
-v /usr/local/project/austin-qiuli/logs:/build/logs \\
-v /usr/local/project/austin-qiuli/mailinglist:/home/austin/mailinglist \\
--name=austin-qiuli \\
-e SET_CONTAINER_TIMEZONE=true \\
-e CONTAINER_TIMEZONE=Asia/Shanghai \\
-e TZ=Asia/Shanghai \\
qiuli/austin-qiuli:1.0''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: 'austin-web/target', sourceFiles: 'austin-web/target/*.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])

(5)将4个片段粘贴到流水线部署脚本中,本次部署脚本如下:

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
pipeline {
agent any

// 导入maven,下文就可以使用’mvn‘构建jar包(“maven”的名称与全局工具配置(tool)-maven安装中的一致)
tools {
maven "maven"
}

stages {
stage('拉取deploy分支代码') {
steps {
git branch: 'deploy', url: 'http://139.155.229.99:3030/lkd7736241/austin_qiuli.git'
echo '拉取成功'
}
}

stage('执行构建') {
steps {
sh 'sh /tool/maven/bin/mvn clean package -Dmaven.test.skip=true'
echo '构建完成'
}
}

stage('清理目标服务器前次部署时创建的容器和镜像') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'centos7-05-austin-qiuli', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''rm -rf *.jar
rm -rf Dockerfile

docker stop austin-qiuli
docker rm -f austin-qiuli
docker rmi -f qiuli/austin-qiuli:1.0''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: 'austin-web/target', sourceFiles: 'austin-web/target/*.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
echo '清理完成'
}
}

stage('推送到目标服务器、生成镜像并运行容器') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'centos7-05-austin-qiuli', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''docker build -t qiuli/austin-qiuli:1.0 /usr/local/project/austin-qiuli

mkdir /usr/local/project/austin-qiuli/logs
mkdir /usr/local/project/austin-qiuli/mailinglist

docker run -d -p 8899:8899 -p 6666:6666 \\
-v /usr/local/project/austin-qiuli/logs:/build/logs \\
-v /usr/local/project/austin-qiuli/mailinglist:/home/austin/mailinglist \\
--name=austin-qiuli \\
-e SET_CONTAINER_TIMEZONE=true \\
-e CONTAINER_TIMEZONE=Asia/Shanghai \\
-e TZ=Asia/Shanghai \\
qiuli/austin-qiuli:1.0''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: 'austin-web/target', sourceFiles: 'austin-web/target/*.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
echo '部署完成'
}
}
}
}
将Jenkinsfile文件推送动到对应分支,启动部署

将编辑好的部署脚本推送到deploy分支,在Jenkins对应项目页面点击’立即扫描多分支流水线‘,则会扫描出含有Jenkinsfile的分支,点击启动按钮就可以进行部署

image-20250507154028245

部署austin-admin项目

  1. 前置安装的服务:node、npm、git、pm2
  2. 使用git克隆austin-admin代码到目标服务器适当的文件夹
1
2
# 注意要调整backend_url(请求后端的url)
git clone -b deploy http://139.155.229.99:3030/lkd7736241/austin-admin.git
  1. 进入austin-admin文件夹,使用npm安装依赖
1
npm i
  1. 使用pm2启动austin-admin服务、查看服务状态
1
2
pm2 start austin-server.js
pm2 status
  • 注:austin-server.js为安装脚本
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
const express = require('express');
const http = require('http');
const path = require('path');
const reload = require('reload');
const bodyParser = require('body-parser');
const logger = require('morgan');

const app = express();

app.set('port', process.env.PORT || 3000);
app.use(logger('deploy'));
app.use(bodyParser.json()); // Parses json, multi-part (file), url-encoded

app.use('/public', express.static('public'));
app.use('/pages', express.static('pages'));
app.use('/sdk', express.static('sdk'));



app.get('/*', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});

const server = http.createServer(app);

// Reload code here
reload(app)
.then(function (reloadReturned) {
// reloadReturned is documented in the returns API in the README

// Reload started, start web server
server.listen(app.get('port'), function () {
console.log(
'Web server listening on port http://ip:' + app.get('port')
);
});
})
.catch(function (err) {
console.error(
'Reload could not start, could not start server/sample app',
err
);
});