第10章 项目自动化部署
1. 整体部署思路
在企业正常的项目部署中,可能会涉及多台服务器共同使用,例如Nginx、Tomcat、Redis、MySQL等都会有对应的单独的服务器,让各个服务器进行互相通信即可完成部署。对于学习阶段,所有的服务都可使用同一个虚拟机来完成项目的部署。
- 微信小程序需要租用腾讯的服务器才能完成部署,但在本示例中不会涉及。
- 学习环境中,所有服务将部署在同一虚拟机上。
2. 环境准备
2.1 基础环境
以下列出了软件环境及版本,并提供了安装方式:
软件 | 版本 | 安装方式 |
---|---|---|
Docker | 20.10.11 | Shell |
Docker Compose | 1.29.1 | Shell |
Java JDK | 11.0.19 | Shell |
Maven | 3.6.1 | Shell |
Git | 1.8.3.1 | Shell |
Redis | 7.2.4 | Docker |
MySQL | 8.0.29 | Docker |
Nginx | 1.25.5 | Docker |
基础环境Shell脚本安装
该shell脚本负责安装Git、Java JDK和Maven。
命名为install.sh。
#!/bin/bash
# 安装 Git
echo "开始安装 Git..."
yum install git -y
echo "Git安装完成"
echo `git --version`
# 下载并安装 JDK 11
echo "开始下载并安装 JDK 11..."
tar -xzf jdk-11.0.21_linux-x64_bin.tar.gz
mv jdk-11.0.21 /usr/local/src/
chmod +x /usr/local/src/jdk-11.0.21/bin/*
# 设置环境变量
echo "export JAVA_HOME=/usr/local/src/jdk-11.0.21" >> /etc/profile
echo "export PATH=\$PATH:\$JAVA_HOME/bin" >> /etc/profile
source /etc/profile
echo `java -version`
echo "JDK 11安装完成"
# 下载并安装 Maven
echo "开始下载并安装 Maven..."
MAVEN_URL="https://dlcdn.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz"
cd /usr/local/src
wget $MAVEN_URL
tar -xzf apache-maven-3.8.8-bin.tar.gz
mv apache-maven-3.8.8 /usr/local/src/
echo "Maven安装完成"
# 设置环境变量
echo "export MAVEN_HOME=/usr/local/src/apache-maven-3.8.8" >> /etc/profile
echo "export PATH=\$PATH:\$MAVEN_HOME/bin" >> /etc/profile
source /etc/profile
echo `mvn -version`
echo "JDK 11和Maven安装完成"
创建shell脚本后,执行下面命令获取执行权限:
# 赋予执行权限(修正后的命令)
chmod +x install.sh
# 执行脚本(建议用 sudo 提升权限)
sudo ./install.sh
2.2 Jenkins介绍和安装
Jenkins是一款开源的自动化服务器,广泛用于支持持续集成与持续交付(CI/CD)流程。通过配置Jenkins,可以实现自动化的构建、测试和部署过程。
- 启动命令: 使用Docker运行
docker start jenkins
- 访问地址: http://192.168.100.168:8000/
- 用户名: zzyl
- 密码: itcast
登录后可根据需求创建流水线任务以自动化CI/CD流程。
3. 部署后端项目
3.1 多环境说明
在项目开发和部署过程中,通常会有三套不同的项目环境:
- Development(开发环境)
- Production(生产环境)
- Test(测试环境)
例如,在开发环境中连接的是本地安装的MySQL数据库,而在生产环境中需要连接线上的MySQL环境。
将application.yml
与application-druid.yml
合并。合并后的配置文件示例如下:
# 项目相关配置
ruoyi:
# 名称
name: RuoYi
# 版本
version: 3.8.8
# 版权年份
copyrightYear: 2024
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
profile: D:/ruoyi/uploadPath
# 获取ip地址开关
addressEnabled: false
# 验证码类型 math 数字计算 char 字符验证
captchaType: math
# 开发环境配置
server:
# 服务器的HTTP端口,默认为8080
port: 8080
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数,默认为100
accept-count: 1000
threads:
# tomcat最大线程数,默认为200
max: 800
# Tomcat启动初始化的线程数,默认值10
min-spare: 100
# 日志配置
logging:
level:
com.zzyl: debug
org.springframework: warn
# 用户配置
user:
password:
# 密码最大错误次数
maxRetryCount: 5
# 密码锁定时间(默认10分钟)
lockTime: 10
# Spring配置
spring:
# 资源信息
messages:
# 国际化资源文件路径
basename: i18n/messages
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
# 服务模块
devtools:
restart:
# 热部署开关
enabled: true
# redis 配置
redis:
# 地址
host: 192.168.100.168
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password: 123456
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://192.168.100.168:3306/ry-zzyl?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: heima123
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置连接超时时间
connectTimeout: 30000
# 配置网络超时时间
socketTimeout: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: ruoyi
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
# token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期(默认30分钟)
expireTime: 30
# MyBatis配置
#mybatis:
# # 搜索指定包别名
# typeAliasesPackage: com.zzyl.**.domain
# # 配置mapper的扫描,找到所有的mapper.xml映射文件
# mapperLocations: classpath*:mapper/**/*Mapper.xml
# # 加载全局的配置文件
# configLocation: classpath:mybatis/mybatis-config.xml
# MyBatisPlus配置
mybatis-plus:
# 搜索指定包别名
typeAliasesPackage: com.zzyl.**.domain
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 全局配置
global-config:
db-config:
id-type: auto #id生成策略为自增
configuration:
map-underscore-to-camel-case: true #字段与属性,自动转换为驼峰命名
# PageHelper分页插件
pagehelper:
helperDialect: mysql
supportMethodsArguments: true
params: count=countSql
# Swagger配置
swagger:
# 是否开启swagger
enabled: true
# 请求前缀
pathMapping: /dev-api
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
oss:
endpoint: oss-cn-beijing.aliyuncs.com
accessKeyId: LTAI5tJe9A6bWwhFEkYyaXjD
accessKeySecret: hLpwmQ6YAS5l0FvsSFuw7aDN3DPNvp
bucketName: itheim
baidu:
accessKey: xEO9h4csw91eUrzWlUiYpkNt
secretKey: T67Xuyx9JoAWt1UEACQcFCVkd2HnZuKH
qianfanModel: ERNIE-4.0-8K-Preview
根据上述配置,创建三个配置文件以适应不同的环境:
application-dev.yml
:开发环境application-test.yml
:测试环境application-prod.yml
:生产环境(注意修改服务端口为9000)
在application.yml
中设置激活的环境配置:
spring:
profiles:
active: prod
3.2 Jenkins 流水线
Jenkins流水线是一种用于定义和执行自动化构建、测试和部署过程的方式。声明式流水线的基本结构如下:
pipeline {
agent any // 指定流水线运行的节点,any 表示任何可用的节点
stages {
stage('Build') { // 定义阶段
steps {
echo 'Building...'
}
},
stage('Test') { // 定义阶段
steps {
echo 'Testing...'
}
},
stage('Deploy') { // 定义阶段
steps {
echo 'Deploying...'
}
}
}
}
主要指令和阶段包括:
agent
: 指定流水线或特定阶段在哪个节点上执行。stages
: 包含流水线中的所有阶段(stage),是流水线的主要分组单元。stage
: 定义一个阶段,阶段内可以包含一个或多个步骤。steps
: 定义在某个阶段内执行的具体操作。post
: 在所有阶段完成后执行的操作,基于不同的条件(如成功、失败、总是)来执行。environment
: 定义流水线中的环境变量。options
: 定义全局选项和配置,如超时设置、并行执行等。parameters
: 定义流水线的参数,用于接收用户输入。triggers
: 定义触发流水线执行的条件或事件,如定时触发、代码推送触发等。
在后端项目中进行 Jenkins 构建配置
1)在父模块主目录下创建 Jenkinsfile
文件,文件内容如下:
pipeline {
agent any
options {
timestamps()
}
tools {
maven 'maven'
jdk 'jdk11'
}
stages {
stage('清除工作空间') {
steps {
cleanWs()
}
}
stage('拉取Git代码') {
steps {
echo "正在拉取代码..."
echo "当前分支:${GIT_TAG},当前服务:${services}"
checkout([$class: 'GitSCM',
branches: [[name: GIT_TAG]],
doGenerateSubmoduleConfigurations: false,
extensions: [],
submoduleCfg: [],
userRemoteConfigs: [[credentialsId: 'Gitee_ID', url: GIT_URL]]
])
sh "pwd"
}
}
stage('重新Maven打包') {
steps {
script {
echo "正在执行maven打包...."
sh "mvn clean install -DskipTests"
}
}
}
stage('重新构建镜像') {
steps {
echo "当前打镜像tag:${DOCKER_TAG}"
script {
for (ds in services.tokenize(",")) {
sh "pwd"
echo "进入target目录执行镜像打包......"
sh "cd ./${ds}/target/ && docker build -t ${ds}:${DOCKER_TAG} -f ../Dockerfile ."
}
}
}
}
stage('部署服务'){
steps {
script {
for (ws in services.tokenize(",")) {
sh "pwd"
sh "cd `pwd`"
echo "部署升级:${ws}服务"
sh "chmod +x ./${ws}/deploy.sh && sh ./${ws}/deploy.sh ${ws} ${DOCKER_TAG}"
}
}
}
}
}
post {
always {
echo '任务构建完毕'
}
}
}
2)在admin模块主目录下创建Dockerfile和deploy.sh。
- Dockerfile
# 基础镜像
FROM openjdk:11.0-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV OSS_ACCESS_KEY_ID LTAI5tFww3VE7EZSs9dfh7j5
ENV OSS_ACCESS_KEY_SECRET 3eVYBbuK1F8rX5Tv0ge8lkKG1TrOLs
# 拷贝jar包
COPY zzyl-admin.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
要记得改为你自己的OSS密钥。
- deploy.sh
#!/bin/bash
# 容器名称
container_name=$1
# 镜像名称
image_name=$1
# 镜像tag
image_tag=$2
# 判断容器是否存在
if docker ps -a | grep $container_name | awk '{print $1}'; then
echo "容器 $container_name 存在"
if docker ps | grep $container_name | awk '{print $1}';then
echo "关闭正在运行的容器 $container_name"
docker stop `docker ps | grep $container_name | awk '{print $1}'`
else
echo "容器 $container_name 都已关闭"
fi
# 删除容器
echo "删除容器 $container_name"
docker rm `docker ps -a | grep $container_name | awk '{print $1}'`
else
echo "容器 $container_name 不存在"
fi
# 启动容器
echo "启动容器 $container_name"
if [ $container_name = "zzyl-admin" ]; then
docker run -d --restart=always --name $container_name -v /usr/local/zzyl-admin/logs:/home/ruoyi/logs -p 9000:9000 $image_name:$image_tag
fi