Jenkins笔记04-Jenkins+Docker+Spring Cloud微服务持续集成

资料有点多,放在云盘了,包括代码和SQL等。
链接:https://pan.baidu.com/s/1-aAEhDP_OWvNH7dgbT2HPg
提取码:pr5c

Jenkins+Docker+Spring Cloud微服务持续集成流程说明

  1. 开发人员将代码提交到GitLab代码仓库
  2. Jenkins从GitLab拉取代码进行编译打包,构建成Docker镜像,将镜像上传到Harbor私有仓库
  3. Jenkins发送SSH远程命令,让生产服务器到Harbor私有仓库拉取镜像到本地,从而创建容器
  4. 用户访问到容器

因此,我们要安装的有GitLab、Jenkins、Maven、Docker、Harbor,视频里是安装在了多台虚拟机上,这里我打算安到一台机器上,用端口区分。

Spring Cloud微服务源码概述

项目采用前后端分离的形式,后端使用Spring Boot+Spring Cloud+SpringDataJPA。

  • parent:父工程,存放基础配置
  • common:通用工程,存放工具类
  • eureka_server:Spring Cloud的Eureka服务注册中心
  • zuul:Spring Cloud的网关服务
  • admin_service:基础权限认证中心,负责用户认证(JWT认证)
  • gathering:业务模块,活动微服务逻辑
    Jenkins笔记04-Jenkins+Docker+Spring Cloud微服务持续集成_第1张图片
    数据库结构:
  • user:用户认证数据库,存放账户数据,对应admin_service微服务
  • gathering:活动微服务数据库,对应gathering微服务

本地部署-Spring Cloud微服务部署

依次启动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来本地化运行。

本地部署-前端静态Web网站

前端使用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快速入门

Docker简介

Docker是一个开源的应用容器引擎,基于Go语言 并遵从Apache2.0协议开源。
Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。
容器是完全使用沙箱机制,相互之间不会有任何接口(类似iPhone的 app),更重要的是容器性能开销极低。
Docker技术就是让我们更加高效轻松地将任何应用在Linux服务器部署和使用。

Docker安装

# 列出已经安装的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"]
}

Docker基本命令快速入门

镜像命令

# 搜索镜像
[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镜像脚本快速入门

Dockerfile简介

Dockerfile其实就是我们用来构建Docker镜像的源码,当然这不是所谓的编程源码,而是一些命令的组合,只要理解它的逻辑和语法格式,就可以编写Dockerfile了。
简单点说,Dockerfile的作用:它可以让用户个性化定制Docker镜像。因为工作环境中的需求各式各样,网络上的镜像很难满足实际的需求。

Dockerfile常见命令

命令 作用
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 设置容器的挂载卷

新的镜像是在基础镜像上一层一层生成,每安装一个软件,就在镜像上加一层。

  • RUN:用于指定docker build过程中要运行的命令,即是创建Docker镜像(image)的步骤
  • CMD:设置容器的启动命令,Dockerfile中只能有一条CMD命令,如果写了多条则最后一条生效,CMD不支持接收docker run的参数
  • ENTRYPOINT:入口程序是容器启动时执行的程序,docker run中最后的命令将作为参数传递给入口程序,ENTRYPOINT类似于CMD指令,但可以接收docker run的参数

使用Dockerfile制作微服务镜像

上传微服务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镜像仓库安装及使用

Harbor简介

Harbor(港口,港湾)是一个用于存储和分发Docker镜像的企业级Registry服务器。
除了Harbor这个私有镜像仓库之外,还有Docker官方提供的Registry。相对Registry,Harbor具有很多优势:

  1. 提供分层传输机制,优化网络传输 Docker镜像是是分层的,而如果每次传输都使用全量文件(所以用FTP的方式并不适合),显然不经济。必须提供识别分层传输的机制,以层的UUID为标识,确定传输的对象
  2. 提供WEB界面,优化用户体验 只用镜像的名字来进行上传下载显然很不方便,需要有一个用户界面可以支持登陆、搜索功能,包括区分公有、私有镜像
  3. 支持水平扩展集群 当有用户对镜像的上传下载操作集中在某服务器,需要对相应的访问压力作分解
  4. 良好的安全机制 企业中的开发团队有很多不同的职位,对于不同的职位人员,分配不同的权限,具有更好的安全性

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创建用户和项目

Harbor的项目分为公开和私有:
公开项目:所有用户都可以访问,通常存放公共的镜像,默认有一个library公开项目
私有项目:只有授权用户才可以访问,通常存放项目本身的镜像
这里创建一个私有项目,点击“新建项目”,填上“项目名称”等信息。
在用户管理里,新建用户,填写基本信息。
点击项目,选择“成员”标签,输入刚才创建的用户,选择角色,这里选择“开发者”。

角色 权限说明
项目管理员 除了读写权限,同时拥有用户管理/镜像扫码扥管理权限
维护人员 对于指定项目拥有读写权限,创建Webhooks
开发者 对于执行项目有读写权限
访客 对于执行项目有只读权限
受限访客 权限更低

使用刚才创建的账户登录Harbor,在项目里,可以看到之前新建的项目,说明权限配置成功。

把镜像上传到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下载镜像

和前面的上传类似,也是要把Harbor加到Docker的insecure-registries里,然后重启Docker,再使用docker login登录Harbor,带着登录信息调用docker pull命令,其实在Harbor里,可以看到有一个“拉取命令”,直接拿来就可以使用。

微服务持续集成-项目上传到GitLab

后端代码使用IDEA打开,前端使用WebStorm打开,将前后端代码分别上传到GitLab上。
首先在GitLab上新建项目,然后在IDEA或WebStorm里,VCS-Enable Version Control Integration,选择Git,之后Git-Add,Commit,在Push之前,要把GitLab上的Clone with HTTP地址填写到这里,最后Push。

微服务持续集成-从GitLab拉取项目代码

在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}"]]])
    }
}

微服务持续集成-提交到SonarQube代码审查

微服务里有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的后台查看代码审查结果。

微服务持续集成-使用Dockerfile编译、生成镜像

在每个微服务的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镜像仓库

因为需要和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笔记04-Jenkins+Docker+Spring Cloud微服务持续集成_第2张图片
来到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个微服务的注册,就说明成功了。
Jenkins笔记04-Jenkins+Docker+Spring Cloud微服务持续集成_第3张图片

微服务持续集成-部署前端静态Web网站

在234服务器上安装Nginx,可以查看这里本地安装。
前端网站需要NodeJS去编译,所以需要在Jenkins里安装一个NodeJS插件。来到Manage Jenkins-Global Tool Configuration-NodeJS-Add NodeJS,输入名称,选择版本,Apply,Save。
Jenkins笔记04-Jenkins+Docker+Spring Cloud微服务持续集成_第4张图片
在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,验证服务部署的正确性。

Jenkins+Docker+Spring Cloud部署方案优化

至此,前后台的持续集成部署都已经完成了,不过还存着一些问题:

  1. 每次只能选择一个微服务进行部署
  2. 只有一台生产服务器
  3. 每个微服务只有一个实例,容错率低

优化方案:

  1. 一个Jenkins工程里可以选择多个微服务同时发布
  2. 一个Jenkins工程可以选择多台生产服务器同时部署
  3. 每个微服务都是集群的形式

Jenkins+Docker+Spring Cloud集群部署流程说明

再开一台生产服务器,构成服务器集群,然后修改配置文件,修改脚本,这里的内容,我没有跟着做,只是看了一遍,我的笔记本好像是带不动了,难度上没有太大了,前面能看懂,这里也能看懂。

修改所有微服务配置

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集群项目构建参数

要想让Jenkins支持多参数,需要安装一个插件:Extended Choice Parameter,在构建的配置里,选择参数就可以看到Extended Choice Parameter了。
Jenkins笔记04-Jenkins+Docker+Spring Cloud微服务持续集成_第5张图片

完成微服务构建镜像,上传私服

修改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里利用这个参数判断执行不同逻辑。

Nginx和Zuul集群实现高可用网关

此时已经有两个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即可,完成。

你可能感兴趣的:(Jenkins,Jenkins)