资料有点多,放在云盘了,包括代码和SQL等。
链接:https://pan.baidu.com/s/1-aAEhDP_OWvNH7dgbT2HPg
提取码:pr5c
因此,我们要安装的有GitLab、Jenkins、Maven、Docker、Harbor,视频里是安装在了多台虚拟机上,这里我打算安到一台机器上,用端口区分。
项目采用前后端分离的形式,后端使用Spring Boot+Spring Cloud+SpringDataJPA。
依次启动EurekaServerApplication、ZuulApplication、AdminApplication、GatheringApplication,注意需要改动application.yml。
EurekaServerApplication的application.yml
server:
port: 10086 # 端口号
# 基本服务信息
spring:
application:
name: eureka-server # 服务id
profiles:
active:
# eureka服务器配置
eureka:
client:
fetch-registry: false # 禁用eureka互相注册
register-with-eureka: false # 禁用eureka互相注册
service-url:
defaultZone: http://localhost:${server.port}/eureka # 暴露服务地址
server:
enable-self-preservation: false # 关闭自我保护
ZuulApplication的application.yml
server:
port: 10020 # 端口
# 基本服务信息
spring:
application:
name: tensquare-zuul # 服务ID
# Eureka配置
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka # Eureka访问地址
instance:
prefer-ip-address: true
# 修改ribbon的超时时间
ribbon:
ConnectTimeout: 1500 # 连接超时时间,默认500ms
ReadTimeout: 3000 # 请求超时时间,默认1000ms
# 修改hystrix的熔断超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 2000 # 熔断超时时长,默认1000ms
# 网关路由配置
zuul:
routes:
admin:
path: /admin/**
serviceId: tensquare-admin-service
gathering:
path: /gathering/**
serviceId: tensquare-gathering
# jwt参数
jwt:
config:
key: itcast
ttl: 1800000
AdminApplication的application.yml
server:
port: 9001
spring:
application:
name: tensquare-admin-service #指定服务名
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.216.123:3306/tensquare_user?characterEncoding=UTF8
username: root
password: root
jpa:
database: mysql
show-sql: true
#Eureka配置
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
instance:
lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳
lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期
prefer-ip-address: true
# jwt参数
jwt:
config:
key: itcast
ttl: 1800000
GatheringApplication的application.yml
server:
port: 9002
spring:
application:
name: tensquare-gathering #指定服务名
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.216.123:3306/tensquare_gathering?characterEncoding=UTF8
username: root
password: root
jpa:
database: mysql
show-sql: true
#Eureka客户端配置
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
instance:
lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳
lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期
prefer-ip-address: true
在idea右侧的Maven标签里,通过mvn clean package
可以完成打包操作,注意这里要在pom.xml添加一个插件,才能生成完整的包,生成的jar包在项目路径下的target目录里。
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
然后尝试用java -jar xxx.jar
来本地化运行。
前端使用NodeJs+VueJs+ElementUI。
使用WebStorm打开项目,打开config下的配置文件,检查BASE_API
的值,这个值要和后台的网关微服务zuul对应起来。
在终端,使用npm run dev
来启动项目,在启动项目的时候报错了,错误提示是:Node Sass does not yet support your current environment: Windows 64-bit with。不大懂这块,搜了下,用这个命令解决的:
cnpm uninstall node-sass
cnpm install node-sass
使用npm run build
来打包静态web网站,打包后,在dist目录会产生静态文件,把dist目录里的文件移动到nginx的html目录下(/usr/local/nginx/html/),启动nginx并访问。
关于Nginx的安装,可以使用docker安装,也可以查看这里本地安装。
Docker是一个开源的应用容器引擎,基于Go语言 并遵从Apache2.0协议开源。
Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。
容器是完全使用沙箱机制,相互之间不会有任何接口(类似iPhone的 app),更重要的是容器性能开销极低。
Docker技术就是让我们更加高效轻松地将任何应用在Linux服务器部署和使用。
# 列出已经安装的Docker,使用yum -y remove Docker名称 卸载Docker
[root@localhost /]# yum list installed | grep docker
# 删除Docker所有镜像和容器
[root@localhost /]# rm -rf /var/lib/docker
# 安装必要软件包
[root@localhost /]# yum install -y yum-utils device-mapper-persistent-data lvm2
# 设置下载的镜像仓库
[root@localhost /]# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 列出可以安装的Docker版本
[root@localhost /]# yum list docker-ce --showduplicates | sort -r
# 安装最新版Docker
[root@localhost /]# yum install -y docker-ce
# 查看Docker版本
[root@localhost /]# docker -v
# 启动Docker
[root@localhost /]# systemctl start docker
# 设置开机启动
[root@localhost /]# systemctl enable docker
# 添加国内镜像,如果没有这个文件,就新建
[root@localhost /]# vim /etc/docker/daemon.json
# 重启Docker
[root@localhost /]# systemctl restart docker
daemon.json
{
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
}
# 搜索镜像
[root@localhost /]# docker search 镜像名称
# 拉取镜像
[root@localhost /]# docker pull 镜像名称
# 查看本地所有镜像
[root@localhost /]# docker images
# 删除镜像
[root@localhost /]# docker -rmi 镜像名称
# 运行容器,默认前台运行
[root@localhost /]# docker run -i 镜像名称:标签
# 后台运行容器
[root@localhost /]# docker run -id 镜像名称:标签
# 查看运行中的镜像
# [root@localhost /]# docker ps
# 查看所有镜像
# [root@localhost /]# docker ps -a
# 进入容器内部
[root@localhost /]# docker exec -it 容器名称/容器id /bin/bash
# 启动/停止/重启容器
[root@localhost /]# docker start/stop/restart 容器名称/容器id
# 删除容器
[root@localhost /]# docker rm -f 容器名称/容器id
还有一些参数:
--name:给容器起名字
-p:指定容器暴露的端口号
-v:给容器挂在某个目录
Dockerfile其实就是我们用来构建Docker镜像的源码,当然这不是所谓的编程源码,而是一些命令的组合,只要理解它的逻辑和语法格式,就可以编写Dockerfile了。
简单点说,Dockerfile的作用:它可以让用户个性化定制Docker镜像。因为工作环境中的需求各式各样,网络上的镜像很难满足实际的需求。
命令 | 作用 |
---|---|
FROM image_name:tag | 基础镜像 |
MAINTAINER use_name | 声明镜像的作者 |
ENV key value | 定义环境变量,可以写多条 |
RUN command | 编译镜像时候运行的脚本,可以写多条 |
CMD | 设置容器启动命令 |
ENTRYPOINT | 设置容器入口程序 |
ADD source_dir/filedest_dir/file | 将宿主机的文件复制到容器内,如果是一个压缩文件,将会在复 制后自动解压 |
COPY source_dir/filedest_dir/file | 和ADD相似,但是如果有压缩文件并不能解压 |
WORKDIR path_dir | 设置工作目录 |
ARG | 设置编译镜像时加入的参数 |
VOLUMN | 设置容器的挂载卷 |
新的镜像是在基础镜像上一层一层生成,每安装一个软件,就在镜像上加一层。
上传微服务jar包到服务器。
Dockerfile
# 以openjdk:8-jdk-alpine为基础镜像
FROM openjdk:8-jdk-alpine
# 定义一个变量JAR_FILE,在执行的时候通过参数传递进来
ARG JAR_FILE
# 重命名为app.jar
COPY ${JAR_FILE} app.jar
# 开放端口
EXPOSE 10086
# 执行命令启动app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
# 编写Dockerfile
[root@localhost tensquare_parent]# vim Dockerfile
# 构建镜像,使用--build-arg将参数传进去,使用-t指定镜像的名称,使用.表示执行当前目录下的Dockerfile
[root@localhost tensquare_parent]# docker build --build-arg JAR_FILE=tensquare_eureka_server-1.0-SNAPSHOT.jar -t eureka:v1 .
# 查看是否创建成功
[root@localhost tensquare_parent]# docker images
# 创建容器并启动
[root@localhost tensquare_parent]# docker run -i -p:10086:10086 eureka:v1
通过浏览器访问容器,验证结果。
Harbor(港口,港湾)是一个用于存储和分发Docker镜像的企业级Registry服务器。
除了Harbor这个私有镜像仓库之外,还有Docker官方提供的Registry。相对Registry,Harbor具有很多优势:
首先需要安装Docker,这个我们在前面已经完成了。
# 下载,其中-L是跟踪重定向,$(uname -s)和$(uname -m)分别是当前操作系统和当前系统的架构,-o是下载的位置
[root@localhost /]# curl -L https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
# 添加docker-compose添加执行权限
[root@localhost /]# chmod +x /usr/local/bin/docker-compose
# 查看docker-compose是否安装成功
[root@localhost /]# docker-compose -v
在下载地址下载Harbor后拷贝到服务器。
# 解压缩
[root@localhost software]# tar -zxvf harbor-offline-installer-v2.5.0.tgz -C /opt/module/
# 修改Harbor配置文件,这里要修改的是hostname和port,hostname改成当前服务器ip,port改成85,并把https的配置注释掉
[root@localhost /]# cd /opt/module/harbor/
[root@localhost harbor]# cp harbor.yml.tmpl harbor.yml
[root@localhost harbor]# vim harbor.yml
# 移动harbor到/opt/下,否则安装不成功,也可以改配置,但是我没找到地方
[root@localhost module]# mv harbor/* /opt/harbor/
# 安装Harbor,会提示Unable to find image,这时候需要等待,会自动下载镜像
[root@localhost harbor]# ./prepare
[root@localhost harbor]# ./install.sh
# 启动Harbor
[root@localhost harbor]# docker-compose up -d
# 停止Harbor
[root@localhost harbor]# docker-compose stop
# 重新启动Harbor
[root@localhost harbor]# docker-compose restart
浏览器访问Harbor服务,默认账户密码是:admin-Harbor12345。
Harbor的项目分为公开和私有:
公开项目:所有用户都可以访问,通常存放公共的镜像,默认有一个library公开项目
私有项目:只有授权用户才可以访问,通常存放项目本身的镜像
这里创建一个私有项目,点击“新建项目”,填上“项目名称”等信息。
在用户管理里,新建用户,填写基本信息。
点击项目,选择“成员”标签,输入刚才创建的用户,选择角色,这里选择“开发者”。
角色 | 权限说明 |
---|---|
项目管理员 | 除了读写权限,同时拥有用户管理/镜像扫码扥管理权限 |
维护人员 | 对于指定项目拥有读写权限,创建Webhooks |
开发者 | 对于执行项目有读写权限 |
访客 | 对于执行项目有只读权限 |
受限访客 | 权限更低 |
使用刚才创建的账户登录Harbor,在项目里,可以看到之前新建的项目,说明权限配置成功。
# 给eureka:v1打标签,标签的值为192.168.216.123:85/tensquare/eureka:v1,前部分是Harbor的地址,tensquare是Harbor项目的名称,eureka是Harbor里的镜像名字
[root@localhost harbor]# docker tag eureka:v1 192.168.216.123:85/tensquare/eureka:v1
# 查看镜像
[root@localhost harbor]# docker images
# 推送镜像
[root@localhost harbor]# docker push 192.168.216.123:85/tensquare/eureka:v1
# 会提示错误,我们需要在Docker里,把Harbor加入白名单
[root@localhost harbor]# vim /etc/docker/daemon.json
# 重启Docker
[root@localhost harbor]# systemctl restart docker
# 再次推送,会提示访问被拒绝,因为我们需要带着Harbor的登录信息才能推送到Harbor
# 登录Harbor,之后输入用户名、密码,提示Login Successed
[root@localhost harbor]# docker login 192.168.216.123:85
# 执行push
[root@localhost harbor]# docker push 192.168.216.123:85/tensquare/eureka:v1
daemon.json
{
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"],
"insecure-registries": ["192.168.216.123:85"]
}
来到Harbor的tensquare项目里,点击“镜像仓库”,就可以看到刚刚推送过来的镜像了。
和前面的上传类似,也是要把Harbor加到Docker的insecure-registries
里,然后重启Docker,再使用docker login
登录Harbor,带着登录信息调用docker pull
命令,其实在Harbor里,可以看到有一个“拉取命令”,直接拿来就可以使用。
后端代码使用IDEA打开,前端使用WebStorm打开,将前后端代码分别上传到GitLab上。
首先在GitLab上新建项目,然后在IDEA或WebStorm里,VCS-Enable Version Control Integration,选择Git,之后Git-Add,Commit,在Push之前,要把GitLab上的Clone with HTTP地址填写到这里,最后Push。
在Jenkins上新建一个Pipeline项目,勾选“This project is parameterized”,添加一个branch参数,默认值为master,Pipeline选择从SCM拉取,填入Git地址和认证信息。
来到IDEA里,在根路径下创建一个Jenkinsfile,通过Pipeline Syntax,选择checkout生成代码,并将脚本文件上传到GitLab,来到Jenkins,触发一次手动构建,观察构建结果。
def credentialsId = "e689 397-c855-4783-9600-b990ea39ab83"
def url = "[email protected]:root/tensquare.git"
node {
stage('拉取代码') {
checkout([$class: 'GitSCM', branches: [[name: ""*/${branch}"]], extensions: [], userRemoteConfigs: [[credentialsId: "${credentialsId}", url: "${url}"]]])
}
}
微服务里有5个模块,除了common外,其他的模块都可以做一个代码审查,于是在Jenkins里,添加一个Choice Parameter,用于选择项目,这里的值要和项目名保持一致,否则cd的时候,会提示找不到路径,在IDEA里的每个项目下都添加一个sonar-project.properties,注意,它们的projectKey要保证唯一,在Jenkinsfile里,加入代码审查的阶段,别忘了在服务器启动SonarQube、MySQL等服务,SonarQube在启动的时候可能会失败,根据SonarQube的log分析问题即可。
sonar-project.properties
# must be unique in a given SonarQube instance
sonar.projectKey=xxx
# this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.
sonar.projectName=xxx
sonar.projectVersion=1.0
# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
# This property is optional if sonar.modules is set.
sonar.sources=.
sonar.exclusions=**/test/**,**/target/**
sonar.java.binaries=.
sonar.java.source=1.8
sonar.java.target=1.8
# Encoding of the source code. Default is default system encoding
sonar.sourceEncoding=UTF-8
Jenkinsfile添加代码审查
stage('代码审查') {
// 这里的SonarQube Scanner要和Manage Jenkins-Global Tool Configuration-SonarQube Scanner里的名字保持一致
def scannerHome = tool 'SonarQube Scanner'
// 这里的SonarQube 6.7.7要和Manage Jenkins-Configure System-SonarQube servers里的名字保持一致
withSonarQubeEnv('SonarQube 6.7.7') {
sh """
cd ${project_name}
"${scannerHome}/bin/sonar-scanner"
"""
}
}
手动触发一次构建,构建成功后,来到SonarQube的后台查看代码审查结果。
在每个微服务的pom.xml里,加入一个dockerfile-maven-plugin的插件,这个插件的作用是用于生成Docker镜像,类似之前的docker build
命令的效果。
插件中${}的值都是变量,动态获取的。
<plugin>
<groupId>com.spotifygroupId>
<artifactId>dockerfile-maven-pluginartifactId>
<version>1.3.6version>
<configuration>
<repository>${project.artifactId}repository>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jarJAR_FILE>
buildArgs>
configuration>
plugin>
在每个微服务项目的根目录创建一个Dockerfile文件,注意修改EXPOSE的值,每个服务的对外端口是不一样的。
# 以openjdk:8-jdk-alpine为基础镜像
FROM openjdk:8-jdk-alpine
# 定义一个变量JAR_FILE,在执行的时候通过参数传递进来
ARG JAR_FILE
# 重命名为app.jar
COPY ${JAR_FILE} app.jar
# 开放端口
EXPOSE 10086
# 执行命令启动app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Jenkinsfile
def credentialsId = "e689f397-c855-4783-9600-b990ea39ab83"
def url = "[email protected]:root/tensquare.git"
def tag = "latest"
node {
stage('拉取代码') {
checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], extensions: [], userRemoteConfigs: [[credentialsId: "${credentialsId}", url: "${url}"]]])
}
// stage('代码审查') {
// // 这里的SonarQube Scanner要和Manage Jenkins-Global Tool Configuration-SonarQube Scanner里的名字保持一致
// def scannerHome = tool 'SonarQube Scanner'
// // 这里的SonarQube 6.7.7要和Manage Jenkins-Configure System-SonarQube servers里的名字保持一致
// withSonarQubeEnv('SonarQube 6.7.7') {
// sh """
// cd ${project_name}
// "${scannerHome}/bin/sonar-scanner"
// """
// }
// }
stage('编译、构建镜像') {
// 定义镜像名称
def imageName = "${project_name}:${tag}"
// 编译安装父项目,也就是全部进行install
sh "mvn clean install"
// 按照参数进行编译,构建本地镜像
sh "mvn -f ${project_name} clean package dockerfile:build"
}
}
手动构建一次,如果成功的话,会创建一个镜像,使用docker images
命令可以查看到。
因为需要和Harbor仓库有联系,所以需要登录,但是登录信息不建议写在脚本里面,首先,我们把登录的用户名和密码配置到Jenkins里,在脚本中想办法通过id获取。
Manage Jenkins-Manage Credentials-Stores scoped to Jenkins-global-Add Credentials,选择Username with password类型的即可。修改Jenkinsfile文件并提交后,尝试构建项目。
def credentialsId = "e689f397-c855-4783-9600-b990ea39ab83"
def url = "[email protected]:root/tensquare.git"
def tag = "latest"
def harbor_url = "192.168.216.123:85"
def harbor_auth = "0e0e4c68-a75f-472b-87e8-3555087aeb47"
def harbor_project_name = "tensquare"
node {
stage('拉取代码') {
checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], extensions: [], userRemoteConfigs: [[credentialsId: "${credentialsId}", url: "${url}"]]])
}
// stage('代码审查') {
// // 这里的SonarQube Scanner要和Manage Jenkins-Global Tool Configuration-SonarQube Scanner里的名字保持一致
// def scannerHome = tool 'SonarQube Scanner'
// // 这里的SonarQube 6.7.7要和Manage Jenkins-Configure System-SonarQube servers里的名字保持一致
// withSonarQubeEnv('SonarQube 6.7.7') {
// sh """
// cd ${project_name}
// "${scannerHome}/bin/sonar-scanner"
// """
// }
// }
stage('编译、构建镜像') {
// 定义镜像名称
def imageName = "${project_name}:${tag}"
// 编译安装父项目,也就是全部进行install
sh "mvn clean install"
// 按照参数进行编译,构建本地镜像
sh "mvn -f ${project_name} clean package dockerfile:build"
// 给镜像打标签
sh "docker tag ${imageName} ${harbor_url}/${harbor_project_name}/${imageName}"
// 登录Harbor,上传镜像
// 代码来源:pipeline-syntax:withCredentials: Bind credentials to variables
// Bindings选择Username and password (separated),然后输入username和password,即自定义变量名,用来代替真实的用户名、密码
// 选择刚才创建的HarborCredentials,生成Pipeline脚本
withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
// 登录Harbor
sh "docker login ${harbor_url}"
// 模拟输入用户名
sh "yes ${username} | head -1"
// 模拟输入密码
sh "yes ${password} | head -1"
// 推送镜像到Harbor
sh "docker push ${harbor_url}/${harbor_project_name}/${imageName}"
}
// 删除本地镜像
sh "docker rmi -f ${imageName}"
sh "docker rmi -f ${harbor_url}/${harbor_project_name}/${imageName}"
}
}
如果构建成功了,就可以在Harbor的仓库里看到推送上去的镜像了。
首先在Jenkins上安装Publish Over SSH插件,用于发送远程Shell命令,因为我这里只用了一台虚拟机,好像没法演示了,我只好再开一台虚拟机了(192.168.216.234)。
# 如果192.168.216.123上没有key,先生成一个key,如果有,就不用生成了
[root@localhost /]# ssh-keygen -t rsa
# ssh-copy-id命令将123的公钥发送给234服务器,此时123ssh连接234就通了
[root@localhost /]# ssh-copy-id 192.168.216.234
来到Manage Jenkins-Configure System-Publish over SSH,Path to key是指用于远程访问的私钥路径,SSH Server里的配置是远程服务器的信息。
来到Jenkins的pipeline syntax,选择“sshPublisher: Send build artifacts over SSH”,选择刚刚的SSH Server,然后点击生成脚本。
Jenkinsfile
def credentialsId = "e689f397-c855-4783-9600-b990ea39ab83"
def url = "[email protected]:root/tensquare.git"
def tag = "latest"
def harbor_url = "192.168.216.123:85"
def harbor_auth = "0e0e4c68-a75f-472b-87e8-3555087aeb47"
def harbor_project_name = "tensquare"
node {
stage('拉取代码') {
checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], extensions: [], userRemoteConfigs: [[credentialsId: "${credentialsId}", url: "${url}"]]])
}
// stage('代码审查') {
// // 这里的SonarQube Scanner要和Manage Jenkins-Global Tool Configuration-SonarQube Scanner里的名字保持一致
// def scannerHome = tool 'SonarQube Scanner'
// // 这里的SonarQube 6.7.7要和Manage Jenkins-Configure System-SonarQube servers里的名字保持一致
// withSonarQubeEnv('SonarQube 6.7.7') {
// sh """
// cd ${project_name}
// "${scannerHome}/bin/sonar-scanner"
// """
// }
// }
stage('编译、构建镜像') {
// 定义镜像名称
def imageName = "${project_name}:${tag}"
// 编译安装父项目,也就是全部进行install
sh "mvn clean install"
// 按照参数进行编译,构建本地镜像
sh "mvn -f ${project_name} clean package dockerfile:build"
// 给镜像打标签
sh "docker tag ${imageName} ${harbor_url}/${harbor_project_name}/${imageName}"
// 登录Harbor,上传镜像
// 代码来源:pipeline-syntax:withCredentials: Bind credentials to variables
// Bindings选择Username and password (separated),然后输入username和password,即自定义变量名,用来代替真实的用户名、密码
// 选择刚才创建的HarborCredentials,生成Pipeline脚本
withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
// 登录Harbor
sh "docker login ${harbor_url}"
// 模拟输入用户名
sh "yes ${username} | head -1"
// 模拟输入密码
sh "yes ${password} | head -1"
// 推送镜像到Harbor
sh "docker push ${harbor_url}/${harbor_project_name}/${imageName}"
}
// 部署应用,主要关注execCommand这个值
// 在远程服务器执行一个deploy.sh脚本
sshPublisher(publishers: [sshPublisherDesc(configName: 'master_server', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "/opt/jenkins/deploy.sh $harbor_url $harbor_project_name $project_name $tag $port", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
// 删除本地镜像
sh "docker rmi -f ${imageName}"
sh "docker rmi -f ${harbor_url}/${harbor_project_name}/${imageName}"
}
}
回到Jenkins里,添加一个String类型的构建参数,用于传递端口号。
来到部署服务器(192.168.216.234),添加一个/opt/jenkins/deploy.sh文件,并添加执行权限(chmod +x deploy.sh
),修改234上的daemon.json,把Harbor的地址加到insecure-registries中,然后重启Docker服务。
#! /bin/sh
#接收外部参数
harbor_url=$1
harbor_project_name=$2
project_name=$3
tag=$4
port=$5
imageName=$harbor_url/$harbor_project_name/$project_name:$tag
echo "$imageName"
#查询容器是否存在,存在则删除
containerId=`docker ps -a | grep -w ${project_name}:${tag} | awk '{print $1}'`
if [ "$containerId" != "" ] ; then
#停掉容器
docker stop $containerId
#删除容器
docker rm $containerId
echo "成功删除容器"
fi
#查询镜像是否存在,存在则删除
imageId=`docker images | grep -w $project_name | awk '{print $3}'`
if [ "$imageId" != "" ] ; then
#删除镜像
docker rmi -f $imageId
echo "成功删除镜像"
fi
# 登录Harbor
docker login -u 登录Harbor的用户名 -p 登录Harbor的密码 $harbor_url
# 下载镜像
docker pull $imageName
# 启动容器
docker run -di -p $port:$port $imageName
echo "容器启动成功"
最后,来到Jenkins触发一次构建,这次需要选择微服务的模块,需要输入微服务模块的端口号,再执行构建。如果构建失败,根据错误信息查找,或者直接在目标服务器上触发deploy.sh脚本,看看能不能运行,如果不能运行,就改脚本或者在目标机上改配置,先让目标机能正常运行,再解决远程ssh的问题。
一切都执行成功后,123服务器上生成的镜像会被删除,Harbor上有123上push的镜像,234从Harbor上拉取镜像后启动服务,如果浏览器访问234服务器上的服务可以访问,说明测试通过。
然后修改其他微服务里的eureka注册地址并上传GitLab,来到Jenkins里,选择不同的微服务,输入对应的接口,进行构建,此时来到234的eureka,可以看到其他3个微服务的注册,就说明成功了。
在234服务器上安装Nginx,可以查看这里本地安装。
前端网站需要NodeJS去编译,所以需要在Jenkins里安装一个NodeJS插件。来到Manage Jenkins-Global Tool Configuration-NodeJS-Add NodeJS,输入名称,选择版本,Apply,Save。
在Jenkins里新建一个Pipeline Project,用于部署前端项目,这里使用Pipeline Script的方式进行构建,脚本里的变量,在parameter里指定一下,比如branch等,填入Pipeline Script,Apply,Save,手动构建一次。
Pipeline Script
def git_auth = "e689f397-c855-4783-9600-b990ea39ab83"
def git_url = "[email protected]:root/tensquare_front.git"
node {
stage('拉取代码') {
checkout([$class: 'GitSCM', branches: [[name: '*/${branch}']],doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [],userRemoteConfigs: [[credentialsId: "${git_auth}", url:"${git_url}"]]])
}
stage('打包,部署网站') {
// 使用NodeJS的npm进行打包
// 这里的NodeJS 12.13.1要与Manage Jenkins-Global Tool Configuration-NodeJS里的名字保持一致
nodejs('NodeJS 12.13.1'){
sh '''
npm install
npm run build
'''
}
// 远程调用进行项目部署
// 这里又用到了sshPublish,所以这里的master_server要与Manage Jenkins-Configure System-SSH Servers里的名字保持一致
// remoteDirectory表示部署的最终位置,这个和nginx里的配置一块看来确定
sshPublisher(publishers: [sshPublisherDesc(configName: 'master_server',transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '',execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes:false, patternSeparator: '[, ]+', remoteDirectory: '/usr/local/nginx/html',remoteDirectorySDF: false, removePrefix: 'dist', sourceFiles: 'dist/**')],usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
天呐,又报错了,可以说坑很多啊,好像是npm环境问题,这个我实在是不会改,先留个坑吧,我把报错信息发出来,如果有大佬知道怎么改,麻烦给指点指点!
0 info it worked if it ends with ok
1 verbose cli [
1 verbose cli '/var/lib/jenkins/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/NodeJS_12.13.1/bin/node',
1 verbose cli '/var/lib/jenkins/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/NodeJS_12.13.1/bin/npm',
1 verbose cli 'run',
1 verbose cli 'build'
1 verbose cli ]
2 info using [email protected]
3 info using [email protected]
4 verbose run-script [ 'prebuild', 'build', 'postbuild' ]
5 info lifecycle [email protected]~prebuild: [email protected]
6 info lifecycle [email protected]~build: [email protected]
7 verbose lifecycle [email protected]~build: unsafe-perm in lifecycle true
8 verbose lifecycle [email protected]~build: PATH: /var/lib/jenkins/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/NodeJS_12.13.1/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/var/lib/jenkins/workspace/tensquare_front/node_modules/.bin:/opt/module/apache-maven-3.8.5//bin:/var/lib/jenkins/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/NodeJS_12.13.1/bin:/opt/module/apache-maven-3.8.5//bin:/sbin:/usr/sbin:/bin:/usr/bin
9 verbose lifecycle [email protected]~build: CWD: /var/lib/jenkins/workspace/tensquare_front
10 silly lifecycle [email protected]~build: Args: [ '-c', 'node build/build.js' ]
11 silly lifecycle [email protected]~build: Returned: code: 1 signal: null
12 info lifecycle [email protected]~build: Failed to exec build script
13 verbose stack Error: [email protected] build: `node build/build.js`
13 verbose stack Exit status 1
13 verbose stack at EventEmitter.<anonymous> (/var/lib/jenkins/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/NodeJS_12.13.1/lib/node_modules/npm/node_modules/npm-lifecycle/index.js:332:16)
13 verbose stack at EventEmitter.emit (events.js:210:5)
13 verbose stack at ChildProcess.<anonymous> (/var/lib/jenkins/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/NodeJS_12.13.1/lib/node_modules/npm/node_modules/npm-lifecycle/lib/spawn.js:55:14)
13 verbose stack at ChildProcess.emit (events.js:210:5)
13 verbose stack at maybeClose (internal/child_process.js:1021:16)
13 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:283:5)
14 verbose pkgid [email protected]
15 verbose cwd /var/lib/jenkins/workspace/tensquare_front
16 verbose Linux 3.10.0-1062.el7.x86_64
17 verbose argv "/var/lib/jenkins/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/NodeJS_12.13.1/bin/node" "/var/lib/jenkins/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/NodeJS_12.13.1/bin/npm" "run" "build"
18 verbose node v12.13.1
19 verbose npm v6.12.1
20 error code ELIFECYCLE
21 error errno 1
22 error [email protected] build: `node build/build.js`
22 error Exit status 1
23 error Failed at the [email protected] build script.
23 error This is probably not a problem with npm. There is likely additional logging output above.
24 verbose exit [ 1, true ]
为了演示效果,在本地打包,并把dist目录的东西上传到nginx的路径下,通过浏览器访问192.168.216.234就可以看到效果了,使用admin-123456登录并查看list,验证服务部署的正确性。
至此,前后台的持续集成部署都已经完成了,不过还存着一些问题:
优化方案:
再开一台生产服务器,构成服务器集群,然后修改配置文件,修改脚本,这里的内容,我没有跟着做,只是看了一遍,我的笔记本好像是带不动了,难度上没有太大了,前面能看懂,这里也能看懂。
eureka服务注册中心:
# 集群版
spring:
application:
name: EUREKA-HA
---
server:
port: 10086
spring:
# 指定profile=eureka-server1
profiles: eureka-server1
eureka:
instance:
# 指定当profile=eureka-server1时,主机名是eureka-server1
hostname: 192.168.66.103
client:
service-url:
# 将自己注册到eureka-server1、eureka-server2这个Eureka上面去
defaultZone: http://192.168.66.103:10086/eureka/,http://192.168.66.104:10086/eureka/
---
server:
port: 10086
spring:
profiles: eureka-server2
eureka:
instance:
hostname: 192.168.66.104
client:
service-url:
defaultZone: http://192.168.66.103:10086/eureka/,http://192.168.66.104:10086/eureka/
在启动微服务的时候,要指定spring.profiles.active
表示启用那个配置。修改其他微服务的注册地址,由原来的一个地址改成两个,提交代码。
要想让Jenkins支持多参数,需要安装一个插件:Extended Choice Parameter,在构建的配置里,选择参数就可以看到Extended Choice Parameter了。
修改Dockerfile文件(重复的部分就不写了),加入for循环,实现按照勾选构建。
// 把选择的项目信息转为数组
def selectedProjects = "${project_name}".split(',')
for (int i = 0; i < selectedProjects.size(); i++) {
// 取出每个项目的名称和端口
def currentProject = selectedProjects[i];
// 项目名称
def currentProjectName = currentProject.split('@')[0]
// 项目启动端口
def currentProjectPort = currentProject.split('@')[1]
}
首先执行ssh-copy-id,让Harbor服务器可以登录到正式服务器上,然后修改正式服务器的Docker配置文件,让这台服务器任何Harbor地址,重启Docker,然后来到Jenkins,和上面一样,添加一个publish_server的参数,参数类型也是Extended Choice Parameter,表示发布到哪台机器上。然后修改Jenkinsfile文件(重复的部分就不写了)。
// 把选择的服务区信息转为数组
def selectedServers = "${publish_server}".split(',')
// 以下为远程调用进行项目部署
for (int j = 0; j < selectedServers.size(); j++) {
// 每个服务名称
def currentServer = selectedServers[j]
// 添加微服务运行时的参数:spring.profiles.active
def activeProfile = "--spring.profiles.active="
if (currentServer == "master_server") {
activeProfile = activeProfile + "eureka-server1"
} else if (currentServer == "slave_server1") {
activeProfile = activeProfile + "eureka-server2"
}
}
这里还有一个activeProfile参数,要传到deploy.sh里,在deploy.sh里利用这个参数判断执行不同逻辑。
此时已经有两个Zuul了,那就用在两台Zuul前面挂一台Nginx实现请求的负载均衡,修改nginx.conf,重启Nginx。
upstream zuulServer {
server 192.168.66.103:10020 weight=1;
server 192.168.66.104:10020 weight=1;
}
server {
...
location / {
### 指定服务器负载均衡服务器
proxy_pass http://zuulServer/;
}
}
修改前端项目的BASE_API地址,让它指向Nginx即可,完成。