Jenkins进阶

Posted 丶落幕

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jenkins进阶相关的知识,希望对你有一定的参考价值。


使用步骤

1.环境准备

需要一个springcloud项目
需要会一些基本的Dockerfile构建镜像
需要四台centos
搞一台,安装好docker,然后克隆三台就可以了(完整克隆)
太冗长,不是重点,就不上代码了

2.下载harbor

代码如下(示例):

#解压
tar -zxf harbor-offline-installer-v1.9.2.tgz
#移动
mv harbor /opt/
#进入harbor
cd /opt/harbor/
#修改yml文件
vim harbor.yml

在这里插入图片描述

#运行prepare
./prepare 
#就会开始下载一些初始化镜像
#2.2.2版本prepare报错了,百度没找到答案,换成和视频相同版本1.9.2
#安装(安装的同时服务就已经开启了,可以用docker ps查看)
./install.sh
#访问harbor
192.168.59.132:85
#默认账号密码
admin--Harbor12345

harbor新建项目(示例):
在这里插入图片描述
harbor创建用户(示例):在这里插入图片描述
harbor新建成员(即给项目分配权限):
在这里插入图片描述

3.将docker镜像push到harbor

代码如下(示例):

#打tag,tag名为 harbor地址+harbor项目名+镜像push后的名字
docker tag eureka-8001:latest 192.168.59.132:85/springcloud/eureka-8001
#修改docker配置文件,将harbor地址添加到docker的信任列表
vim /etc/docker/daemon.json
#修改内容
"insecure-registries": ["192.168.59.132:85"]
#重启docker
systemctl restart docker

在这里插入图片描述

#push发现访问被拒绝,因为harbor中的项目为私有,所以需要登入
#登入之前注册的账号,或者harbor默认的账号(Login Succeeded代表登入成功)
docker login -u chen -p Bmw123456 192.168.59.132:85
#push镜像
docker push 192.168.59.132:85/springcloud/eureka-8001

在这里插入图片描述

4.把镜像从harbor pull下来

代码如下(示例):

  1. 添加信任列表:不论上传或者下载都需要添加信任列表
  2. 登入,因为私有,需要权限
  3. 回到harbor界面
    在这里插入图片描述

5.环境搭建小结

代码如下(示例):

  1. 不管push 还是 pull 都需要登入,登入就需要加入信任列表,加了信任列表需要restart docker
  2. Jenkinsfile要引用变量 "${}" 需要用双引号,单引号不能解析
  3. maven打包插件父项目和common是不需要的
  4. common是依赖父项目的,如果没有父项目的pom,其他依赖common的项目是打不了包的(就算是install了common )
  5. 多看日志,大多数问题静下心看日志都能解决

6.Jenkins+sonarqube+docker+harbor

Jenkins(编译,打包)–>sonarqube(代码审查)–>docker(构建镜像)–>发布到harbor(示例):

编写Dockerfile在这里插入图片描述
编写sonar-project.properties(如果报错说找不到class什么,末尾追加sonar.java.binaries=.)
在这里插入图片描述
pom文件添加maven插件(dockerfile 构建 image 插件)

<plugin>
    <groupId>com.spotify</groupId>
    <artifactId>dockerfile-maven-plugin</artifactId>
    <version>1.3.6</version>
    <configuration>
        <repository>${project.artifactId}</repository>
        <!--这个就是dockerfile里面需要传进去的arg参数,动态获取-->
        <buildArgs>
            <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
        </buildArgs>
    </configuration>
</plugin>

创建Jenkins凭证
在这里插入图片描述

在Jenkins中使用凭证(流水线语法生成代码)
片段生成器–>withCredentials: Bind credentials to variables–>Username and password (separated)

编写Jenkinsfile

//git凭证
def git_auth="a5851cde-faab-47fb-92f0-7e0066dc110a"
//git url
def git_url="git@gitee.com:jx-chen/jenkins_springcloud.git"
//tag
def tag="latest"
//harbor-url
def harbor_url="192.168.59.132:85"
//harbor项目名
def harbor_project="springcloud"
//harbor凭证id
def harbor_auth="7a206b05-1a6b-46bb-a1ba-b632fd739db1"

node {
    stage('拉取代码') {
        checkout([$class: 'GitSCM', branches: [[name: '*/${branch}']], extensions: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])
    }
    stage('代码审查') {
        script {
            //引用了之前全局工具配置里面配置的sonar-scanner
            scannerHome = tool 'sonar-scanner'
        }
        //引用之前系统配置里面配置的sonarqube-server,sonarqube是当时起的名字
        withSonarQubeEnv('sonarqube') {
            sh """
                cd ${project_name}
                ${scannerHome}/bin/sonar-scanner
               """
        }
    }
    stage('编译,安装公共子工程') {
        sh "mvn clean install"
    }
    stage('编译,打包微服务工程,上传镜像') {
        sh "mvn -f ${project_name} clean package dockerfile:build"
        //定义镜像名字
        def imageName="${project_name}:${tag}"
        //打tag
        sh "docker tag ${imageName} ${harbor_url}/${harbor_project}/${imageName}"
        //登入harbor账号 and push镜像
        withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
            // 登入
            sh "docker login -u ${username} -p ${password} ${harbor_url}"
            // push镜像到harbor
            sh "docker push ${harbor_url}/${harbor_project}/${imageName}"
            sh "echo 镜像push成功"
        }
    }
}

其他服务基本上都差不多
Dockerfile改下端口
sonar-project.properties改下名字
Dockerfile-maven插件复制粘贴
当在harbor中看到上传的镜像,说明成功了

7.从harbor拉取镜像并部署

由Jenkins发送SSH远程调用,并执行被调用者的shell脚本,实现pull and deploy(示例):

搜索插件: Publish Over SSH --> install
Manage Jenkins --> Configure System

#在Jenkins服务器上使用ssh-copy-id将秘钥copy给pull and deploy服务器(需要输入密码)
ssh-copy-id 192.168.59.133

在这里插入图片描述
在这里插入图片描述

如果测试失败,报错(Failed to add SSH key. Message [invalid privatekey: [B@2a9cc18e])
可能是密匙版本太高,这种开头的(-----BEGIN OPENSSH PRIVATE KEY-----)
使用这个命令重新生成密匙并copy(ssh-keygen -m PEM -t rsa -b 4096)
生成后是这种开头的(-----BEGIN RSA PRIVATE KEY-----)
然后重新copy应该就可以了
生成的密匙都在 /root/.ssh 目录下

编写Jenkinsfile
片段生成器 --> sshPublisher: Send build artifacts over SSH(直接生成,不需要填写,其中最主要的参数为Exec command)在这里插入图片描述
可以看到用到了前面创建的SSH Server
execCommand中的意思是去执行这个文件/opt/jenkins_shell/deploy.sh
并且携带了4个参数(最后一个参数port , 添加一个参数化构建)

//部署应用
sshPublisher(publishers: [sshPublisherDesc(configName: 'master_server', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "/opt/jenkins_shell/deploy.sh $harbor_url $harbor_project $project_name $tag $port", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])

编写shell脚本

#! /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 chen -p Bmw123456 $harbor_url
# 下载镜像
docker pull $imageName
# 启动容器
docker run -di -p $port:$port $imageName
echo "容器启动成功"

当容器在master_server服务器启动成功,代表部署成功(不知道为什么shell脚本的echo没有在Jenkins日志中输出)

最后修改application.yaml文件里的地址(改成master_server的地址,全部服务都要改)
在这里插入图片描述
费劲千辛万苦,终于都启动了
在这里插入图片描述

在这里插入图片描述

当执行feign调用时发现异常(java.net.UnknownHostException: cf90055b05e9)
cf90055b05e9为容器id
幸好docker有些基础,默认的network容器之间是无法ping通的
所以要使用自己创建的网络

#创建mynet网络
docker network create mynet
#将所有容器都连接到自己创建mynet上
docker network connect mynet 4bf778c8575d
docker network connect mynet f39997bd7ab2
docker network connect mynet cf90055b05e9
docker network connect mynet 3d64bb63762a
#查看mynet
docker network inspect mynet 

现在feign和gateway都能正常访问了
不知道视频上为什么直接就成功了,有知道的大佬告知下
可以直接修改deploy.sh,启动容器的时候加上参数 --net mynet

8.部署前端网站

代码如下(示例):

前端我没写,我也没有视频源码(记录一下步骤)
搜索插件:NodeJS --> install
Manage Jenkins --> Global Tool Configuration(全局工具配置)
在这里插入图片描述
创建一个流水线项目拉取前端代码
前端Jenkinsfile脚本

//gitlab的凭证
def git_auth = "a5851cde-faab-47fb-92f0-7e0066dc110a"
node {
stage('拉取代码') {
checkout([$class: 'GitSCM', branches: [[name: '*/${branch}']],
doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [],
userRemoteConfigs: [[credentialsId: "${git_auth}", url:
'git@192.168.66.100:itheima_group/tensquare_front.git']]])
}
stage('打包,部署网站') {
//使用NodeJS的npm进行打包
nodejs('nodejs12'){
sh '''
npm install
npm run build
'''
}
//=====以下为远程调用进行项目部署========
sshPublisher(publishers: [sshPublisherDesc(configName: 'master_server',
transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '',
execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes:
false, patternSeparator: '[, ]+', remoteDirectory: '/usr/share/nginx/html',
remoteDirectorySDF: false, removePrefix: 'dist', sourceFiles: 'dist/**')],
usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}

核心代码:
sourceFiles: 'dist/**' (此处为npm build后的代码)
remoteDirectory: '/usr/share/nginx/html'(此处为远程目录)
和之前execCommand(执行远程脚本)不同,此处执行的是一个copy操作,减npm build后的代码copy到远程的/usr/share/nginx/html中(Nginx)

9.持续部署方案优化

代码如下(示例):

上述部署方案存在的问题:

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

优化方案:

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

10.cluster部署

1台Jenkins,一台harbor,两台deploy(一台master,一台slave):

修改eureka的application.yml

spring:
  application:
    name: eureka
---
server:
  port: 8001
spring:
  profiles: eureka-server1
eureka:
  instance:
    hostname: 192.168.59.133
  client:
    service-url:
      defaultZone: http://192.168.59.133:8001/eureka/,http://192.168.59.129:8001/eureka/
---
server:
  port: 8001
spring:
  profiles: eureka-server2
eureka:
  instance:
    hostname: 192.168.59.129
  client:
    service-url:
      defaultZone: http://192.168.59.133:8001/eureka/,http://192.168.59.129:8001/eureka/

创建一个Jenkins流水线项目(从Git上面拿Jenkinsfile)
参数化构建默认是没有多选框的,所以需要去下载一个查件
搜索插件: Extended Choice Parameter --> install
在这里插入图片描述
在这里插入图片描述
点击构建,就可以看到下面的效果
在这里插入图片描述
添加第二台SSH Server
添加docker信任列表
在Jenkins服务器运行: ssh-copy-id 192.168.59.129 (将公匙copy给第二台ssh server)
在这里插入图片描述
添加参数化构建(选择服务器 master or slave)
在这里插入图片描述在这里插入图片描述

修改Jenkinsfile

//git凭证
def git_auth="a5851cde-faab-47fb-92f0-7e0066dc110a"
//git url
def git_url="git@gitee.com:jx-chen/jenkins_springcloud.git"
//tag
def tag="latest"
//harbor-url
def harbor_url="192.168.59.132:85"
//harbor项目名
def harbor_project="springcloud"
//harbor凭证id
def harbor_auth="7a206b05-1a6b-46bb-a1ba-b632fd739db1"

node {
    //获取当前选择项目的名称
    def selectedProjectNames="${project_name}".split(",")
    //获取当前选择服务器的名称
    def selectedServers="${publish_server}".split(",")
    stage('拉取代码') {
        checkout([$class: 'GitSCM', branches: [[name: '*/${branch}']], extensions: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])
    }
    stage('代码审查') {
        //循环遍历出所有的项目名
        for(int i=0;i<selectedProjectNames.length;i++){
            //eureka-8001@8001
            def projectInfo=selectedProjectNames[i];
            //当前遍历的项目名
            def currentProjectName=projectInfo.split("@")[0]
            //当前遍历的端口
            def currentProjectPort=projectInfo.split("@")[1]

            script {
                //引用了之前全局工具配置里面配置的sonar-scanner
                scannerHome = tool 'sonar-scanner'
            }
            //引用之前系统配置里面配置的sonarqube-server,sonarqube是当时起的名字
            withSonarQubeEnv('sonarqube') {
                sh """
                    cd ${currentProjectName}
                    ${scannerHome}/bin/sonar-scanner
                   """
            }
        }

    }
    stage('编译,安装公共子工程') {
        sh "mvn clean install"
    }
    stage('编译,打包微服务工程,上传镜像') {
        //循环遍历出所有的项目名
        for(int i=0;i<selectedProjectNames.length;i++){
            //eureka-8001@8001
            def projectInfo=selectedProjectNames[i];
            //当前遍历的项目名
            def currentProjectName=projectInfo.split("@")[0]
            //当前遍历的端口
            def currentProjectPort=projectInfo.split("@")[1]

            sh "mvn -f ${currentProjectName} clean package dockerfile:build"
            //定义镜像名字
            def imageName="${currentProjectName}:${tag}"
            //打tag
            sh "docker tag ${imageName} ${harbor_url}/${harbor_project}/${imageName}"
            //登入harbor账号 and push镜像
            withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
                // 登入
                sh "docker login -u ${username} -p ${password} ${harbor_url}"
                // push镜像到harbor
                sh "docker push ${harbor_url}/${harbor_project}/${imageName}"
                sh "echo 镜像push成功"
            }
            //遍历所有的服务器分别部署
            for(int y=0;y<selectedServers.length;y++){
                //获取当前服务器的名称
                def currentServer=selectedServers[y]

                //加上的参数格式,通过不同的参数,启动多文档模块下不同的eureka --spring.profiles.active=
                def activeProfile="--spring.profiles.active="
                if(currentServer=="master_server"){
                    activeProfile=activeProfile+"eureka-server1"
                }else if(currentServer=="slave_server"){
                    activeProfile=activeProfile+"eureka-server2"
                }
                //部署应用
                sshPublisher(publishers: [sshPublisherDesc(configName: "${currentServer}", transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "/opt/jenkins_shell/deployCluster.sh $harbor_url $harbor_project $currentProjectName $tag $currentProjectPort $activeProfile", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
            }
        }
    }
}

在这里插入图片描述
在这里插入图片描述

记得改名字,给执行权限
可以看到已经成功了
在这里插入图片描述

11.Jenkins主从架构

代码如下(示例):

Manage Jenkins --> Configure Global Security --> 代理
在这里插入图片描述
Manage Jenkins --> Manage Nodes and Clouds
在这里插入图片描述
在这里插入图片描述

将jar包放在root目录下,然后执行命令(当然需要java环境)

java -jar agent.jar -jnlpUrl http://192.168.59.131:8888/computer/slave1/jenkins-agent.jnlp -secret 65bd25c2158b08eeba6867864f924096496cea5169e71a00222a34ac3a4586ee -workDir "/root/jenkins"

在会到Jenkins刷新页面,可以看到已经连接了
在这里插入图片描述
创建一个自由风格项目,并让其在salve1节点上运行
在这里插入图片描述
当然要确保salve1服务器上面有git环境,不然会报错,无法init
在slave1节点的工作目录(前面配置的),里面可以看到workspace,workspace内存放的就是刚刚拉取下来的项目了

创建流水线项目,并指定节点
在这里插入图片描述
一旦退出这个界面,连接就会断开
在这里插入图片描述

12.引入kubernates(即 k8s)

传统Jenkins的master-slave方案的缺陷:

  1. master节点发生单点故障时,整个流程都不可用了
  2. 每个slave节点的配置环境不一样,来完成不同语言的编译打包等操作,但是这些差异化的配置导致管理起来非常不方便,维护起来也是比较费劲
  3. 资源分配不均衡,有的slave节点要运行的job出现排队等待,而有的slave节点处于空闲状态
  4. 资源浪费,每台slave节点可能是实体机或者VM,当slave节点处于空闲状态时,也不会完全释放掉资源

kubernates简介:

kubernates(简称,k8s)是Google开源的容器集群管理系统,在docker技术的基础上,为容器化的应用提供部署运行,资源调度,服务发现和动态伸缩等一系列完整功能,提高了大规模容器集群管理的便捷性
其主要

以上是关于Jenkins进阶的主要内容,如果未能解决你的问题,请参考以下文章

我的Android进阶之旅NDK开发之在C++代码中使用Android Log打印日志,打印出C++的函数耗时以及代码片段耗时详情

Jenkins进阶

Jenkins 进阶篇

Atom编辑器入门到精通 Atom使用进阶

Jenkins进阶-Slave节点配置(16)

我的C语言学习进阶之旅解决 Visual Studio 2019 报错:错误 C4996 ‘fscanf‘: This function or variable may be unsafe.(代码片段