最近在做基于Docker+Jenkins+Github+Maven的持续集成环境,目的是自动化构建springboot项目并发布到生产环境。小公司没有自动化构建系统,项目发布过程是这样的:
1、在本地写好代码、测试,测试通过后打成war包或jar包;
2、将war包或jar包拷贝到云服务器上;
3、重启服务。
整个过程,特别是第2、3步靠手工操作可能会引入错误(比如命令行下不小心可能会删掉某个文件),不利于项目的可靠性。为此,想起上家公司采用了Jenkins实现了项目的自动化构建,不需要本地打包、上传,因此干脆就做一回运维,花点时间自己搭建一个持续集成系统,以实现:
只需本地写好代码并完成测试,然后将代码push到Github,后面的编译、打包、发布等工作交由持续集成系统自动完成。
恩,理想很丰满,现实嘛骨感。以前工作中都是使用现成,第一次自己搭建踩了无数的坑。好在经过一段时间的学习、实操,今天终于初步达成了目标。在此,要对Google搜索同学提出表扬。
由于比较忙,先大致说一下整个过程。
本地开发环境:
操作系统:Ubuntu 16.04
Java开发集成环境:Intellij idea 2019.1.3(Ultimate Edition)
JDK版本:1.8.0_191
数据库:Mongodb 4.0.10、Redis 3.0.6
Maven:3.6.1
Tomcat:8.5.41
云服务器环境:
操作系统:阿里云ECS,Ubuntu 16.04
JDK版本:使用镜像openjdk8,目前版本1.8.0_212
数据库:Mongodb 4.0.10、Redis 3.0.6
Maven:3.6.1
Tomcat:2.1.1,Springboot自带
大致步骤(后面有时间再补充):
注意:我的账户默认是root,因此下面的命令均不需要sudo前缀
第1步:安装Docker(按照官网安装即可)
(1)apt-get update
(2)apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
(3)curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
(4)add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
(5)apt-get update
(6)apt-get install docker-ce docker-ce-cli containerd.io
安装好后,运行“docker run hello-world”看是否安装正确。
第2步:安装Jenkins
这里通过Jenkins镜像来安装。注意:Jenkins镜像版本越高越好,否则可能会出现某些插件不兼容的问题。安装过程见我的另一篇博客https://www.jianshu.com/p/15c1addd1733。这篇博客中的汉化方法有些问题,重启Jenkins后会出现部分中文部分英文的情况,问题还没有找到。在此建议先不要汉化,因为很多问题可以在google上搜索,汉化后搜索结果的质量你懂的。
第3步:配置Jenkins和Github
这一步的目的是,当我们将本地项目push到Github后,会主动触发Jenkins从Github上拉取该项目,然后运行配置好的脚本。具体配置过程见我的另一篇博客https://www.jianshu.com/p/29d2a339a57a。
第4步:配置Springboot项目
这一步主要是在项目pom.xml中引入dockerfile-maven-plugin插件,在该插件中可以指定项目打包后的名称、版本以及本地JAR包的位置。另外,还需要在项目根目录下编写Dockerfile以及build.sh、run.sh脚本。
Dockerfile:用于构建Springboot项目的镜像
build.sh:包含编译Dockerfile的命令
run.sh:用于基于项目镜像启动Docker容器,即运行项目
注意:这三个文件在项目根目录下,与src同级,见下图
image.png
4.1 引入dockerfile-maven-plugin插件
4.1.1 在pom.xml的plugins下插入一下配置:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.company.testproject.TestProjectApplication</mainClass>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.10</version>
<configuration>
<repository>testproject</repository>
<tag>20190628-1.0</tag>
<buildArgs>
<JAR_FILE>/target/testproject-0.0.1-SNAPSHOT.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
注意:如果你原来将项目打包为war包,那么需要将pox.xml中的<packaging>war</packaging>注释掉(如果有的话)
4.1.2 配置Tomcat
由于在本地开发及测试时使用的是单独安装的Tomcat,没有使用Springboot自带的Tomcat,因此在pom.xml中是将该自带的Tomcat依赖去掉了的,即原配置如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>-->
<exclusion>-->
<groupId>org.springframework.boot</groupId>-->
<artifactId>spring-boot-starter-tomcat</artifactIyincid>-->
</exclusion>-->
</exclusions>-->
</dependency>
由于阿里云内存空间有限,为了减少不必要的内存开销,同时启动/停止项目均通过容器来实现,不需要显式执行Tomcat的startup.sh脚本来启动项目,因此没必要单独安装Tomcat。故将上面的依赖修改为:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
同时,因为使用内置的Tomcat,因此还要增加:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
4.2 编写Dockerfile
Dockerfile用于构建项目镜像。在项目的根目录下新建文件Dockerfile,内容如下:
FROM openjdk:8
ARG JAR_FILE
RUN apt-get update
RUN apt-get install vim -y
RUN echo "Asia/Shanghai" > /etc/timezone
RUN dpkg-reconfigure -f noninteractive tzdata
RUN mkdir /testproject
ADD ${JAR_FILE} /testproject
EXPOSE 9081
ENTRYPOINT ["java","-jar","/testproject/testproject-0.0.1-SNAPSHOT.jar"]
这里,我没有使用宿主机中安装的JDK,而是基于基础镜像openjdk:8来构建项目镜像。
这里简要说明该Dockerfile的内容:
第1行:表示当前要构建的镜像的基础镜像是openjdk:8
第2行:设置环境变量,暂未指定值,在后面指定
第3、4行:可选。这两行目的是更新并安装vim,这两行主要是为了安装vim,因为基础镜像中除了jdk之外,没有其他软件可用。为了容器启动后可用对某些配置进行编辑或查看(当然也可以用cat命令查看,但我习惯了用vim)。如果你不需要vim,可以不安装
第5、6行:设置容器中的时区为东八区(北京时间)。容器的默认时区不是东八区,因此需要进行设置。需要注意的是,通过这两行设置后的时间也早于当前时间8小时。网上有方法是在启动容器时,加上参数-v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone,但我没有验证此方法。
第7行:在阿里云服务器上创建目录testproject,用于保存testproject-0.0.1-SNAPSHOT.jar
第8行:将环境变了JAR_FILE指向/testproject
第9行:将容器的端口9081暴露到容器外,即在宿主机可以访问该端口(即访问服务的端口)
第10行:指定容器启动时执行的程序及参数。这里表示在容器启动后就发布项目,如果不想立即发布项目,可以替换为其他命令,比如ENTRYPOINT ["ls", "-l", "/testproject"]
4.3 编写build.sh
这里不多说,见下面的代码:
mvn clean
mvn package -DskipTests
docker rmi -f testproject:20190628-1.0
mvn Dockerfile:build
docker images
其中,第3行表示删除上一个版本的镜像(可选,如果要考虑回滚的话就不要删除);第四行是根据Dockerfile重新生成新镜像。
4.4 编写run.sh
这个脚本主要是用于启动容器,代码如下:
docker ps -a
aa-remove-unknown
docker stop ruleparser
docker rm -f ruleparser
docker run -d --name ruleparser --network jids --network-alias jids -p 9081:9081 -v /var/jenkins_home/workspace/java_tale/data:/data -v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone ruleparser:20190628-1.0
这里要稍微解释一下:
第2行:防止第3行命令执行后出现”cannot stop container”的情况
第3、4行:停止前一版本的容器,并删除该容器(不删的话,第5行的docker run命令会因存在相同名称的容器而失败)
第5行:启动容器。参数说明如下:
--name:指定了容器名称;
--network:指定该容器所在的网桥,该网桥要与后文的mongodb容器所在网桥相同,否则容器不能访问mongod;
--network-alias:表示网桥的别名,如果没有别名的话不需要此参数;
-p:端口映射,格式为"-p 宿主机端口:容器端口";
-v /var/jenkins_home/workspace/java_tale/data:/data:表示将宿主机的目录/var/jenkins_home/workspace/java_tale/data映射到容器目录/data。宿主机目录存放的是项目需要的数据,在项目中位于data目录下,data目录与src目录同级,见第4步附图。这样配置后,需要在项目的properties文件中,制定数据路径,如下图:
image.png
-v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone:指定容器中的时区,主要是方便查看日志
ruleparser:20190628-1.0:镜像名称及标签,注意:名称及标签要与pom.xml中的配置相同。
第5步:mongodb容器及配置主从节点
参考博客https://outmanzzq.github.io/2019/01/30/docker-mongo-replica/
这里贴出我的配置过程。
5.1 运行mongodb replica的三个容器
docker run -dit -p 27000:27017 --name mongo-master -v /server/data/mongodb-docker/db/master:/data/db --network jids --network-alias jids mongo mongod --replSet rs
docker run -dit -p 27001:27017 --name mongo-slave-1 -v /server/data/mongodb-docker/db/slave-1:/data/db --network jids --network-alias jids mongo mongod --replSet rs
docker run -dit -p 27002:27017 --name mongo-slave-2 -v /server/data/mongodb-docker/db/slave-2:/data/db --network jids --network-alias jids mongo mongod --replSet rs
其中:
-p 27000:27017 表示映射宿主机27000端口到容器的27017端口
--name mongo-master 表示容器名为mongo-master
-v /server/data/mongodb-docker/db/master:/data/db 表示映射宿主机目录到mongodb容器/data/db目录 (mongodb容器运行时的默认目录),两个目录可以自定义
--net jids 指定容器网络为local-mongo-cluster
mongo 容器使用的mongodb镜像
mongod --replSet rs 执行mongod命令,将该实例(容器)添加到名为rs的副本集
5.2 进入mongo-master容器的mongo命令行
docker exec -it mongo-master mongo
(进入容器的命令是docker exec -it mongo-master /bin/bash,不要搞混)
5.3 配置主从节点
use jids
(1)确定主从配置
config = {
"_id" : "rs",
"members" : [
{
"_id" : 0,
"host" : "mongo-master:27017"
},
{
"_id" : 1,
"host" : "mongo-slave-1:27017"
},
{
"_id" : 2,
"host" : "mongo-slave-2:27017"
}
]
}
(2)初始化
rs.initiate(config)
(3)切换到MASTER节点(如果切换后还是SECONDARY,可能由于MASTER连接问题,多尝试几次即可)
rs.config()
5.4 从宿主机导入数据到容器
5.4.1 首先进入容器并在/root下创建data文件夹
docker exec -it mongo-master /bin/bash
mkdir /root/data
5.4.2 然后退出容器回到宿主机,复制数据到容器。命令格式为:docker cp 数据文件夹 容器名:容器内保存数据的目录。例如:
//假设在宿主机中保存bson和json文件的jids文件夹在/root/jids/data下
docker cp /root/jids/data/jids mongo-master:/root/data
然后就可以验证主从节点是否有效了(没有包括更多验证,如某个节点宕机),附常用命令:
(1)创建数据库
use 数据库名
(2)创建集合
db.createCollection("集合名")
(3)显示所有数据库
show dbs
注意:当使用show dbs时,会提示Error: listDatabases failed:..."errmsg" : "not master and slaveOk=false"。此时执行一下命令rs.slaveOk()即可
(4)显示所有集合
show collections
(5)显示集合中的所有文档
db.集合名.find()
第6步:在项目中配置mongodb和redis
修改项目的properties文件,根据宿主机及容器情况配置mongodb和redis的IP和Port
6.1 配置mongodb
spring.data.mongodb.uri=mongodb://172.17.0.1:27000,172.17.0.1:27001,172.17.0.1:27002/jids?replicaSet=rs
这里的172.17.0.1是宿主机中输入ifconfig后,docker0对应的虚拟IP地址。这个地址被所有容器共享,相当于容器组成的虚拟网络的localhost。注意,不要使用127.0.0.1来试图使用宿主机的服务,因为容器所在的虚拟网络与宿主机所在的网络是隔断的。
6.2 配置redis
redis.host=172.17.0.1
redis.port=6379
这里的host同6.1中的IP。
第7步:访问项目
这里有个坑。假如测试项目的controller如下
controller.png
要访问的功能模块如下:
method.png
如果将war包扔在tomcat的webapps中,那么访问该模块时需要采用"IP:PORT/testproject/test_parser?"这样的URI。然而,由于我们在Docker中使用的是Springboot内置的Tomcat,访问的URI变为了"IP:PORT/test_parser?"。这一点需要注意。
到此,整个构建过程基本完成了。过程这么长,可以想象遇到了多少坑。上面的流程都是在填完坑后的总结,其中的心酸就不提了。还有很多细节还可以优化,只有待后面熟悉Docker和Jenkins后再做打算了。
网友评论