3

场景

我的CentOS上有一个spring boot的应用,使用systemd部署,平时更新版本我是这样操作的:

1、本地打包好文件:mvn clean package
2、使用ftp上传jar文件到服务器上。
3、使用ssh登录到服务器。
4、修改shell文件,更新里面的版本号,如:java -jar xxx-v1.0.0.jar ...。
5、重启服务:sudo systemctl restart xxx

现在,我要使用jenkins来完成这些操作。

学习jenkins时,真是填了一个坑下一步就出现一个坑,坑到让我怀疑特么的为什么要花费这么多心力来自动化上面那些操作,另外jenkins的UI感觉真的难用。

开始

安装环境:CentOS 7.x
依赖软件:Docker 19.03.12
前置服务:Nginx

Nginx我没有docker化,因为我的服务器的nginx已经运行很多东西了,如果你希望jenkins通过ip+port来访问,其实也不用nginx。

本文章不讲述如何安装docker,请自行找官网文档学习安装,这里我假设主机上已经安装好最新版本的docker(我使用的是19.03.12)了。

安装jenkins

准备

mkdir -p /docker/jenkins/home
cd /docker/jenkins
vim docker-compose.yml

创建配置文件

docker-compose.yml

version: "3.8"
services:
  dind:
    container_name: jenkins-docker
    image: docker:dind # tip1
    privileged: true
    restart: always
    networks:
      jenkins:
        aliases:
        - docker
    environment:
    - DOCKER_TLS_CERTDIR=/certs
    volumes:
    - /docker/jenkins/home:/var/jenkins_home
    - jenkins-docker-certs:/certs/client

  jeknins:
    container_name: jenkins
    image: jenkinsci/blueocean # tip2
    restart: always
    networks:
      jenkins:
    environment:
    - DOCKER_CERT_PATH=/certs/client
    - DOCKER_TLS_VERIFY=1
    - DOCKER_HOST=tcp://docker:2376
    ports:
    - "9966:8080"
    volumes:
    - /docker/jenkins/home:/var/jenkins_home # tip3
    - jenkins-docker-certs:/certs/client:ro

volumes:
  jenkins-docker-certs:
networks:
  jenkins:
  • tip1:如果你在jenkins里用到docker来构建你的应用的话,就需要用到dind这个镜像,它可以让你的jenkins在容器里使用docker。注意,因为我的jenkins是用docker安装的才要这样,如果jenkins是直接在主机运行war,就不需要了,直接可以使用主机的docker,另,如果你不需要在jenkins里用docker来构建应用,也不需要dind
  • tip2:我使用的是blueocean这个镜像,它包含了blueocean插件,可以更直观的看到构建流程。
  • tip3:jenkins_home的volume我是用绑定挂载的方式,因为之后我要用nginx来做反向代理,nginx需要用到这个实际的目录,如果不用nginx可以用数据卷的方式(我不知道nginx如何使用数据卷)。
在jenkins的容器里使用docker,一开始找了很久都找不到方法,google上都是用主机的docker作为解决方案,最后在jenkins的官网里的一篇<<用Maven构建Java应用>>文章里找到jenkins + dind这个方式,真是活见鬼。https://www.jenkins.io/doc/tu...

启动jenkins

docker-compose up -d

jenkins的初始化工作

启动后,可以先通过ip来访问(http://ip:9966),等页面提示输入密码(有时候要几分钟,貌似是在检查版本更新),先不要进行下一步操作。

sed -i 's#https://updates.jenkins.io/update-center.json#https://cdn.jsdelivr.net/gh/lework/jenkins-update-center/updates/huawei/update-center.json#' /docker/jenkins/home/hudson.model.UpdateCenter.xml

先用华为的插件源地址替换掉jenkins默认的,然后重启jenkins

sudo docker restart jenkins

等提示输入密码的对话框出现

cat /docker/jenkins/home/secrets/initialAdminPassword

复制显示出来的密码粘贴到页面的输入框上。

注意:页面上提示的密码地址和我上面是不一样的,它提示的是容器里的路径,我们已经映射这个路径到主机上了,就是上面的路径。

安装推荐插件

image.png
之后按引导填写即可。

安装Gitee插件

如果你的项目不是托管在gitee上的话,可以忽略这个。
Manage Jenkins > Manage Plugins
image.png

配置Gitee的帐号密码

如果你的项目不是托管在gitee上的话,可以忽略这个。
Manage Jenkins > Manage Credentials
在创建构建任务时,jenkins从你的源代码托管平台上拉取代码时用到。

也可以用Gitee Api Token(配置时选择这个类型即可),但我配置反向代理后,发现创建pipeline任务时,显示不出这个token给我选择,可以参考这里的问题:https://gitee.com/oschina/Git...

image.png

安装ssh-agent插件

构建后,需要将文件上传到服务器,并执行服务器上的脚本重启服务等操作,需要用到ssh agent

Manage Jenkins > Manage Plugins
image.png

如果出现下面这样的问题,重启下服务就自动安装好了。
image.png

开始构建

创建构建任务

image.png

直接拉到最下面
image.png

注意Build Triggers里的Build when a change is pushed to Gitee.这个,如果你希望推送代码之类的操作就自动进行构建(不需要登录到jenkins自己手动操作),可以设置这个选项。本文章不涉及这个,目前我都是登录到jenkins上操作。

项目demo

接下来,使用一个demo项目做一下测试,新建一个spring boot项目,增加一个get请求,具体细节这里就不做描述了,不同的是,你需要在项目根目录下添加一个Jenkinsfile文件。

image.png

Jenkinsfile

pipeline {  
    agent none  
  
    stages {  
        stage('Build') {  
           agent {  
              docker {  
                 image 'maven:3-alpine'  
                 args '-v /root/.m2:/root/.m2'  
              }  
           }  
            steps {  
                sh 'mvn -B -DskipTests clean package' 
                // 获取pom里的项目版本号和名称,写到文件里,方便下一个Deploy agent获取
                sh 'mvn help:evaluate -Dexpression=project.name | grep "^[^[]" > project-name'  
                sh 'mvn help:evaluate -Dexpression=project.version | grep "^[^[]" > project-ver'  
            }  
        }  
        stage('Deploy') {  
           agent any  
           environment {  
              HOST = "${HEHU_HOST}" // 需要在jenkins全局environment配置,待会儿说到
              USER = "yunwei"  
              DIR = "/www/java/sb-demo"  
              VERSION_FILE = "${DIR}/version"  
              CMD_SERVICE = "${DIR}/start-service.sh"  
           }  
           steps {  
               sshagent (credentials: ['hehu-yunwei']) {  
                   sh '''  
                       name=$(cat project-name) 
                       ver=$(cat project-ver) 
                       echo "=========${name}-${ver}========="
                       jarFile=${name}-${ver}.jar
                       scp target/${jarFile} ${USER}@${HOST}:${DIR}/${jarFile}
                       scp project-ver ${USER}@${HOST}:${VERSION_FILE}
                       ssh -o StrictHostKeyChecking=no -l ${USER} ${HOST} -a ${CMD_SERVICE}
                    '''               
               }  
           }  
       }  
    }  
}

这个文件做了哪些工作?

这个文件里没有checkout源码的配置,我们创建pipeline的时候已经做了那一步的配置了。

Build stage中,使用docker下载了maven:3-alpine镜像作为构建应用的环境,在这个环节里,做了三件事:

1、打包jar文件:mvn package
2、使用maven的help插件获取项目的构建名称,并写到project-name文件中。
3、使用maven的help插件获取项目的版本号,并写到project-ver文件中。

为啥将名称和版本号写到文件中,因为我要在下一个agent中用到。我尝试了很多方法都没能用变量的方式传到下一个agent,只能放弃改为使用文件的方式。

Deploy stage中:

1、获取上一个agent的project-nameproject-ver,拼成一个[name]-[ver].jar的文件名(pom打包的最终文件名就是这样的)。
2、通过scp将打包好的jar文件上传到服务器。
3、通过scpproject-ver上传到服务器。
4、通过ssh远程到服务器并执行CMD_SERVICE脚本来重启服务。

HEHU_HOST
Manage Jenkins > Configure System
Jenkinsfile里有一个HEHU_HOST环境变量,这个变量是全局配置的,一些敏感信息可以这样配置,避免写死在文件里。
image.png

ssh凭证

sshagent (credentials: ['hehu-yunwei']) {
}

这里用到了之前安装的ssh-agent插件,hehu-yunwei是我配置的凭证,作用是免密登录。
Manage Jenkins > Manage Credentials
image.png

注意ssh-agent用的是私钥,Passphrase填公钥最后的那串字符,如下图:
image.png

如果免密登录出现问题,记得看下你的.ssh目录下是否有authorized_keys这个文件,需要将公钥追加到这个文件里,并设置为755。

cat id_rsa.pub >> authorized_keys

image.png

project-ver
在我的服务器上,有一个version文件只保存一个版本号信息,像这样:
image.png

1.0.0

sb.sh会读取这个文件里的版本号信息来执行对应的jar文件,像这样:

#!/bin/bash
ver=$(cat ./version)
chmod 500 ./spring-boot-demo-${ver}.jar
java -jar ./spring-boot-demo-${ver}.jar
sb.sh是由systemd服务来执行的。

CMD_SERVICE
这个就是上上个图里的start-version.sh文件,里面只是重启服务:

#!/bin/bash
sudo systemctl restart sb

执行构建

推送代码到master分支后,最后等待个30秒这样再取构建,否则有可能拉取的代码不是最新的。

再左侧菜单栏点击"Open Blue Ocean"菜单,点击我们创建的任务
image.png

点击运行,第一次构建时比较久,要下载maven镜像,maven也要下载一堆依赖包,这里顺便贴一下我的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jxs</groupId>
    <artifactId>spring-boot-demo</artifactId>
    <version>0.0.9</version>
    <packaging>jar</packaging>

    <properties>
        <java.version>1.8</java.version>
        <maven.test.skip>true</maven.test.skip>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
    </parent>

    <!-- Package as an executable jar -->
    <build>
        <finalName>${project.artifactId}-${project.version}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>nexus-aliyun</id>
            <name>Nexus aliyun</name>
            <layout>default</layout>
            <url>http://maven.aliyun.com/nexus/content/groups/public</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>
</project>

构建结果

image.png

image.png

使用Nginx作为反向代理

直接贴上官方的配置

upstream jenkins {
  keepalive 32; # keepalive connections
  server 127.0.0.1:9966; # jenkins ip and port
}

# Required for Jenkins websocket agents
map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
}

server {
  listen          80;       # Listen on port 80 for IPv4 requests

  server_name     example.com;

  # this is the jenkins web root directory
  # (mentioned in the /etc/default/jenkins file)
  root            /docker/jenkins/home/war/;

  access_log      /var/log/nginx/jenkins/access.log;
  error_log       /var/log/nginx/jenkins/error.log;
  # pass through headers from Jenkins that Nginx considers invalid
  ignore_invalid_headers off;

  location ~ "^/static/[0-9a-fA-F]{8}\/(.*)$" {
    #rewrite all static files into requests to the root
    #E.g /static/12345678/css/something.css will become /css/something.css
    rewrite "^/static/[0-9a-fA-F]{8}\/(.*)" /$1 last;
  }

  location /userContent {
    # have nginx handle all the static requests to userContent folder
    #note : This is the $JENKINS_HOME dir
    root /docker/jenkins/home/;
    if (!-f $request_filename){
      #this file does not exist, might be a directory or a /**view** url
      rewrite (.*) /$1 last;
      break;
    }
    sendfile on;
  }

  location / {
      sendfile off;
      proxy_pass         http://jenkins;
      proxy_redirect     default;
      proxy_http_version 1.1;

      # Required for Jenkins websocket agents
      proxy_set_header   Connection        $connection_upgrade;
      proxy_set_header   Upgrade           $http_upgrade;

      proxy_set_header   Host              $host;
      proxy_set_header   X-Real-IP         $remote_addr;
      proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
      proxy_set_header   X-Forwarded-Proto $scheme;
      proxy_max_temp_file_size 0;

      #this is the maximum upload size
      client_max_body_size       10m;
      client_body_buffer_size    128k;

      proxy_connect_timeout      90;
      proxy_send_timeout         90;
      proxy_read_timeout         90;
      proxy_buffering            off;
      proxy_request_buffering    off; # Required for HTTP CLI commands
      proxy_set_header Connection ""; # Clear for keepalive
  }

}

最后到Manage Jenkins > Configure System > Jenkins Location,将你在nginx配置的域名填入:http://example.com


kanbekotori
20 声望0 粉丝

引用和评论

0 条评论