Docker + Jenkins 可持续集成实战

 

前言

本篇文章将会从无到有,构建一套持续集成环境,该篇涉及以下技术点:

  1. JavaWeb基础 SpringBoot框架
  2. Docker 容器引擎
  3. Jenkins自动化部署工具
  4. Linux基础,Shell脚本基础
  5. Nginx反向代理服务器
  6. 虚拟机技术(Hyper-V或VMWare)
  7. Xshell工具

一、课程目标

这里我们先简单介绍一下自动化部署和持续集成的概念:持续集成,简称CI,是一项由Grady Booch提出的技术,它鼓励开发者们持续不断地将他们的代码合并到主干源码仓库。这些'合并'或者'提交',每一次并入到仓库通常都伴随着执行一系列的自动化任务:代码的编译,单元测试和集成测试的执行,评判代码质量是否下降的静态代码质量分析等等。Docker + Jenkins 可持续集成实战_第1张图片

在本次搭建中我们需要简单实现自动化部署,以下我们会一步步地将springBoot项目发布到服务器,并通过Jenkins自动拉取Git代码打包并发布(也可以采用Git webHook触发Jenkins的触发器实现自动构建)

二、环境准备

本次我们使用win10企业版自带的虚拟机Hyper-V

2.1开启Hyper-V 

参考百度百科:https://jingyan.baidu.com/article/ac6a9a5e1f164a2b653eac33.html

2.1装载CentOS7,下载Centos7的ios镜像

参考百度资料:https://www.cnblogs.com/cxxjohnson/p/9267988.html

2.3开启CentOS7虚拟机,如图:

Docker + Jenkins 可持续集成实战_第2张图片

2.4下载Xshell

Xshell下载地址:https://xshell.en.softonic.com/

大家可以自行破解,破解过程比较麻烦,不做介绍

打开XShell链接到CentOS7,这里我们需要知道CentOS7的ip,我们在虚拟机中输入

ifconfig

获取ip,填写账户密码,以后使用XShell的session窗口进行操作,很方便

Docker + Jenkins 可持续集成实战_第3张图片

后面我们所有的操作,环境搭建都会在这一台叫做‘纯净Linux’的虚拟机上进行,至此,我们的基础环境准备OK

三、Docker安装

3.1Docker简介:http://dockone.io/article/2804

3.2在CentOS7上安装Docker

3.2.1、Docker 要求 CentOS 系统的内核版本高于 3.10 ,输入以下命令验证你的CentOS 版本是否支持 Docker 。

通过 uname -r 命令查看你当前的内核版本

$ uname -r

3.2.2、使用 root 权限登录 Centos。确保 yum 包更新到最新。

$ sudo yum update

这个过程可能会比较慢,要耐心等待

3.2.3、卸载旧版本(如果安装过旧版本的话)

$ sudo yum remove docker docker-common docker-selinux docker-engine

3.2.4、安装需要的软件包, yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动依赖的

$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2

3.2.5、设置yum源

$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

3.2.6、可以查看所有仓库中所有docker版本,并选择特定版本安装

$ yum list docker-ce --showduplicates | sort -r

3.2.7、安装docker

$ sudo yum install docker-ce #由于repo中默认只开启stable仓库,故这里安装的是最新稳定版17.12.0
$ sudo yum install  # 例如:sudo yum install docker-ce-17.12.0.ce

3.2.8、启动并加入开机启动

$ sudo systemctl start docker 
$ sudo systemctl enable docker

3.2.9、验证安装是否成功(有client和service两部分表示docker安装启动都成功了)

$ docker version

Docker + Jenkins 可持续集成实战_第4张图片

3.2.10.超时解决办法

curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://3056ffe7.m.daocloud.io

 方案2:在/etc/docker目录下创建daemon.json文件,添加如下内容


{ "registry-mirrors": ["https://almtd3fa.mirror.aliyuncs.com"] }
systemctl daemon-reload
service docker restart

3.2.11.Docker常用命令

docker search []  //查找
docker pull [] //拉取镜像
docker images //查看宿主机镜像
docker ps //查看Docker正在运行的容器
docker ps -a //查看已经推出的容器
docker exec -it e6bcab9047a2 /bin/bash //进入容器内部

至此我们的docker环境安装完成!

四、SpringBoot项目准备

4.1打开IDEA,File->New->Project,使用spring initializr构建一个简单的springboot项目

Docker + Jenkins 可持续集成实战_第5张图片

4.2编写一个RestController,便于测试

package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.net.InetAddress;
import java.net.UnknownHostException;

@Controller
@RequestMapping("index")
public class IndexController {

    @RequestMapping("hello")
    @ResponseBody
    public String hello(String name){
        // 获取IP地址
        try {
            String ip = InetAddress.getLocalHost().getHostAddress();
            String msg = String.format("Hello!you are %s,the ip is %s !",name,ip);
            return msg;
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return null;
    }
}

4.3启动springBoot,访问 localhost:8080/index/hello?name=hqa ,正常访问则,项目构建成功,这里很简单,不在贴图

4.4安装Git,Git下载地址:https://git-scm.com/

Docker + Jenkins 可持续集成实战_第6张图片

我们将项目发布到Git的demo仓库,这里请读者自行创建

4.5现在我们回到本地项目,执行打包命令

mvn clean package

4.6在本地编写一个Dockerfile文件,用记事本打开,输入以下脚本

FROM java:8
VOLUME /tmp
COPY demo-0.0.1-SNAPSHOT.jar app.jar
RUN bash -c "touch /app.jar"
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar", "--server.port=8080", "> /log/app.log"]

解释:
java:8是指Docker Hub上官方提供的java镜像,版本号是8也就是jdk1.8,有了这个基础镜像后,Dockerfile可以通过FROM指令直接获取它的状态——也就是在容器中java是已经安装的,接下来通过自定义的命令来运行Spring Boot应用:
VOLUME指向了一个/tmp的目录,由于Spring Boot使用内置的Tomcat容器,Tomcat默认使用/tmp作为工作目录。效果就是在主机的/var/lib/docker目录下创建了一个临时文件,并连接到容器的/tmp。
将项目的jar文件作为app.jar添加到容器
RUN表示在新创建的镜像中执行一些命令,然后把执行的结果提交到当前镜像。这里使用touch命令来改变文件的修改时间,Docker创建的所有容器文件默认状态都是“未修改”。这对于简单应用来说不需要,不过对于一些静态内容(比如:index.html)的文件就需要一个“修改时间”。
EXPOSE 容器暴露端口
ENTRYPOINT 应用启动命令 参数设定

4.7在linux主机根目录新建/hqa/docker 文件夹,cd进去,XShell 使用rz命令,或者使用xftp,或打开XShell文件传输窗口,将文件拖拽进去

Docker + Jenkins 可持续集成实战_第7张图片

4.8在本目录执行构建,执行如下命令

docker build -t [images名称] . //注意最后有个.,代表当前目录,镜像名称可以自己随便取

由于之前linux主机并未安装jdk8基础镜像,此处构建Docker会自动拉取JDK镜像

Docker + Jenkins 可持续集成实战_第8张图片

4.9查看springboot镜像

docker images

Docker + Jenkins 可持续集成实战_第9张图片

4.10

启动springboot-demo容器

docker run --name spring-boot-docker -d -v /opt/jar/springBootDocker/logs:/log -p 8099:8080 springboot-demo 
//-d代表后台运行,下面demo为演示启动效果未加-d

解释说明:

run: 容器启动命令

--name spring-boot-docker 自定义容器命名

-d 程序后台启动

-v /opt/jar/boot-docker/logs:/log 应用日志 数据卷的方式存储

-p 8099:8080 用主机8099端口映射容器端口

boot-docker 镜像名称

启动界面:熟悉的感觉~~

Docker + Jenkins 可持续集成实战_第10张图片

4.11大功告成!访问 http://192.168.38.35:8099/index/hello?name=hqa  可以看到页面正常显示

Docker + Jenkins 可持续集成实战_第11张图片

4.12关闭容器

docker stop spring-boot-docker

五、Docker安装Nginx

5.1Nginx的介绍这里就不做介绍了,我们直接开始动手

5.2安装步骤

5.2.1安装Nginx Docker镜像

docker pull Nginx

5.2.2创建Nginx挂载目录

mkdir -p /data/nginx/{conf,conf.d,html,logs}

关于挂在目录的含义读者可以自行百度,主要用于在宿主机和docker容器之间做一个共享映射,方便我们配置文件

5.2.3我们先来配置一下咱们的Nginx文件,再次之前我们将springboot镜像再启动一个容器,映射端口为6099,这里参考上面就行,两个springboot项目方便我们做负载均衡测试

vim /data/nginx/conf/nginx.conf
###################################################nginx.conf配置如下###############################################################
# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  102.168.38.35; //宿主机ip
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;
  
        location / {
        proxy_pass http://pic; //代理设置
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

    upstream pic{
                server 192.168.38.35:8099 weight=5; //springboot ip 配置
                server 192.168.38.35:6099 weight=5; //springboot ip 配置
    }
}

5.2.4启动Nginx容器

#将容器中nginx的80端口映射到本地的81端口
docker run --name nginx81 -d -p 81:80 -v /data/nginx/html:/usr/share/nginx/html -v /data/nginx/conf/nginx.conf:/etc/nginx/nginx.conf  -v /data/nginx/logs:/var/log/nginx -v /data/nginx/conf.d:/etc/nginx/conf.d -d nginx:latest

5.2.5查看启动的容器

[root@promote logs]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e77a62262f8a springboot-demo "java -jar app.jar -…" 22 hours ago Up 16 minutes 0.0.0.0:6099->8080/tcp spring-boot-docker2
9e277aba4a0f nginx:latest "nginx -g 'daemon of…" 23 hours ago Up 12 minutes 0.0.0.0:81->80/tcp nginx81
e6bcab9047a2 springboot-demo "java -jar app.jar -…" 24 hours ago Up 17 minutes 0.0.0.0:8099->8080/tcp spring-boot-docker

5.2.6打开浏览器输入Nginx 访问地址 http://192.168.38.35:81/index/hello?name=hqa

Docker + Jenkins 可持续集成实战_第12张图片

若ip交替出现,则配置成功!

5.2.7若无法访问显示404或403,则关闭Centos防火墙

firewall-cmd --state //查看防火墙
systemctl stop firewalld.service //临时关闭
systemctl disable firewalld.service //永久关闭

至此我们的Nginx负载均衡配置完成!

六、Docker安装Jenkins

6.1jenkins介绍:Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能。参考地址:https://jenkins.io/zh/doc/

6.2安装步骤

6.2.1拉取官方镜像地址

docker pull jenkins/jenkins:latest
docker images | grep jenkins/jenkins:latest

这里一定要拉取最新的版本!这里一定要拉取最新的版本!这里一定要拉取最新的版本!重要的事情说三遍,要是拉了旧版本很可能会有各种蛋疼的错误,如提示升级,插件失败,或无法安装,而我们的jenkins是部署在Docker容器的中的,因此这些错误很难搞定!拉取最新的就不会有这些问题~

6.2.2创建挂载目录(不创建这个问题也不大)

mkdir /home/jenkins_home

6.2.3构建image

docker run -d --name myjenkins -p 10086:8080 -v /home/jenkins_home:/var/jenkins_home jenkins
#若失败,可能是jenkins安装路径问题,则执行下面这个
docker run -d --name myjenkins -p 10086:8080 -v /home/jenkins_home:/home/jenkins_home jenkins

6.2.4构建成功后,访问10086端口如图:等待一段时间

Docker + Jenkins 可持续集成实战_第13张图片

搭建成功!

6.2.3拷贝出上一步需要的授权密码

docker exec -it myjenkins /bin/bash //进入容器
cat /var/jenkins_home/secrets/initialAdminPassword //将密码复制出来

6.2.4点击continue我们将看到如下新界面,直接点击导入插件,等待更新,此段时间将会比较漫长

Docker + Jenkins 可持续集成实战_第14张图片

插件安装:

Docker + Jenkins 可持续集成实战_第15张图片

首先填入信息,若进入主界面后如果看到右上角有错误的提示信息,那么请注意上面那个说了三遍的问题!!或者尝试更新修复!

6.2.5配置加速器

【系统管理】-> 【插件管理】-> 【高级】-> 【升级站点】

更换地址:http://mirror.xmission.com/jenkins/updates/current/update-center.json

至此Jenkins的配置就结束了!

记住你的用户名密码,这一点不用多说!

七、使用Jenkins进行远程部署

7.1终于到了最后一步,也是比较有难度的一步,这一步完成以后整个教程到此就结束啦

7.2配置步骤

7.2.1首先,我们需要给Jekins安装三个插件,点击插件管理,搜索安装,非常的简单

  • maven插件 参考资料:https://blog.csdn.net/qq_32218457/article/details/80775049
  • Git插件 参考资料:https://cloud.tencent.com/developer/article/1360527
  • SSH插件 用于远程发布 参考资料:https://www.cnblogs.com/zz0412/p/jenkins_jj_10.html

Docker + Jenkins 可持续集成实战_第16张图片

7.2.2Jekins全局配置

Jenkins->全局工具配置,由于我们的jenkins跑在docker容器上面,因此以下配置我们都需要勾选自动安装

7.2.2.1JDK配置,此处需要注册Oracle账号,没有的话需要去官网注册Docker + Jenkins 可持续集成实战_第17张图片

 

7.2.2.2GIT配置

Docker + Jenkins 可持续集成实战_第18张图片

7.2.2.3MAVEN配置

Docker + Jenkins 可持续集成实战_第19张图片

7.2.2.4SSH(Publish over SSH)配置

Docker + Jenkins 可持续集成实战_第20张图片

7.2.2.5配置一个邮箱,用于Jenkins给我们发邮件(非必须)

参考资料:https://www.cnblogs.com/gcgc/p/5631385.html

7.2.3新建一个工程,选择新建一个自由风格的软件项目,任务名称叫做 Test,点击确定~

Docker + Jenkins 可持续集成实战_第21张图片

7.2.4工程配置清单

7.2.4.1Git仓库

Docker + Jenkins 可持续集成实战_第22张图片

7.2.4.2构建配置,这边做的比较简单,仅仅是进行Maven打包

Docker + Jenkins 可持续集成实战_第23张图片

7.2.4.3构建完成后,发送邮件,此处仅仅在构建失败的情况下发送

Docker + Jenkins 可持续集成实战_第24张图片

7.3构建测试

7.3.1一切准备成功,现在主控制台,看到我们的测试工程Test

Docker + Jenkins 可持续集成实战_第25张图片

7.3.2点击立即构建,或者双击Test项目,进去里面

Docker + Jenkins 可持续集成实战_第26张图片

7.3.2点击控制台输出,可以看到右侧的项目构建输出信息

Docker + Jenkins 可持续集成实战_第27张图片

7.3.3此处可以看到打包位置

Docker + Jenkins 可持续集成实战_第28张图片

7.3.4打开Xshell,进入/var/jenkins_home/workspace,我们可以看到项目jar包已经打包完成!

docker exec -it myjenkins /bin/bash
cd /var/jenkins_home/workspace/Test

Docker + Jenkins 可持续集成实战_第29张图片

7.4自动化远程发布

实验进行到这里,已经完成得差不多了,接下来是难处所在,我们需要将target目录下的jar包,发布到springboot容器的宿主机,(也是本台机器,实际上到目前为止我们的jenkins,nginx,java,springboot全部运行在一台宿主机上面(ip:192.168.38.35),实际环境中,它们肯定不会只在一台机器上),然后进springboot镜像重新打包部署!这里使用阿里云镜像库会比较容易实现这个流程,但是现在我们这里不那么做,好,我们先来整理一下流程思路:

  1. 将jekins容器本地 jar包上传至宿主机 springboot Dockerfile目录,也就是第一章的 /hqa/docker目录
  2. 链接到宿主机,停止当前springboot容器,删除容器,删除镜像,然后进入Dockerfile目录,重新执行镜像打包构建

7.4.1增加构建步骤,将jekins容器jar包文件上传至宿主机

Docker + Jenkins 可持续集成实战_第30张图片

7.4.2编写Shell脚本,将内容填充至Exec command(什么!Shell脚本不会写点击此处转到菜鸟教程学习一把)

echo '===============项目已经上传至/hqa/docker====================='
echo '==当前分支:${GIT_BRANCH},当前git地址:${GIT_URL}'
#停止当前springboot镜像
echo '===============正在停止所有springboot-demo镜像容器====================='
sudo docker stop `docker ps -a|grep springboot-demo|awk '{print $1}'`
echo '===============正在删除所有springboot-demo镜像容器====================='
#删除当前springboot容器以及镜像
sudo docker rm `docker ps -a|grep springboot-demo|awk '{print $1}'`
sudo docker rmi springboot-demo
echo '===============删除springboot-demo镜像完成====================='
echo '===============正在打包并运行所有springboot-demo镜像====================='
cd  /hqa/docker
docker build -t springboot-demo .
echo '===============打包新的springboot-demo镜像完成====================='
#启动springboot容器
echo '===============正在启动所有springboot-demo镜像容器====================='
docker run --name spring-boot-docker -d -v /opt/jar/springBootDocker/logs:/log -p 8099:8080 springboot-demo
docker run --name spring-boot-docker2 -d -v /opt/jar/springBootDocker2/logs:/log -p 6099:8080 springboot-demo
echo '===============启动所有springboot-demo镜像容器完成====================='
echo '===============正在重启Nginx====================='
docker restart nginx81
echo '===============Nginx重启OK====================='
echo '===============[JENKINS BUILD SUCCESS (*_*) - BY MC.HAN]====================='

注:更优雅的Shell脚本可以使用funciton,这里咱就不搞那么多花里胡哨的了,能跑就行了...这里需要说明一下,这里为了演示效果,重启了Nginx,实际生产环境决不能这么做!而且我们还必须要考虑可用性与梯度发布的问题,不能将服务器上的机器全部停掉!

7.4.3万事俱备,我们现在来测试一下最终的效果

更新一下项目的测试代码,提交至git仓库,注意我们打印字符串加上了‘jenkins updated test’

package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.net.InetAddress;
import java.net.UnknownHostException;

@Controller
@RequestMapping("index")
public class IndexController {

    @RequestMapping("hello")
    @ResponseBody
    public String hello(String name){
        // 获取IP地址
        try {
            String ip = InetAddress.getLocalHost().getHostAddress();
            String msg = String.format("jenkins updated test,Hello!you are %s,the ip is %s !",name,ip);
            return msg;
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return null;
    }
}

7.4.4使用jenkins重新构建项目,走起~

查看构建控制台

===============项目已经上传至/hqa/docker=====================
==当前分支:origin/master,当前git地址:https://github.com/hqa11/demo.git
===============正在停止所有springboot-demo镜像容器=====================
"docker stop" requires at least 1 argument.
See 'docker stop --help'.

Usage:  docker stop [OPTIONS] CONTAINER [CONTAINER...]

Stop one or more running containers
===============正在删除所有springboot-demo镜像容器=====================
"docker rm" requires at least 1 argument.
See 'docker rm --help'.

Usage:  docker rm [OPTIONS] CONTAINER [CONTAINER...]

Remove one or more containers
Error: No such image: springboot-demo
===============删除springboot-demo镜像完成=====================
===============正在打包并运行所有springboot-demo镜像=====================
Sending build context to Docker daemon  16.77MB

Step 1/6 : FROM java:8
 ---> d23bdf5b1b1b
Step 2/6 : VOLUME /tmp
 ---> Using cache
 ---> 5813748c6b0d
Step 3/6 : COPY demo-0.0.1-SNAPSHOT.jar app.jar
 ---> 48db7d175520
Step 4/6 : RUN bash -c "touch /app.jar"
 ---> Running in 9bbcbaf73ead
Removing intermediate container 9bbcbaf73ead
 ---> 9a83afdc5eb5
Step 5/6 : EXPOSE 8080
 ---> Running in e577d678f3a8
Removing intermediate container e577d678f3a8
 ---> f2509de02a09
Step 6/6 : ENTRYPOINT ["java", "-jar", "app.jar", "--server.port=8080", "> /log/app.log"]
 ---> Running in c07c1b7863c1
Removing intermediate container c07c1b7863c1
 ---> 47bb8ced22b9
Successfully built 47bb8ced22b9
Successfully tagged springboot-demo:latest
===============打包新的springboot-demo镜像完成=====================
===============正在启动所有springboot-demo镜像容器=====================
fd42095d132e132b826b5c9f700ae2893306f59f854921e73c7a95a509ce2828
c29e1b62f7339e509af6224d657cfe3db723733b6825fce386a19b2d4da80c74
===============启动所有springboot-demo镜像容器完成=====================
===============正在重启Nginx=====================
nginx81
===============Nginx重启OK=====================
===============[JENKINS BUILD SUCCESS (*_*) - BY MC.HAN]=====================
SSH: EXEC: completed after 49,092 ms
SSH: Disconnecting configuration [Hyper-V纯净Linux] ...
SSH: Transferred 1 file(s)
No emails were triggered.
Finished: SUCCESS

稍等片刻,等待容器启动,现在访问一下nginx http://192.168.38.35:81/index/hello?name=hqaDocker + Jenkins 可持续集成实战_第31张图片

我们按几下F5,ip交替变换着,说明已经完全部署成功了!

八、结束语

到此阶段,我们的部署流程还远没有达到持续集成和部署的应有要求,这里就需要老铁们继续的深入学习了~~~

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(持续集成)