1. 概述

功能需求

目前的流水线的功能比较简单,对于有服务实例的仓库,只需要实现maven 构建、测试、打包镜像。对于私有maven仓库,实现自动mvn deploy

技术选取

1. kaniko

使用kaniko来打包镜像,kaniko 是一种在容器或 Kubernetes 集群内从 Dockerfile 构建容器镜像的工具。kaniko 不依赖于 Docker 守护进程,而是完全在用户空间中执行 Dockerfile 中的每个命令。这使得在无法轻松或安全地运行 Docker 守护程序的环境中构建容器镜像成为可能,例如标准的 Kubernetes 集群。

kaniko是谷歌开发的,墙内获取比较麻烦,此外,命令参数较多,使用繁琐。因此考虑搭建自己的kaniko仓库,自定义kaniko镜像。使得其他仓库流水线可以方便地使用"kaniko"命令即可打包镜像。

2. gitlab-ci

我们没有使用jenkins等工具,选择使用了gitlab提供的GitLab CI。GitLab CI 是 GitLab 内置的进行持续集成的工具,只需要在仓库根目录下创建 .gitlab-ci.yml 文件,并配置 GitLab Runner;每次提交的时候,GitLab 将自动识别到 .gitlab-ci.yml 文件,并且使用 Gitlab Runner 执行该脚本。

2. 实现

gitlab-ci.yml

技术文档参考The gitlab-ci.yml file | GitLab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
stages:
- test
- build

test_job:
stage: test
image:
    # 镜像名
name: docker.nju.edu.cn/maven:3.8.7-eclipse-temurin-8-alpine
allow_failure: true # 测试是否允许失败,一般还是默认false
script: mvn test

build:
stage: build
image:
name: gcr.io/kaniko-project/executor:v1.9.0-debug
entrypoint: [""]
script:
- /kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "${CI_PROJECT_DIR}/Dockerfile"
--destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}"
rules:
- if: $CI_COMMIT_TAG

build_job:
stage: build
image:
name: # 镜像名
script:
- kaniko

上面的build任务为gitlab文档中的kaniko使用方法,而build_job中的写法则是我们希望达成的。

自定义kaniko

自己打镜像,即使用Dockerfile COPY一些可执行文件到基础kaniko镜像。

这里,自己写一个kaniko脚本,封装kaniko的几个必选参数,可以通过环境变量获取一些仓库或者流水线的信息,如:${PIPELINE_ID}。为了执行它,也拷贝了一个bash(直接从ubuntu拷贝的二进制文件),注意如果文件是通过window上传的,会丢失可执行权限,需要在Dockerfile里面写明chmod等。

Dockerfile

kaniko打镜像同样依据Dockerfile,这里给出一个简单的maven项目例子

1
2
3
4
5
6
7
8
9
10
FROM maven:main AS MAVEN_BUILD
COPY . /build
WORKDIR /build
RUN mvn package -Dmaven.test.skip=true

FROM openjdk:8-jdk-alpine
COPY --from=MAVEN_BUILD /build/target/*.jar /app/app.jar
WORKDIR /app
CMD ["java", "-jar", "app.jar", "-Duser.timezone=Asia/Shanghai"]
EXPOSE 8081

这里不多的值得说的就是设置时区。博客和GPT会看到一种写法:RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/time

这是从宿主机拷贝时间,但可能会不存在这个文件,因此最简单的方法是用java加参数。

自动maven deploy私人仓库

1
2
3
4
5
6
7
8
9
10
stages:
- deploy

deploy:
stage: deploy
image: maven:main
script:
- mkdir -p /root/.m2
- echo -ne '<?xml version="1.0" encoding="UTF-8"?>\n<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"\n xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">\n <servers>\n <server>\n <id>lx-nju</id>\n <username>maven</username>\n <password>maven</password>\n </server>\n </servers>\n <mirrors>\n <mirror>\n <id>alimaven</id>\n <name>aliyun maven</name>\n <url>http://maven.aliyun.com/nexus/content/groups/public/</url>\n <mirrorOf>central</mirrorOf>\n </mirror>\n\n <mirror>\n <id>CN</id>\n <name>OSChina Central</name>\n <url>http://maven.oschina.net/content/groups/public/</url>\n <mirrorOf>central</mirrorOf>\n </mirror>\n\n <mirror>\n <id>nexus</id>\n <name>internal nexus repository</name>\n <url>http://repo.maven.apache.org/maven2</url>\n <mirrorOf>central</mirrorOf>\n </mirror>\n\n </mirrors>\n</settings>\n' > /root/.m2/settings.xml
- mvn deploy

Springboot项目pom配置

需要注意配置mainClass,否则会报错no main manifest attribute,in app.jar。解决这个问题无疑需要添加插件。一开始我使用maven-jar-plugin插件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<plugin>
<!-- Build an executable JAR -->
<groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<addCasspath>true</addclasspath>
<classpathPrefix>ib/</classpathPrefix>
<mainclass>org.lexiang.order.OrderApplication</mainclass>
</manifest>
</archive>
</configuration>
</plugin>

但在springboot项目中使用这个插件,报了NoclassDefFoundError的错,经过研究,使用spring-boot-maven-plugin可以解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<mainClass>org.lexiang.order.OrderApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>