场景
我的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
复制显示出来的密码粘贴到页面的输入框上。
注意:页面上提示的密码地址和我上面是不一样的,它提示的是容器里的路径,我们已经映射这个路径到主机上了,就是上面的路径。
安装推荐插件
之后按引导填写即可。
安装Gitee插件
如果你的项目不是托管在gitee
上的话,可以忽略这个。
Manage Jenkins > Manage Plugins
配置Gitee的帐号密码
如果你的项目不是托管在gitee
上的话,可以忽略这个。
Manage Jenkins > Manage Credentials
在创建构建任务时,jenkins
从你的源代码托管平台上拉取代码时用到。
也可以用Gitee Api Token(配置时选择这个类型即可),但我配置反向代理后,发现创建pipeline任务时,显示不出这个token给我选择,可以参考这里的问题:https://gitee.com/oschina/Git...
安装ssh-agent插件
构建后,需要将文件上传到服务器,并执行服务器上的脚本重启服务等操作,需要用到ssh agent
。
Manage Jenkins > Manage Plugins
如果出现下面这样的问题,重启下服务就自动安装好了。
开始构建
创建构建任务
直接拉到最下面
注意Build Triggers里的Build when a change is pushed to Gitee.这个,如果你希望推送代码之类的操作就自动进行构建(不需要登录到jenkins自己手动操作),可以设置这个选项。本文章不涉及这个,目前我都是登录到jenkins
上操作。
项目demo
接下来,使用一个demo项目做一下测试,新建一个spring boot
项目,增加一个get
请求,具体细节这里就不做描述了,不同的是,你需要在项目根目录下添加一个Jenkinsfile
文件。
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-name
和project-ver
,拼成一个[name]-[ver].jar
的文件名(pom打包的最终文件名就是这样的)。
2、通过scp
将打包好的jar文件上传到服务器。
3、通过scp
将project-ver
上传到服务器。
4、通过ssh
远程到服务器并执行CMD_SERVICE
脚本来重启服务。
HEHU_HOST
Manage Jenkins > Configure SystemJenkinsfile
里有一个HEHU_HOST
环境变量,这个变量是全局配置的,一些敏感信息可以这样配置,避免写死在文件里。
ssh凭证
sshagent (credentials: ['hehu-yunwei']) {
}
这里用到了之前安装的ssh-agent
插件,hehu-yunwei
是我配置的凭证,作用是免密登录。
Manage Jenkins > Manage Credentials
注意ssh-agent
用的是私钥,Passphrase
填公钥最后的那串字符,如下图:
如果免密登录出现问题,记得看下你的.ssh
目录下是否有authorized_keys
这个文件,需要将公钥追加到这个文件里,并设置为755。
cat id_rsa.pub >> authorized_keys
project-ver
在我的服务器上,有一个version
文件只保存一个版本号信息,像这样:
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"菜单,点击我们创建的任务
点击运行,第一次构建时比较久,要下载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>
构建结果
使用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
。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。