Jenkins作为CI/CD方面的流行技术,同时还相对(k8s之流)易于安装,不自己整一整实在是可惜。
本文介绍jenkins从零到部署一个简单java web应用的过程,力求简单优雅。如果想要跟随本文操作学习,准备如下:

  • 一个linux环境(虚拟机或者云服务器,windows当然也可以,不过windows有什么好玩的啊)
  • 安装好docker、git、java、maven、nginx(可选)

本文最终希望实现的效果如下:

  • jenkinsfile(pipeline定义文件)和dockerfile(docker构建镜像的文件)都在git代码仓库中
  • jenkins构建帮助完成如下步骤:
    1. 服务器(docker的宿主机)拉取git代码
    2. 在服务器(docker的宿主机)上完成编译打包
    3. 基于仓库中拉取的dockerfile构建docker镜像
    4. 使用上一步的镜像启动一个docker容器
      这样从推送代码到服务更新,中间只有在jenkins中点击构建这一个操作(这个操作甚至也可以不用,可以配置jenkins监控仓库的代码变更,自动完成构建)

安装Jenkins

Jenkins官网主要介绍了三种方式来安装Jenkins

  • 包管理工具安装
  • war包安装
  • docker安装

包管理器的安装方式虽然方便,但是活都让别人干了,不同系统的安装也可能不太一样,有点隔靴挠痒的感觉,故没有采用。
笔者一开始尝试docker安装,但是后来发现docker方式有诸多麻烦,首先docker中安装的Jenkins需要操作docker。第一种方式docker out of docker,Jenkins在一个docker容器中,需要操作宿主机的docker,设置起来颇为麻烦,也没有找到完整的教程,撇下不谈。
另一种方式是使用docker in docker(Dind),Jenkins官网介绍了这种方式,就是在宿主机的docker中再以(Dind镜像)启动一个docker,这样宿主的docker容器列表里有一个提供Jenkins的容器,还有一个提供docker的容器,再通过socket挂载的方式,让Jenkins容器中的Jenkins可以使用兄弟容器中的docker。
此时宿主机上执行docker ps 效果如下,一个docker:dind镜像启动的容器,直译就是docker中的docker,一个jenkinsci/blueocean镜像启动的容器,里面是jenkins服务。
宿主机上执行docker ps的效果图
笔者最终没有使用这种方式,一是因为这样就有了内外两个docker,他们的镜像缓存是不共享的,内部docker启动后,由于jenkins操作的是内部docker,最终的应用是在docker中的docker中的容器中运行的,性能损耗很大,需要的基础镜像和打包所需的jar包都需要重新下载,相当麻烦,最终没有使用这种方式。
最终选择的是war包的方式安装,首先下载Jenkins的war包,然后很简单,按照官方:

java -jar jenkins.war

或者使用自己写的更复杂一点的,笔者的供参考,部分参数请自行去掉或替换:

nohup java -Xms${opt_xms} -Xmx${opt_xmx} -Xmx${opt_xmx} -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/jenkins.hprof -jar ${jar_dir}/${jar_name} --httpPort=${opt_port} --prefix=/jenkins >> ${log_path}/${project_name}.log &

然后访问 http://localhost:8080就可以解锁Jenkins了。

这里因为jenkins使用了hudson,所以这个war包既可以java -jar的方式启动,也可以放到web容器中启动

解锁jenkins需要一个密码,密码打印在启动jenkins的控制台输出里了,或者参照官网文档找找。我的文件是在这里的:

cat /home/username/.jenkins/secrets/initialAdminPassword 

username换成你的linux用户名。

配置Jenkins

配置访问路径和端口号

如果希望通过 http://localhost:9090访问Jenkins:

java -jar jenkins.war --prefix=/jenkins --httpPort=9090

如果修改了jenkins的路径,需要在系统管理->系统配置中修改如下配置:
修改路径配置
这个配置项里需要加上路径配置,端口也是需要的,图中因为我使用了nginx转发至80端口,所以省略了端口号,nginx转发相关,稍后再聊

配置Jenkins用户

通过初始化密码登录后第一件事就是(系统管理->管理用户)创建一个用户,然后(系统管理->全局安全配置)设置授权策略,登录以后才可以操作jenkins,这里我还选择了匿名用户可读。

在这里插入图片描述

配置全局工具

前往(系统管理->全局工具配置)配置git、maven、docker,笔者配置简单供参考下
在这里插入图片描述

配置插件

前往(系统管理->插件管理)安装以下插件

  • 必要插件:
    * Git Parameter
    构建时可以使用git分支作为参数
    * Pipeline Maven Integration
    在pipeline中使用mvn指令
    * Docker Pipeline
    在pipeline中使用docker指令
  • 推荐插件:
    * Localization: Chinese (Simplified)
    中文插件,推荐安装,因为我使用了这个插件,所以文章中的名词都取自中文版

配置nginx转发

因为jenkins不建议root运行,再加上服务器上可能有多个服务,所以jenkins可能难以直接使用80端口,这样访问jenkins还需要带上端口号(太low了吧),所以需要nginx转发。官方有一篇文章介绍了这方面内容。
又因为笔者服务器的域名备案过期,所以多个服务不能使用二级域名来区分,只能都挂在80端口的default_server下,然后根据不同的路径来定位不同的服务。也就是我期望的jenkins访问路径是这样的:http://111.231.101.154/jenkins/所以上面文章中的方案并不能直接使用。笔者贴出自己的nginx配置文件关键部分供参考:

 	upstream jenkins {
        keepalive 32; # keepalive connections
        server 127.0.0.1:9090; # jenkins ip and port
    }
    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  _;
        location ~ "^jenkins/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 "^jenkins/static/[0-9a-fA-F]{8}\/(.*)" /$1 last;
        }
        location /jenkins/userContent {
            #have nginx handle all the static requests to the userContent folder files
            #note : This is the $JENKINS_HOME dir
            root /var/lib/jenkins/;
            if (!-f $request_filename){
            #this file does not exist, might be a directory or a /**view** url
            rewrite (.*) /$1 last;
            break;
            }
            sendfile on;
        }
        location /jenkins {
            proxy_pass http://jenkins;
            proxy_redirect     default;
            proxy_http_version 1.1;

            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 in Jenkins > 2.54
            proxy_set_header Connection ""; # Clear for keepalive
        }
    }

新建构建任务

直接上图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
笔者仓库地址:https://github.com/javaisgood/urltest.git
保存后自动跳转至任务主页,点击立即构建
在这里插入图片描述
稍等之后,构建完成:
在这里插入图片描述
这时候服务已经启动成功,pipeline的执行步骤也和Jenkinsfile中定义的一样。现在访问http://{{host}}:9000/url/hello_jenkins应该可以看到响应结果,结果就是打印请求的url,具体实现可查看仓库中代码。
如笔者的http://111.231.101.154/url/hello_jenkins
这里因为笔者使用了nginx,将80端口的/url路径请求转发至9000,所以省略了端口号。

参数化构建

使用参数

其实在之前的构建完成后,仔细看图就会发现原来的立即构建变成了“Build with Parameters”参数化构建:

在这里插入图片描述
点开下面的配置,也可以在配置页找到如下内容:
在这里插入图片描述
此时点击“Build with Parameters”可以看到构建不会直接开始,而是加入了新的步骤:
在这里插入图片描述
填写AppName,这其实是因为在代码仓库的Jenkinsfile中,定义了一个参数,这个参数会被直接应用于这个任务的配置中(第一次取的默认值直接构建了,之后会加入填写参数步骤)。
可以看到在Jenkinsfile文件中的parameters 部分定义了参数和默认值

pipeline {
    agent any
    parameters {
        string(name: 'AppName', defaultValue: 'jswdwsx/url', description: 'this is the app name')
    }
    ...省略...
}

指定分支构建

参数化构建的常见应用就是用来选择git分支了,指定分支构建依赖上面提到的Git Parameter插件,安装好这个插件后,再次配置任务
在这里插入图片描述
在参数化构建过程里添加一个参数,类型选择Git参数(没装插件没这个选项),填写内容如下
在这里插入图片描述
名称:branch
描述:随便写
参数类型:分支或标签
默认值:origin/master
再往下,到流水线定义部分:
在这里插入图片描述
选择构建分支这里,原来填的是master分支,现在要选择分支构建,分支当然需要取自上面的git参数,引用参数的写法我试了很久就发现一种写法可以:
在这里插入图片描述
指定分支:refs/remotes/$branch
$branch表示引用之前定义的参数的值。
关于这个写法为什么要这么写,我是根据报错的日志倒推出来的。开始试了很多种写法,头都大了,所以弄完专门水了一篇,参考此文
保存以后就可以重新尝试构建了:
在这里插入图片描述
可以看到新增了一个参数,和之前的设置也是一一对应,如此一来点击选择分支,点击开始构建,就能开始构建指定的分支了!

原理简析

Jenkinsfile文件

我们需要在jenkins中的配置操作,前面基本已经说完了。接下来,每次构建,jenkins就会拉取pull代码,然后读取其中的Jenkinsfile文件,按照文件的内容来执行一系列操作。
Jenkinsfile定义了一个流水线(Pipeline),其中stages部分定义了jenkins一次构建需要执行哪些操作,一起来看看:

...省略...
stages {
        stage('Build jar') {
            steps {
                sh 'mvn -B -DskipTests clean package'
                sh 'echo Build jar done!'
            }
        }
        stage('Remove image') {
            steps {
                script {
                    try {
                        sh "docker ps -a | grep ${params.AppName} | awk '{print \$1}'| xargs docker rm -f"
                        sh "docker rmi ${params.AppName}"
                        sh 'echo Remove image done!'
                    }
                    catch (exc) {
                        echo 'image do not exist!'
                    }
                    finally {
                        sh 'echo Remove image finally done!'
                    }
                }
            }
        }
        stage('Build image') {
            steps {
                sh 'mvn docker:build'
                sh 'echo Build image done!'
            }
        }
        stage('Deploy') {
            steps {
                sh "docker run -p 9000:8080 -d ${params.AppName}"
            }
        }
    }
...省略...

5个stage分别完成以下事情:

  • Build jar
    mvn完成java web应用的打包
  • Remove image
    先删除上一次启动的容器,再删除上一次生成的镜像,如果报错(之前容器或镜像不存在的时候,比如第一次构建时),只打印提示信息,不能停止后续构建操作。
  • Build image
    使用mvn的docker插件build一个docker镜像,构建细节在dockerfile中定义
  • Deploy
    使用上一步构建的镜像,启动一个容器。

这样就定义了一次完整的构建的过程了。
构建的阶段视图中可以看到,每个阶段(stage)都会分开展示,点击绿色方块可以查看对应stage的日志
在这里插入图片描述

dockerfile文件

上面说到build镜像的时候,步骤定义在dockerfile中,再来看看dockerfile

FROM openjdk:8u265-slim

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone

ADD target/url-1.0.jar /usr/share/jswdwsx/url.jar

CMD ["/bin/bash", "-c", "java -jar -server /usr/share/jswdwsx/url.jar"]

EXPOSE 8080

大概就是如下内容:

  • 指定一个基础镜像
  • 设置时区
  • 添加jar包
  • 设置启动命令
  • 暴露端口

这里注意docker镜像定义的端口号是8080,但是Jenkinsfile里又将容器的8080端口映射到宿主机的9000端口了,所以访问服务的端口最终是9000,如果你像我一样还使用了nginx转发,那么就是80端口了。

总结

至此,我们完成了jenkins发布java web应用的体验。
我们比较了jenkins几种安装方式的特点,说明了我们选择war包安装的理由。
我们定义了一个流水线来完成构建,另一种定义构建的方式是直接让jenkins执行一段shell脚本,相对来说流水线是更优雅的,有着更清晰的语法和结构,以及更直观的构建过程展示。
我们使用了git分支参数化构建,可以指定分支进行构建。
我们将Jenkinsfile和dockerfile都纳入版本管理,这样这两个文件可以方便地修改,也可以追踪其变化。
我们使用了一些插件,包括jenkins插件和mvn插件,有效简化了整个过程。
我们还使用了nginx进行转发,更贴近真实生产环境的情况。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐