構(gòu)建基于Springboot+Docker+Jenkins+Github+Maven的持續(xù)集成系統(tǒng)

最近在做基于Docker+Jenkins+Github+Maven的持續(xù)集成環(huán)境,目的是自動(dòng)化構(gòu)建springboot項(xiàng)目并發(fā)布到生產(chǎn)環(huán)境。小公司沒(méi)有自動(dòng)化構(gòu)建系統(tǒng),項(xiàng)目發(fā)布過(guò)程是這樣的:

1、在本地寫好代碼、測(cè)試,測(cè)試通過(guò)后打成war包或jar包;
2、將war包或jar包拷貝到云服務(wù)器上;
3、重啟服務(wù)。

整個(gè)過(guò)程,特別是第2、3步靠手工操作可能會(huì)引入錯(cuò)誤(比如命令行下不小心可能會(huì)刪掉某個(gè)文件),不利于項(xiàng)目的可靠性。為此,想起上家公司采用了Jenkins實(shí)現(xiàn)了項(xiàng)目的自動(dòng)化構(gòu)建,不需要本地打包、上傳,因此干脆就做一回運(yùn)維,花點(diǎn)時(shí)間自己搭建一個(gè)持續(xù)集成系統(tǒng),以實(shí)現(xiàn):
只需本地寫好代碼并完成測(cè)試,然后將代碼push到Github,后面的編譯、打包、發(fā)布等工作交由持續(xù)集成系統(tǒng)自動(dòng)完成。

恩,理想很豐滿,現(xiàn)實(shí)嘛骨感。以前工作中都是使用現(xiàn)成,第一次自己搭建踩了無(wú)數(shù)的坑。好在經(jīng)過(guò)一段時(shí)間的學(xué)習(xí)、實(shí)操,今天終于初步達(dá)成了目標(biāo)。在此,要對(duì)Google搜索同學(xué)提出表?yè)P(yáng)。

由于比較忙,先大致說(shuō)一下整個(gè)過(guò)程。

本地開(kāi)發(fā)環(huán)境:
操作系統(tǒng):Ubuntu 16.04
Java開(kāi)發(fā)集成環(huán)境:Intellij idea 2019.1.3(Ultimate Edition)
JDK版本:1.8.0_191
數(shù)據(jù)庫(kù):Mongodb 4.0.10、Redis 3.0.6
Maven:3.6.1
Tomcat:8.5.41

云服務(wù)器環(huán)境:
操作系統(tǒng):阿里云ECS,Ubuntu 16.04
JDK版本:使用鏡像openjdk8,目前版本1.8.0_212
數(shù)據(jù)庫(kù):Mongodb 4.0.10、Redis 3.0.6
Maven:3.6.1
Tomcat:2.1.1,Springboot自帶

大致步驟(后面有時(shí)間再補(bǔ)充):
注意:我的賬戶默認(rèn)是root,因此下面的命令均不需要sudo前綴
第1步:安裝Docker(按照官網(wǎng)安裝即可)
(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
安裝好后,運(yùn)行“docker run hello-world”看是否安裝正確。

第2步:安裝Jenkins
這里通過(guò)Jenkins鏡像來(lái)安裝。注意:Jenkins鏡像版本越高越好,否則可能會(huì)出現(xiàn)某些插件不兼容的問(wèn)題。安裝過(guò)程見(jiàn)我的另一篇博客http://www.itdecent.cn/p/15c1addd1733。這篇博客中的漢化方法有些問(wèn)題,重啟Jenkins后會(huì)出現(xiàn)部分中文部分英文的情況,問(wèn)題還沒(méi)有找到。在此建議先不要漢化,因?yàn)楹芏鄦?wèn)題可以在google上搜索,漢化后搜索結(jié)果的質(zhì)量你懂的。

第3步:配置Jenkins和Github
這一步的目的是,當(dāng)我們將本地項(xiàng)目push到Github后,會(huì)主動(dòng)觸發(fā)Jenkins從Github上拉取該項(xiàng)目,然后運(yùn)行配置好的腳本。具體配置過(guò)程見(jiàn)我的另一篇博客http://www.itdecent.cn/p/29d2a339a57a

第4步:配置Springboot項(xiàng)目
這一步主要是在項(xiàng)目pom.xml中引入dockerfile-maven-plugin插件,在該插件中可以指定項(xiàng)目打包后的名稱、版本以及本地JAR包的位置。另外,還需要在項(xiàng)目根目錄下編寫Dockerfile以及build.sh、run.sh腳本。
Dockerfile:用于構(gòu)建Springboot項(xiàng)目的鏡像
build.sh:包含編譯Dockerfile的命令
run.sh:用于基于項(xiàng)目鏡像啟動(dòng)Docker容器,即運(yùn)行項(xiàng)目
注意:這三個(gè)文件在項(xiàng)目根目錄下,與src同級(jí),見(jiàn)下圖


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>

注意:如果你原來(lái)將項(xiàng)目打包為war包,那么需要將pox.xml中的<packaging>war</packaging>注釋掉(如果有的話)
4.1.2 配置Tomcat
由于在本地開(kāi)發(fā)及測(cè)試時(shí)使用的是單獨(dú)安裝的Tomcat,沒(méi)有使用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>

由于阿里云內(nèi)存空間有限,為了減少不必要的內(nèi)存開(kāi)銷,同時(shí)啟動(dòng)/停止項(xiàng)目均通過(guò)容器來(lái)實(shí)現(xiàn),不需要顯式執(zhí)行Tomcat的startup.sh腳本來(lái)啟動(dòng)項(xiàng)目,因此沒(méi)必要單獨(dú)安裝Tomcat。故將上面的依賴修改為:

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

同時(shí),因?yàn)槭褂脙?nèi)置的Tomcat,因此還要增加:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
    </dependency>

4.2 編寫Dockerfile
Dockerfile用于構(gòu)建項(xiàng)目鏡像。在項(xiàng)目的根目錄下新建文件Dockerfile,內(nèi)容如下:

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"]

這里,我沒(méi)有使用宿主機(jī)中安裝的JDK,而是基于基礎(chǔ)鏡像openjdk:8來(lái)構(gòu)建項(xiàng)目鏡像。
這里簡(jiǎn)要說(shuō)明該Dockerfile的內(nèi)容:
第1行:表示當(dāng)前要構(gòu)建的鏡像的基礎(chǔ)鏡像是openjdk:8
第2行:設(shè)置環(huán)境變量,暫未指定值,在后面指定
第3、4行:可選。這兩行目的是更新并安裝vim,這兩行主要是為了安裝vim,因?yàn)榛A(chǔ)鏡像中除了jdk之外,沒(méi)有其他軟件可用。為了容器啟動(dòng)后可用對(duì)某些配置進(jìn)行編輯或查看(當(dāng)然也可以用cat命令查看,但我習(xí)慣了用vim)。如果你不需要vim,可以不安裝
第5、6行:設(shè)置容器中的時(shí)區(qū)為東八區(qū)(北京時(shí)間)。容器的默認(rèn)時(shí)區(qū)不是東八區(qū),因此需要進(jìn)行設(shè)置。需要注意的是,通過(guò)這兩行設(shè)置后的時(shí)間也早于當(dāng)前時(shí)間8小時(shí)。網(wǎng)上有方法是在啟動(dòng)容器時(shí),加上參數(shù)-v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone,但我沒(méi)有驗(yàn)證此方法。
第7行:在阿里云服務(wù)器上創(chuàng)建目錄testproject,用于保存testproject-0.0.1-SNAPSHOT.jar
第8行:將環(huán)境變了JAR_FILE指向/testproject
第9行:將容器的端口9081暴露到容器外,即在宿主機(jī)可以訪問(wèn)該端口(即訪問(wèn)服務(wù)的端口)
第10行:指定容器啟動(dòng)時(shí)執(zhí)行的程序及參數(shù)。這里表示在容器啟動(dòng)后就發(fā)布項(xiàng)目,如果不想立即發(fā)布項(xiàng)目,可以替換為其他命令,比如ENTRYPOINT ["ls", "-l", "/testproject"]

4.3 編寫build.sh
這里不多說(shuō),見(jiàn)下面的代碼:

mvn clean
mvn package -DskipTests
docker rmi -f testproject:20190628-1.0
mvn Dockerfile:build
docker images

其中,第3行表示刪除上一個(gè)版本的鏡像(可選,如果要考慮回滾的話就不要?jiǎng)h除);第四行是根據(jù)Dockerfile重新生成新鏡像。

4.4 編寫run.sh
這個(gè)腳本主要是用于啟動(dòng)容器,代碼如下:

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行命令執(zhí)行后出現(xiàn)”cannot stop container”的情況
第3、4行:停止前一版本的容器,并刪除該容器(不刪的話,第5行的docker run命令會(huì)因存在相同名稱的容器而失敗)
第5行:?jiǎn)?dòng)容器。參數(shù)說(shuō)明如下:
--name:指定了容器名稱;
--network:指定該容器所在的網(wǎng)橋,該網(wǎng)橋要與后文的mongodb容器所在網(wǎng)橋相同,否則容器不能訪問(wèn)mongod;
--network-alias:表示網(wǎng)橋的別名,如果沒(méi)有別名的話不需要此參數(shù);
-p:端口映射,格式為"-p 宿主機(jī)端口:容器端口";
-v /var/jenkins_home/workspace/java_tale/data:/data:表示將宿主機(jī)的目錄/var/jenkins_home/workspace/java_tale/data映射到容器目錄/data。宿主機(jī)目錄存放的是項(xiàng)目需要的數(shù)據(jù),在項(xiàng)目中位于data目錄下,data目錄與src目錄同級(jí),見(jiàn)第4步附圖。這樣配置后,需要在項(xiàng)目的properties文件中,制定數(shù)據(jù)路徑,如下圖:


image.png

-v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone:指定容器中的時(shí)區(qū),主要是方便查看日志
ruleparser:20190628-1.0:鏡像名稱及標(biāo)簽,注意:名稱及標(biāo)簽要與pom.xml中的配置相同。

第5步:mongodb容器及配置主從節(jié)點(diǎn)
參考博客https://outmanzzq.github.io/2019/01/30/docker-mongo-replica/
這里貼出我的配置過(guò)程。

5.1 運(yùn)行mongodb replica的三個(gè)容器
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 表示映射宿主機(jī)27000端口到容器的27017端口
--name mongo-master 表示容器名為mongo-master
-v /server/data/mongodb-docker/db/master:/data/db 表示映射宿主機(jī)目錄到mongodb容器/data/db目錄 (mongodb容器運(yùn)行時(shí)的默認(rèn)目錄),兩個(gè)目錄可以自定義
--net jids 指定容器網(wǎng)絡(luò)為local-mongo-cluster
mongo 容器使用的mongodb鏡像
mongod --replSet rs 執(zhí)行mongod命令,將該實(shí)例(容器)添加到名為rs的副本集

5.2 進(jìn)入mongo-master容器的mongo命令行
docker exec -it mongo-master mongo
(進(jìn)入容器的命令是docker exec -it mongo-master /bin/bash,不要搞混)

5.3 配置主從節(jié)點(diǎn)
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節(jié)點(diǎn)(如果切換后還是SECONDARY,可能由于MASTER連接問(wèn)題,多嘗試幾次即可)
rs.config()

5.4 從宿主機(jī)導(dǎo)入數(shù)據(jù)到容器
5.4.1 首先進(jìn)入容器并在/root下創(chuàng)建data文件夾
docker exec -it mongo-master /bin/bash
mkdir /root/data
5.4.2 然后退出容器回到宿主機(jī),復(fù)制數(shù)據(jù)到容器。命令格式為:docker cp 數(shù)據(jù)文件夾 容器名:容器內(nèi)保存數(shù)據(jù)的目錄。例如:
//假設(shè)在宿主機(jī)中保存bson和json文件的jids文件夾在/root/jids/data下
docker cp /root/jids/data/jids mongo-master:/root/data

然后就可以驗(yàn)證主從節(jié)點(diǎn)是否有效了(沒(méi)有包括更多驗(yàn)證,如某個(gè)節(jié)點(diǎn)宕機(jī)),附常用命令:
(1)創(chuàng)建數(shù)據(jù)庫(kù)
use 數(shù)據(jù)庫(kù)名
(2)創(chuàng)建集合
db.createCollection("集合名")
(3)顯示所有數(shù)據(jù)庫(kù)
show dbs
注意:當(dāng)使用show dbs時(shí),會(huì)提示Error: listDatabases failed:..."errmsg" : "not master and slaveOk=false"。此時(shí)執(zhí)行一下命令rs.slaveOk()即可
(4)顯示所有集合
show collections
(5)顯示集合中的所有文檔
db.集合名.find()

第6步:在項(xiàng)目中配置mongodb和redis
修改項(xiàng)目的properties文件,根據(jù)宿主機(jī)及容器情況配置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是宿主機(jī)中輸入ifconfig后,docker0對(duì)應(yīng)的虛擬IP地址。這個(gè)地址被所有容器共享,相當(dāng)于容器組成的虛擬網(wǎng)絡(luò)的localhost。注意,不要使用127.0.0.1來(lái)試圖使用宿主機(jī)的服務(wù),因?yàn)槿萜魉诘奶摂M網(wǎng)絡(luò)與宿主機(jī)所在的網(wǎng)絡(luò)是隔斷的。

mongodb.png

6.2 配置redis
redis.host=172.17.0.1
redis.port=6379
這里的host同6.1中的IP。

redis.png

第7步:訪問(wèn)項(xiàng)目
這里有個(gè)坑。假如測(cè)試項(xiàng)目的controller如下


controller.png

要訪問(wèn)的功能模塊如下:


method.png

如果將war包扔在tomcat的webapps中,那么訪問(wèn)該模塊時(shí)需要采用"IP:PORT/testproject/test_parser?"這樣的URI。然而,由于我們?cè)贒ocker中使用的是Springboot內(nèi)置的Tomcat,訪問(wèn)的URI變?yōu)榱?IP:PORT/test_parser?"。這一點(diǎn)需要注意。

到此,整個(gè)構(gòu)建過(guò)程基本完成了。過(guò)程這么長(zhǎng),可以想象遇到了多少坑。上面的流程都是在填完坑后的總結(jié),其中的心酸就不提了。還有很多細(xì)節(jié)還可以優(yōu)化,只有待后面熟悉Docker和Jenkins后再做打算了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容