持续集成环境说明:
集成工具:Jenkins
版本控制:Gitlab
代码审查:SonarQube
容器 Docker
容器仓库:Harbor
服务器说明,我只准备了两台阿里云服务器,按理说应该至少需要三台服务器
第一台(112.74.167.52)装了Gitlab,Docker
第二台(39.108.6.54)装了Jenkins,Docker,SonarQube,Harbor,Mysql 所有的服务都是用的Docker容器来安装的
现在我要做的是在第一台服务器上拉取镜像并运行,当然,这一系列的动作都是在Jenkins上集成实现的!
微服务工程说明
docker-springcloud-api 提供微服务工程的公共类docker-springcloud-eureka-7001 注册中心(单机版)docker-springcloud-provider-8001 服务提供docker-springcloud-consumer-9001 服务消费1、docker-springcloud-api 提供微服务工程的公共类
# 公共类 public class Dept { private Long deptno; //主键 private String dname;//部门名称 private String db_source; //来自哪个数据库 getter,setter,toString.... }2、docker-springcloud-eureka-7001 注册中心(单机版)
# application.yml server: port: 7001 #设置注册中心的端口 eureka: instance: hostname: eureka7001.com #eureka服务端的实例名称(hosts文件配置映射过 127.0.0.1 eureka7001.com ) client: register-with-eureka: false #false表示不向注册中心注册自己(默认所有的服务都会注册;物业公司不会向自己收取物业费) fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务 service-url: defaultZone: http://localhost:${server.port}/eureka/ #设置与Eureka Server交互的地址查询服务和注册服务 单机的配置3、docker-springcloud-provider-8001 服务提供
# application.yml server: port: 8001 mybatis: config-location: classpath:mybatis/mybatis.cfg.xml #mybatis配置文件所在路径 type-aliases-package: com.pihao.cloud.entity #所有Entity别名类所在包 mapper-locations: - classpath:mybatis/mapper/**/*.xml #mapper映射文件 spring: application: name: micro-server-dept #对外暴露微服务的名字 datasource: type: com.alibaba.druid.pool.DruidDataSource #当前数据源操作类型 driver-class-name: org.gjt.mm.mysql.Driver #mysql驱动包 url: jdbc:mysql://39.108.6.54:3306/cloudDB01 #数据库名称 username: root password: ****** # mysql数据库连接密码 dbcp2: min-idle: 5 #数据库连接词的最小维持连接数 initial-size: 5 #初始化连接数 max-total: 5 #最大连接数 max-wait-millis: 1000 #等待连接获取的最大超时时间 #注册中心的配置 eureka: client: service-url: defaultZone: http://localhost:7001/eureka #注册中心中配置文件yml中配置的地址 fetch-registry: true #通过服务发现可以获取到该微服务的id instance: instance-id: micro-server-dept8001 #给当前的微服务取个名字 prefer-ip-address: true #配置info界面的显示内容 info: app.name: pihao-microservicecloud company.name: pihao@ltd build.artifactId: ${project.artifactId} build.version: ${project.version} ##打印sql的日志信息 logging: level: com.pihao.cloud.dao: debug # 服务提供者工程的接口 @RestController public class DeptController { @Autowired private DeptService service; # service,dao哪些类略...... # 添加员工 @RequestMapping(value = "/dept/add",method = RequestMethod.POST) public boolean add(@RequestBody Dept dept){ return service.add(dept); } #根据编号查询具体员工信息 @RequestMapping(value = "/dept/get/{id}",method = RequestMethod.GET) public Dept get(@PathVariable("id") Long id){ return service.get(id); } # 查询所有员工信息 @RequestMapping(value = "/dept/list",method = RequestMethod.GET) public List<Dept> list(){ return service.list(); } }4、docker-springcloud-consumer-9001 服务消费
# application.yml server: port: 9001 #将9001注册进服务中心 eureka: instance: prefer-ip-address: true instance-id: micro-server-consumer-9001 client: fetch-registry: true #巨坑,千万不能设置为false,否则ribbon找不到服务名称,不能实现远程的负载均衡调用 service-url: defaultZone: http://localhost:7001/eureka/ #将本微服务注册进三个注册中心 spring: application: name: micro-server-consumer-9001 # 服务消费者的接口(对应上面服务提供的接口) @RestController public class DeptController_Consumer { private static final String REST_URL_PREFIX="http://MICRO-SERVER-DEPT"; //通过访问微服务的名字来访问 /** * 使用restTemplate访问restful接口非常的简单粗暴无脑 * (url,requestMap,ResponseBean.class这三个参数分别是REST请求地址,请求参数,HTTP响应转换被转换的对象类型) */ @Autowired @LoadBalanced private RestTemplate restTemplate; @RequestMapping(value="/consumer/dept/add") public boolean add(Dept dept){ return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class); } @RequestMapping(value="/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long id){ return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id,Dept.class); } @RequestMapping(value="/consumer/dept/list") public List<Dept> list(){ return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class); } }Gitlab创建仓docker-springcloud-test
clone仓库地址,然后将工程的代码push至Gitlab
push成功
在Jenkins页面创建流水线项目
拉取项目的初步配置
在项目的根目录下创建Jenkinsfile文件,使用流水线语法帮我们生成拉取代码的脚本
为了使脚本更好维护,我们可以添加参数化构建,先添加分支branch参数,更方便我们维护
Jenkinsfile文件,然后将这个文件上传到Gitlab
# git凭证ID def git_auth = "989a1058-9976-461b-9afa-e7eeba771b0c" # git的url地址 def git_url = "git@112.74.167.52:root/docker-springcloud-test.git" node { stage('拉取代码'){ # 这里的branch就是上一步参数构建那里的 checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]]) } }在Jenkins页面测试拉取代码构建项目
查看输出台,构建成功
为了验证源码真的被拉取下来了,我们可以在Jenkins服务器中查看一下,在 Jenkins_home目录下的workspace中
# 切换到Docker Jenkins的挂载目录 [root@iZwz9eq1jai7e87n6vw5liZ _data]# cd /var/lib/docker/volumes/myjenkins/_data/workspace/ [root@iZwz9eq1jai7e87n6vw5liZ workspace]# ls docker-springcloud-test docker-springcloud-test@tmp # 再次确认真的有这个项目 [root@iZwz9eq1jai7e87n6vw5liZ workspace]#在这里,需要审查的代码不止一个,而是多个,所以我们需要设置一个参数,以此来选择我们需要代码审查的项目
添加可选参数
在每个需要检测的项目的的根目录下创建一个sonar-project.properties文件
# 以docker-springcloud-provider-8001项目为例,添加如下的内容 sonar.projectKey=docker-springcloud-provider-8001 # 在sonarqube中唯一,注意每个项目中区分开 sonar.projectName=docker-springcloud-provider-8001 sonar.projectVersion=1.0 sonar.sources=. sonar.exclusions=**/test/**,**/target/** sonar.java.binaries=. sonar.java.source=1.8 sonar.java.target=1.8 sonar.sourceEncoding=UTF-8修改Jenkinsfile构建脚本
之前的拉取代码已经完成,现在需要将代码给到sonarqube进行代码审查,所以在此编辑Jenkins脚本
# git凭证ID def git_auth = "989a1058-9976-461b-9afa-e7eeba771a0c" # git的url地址 def git_url = "git@112.74.167.52:root/docker-springcloud-test.git" node { stage('拉取代码'){ checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]]) } stage('代码审查'){ # 定义当前Jenkins的SonarQubeScanner工具的环境,这个是我们在Jenkins页面全局工具配置 SonarQube Scanner选项中添加的那个默认的sonar-scanner def scannerHome = tool 'sonar-scanner' # 引入SonarQube的服务器环境,这个是我们在系统配置中 的SonarQube servers选项这里添加的 sonarqube的环境 withSonarQubeEnv('sonarqube'){ sh """ cd ${project_name} # 切换到哪个具体的项目 ${scannerHome}/bin/sonar-scanner """ } } }再将之前编写好的sonar-project.properties以及Jenkinsfile文件提交到Gitlab
构建测试,这是没有构建前的SonarQube界面
构建测试
构建成功
查看SonarQube界面
剩余的docker-springcloud-provider-8001、docker-springcloud-consumer-9001也采用同样的方式一次进行代码审查,效果如下
在生成镜像之前,需要对项目中的公共子工程先编译,因为其他的微服务也需要公共子工程的依赖,不然也会导致编译不通过
编辑Jenkinsfile文件
# git凭证ID def git_auth = "989a1058-9976-461b-9afa-e7eeba771a0c" # git的url地址 def git_url = "git@112.74.167.52:root/docker-springcloud-test.git" node { stage('拉取代码'){ checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]]) } stage('代码审查'){ # 定义当前Jenkins的SonarQubeScanner工具的环境,这个是我们在Jenkins页面全局工具配置 SonarQube Scanner选项中添加的那个默认的sonar-scanner def scannerHome = tool 'sonar-scanner' # 引入SonarQube的服务器环境,这个是我们在系统配置中 的SonarQube servers选项这里添加的 sonarqube的环境 withSonarQubeEnv('sonarqube'){ sh """ cd ${project_name} # 切换到哪个具体的项目 ${scannerHome}/bin/sonar-scanner """ } } # -f 选择具体的工程 stage('编译,安装公共子工程'){ sh "mvn -f docker-springcloud-api clean install" } }将Jenkinsfile文件提交到Gitlab,然后随便选择一个工程进行构建,测试 子工程的编译能不能生效
公共子工程的编译已经完成,那么就下来就是编译、打包其他业务的微服务工程了
编辑Jenkinsfile文件
# git凭证ID def git_auth = "989a1058-9976-461b-9afa-e7eeba771b0c" # git的url地址 def git_url = "git@112.74.167.52:root/docker-springcloud-test.git" node { stage('拉取代码'){ checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]]) } stage('代码审查'){ # 定义当前Jenkins的SonarQubeScanner工具的环境,这个是我们在Jenkins页面全局工具配置 SonarQube Scanner选项中添加的那个默认的sonar-scanner def scannerHome = tool 'sonar-scanner' # 引入SonarQube的服务器环境,这个是我们在系统配置中 的SonarQube servers选项这里添加的 sonarqube的环境 withSonarQubeEnv('sonarqube'){ sh """ cd ${project_name} # 切换到哪个具体的项目 ${scannerHome}/bin/sonar-scanner """ } } # -f 选择具体的工程 stage('编译,安装公共子工程'){ sh "mvn -f docker-springcloud-api clean install" } stage('编译,打包微服务工程'){ sh "mvn -f ${project_name} clean package" } }将Jenkinsfile文件push到Gitlab,然后随便选择一个供货才能进行构建测试
发现构建失败,原因是在Jenkins的服务中没有找到父工程的一个pom文件,那么我们就需要手动的把父工程的目录结构添加到Jenkins服务器对应的maven仓库中
好了,现在已经手动添加完毕,再来测试微服务打包
成功!!!
紧接着依次编译打包剩余的微服务项目
制作镜像
1、在每个微服务项目更目录下建立Dockerfile文件
FROM openjdk:8-jdk-alpine ARG JAR_FILE COPY ${JAR_FILE} app.jar EXPOSE 7001 # 注意每个项目暴露的端口不一样 ENTRYPOINT ["java","-jar","/app.jar"]2、在每个微服务项目的pom.xml文件中添加dockerfile-maven-plugin插件
<plugin> <groupId>com.spotify</groupId> <artifactId>dockerfile-maven-plugin</artifactId> <version>1.3.6</version> <configuration> <repository>${project.artifactId}</repository> <buildArgs> <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE> </buildArgs> </configuration> </plugin>3、修改Jenkinsfile文件,添加一个触发Dockerfile插件的命令
# 注意:这里的dockerfile:build就会触发dockerfile插件的执行 stage('编译,打包微服务工程'){ sh "mvn -f ${project_name} clean package dockerfile:build" }将修改和新添加的Dockerfile,pom.xml,Jenkinsfile文件上传到Gitlab
然后重新构建
查看生成的镜像:
ok,紧接着,将其他的微服务都打包成docker镜像
[root@iZwz9eq1jai7e87n6vw5liZ ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE docker-springcloud-consumer-9001 latest 7dc8d155120a 14 seconds ago 141MB docker-springcloud-provider-8001 latest fd0a432ffd95 2 minutes ago 154MB docker-springcloud-eureka-7001 latest 78fb3f7aa2d5 8 minutes ago 145MB [root@iZwz9eq1jai7e87n6vw5liZ ~]# ok 镜像生成完毕小记:在这个打包生成镜像的过程中其实是踩过一些坑的,原因是因为我是采用docker的方式安装的Jenkins,而在后续将微服务工程制作为docker镜像的时候是需要用到Docker的命令的,而我这个Jenkins容器内部是没有docker环境的,最初想法是:我maven也是这么安装的,要不在Jenkins的容器内容再安装一个docker的环境,但再想想,这有点不符合实际情况啊,这不是套娃嘛。。。网上馊了一下相关博客,找到 docker in docker 这种说法,意思是,在容器内部可操作宿主机上的docker,即可以在容器内部使用docker命令。这样一来,我的问题也就解决了。
这里的方法参考自: https://blog.csdn.net/catoop/article/details/91042007
具体方法是,在原来Jenkins容器的基础上再新增两个文件挂载
-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker但是呢?我现在这个jenkins的容器已经在运行呀,怎么在不影响原来容器的基础上增加新卷挂载呢
可以使用commit命令,将现有的Jenkins容器提交为一个新的镜像,然后在这个新的镜像上添加卷挂载(注意:原来的卷挂载也要添加上去)
最后的Jenkins容器运行命令为:
# myjenkins 之前的宿主机挂载目录 [root@iZwz9eq1jai7e87n6vw5liZ ~]# docker run -d -p 8088:8080 -p 50000:50000 -v myjenkins:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker --privileged=true --name newjenkins newjenkins:2.0上一步将微服务打包生成镜像我们已经完成,那么接下来就是要将生成好的镜像推送到Harbor仓库中,如果要推送到Harbor仓库中,那么就需要先将生成好的镜像打标签,然后再通过docker push命令来完成,这些都需要在Jenkinsfile文件中定义好
编写Jenkinsfile文件测试打标签功能
//git凭证ID def git_auth = "989a1058-9976-461b-9afa-e7eeba771a0c" //git的url地址 def git_url = "git@112.74.167.52:root/docker-springcloud-test.git" //镜像标签 def tag = "latest" //Harbor私有仓库的url地址 def harbor_url = "39.108.6.54"; //Harbor仓库中项目的名字 def harbor_projectName = "docker-springcloud-test"; node { stage('拉取代码'){ checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]]) } stage('代码审查'){ //定义当前Jenkins的SonarQubeScanner工具的环境,这个是我们在Jenkins页面全局工具配置 SonarQube Scanner选项中添加的那个默认的 sonar-scanner def scannerHome = tool 'sonar-scanner' //引入SonarQube的服务器环境,这个是我们在系统配置中 的SonarQube servers选项这里添加的 sonarqube的环境 withSonarQubeEnv('sonarqube'){ sh """ cd ${project_name} ${scannerHome}/bin/sonar-scanner """ } } stage('编译,安装公共子工程'){ sh "mvn -f docker-springcloud-api clean install" } stage('编译,打包微服务工程,生成镜像'){ sh "mvn -f ${project_name} clean package dockerfile:build" } stage('将镜像推送到Harbor仓库'){ //定义镜像名称 def imageName = "${project_name}:${tag}" //对镜像打上标签 sh "docker tag ${imageName} ${harbor_url}/${harbor_projectName}/${imageName}" } }将Jenkinsfile文件上传到Gitlab,然后在Jenkins页面上随便选择一个微服务项目构建测试,看打标签功能能不能实现
查看打好标签的镜像
[root@iZwz9eq1jai7e87n6vw5liZ harbor]# docker images REPOSITORY TAG IMAGE ID CREATED 39.108.6.54/docker-springcloud-test/docker-springcloud-provider-8001 latest 68a1ca8b4737 41 seconds ago # 打标签功能测试okOK,现在打标签的功能是ok的,那么接下来就是将这个镜像推送至Harbor了,继续编写Jenkinsfile脚本文件,既然要push,那么首先肯定要登录Harbor了,但是呢?登入的用户和密码又不能直接写在Jenkinsfile文件中,那这样所有的开发人员都知道密码了,就没有安全性了,那么怎么办呢?在这里Jenkins为我们提供了一种解决方案,即使用凭证的方式来登入,具体操作如下:
在Jenkins页面添加一个凭证(Harbor中可见项目的用户和密码)
在jenkins页面使用流水线语法将harbor凭证转化为用户和密码,然后复制生成的脚本
再次修改Jenkinsfile文件,然后上传到Gitlab
//git凭证ID def git_auth = "989a1058-9976-461b-9afa-e7eefa771a0c" //git的url地址 def git_url = "git@112.74.167.52:root/docker-springcloud-test.git" //镜像标签 def tag = "latest" //Harbor私有仓库的url地址 def harbor_url = "39.108.6.54" //Harbor仓库中项目的名字 def harbor_projectName = "docker-springcloud-test" //Harbor的登入凭证 def harbor_auth = "ecd55d01-e80a-4481-903f-21097f589625" node { stage('拉取代码'){ checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]]) } stage('代码审查'){ //定义当前Jenkins的SonarQubeScanner工具的环境,这个是我们在Jenkins页面全局工具配置 SonarQube Scanner选项中添加的那个默认的 sonar-scanner def scannerHome = tool 'sonar-scanner' //引入SonarQube的服务器环境,这个是我们在系统配置中 的SonarQube servers选项这里添加的 sonarqube的环境 withSonarQubeEnv('sonarqube'){ sh """ cd ${project_name} ${scannerHome}/bin/sonar-scanner """ } } stage('编译,安装公共子工程'){ sh "mvn -f docker-springcloud-api clean install" } stage('编译,打包微服务工程,生成镜像'){ sh "mvn -f ${project_name} clean package dockerfile:build" } stage('将镜像推送到Harbor仓库'){ //定义镜像名称 def imageName = "${project_name}:${tag}" //对镜像打上标签 sh "docker tag ${imageName} ${harbor_url}/${harbor_projectName}/${imageName}" //使用凭证的方式登入,避免直接写harbor的用户和密码 withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) { //登入harbor sh "docker login -u ${username} -p ${password} ${harbor_url}" //镜像上传 sh "docker push ${harbor_url}/${harbor_projectName}/${imageName}" sh "echo '镜像上传成功'" } } }然后在Jenkins页面随便选择一个微服务工程构建测试,看看镜像最后能不能上传到harbor服务器上
在Harbor页面查看推送的镜像
好了,现在镜像也成功推送到harbor服务器中,接下来,就把其他的几个微服务工程也都制作成镜像然后推送到harbor中
镜像上传完毕!
写在前头,目前我们所有的微服务镜像都已经上传到了harbor仓库中,harbor是在39.108.6.54服务器上,现在需要112.74.167.52这台服务器去harbor仓库拉取镜像来发布。jenkins也是安装在39.108.6.54这台服务器上的。
关键点:如何去触发112.74.167.52拉取镜像的动作呢?需要在Jenkins上安装一个 Publish Over SSH插件,他可以帮助我们在一台服务器上去发送一些远程命令到其他服务器上
Jenkins 安装 Publish Over SSH 插件
因为我们需要从Jenkins服务器服务器发送命令到112.74.167.52服务器,而我这里的Jenkins特殊一点,我是使用docker安装的,所以我要在Jenkins的容器中发送命令到112.74.167.52服务器,但是呢?现在这个容器和服务器之间并没有连通,所以要通过私钥和公钥的方式来打通
进入Jenkins容器内,将 .ssh目录下的公钥文件发送至112.74.167.52这台服务器的 /root/.ssh 目录下面
(注意:这里的思路是对的,但是实际上在Publish Over SSH 配置远程服务器时的私钥是宿主机上的,而不是Jenkins容器内部的,这里是大坑啊,注意和配置gitlab的ssh拉取代码时那个私钥区分开,那个私钥是在Jenkins的容器内部生成的)
# 注意这里是Jenkins容器的宿主机的文件内部 如果没有私钥的话使用命令 ssh-keygen -t rsa 来生成 [root@iZwz9eq1jai7e87n6vw5liZ .ssh]# ssh-copy-id 112.74.167.52 /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub" The authenticity of host '112.74.167.52 (112.74.167.52)' can't be established. ECDSA key fingerprint is SHA256:HTfseOtxcyRGjC+3cIa97LBPUrv2gvLWYbGEj/IDCpU. ECDSA key fingerprint is MD5:7c:91:03:e6:60:d6:1e:a9:bb:07:14:ff:c7:cb:04:8d. Are you sure you want to continue connecting (yes/no)? yes # 输入yes /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed /usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys root@112.74.167.52's password: # 输入另一台服务器的密码 Number of key(s) added: 1 Now try logging into the machine, with: "ssh '112.74.167.52'" and check to make sure that only the key(s) you wanted were added. [root@iZwz9eq1jai7e87n6vw5liZ .ssh]# 发送成功 # 112.74.167.52服务器查看收到的公钥文件 [root@pihao .ssh]# ls authorized_keys [root@pihao .ssh]#公钥发送完毕,回到Jenkins的系统配置页面的Publish over SSH 配置部分
测试通过,那么就说明可以从jenkins这台服务器发送远程命令去操作配置的服务器了,继续编写Jenkinsfile脚本,去harbor拉取镜像,然后创建容器,这里的脚本同样可以通过流水线语法来生成
在另外一台服务器编写脚本,用于去harbor拉取镜像,并运行容器
deploy.sh
这里脚本文件很简单,就是查询本台服务器有没有相同的容器或者镜像,如果有那么就删除,然后登入Harbor服务器去拉取镜像再运行,完事!!
#! /bin/bash #接受外部参数 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 pihao -p PIhao@123 $harbor_url # 下载镜像 docker pull $imageName #启动容器 docker run -di -p $port:$port $imageName echo "容器启动成功"将depply.sh这个脚本文件上传到服务器的 /opt/jenkins_shell/ 目录下面
[root@pihao jenkins_shell]# pwd /opt/jenkins_shell #目录,到时候远程调用这个指令的时候需要传入目录 [root@pihao jenkins_shell]# ls deploy.sh [root@pihao jenkins_shell]# chmod +x deploy.sh 添加执行权限 [root@pihao jenkins_shell]# ls deploy.sh [root@pihao jenkins_shell]#最后的Jenkinfile文件为
//git凭证ID def git_auth = "23873dc2-1086-415a-ab12-38c5518784f1" //git的url地址 def git_url = "git@112.74.167.52:root/docker-springcloud-test.git" //镜像标签 def tag = "latest" //Harbor私有仓库的url地址 def harbor_url = "39.108.6.54" //Harbor仓库中项目的名字 def harbor_projectName = "docker-springcloud-test" //Harbor的登入凭证 def harbor_auth = "ecd55d01-e80a-4481-903f-26097f389625" node { stage('拉取代码'){ checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]]) } stage('代码审查'){ //定义当前Jenkins的SonarQubeScanner工具的环境,这个是我们在Jenkins页面全局工具配置 SonarQube Scanner选项中添加的那个默认的 sonar-scanner def scannerHome = tool 'sonar-scanner' //引入SonarQube的服务器环境,这个是我们在系统配置中 的SonarQube servers选项这里添加的 sonarqube的环境 withSonarQubeEnv('sonarqube'){ sh """ cd ${project_name} ${scannerHome}/bin/sonar-scanner """ } } stage('编译,安装公共子工程'){ sh "mvn -f docker-springcloud-api clean install" } stage('编译,打包微服务工程,生成镜像'){ sh "mvn -f ${project_name} clean package dockerfile:build" } stage('将镜像推送到Harbor仓库'){ //定义镜像名称 def imageName = "${project_name}:${tag}" //对镜像打上标签 sh "docker tag ${imageName} ${harbor_url}/${harbor_projectName}/${imageName}" //使用凭证的方式登入,避免直接写harbor的用户和密码 withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) { //登入harbor sh "docker login -u ${username} -p ${password} ${harbor_url}" //镜像上传 sh "docker push ${harbor_url}/${harbor_projectName}/${imageName}" sh "echo '镜像上传成功'" } } //使用流水线语法生成的脚本,主要是编写execCommand 中的内容,去触发另外一台服务器的脚本,以此来拉取镜像并运行 stage('操作其他服务器从harbor拉取镜像,创建容器'){ sshPublisher(publishers: [sshPublisherDesc(configName: '112.74.167.52', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "/opt/jenkins_shell/deploy.sh $harbor_url $harbor_projectName $project_name $tag $port", execTimeout: 720000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)]) } }将最后的 Jenkinsfile文件上传到Gitlab,然后在Jenkins上构建项目测试。
测试之前,先来看一下我112.74.167.52这台服务器(远程服务器)的容器,等会用于比较
[root@pihao jenkins_shell]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b97ca3ae652d gitlab/gitlab-ce "/assets/wrapper" 2 weeks ago Up 3 days (healthy) 0.0.0.0:222->22/tcp, 0.0.0.0:8090->80/tcp, 0.0.0.0:8443->443/tcp gitlab [root@pihao jenkins_shell]# 我这里只有一个正在运行的Gitlab容器 [root@pihao jenkins_shell]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE gitlab/gitlab-ce latest 2b9ac1a40dd1 2 weeks ago 1.81GB [root@pihao jenkins_shell]# 我这里只有一个Gitlab镜像在Jenkins开始构建
构建成功之后我们再来看一下112.74.167.52这台服务器(远程服务器)的容器
# 发现euraka-7001容器已经自动启动 [root@pihao jenkins_shell]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4df50539040c 39.108.6.54/docker-springcloud-test/docker-springcloud-eureka-7001:latest "java -jar /app.jar" 3 minutes ago Up 3 minutes 0.0.0.0:7001->7001/tcp b97ca3ae652d gitlab/gitlab-ce "/assets/wrapper" 2 weeks ago Up 3 days (healthy) 0.0.0.0:222->22/tcp, 0.0.0.0:8090->80/tcp, 0.0.0.0:8443->443/tcp gitlab # 发现eureka-7001镜像成功下载 [root@pihao jenkins_shell]# docker images REPOSITORY TAG IMAGE ID CREATED 39.108.6.54/docker-springcloud-test/docker-springcloud-eureka-7001 latest 7feea91d38c2 13 minutes ago gitlab/gitlab-ce latest 2b9ac1a40dd1 2 weeks ago [root@pihao jenkins_shell]# 我们发现镜像已经拉取成功并且运行成功!微服务全套集成完毕。这里再记录一下这次构建过程中遇到的两个小bug吧
1、脚本的格式错误,导致执行命令的时候报 bin/bash^M: bad interpreter: No such file or directory 的错误
原因:当时创建deploy.sh的时候是在windos下面用记事本写的,然后发送到linux服务器中,这样会导致格式错误。
解决办法:
vim deploy.sh 编辑文件:set ff 或者 :set fileformat 查看文件格式(如果是dos格式组需要改为unix格式,不然会报错):set ff=unix 将格式设置为unix:wq 保存退出2、在构建的过程中,由于需要将微服务工程制作为镜像,在发布到harbor服务器,然后又要从harbor服务器拉取镜像并运行,这个过程中是需要一定的时间的,执行脚本的时候默认超时的时间是120000毫秒,所有我在这个过程中 报错 :ERROR: Exception when publishing, exception message [Exec timed out or was interrupted after 120,000
解决办法:在Jenkinsfile脚本中
# 将execTimeout的默认值120000设置为720000毫秒 stage('操作其他服务器从harbor拉取镜像,创建容器'){ sshPublisher(publishers: [sshPublisherDesc(configName: '112.74.167.52', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "/opt/jenkins_shell/deploy.sh $harbor_url $harbor_projectName $project_name $tag $port", execTimeout: 720000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)]) }查看最后所有微服务容器
好,现在项目都已经全部部署完毕,接着我们来测试一下,调用消费者的接口去查询所有的员工信息,消费者内部调用的是提供者的接口,那我们就实现了微服务的一个远程调用的简单实例
ok至此,微服务SpringCloud持续集成已经完毕,现在开发人员只要将自己的代码提交到gitlab即可,当然,你也选择手动构建项目,也可以配置githook钩子函数来触发构建,后续的微服务工程打包,制做镜像,发布到harbor私有仓库,然后拉取镜像,部署都是自动化完成,这就是DevOps的概念。
2020/6/30 23:50:30
啊!Jenkins流水线,微服务持续集成终于快要学完了,从最初学习docker,然后手动搭建Gitlab、Jenkins、SonarQube、Harbor服务器,中间遇到到无数的坑,不过都被我解决,中间有过多次卡顿,庆幸自己坚持学了下来!现在想想这中间最珍贵的不是完成了持续集成,应该遇到问题后的解决思维。加油吧,程序员,只要学不死,就往死里学~