本文由Markdown語法編輯器編輯完成.
1.背景:
之前從同事手里接手了一個java的項目,是用maven構建項目的.由于我們的服務都是基于docker來部署的,因此這個java項目也是要編譯成docker image然后發布.但是之前一直都是在開發者的本機電腦上進行構建,雖然代碼已經部署在了公司的Gitlab倉庫,但是沒有配置CI/CD. 因此,該項目每次新增需求后,我就在我自己的服務器上面,運行mvn clean && mvn install編出鏡像,然后就docker push到公司的hub上面了.
這個模式已經持續了兩三年.主要也是因為這個項目,目前基本只有我一個人在維護,而且代碼也基本穩定了,沒有太多的需求要開發.所以,每次我想把它在gitlab上面自動構建時,都因為遇到了一些問題,而放棄,轉而去做其他更緊急的事情了.但是這個問題,一直遺留著,也怪難受的.
直到最近,這個項目,也加了很多新的需求,我越來越覺得有必要,需要讓gitlab的CI來自動構建了.要不那一天我有事不在,那這個項目的修改,都不能生成鏡像了.
于是,我又一次鼓足勇氣,準備搞定這件事.大概前后經過了兩周的折騰,終于看到了gitlab上,構建成功的標識,還是蠻開心.當然過程中,也是遇到了很多很多的問題.最終在經過50多次構建后,終于是成功了.在看到構建成功的那一刻,比吃了糖還要開心.
這里記錄一些主要的問題,以及解決方案,希望對后面的,有類似需求的開發者,可以借鑒一些經驗.
2. Gitlab-CI/CD構建:
按照Gitlab的要求,要想讓項目能夠實現自動構建,需要在你的項目中,添加一個.gitlab-ci.yml, 然后在gitlabci.yml里面定義好這個項目的構建步驟,以及觸發時機,和所使用的runner. 這樣當每次提交代碼到Gitlab后,便會觸發CI/CD開始工作,然后它便會按照yml中的stage, 順序依次完成所有的job, 成功后,便會在Build->Pipelines里面,顯示綠色的標識.
那么本次我在構建時遇到的主要問題是哪些呢,以下依次介紹:
2.1 修改maven鏡像,解決拉取依賴超時的問題
mvn項目在構建時,會讀取項目下面的另一個文件 settings.xml, 這個文件主要是定義在構建java項目時,從哪些倉庫拉取依賴.
這個時候,一般都可以配置國內的鏡像,這樣在拉取依賴時會比較快.
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"><profiles><profile><id>multi-repos</id><repositories><repository><id>aliyunmaven</id><url>https://maven.aliyun.com/repository/public</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository><repository><id>central</id><url>https://repo.maven.apache.org/maven2</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository><repository><id>dcm4chee-online</id><url>https://www.dcm4che.org/maven2/</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository></repositories></profile></profiles><activeProfiles><activeProfile>multi-repos</activeProfile></activeProfiles>
</settings>
可以看出,我這里主要是定義了三個倉庫.
https://maven.aliyun.com/repository/public
https://repo.maven.apache.org/maven2
https://www.dcm4che.org/maven2/
配置了上面的三個倉庫地址后, maven在構建依賴時,就會從這三個倉庫中去找,哪個找到都可以, 這樣可以解決部分倉庫無法下載依賴的問題.
但是在實際構建時,還是會遇到一些jar包,拉取超時.如以下:
這樣的錯誤,困擾了我很久.我在網上詢問時,一般給出的建議,都是先把需要依賴的jar包下載到本地,然后再通過mvn的指令,把它推到本地的倉庫中.這樣mvn在構建時,會優先從本地的.m2中查找,這樣就可以避免由于找不到jar包而失敗的問題了.
于是,我就先從相應的位置,把jar包下載下來,然后都拷貝到CI這臺機器的某一個目錄下面.同時在這個目錄下面,用python啟動一個http-server的服務.這樣當CI在這臺服務器構建時,就可以通過wget的方式,把包下載下來,再安裝到構建時的服務器上面了.
如下圖所示,我把在mvn構建時,提示無法下載的jar包等,提前下載下來,cp到CI所部署的這臺服務器,比如指定一個目錄,mvn-build.
然后在當前目錄下,運行python3 http.server 9008,
這樣便可以在瀏覽器上面下載CI服務器上面的文件,同時也可以在構建時,通過
wget -P /app http://172.16.10.28:9008/clib_jiio-1.2-pre-dr-b04.zip, 將服務器上面的文件,下載拷貝到/app目錄下面.
為此,我專門先制作了一個用于mvn構建的鏡像,里面都把需要本地安裝的jar包,都提前安裝到這個鏡像里面.然后便可以通過mvn的構建了.
我用于構建mvn編譯環境的鏡像的Dockerfile是這樣寫的:
# 構建時的基礎鏡像為, ubuntu 24.04
FROM ubuntu:24.04 # Set environment variables, 定義環境變量.
ENV DEBIAN_FRONTEND=noninteractive
ENV MAVEN_VERSION=3.8.4
ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
ENV MAVEN_HOME=/opt/maven
ENV PATH=$MAVEN_HOME/bin:$PATH# 添加代理,這樣在從官網一些依賴時,可以優化速度,解決timeout的問題.
RUN export https_proxy=http://192.168.110.201:10500;export http_proxy=http://192.168.110.201:10500;export all_proxy=http://192.168.110.201:10500;# Install dependencies
RUN apt-get update && \apt-get install -y openjdk-8-jdk wget curl unzip && \rm -rf /var/lib/apt/lists/*# Install Maven
RUN wget https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/${MAVEN_VERSION}/apache-maven-${MAVEN_VERSION}-bin.tar.gz && \tar xzf apache-maven-${MAVEN_VERSION}-bin.tar.gz -C /opt && \ln -s /opt/apache-maven-${MAVEN_VERSION} /opt/maven && \rm apache-maven-${MAVEN_VERSION}-bin.tar.gz# Set working directory
WORKDIR /app# 我提前把從官網下載的jar,zip包等,拷貝到gitlab-ci所在的服務器,如172.16.10.28.
# 通過wget下載了文件后,然后運行 mvn install:install-file, 然后指定文件的groupId, artifactId, version, packaging(文件格式, .jar,.zip,.so等), file是它在本地的路徑.
RUN wget -P /app http://172.16.10.28:9008/clib_jiio-1.2-pre-dr-b04.zip && \mvn install:install-file \-DgroupId=com.sun.media \-DartifactId=clib_jiio \-Dversion=1.2-pre-dr-b04 \-Dpackaging=zip \-Dfile=clib_jiio-1.2-pre-dr-b04.zipRUN wget -P /app http://172.16.10.28:9008/weasis-core-img-4.3.0.jar && \mvn install:install-file \-DgroupId=org.weasis.core \-DartifactId=weasis-core-img \-Dversion=4.3.0 \-Dpackaging=jar \-Dfile=weasis-core-img-4.3.0.jarRUN wget -P /app http://172.16.10.28:9008/libopencv_java-4.3.0-dcm-linux-x86-64.so && \mvn install:install-file \-DgroupId=org.weasis.thirdparty.org.opencv \-DartifactId=libopencv_java \-Dversion=4.3.0-dcm \-Dpackaging=so \-Dclassifier=linux-x86-64 \-Dfile=libopencv_java-4.3.0-dcm-linux-x86-64.so# 通過運行ls, 可以查看是否已經將相關的包,安裝到了構建的runner里面的.m2倉庫里面.
RUN ls ~/.m2/repository/com/sun/media/clib_jiio/1.2-pre-dr-b04/clib_jiio-1.2-pre-dr-b04.zip && \ls ~/.m2/repository/org/weasis/core/weasis-core-img/4.3.0/weasis-core-img-4.3.0.jar && \ls ~/.m2/repository/org/weasis/thirdparty/org/opencv/libopencv_java/4.3.0-dcm/libopencv_java-4.3.0-dcm-linux-x86-64.so# Default command
CMD ["mvn", "-version"]
然后根據以上的Dockerfile, 就可以生成一個基礎的鏡像, 運行:
docker build -t myproject/dev/ubuntu24.04-jdk8-maven384:20250429 .
運行以后,就會生成一個鏡像,然后:
docker push myproject/dev/ubuntu24.04-jdk8-maven384:20250429
就可以把它作為我們接下來要基于mvn, 構建java包時的基礎環境了.
以上可以作為gitlab-ci.yml的第一個stage, 我們可以把這個stage命名為build. 它的主要任務,就是基于項目的pom.xml, 基于mvn, 構建出一個項目的jar包來.
這個時候,gitlab-ci.yml里面,大概是這樣的:
# 定義緩存,加快依賴下載速度
cache:paths:- ~/.m2/repository# 定義不同的階段
stages:- build# 編譯階段
build:stage: buildimage: myproject/dev/ubuntu24.04-jdk8-maven384:20250429script:- rm -rf ~/.m2/repository/org/apache/maven/plugins/maven-clean-plugin- export MAVEN_OPTS="-Dhttp.proxyHost=192.168.110.201 -Dhttp.proxyPort=10500"- export https_proxy=http://192.168.110.201:10500;export http_proxy=http://192.168.110.201:10500;export all_proxy=http://192.168.110.201:10500;- mvn -s settings.xml clean install- ls -lh /builds/cloud/target/mypacs-1.5.1jar only:- tags
2.2 解決gitlab-ci的兩個階段無法共享jar包的問題
第一個階段,在按上述的步驟執行后,就已經成功地編譯出了項目所需要的jar包.
那么接下來,就是如何把第一步生成的jar包,在一個image中里面啟動了.
我之前的想法是,既然都已經生成了jar包了,那我就直接把它放在啟動目錄里面,就可以啟動了.因此我的Dockerfile是這么寫的:
FROM xxx.com/vendor/openjdk-8WORKDIR /usr/share/project/RUN mkdir -p ./lib
COPY ./extra/* /usr/share/project/lib/
COPY target/project-1.5.1.jar /usr/share/project/project.jar
ADD conf.yml /usr/share/project/conf.ymlENV TZ=Asia/ShanghaiENTRYPOINT ["java", "-Djava.library.path=/usr/share/project/lib/", "-jar", "/usr/share/project/project.jar"]
這樣,再運行docker build就可以完成了.但是在實際執行的時候,遇到了第二個問題.就是在執行
COPY target/project-1.5.1.jar /usr/share/project/project.jar的時候,提示這個project-1.5.1.jar Not found. 因此COPY這條指令運行失敗了.
這個報錯的大概含義就是,在copy階段時,找不到制定路徑下面的這個xxx.jar包,導致COPY失敗,CI無法繼續構建.
我嘗試了好幾次,修改這個COPY jar包的源路徑,但是接連好幾次都失敗了.
后來在請教Deepseek后,找到了問題的解決方案.
這個也是在CI/CD構建過程中,最長遇到的問題,就是因為第一步和第二步的工作目錄不同,導致第二步是找不到第一步mvn build出來的jar包的.
在gitlab-ci.yml中,增加這個關鍵字: artifacts. 這個關鍵字的作用是什么呢?
在 GitLab CI/CD 中,artifacts 是一個非常重要的功能,主要用于在不同 Job 或 Stage 之間傳遞文件或目錄,并可以保留構建產物供后續使用或下載。以下是它的核心作用及使用場景:
(1) 跨 Job/Stage 傳遞文件
默認情況下,每個 Job 運行在獨立的環境中,前一個 Job 生成的文件不會自動傳遞給下一個 Job。
artifacts 允許將指定文件或目錄保存下來,供后續的 Job 使用。
build:stage: buildscript:- mvn package # 生成 target/app.jarartifacts:paths:- target/app.jar # 保存 app.jar 供后續 Job 使用deploy:stage: deployscript:- ls -l # 可以看到 target/app.jar 被自動下載- scp target/app.jar user@server:/deploy/
(2) 保留構建產物供下載
可以在 GitLab 的 Pipeline 頁面直接下載 artifacts(如編譯后的二進制文件、日志、測試報告等)。
適用于:
打包后的應用(.jar、.war、.exe)
測試報告(junit.xml、coverage.html)
日志文件(build.log、debug.log).
比如當我在gitlab-ci.yml的第一個Job后面,加上artifacts, 便會將mvn編譯出來的jar包保存下來,而且還可以直接從Gitlab的pipeline的頁面上,下載構建出來的jar包.
從上面紅框內的關鍵字可以看出: 當構建完成后,由于加了artifacts關鍵字,它會Uploading文件到服務器上,供后續需要該文件的job使用.
點擊右側紅色框內的按鈕,可以直接下載編譯出來的jar包,或者瀏覽artifacts指定目錄下面的文件.
而這個路徑里面的文件,在該pipeline后續的job里面,都是可以直接讀取和應用的.這樣也就解決了,原來多個job之間,由于各自的工作目錄不同,而導致無法共享文件的問題.
2.3 artifacts關鍵字的應用