写在最前
jenkins做的最多的就是用来编译前后端项目,这一篇章我们就来完成后端的maven构建器,不过在开始之前我建议先从前置要求2做起,里面有非常详细的连接步骤,不然本篇章构建完了可能你还不知道怎么去连接它。
1. 前置要求
2. 构建流程
2.1 构建镜像
因为jenkins/inbound-agent:3107.v665000b_51092-15没有jdk21,所以需要它作为基础环境来安装jdk21和maven3.9.9
docker build -t tanqidi/jnlp-slave-maven3.9.9-jdk21:latest .
# jenkins/inbound-agent:3107.v665000b_51092-15 作为基础镜像
FROM jenkins/inbound-agent:3107.v665000b_51092-15
# 切换到 root 用户
USER root
# 安装必要的工具和依赖
RUN apt-get update && \
apt-get install -y wget && \
apt-get clean
# 下载并安装 Eclipse Temurin JDK 21
RUN wget https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21%2B35/OpenJDK21U-jdk_x64_linux_hotspot_21_35.tar.gz -O /tmp/temurin-jdk21.tar.gz && \
tar -xzf /tmp/temurin-jdk21.tar.gz -C /opt && \
rm /tmp/temurin-jdk21.tar.gz
# 设置 JDK 21 环境变量
ENV JAVA_HOME /opt/jdk-21+35
ENV PATH $JAVA_HOME/bin:$PATH
# 安装 Maven 3.9.9
ENV MAVEN_VERSION=3.9.9
ENV MAVEN_MAJOR_VERSION=3
ENV MAVEN_HOME=/opt/maven-$MAVEN_VERSION
ENV PATH="$MAVEN_HOME/bin:$PATH"
RUN wget https://archive.apache.org/dist/maven/maven-${MAVEN_MAJOR_VERSION}/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz && \
mkdir -p /opt/maven-$MAVEN_VERSION && \
tar -xzf apache-maven-$MAVEN_VERSION-bin.tar.gz -C /opt/maven-$MAVEN_VERSION --strip-components=1 && \
rm apache-maven-$MAVEN_VERSION-bin.tar.gz
# 查看解压后的 Maven 目录内容
RUN ls -la $MAVEN_HOME/bin
# 设置工作目录
WORKDIR /home/jenkins/agent
# 切换回 jenkins 用户
USER jenkins
# 测试 JDK 和 Maven 是否有效
RUN java -version && \
mvn -v
2.2 启动连接
将maven的settings.xml外挂出来方便后续编辑
mkdir -p /app/maven-3.9.9/conf
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<!--
| This is the configuration file for Maven. It can be specified at two levels:
|
| 1. User Level. This settings.xml file provides configuration for a single user,
| and is normally provided in ${user.home}/.m2/settings.xml.
|
| NOTE: This location can be overridden with the CLI option:
|
| -s /path/to/user/settings.xml
|
| 2. Global Level. This settings.xml file provides configuration for all Maven
| users on a machine (assuming they're all using the same Maven
| installation). It's normally provided in
| ${maven.conf}/settings.xml.
|
| NOTE: This location can be overridden with the CLI option:
|
| -gs /path/to/global/settings.xml
|
| The sections in this sample file are intended to give you a running start at
| getting the most out of your Maven installation. Where appropriate, the default
| values (values used when the setting is not specified) are provided.
|
|-->
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->
<!-- interactiveMode
| This will determine whether maven prompts you when it needs input. If set to false,
| maven will use a sensible default value, perhaps based on some other setting, for
| the parameter in question.
|
| Default: true
<interactiveMode>true</interactiveMode>
-->
<!-- offline
| Determines whether maven should attempt to connect to the network when executing a build.
| This will have an effect on artifact downloads, artifact deployment, and others.
|
| Default: false
<offline>false</offline>
-->
<!-- pluginGroups
| This is a list of additional group identifiers that will be searched when resolving plugins by their prefix, i.e.
| when invoking a command line like "mvn prefix:goal". Maven will automatically add the group identifiers
| "org.apache.maven.plugins" and "org.codehaus.mojo" if these are not already contained in the list.
|-->
<pluginGroups>
<!-- pluginGroup
| Specifies a further group identifier to use for plugin lookup.
<pluginGroup>com.your.plugins</pluginGroup>
-->
</pluginGroups>
<!-- TODO Since when can proxies be selected as depicted? -->
<!-- proxies
| This is a list of proxies which can be used on this machine to connect to the network.
| Unless otherwise specified (by system property or command-line switch), the first proxy
| specification in this list marked as active will be used.
|-->
<proxies>
<!-- proxy
| Specification for one proxy, to be used in connecting to the network.
|
<proxy>
<id>optional</id>
<active>true</active>
<protocol>http</protocol>
<username>proxyuser</username>
<password>proxypass</password>
<host>proxy.host.net</host>
<port>80</port>
<nonProxyHosts>local.net|some.host.com</nonProxyHosts>
</proxy>
-->
</proxies>
<!-- servers
| This is a list of authentication profiles, keyed by the server-id used within the system.
| Authentication profiles can be used whenever maven must make a connection to a remote server.
|-->
<servers>
<!-- server
| Specifies the authentication information to use when connecting to a particular server, identified by
| a unique name within the system (referred to by the 'id' attribute below).
|
| NOTE: You should either specify username/password OR privateKey/passphrase, since these pairings are
| used together.
|
<server>
<id>deploymentRepo</id>
<username>repouser</username>
<password>repopwd</password>
</server>
-->
<!-- Another sample, using keys to authenticate.
<server>
<id>siteServer</id>
<privateKey>/path/to/private/key</privateKey>
<passphrase>optional; leave empty if not used.</passphrase>
</server>
-->
</servers>
<!-- mirrors
| This is a list of mirrors to be used in downloading artifacts from remote repositories.
|
| It works like this: a POM may declare a repository to use in resolving certain artifacts.
| However, this repository may have problems with heavy traffic at times, so people have mirrored
| it to several places.
|
| That repository definition will have a unique id, so we can create a mirror reference for that
| repository, to be used as an alternate download site. The mirror site will be the preferred
| server for that repository.
|-->
<mirrors>
<!-- mirror
| Specifies a repository mirror site to use instead of a given repository. The repository that
| this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
| for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
|
<mirror>
<id>mirrorId</id>
<mirrorOf>repositoryId</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
-->
<mirror>
<id>maven-default-http-blocker</id>
<mirrorOf>external:http:*</mirrorOf>
<name>Pseudo repository to mirror external repositories initially using HTTP.</name>
<url>http://0.0.0.0/</url>
<blocked>true</blocked>
</mirror>
</mirrors>
<!-- profiles
| This is a list of profiles which can be activated in a variety of ways, and which can modify
| the build process. Profiles provided in the settings.xml are intended to provide local machine-
| specific paths and repository locations which allow the build to work in the local environment.
|
| For example, if you have an integration testing plugin - like cactus - that needs to know where
| your Tomcat instance is installed, you can provide a variable here such that the variable is
| dereferenced during the build process to configure the cactus plugin.
|
| As noted above, profiles can be activated in a variety of ways. One way - the activeProfiles
| section of this document (settings.xml) - will be discussed later. Another way essentially
| relies on the detection of a property, either matching a particular value for the property,
| or merely testing its existence. Profiles can also be activated by JDK version prefix, where a
| value of '1.4' might activate a profile when the build is executed on a JDK version of '1.4.2_07'.
| Finally, the list of active profiles can be specified directly from the command line.
|
| NOTE: For profiles defined in the settings.xml, you are restricted to specifying only artifact
| repositories, plugin repositories, and free-form properties to be used as configuration
| variables for plugins in the POM.
|
|-->
<profiles>
<!-- profile
| Specifies a set of introductions to the build process, to be activated using one or more of the
| mechanisms described above. For inheritance purposes, and to activate profiles via <activatedProfiles/>
| or the command line, profiles have to have an ID that is unique.
|
| An encouraged best practice for profile identification is to use a consistent naming convention
| for profiles, such as 'env-dev', 'env-test', 'env-production', 'user-jdcasey', 'user-brett', etc.
| This will make it more intuitive to understand what the set of introduced profiles is attempting
| to accomplish, particularly when you only have a list of profile id's for debug.
|
| This profile example uses the JDK version to trigger activation, and provides a JDK-specific repo.
<profile>
<id>jdk-1.4</id>
<activation>
<jdk>1.4</jdk>
</activation>
<repositories>
<repository>
<id>jdk14</id>
<name>Repository for JDK 1.4 builds</name>
<url>http://www.myhost.com/maven/jdk14</url>
<layout>default</layout>
<snapshotPolicy>always</snapshotPolicy>
</repository>
</repositories>
</profile>
-->
<!--
| Here is another profile, activated by the property 'target-env' with a value of 'dev', which
| provides a specific path to the Tomcat instance. To use this, your plugin configuration might
| hypothetically look like:
|
| ...
| <plugin>
| <groupId>org.myco.myplugins</groupId>
| <artifactId>myplugin</artifactId>
|
| <configuration>
| <tomcatLocation>${tomcatPath}</tomcatLocation>
| </configuration>
| </plugin>
| ...
|
| NOTE: If you just wanted to inject this configuration whenever someone set 'target-env' to
| anything, you could just leave off the <value/> inside the activation-property.
|
<profile>
<id>env-dev</id>
<activation>
<property>
<name>target-env</name>
<value>dev</value>
</property>
</activation>
<properties>
<tomcatPath>/path/to/tomcat/instance</tomcatPath>
</properties>
</profile>
-->
</profiles>
<!-- activeProfiles
| List of profiles that are active for all builds.
|
<activeProfiles>
<activeProfile>alwaysActiveProfile</activeProfile>
<activeProfile>anotherAlwaysActiveProfile</activeProfile>
</activeProfiles>
-->
</settings>
注意!我的 dnsmasq 解析的是 jenkins.dev.tanqidi.com 你不能直接粘贴就拿来用,需要改成你的ip或域名。
# 创建数据目录
mkdir -p /data/maven/repository && chown -R 1000 /data/maven/repository
# 运行容器, 注意我将 settings.xml 挂载出来了
docker run --name build-maven3.9.9-jdk21 -d \
--init \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /etc/sysconfig/docker:/etc/sysconfig/docker \
-v /usr/bin/docker:/usr/bin/docker \
-v /usr/bin/docker-current:/usr/bin/docker-current \
-v /data/maven/repository:/home/jenkins/.m2/repository \
-v /app/maven-3.9.9/conf/settings.xml:/opt/maven-3.9.9/conf/settings.xml \
tanqidi/jnlp-slave-maven3.9.9-jdk21:latest \
-url http://jenkins.dev.tanqidi.com \
你的秘钥 \
build-maven3.9.9-jdk21
2.3 试验结果
java -version && mvn -v
mvn clean package -Dmaven.test.failure.ignore=true -DskipTests=true -U
cd ./ruoyi-gateway
ls -la
ls -la target
3. 异常解决
slave操作docker时出现权限不足异常
3.1 permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
临时
这是因为挂载进去的 /var/run/docker.sock 的权限,和容器内的执行者权限不一样所以出现了无法调用docker.sock的问题,最快速的就是在宿主机上对docker.sock做权限变更,但这只是临时的,下次重启docker这个sock文件会被重建权限就又丢失了。
chmod 666 /var/run/docker.sock
永久
本质上,就是要确保确保Docker运行的组和Jenkins slave运行的组一致。具体步骤如下:
我在
docker-install.sh
中优化了一下,先创建一个GID为999
的docker
组,这样在生成docker.service
文件时,docker.sock
会自动拥有该组的权限。在Jenkins slave的Dockerfile中,同样创建一个名为
docker
、GID为999
的组。将Jenkins用户(即容器中的运行用户)添加到这个
docker
组中。
通过这种方式,Jenkins slave在调用docker.sock
时,其GID为999
,与宿主机一致,从而获得必要的权限。这样可以确保容器与主机之间的Docker访问权限协调一致。
# 创建docker组并指定GID为999或者1000,具体需要先查看宿主机的 cat /etc/group | grep docker 具体的ID数字是什么
RUN groupadd -g 999 docker && \
usermod -aG docker jenkins
3.2 tcpSlaveAgentListener/ is invalid: 404 null
方式一
默认docker启动slave的时候不要尝试添加端口,他会使用jenkins的50000端口作为JNLP连接,非必要不要修改务必将其指定为50000。
Feb 24, 2025 8:13:38 AM hudson.remoting.jnlp.Main createEngine
INFO: Setting up agent: build-maven3.9.9-jdk21
Feb 24, 2025 8:13:38 AM hudson.remoting.Engine startEngine
INFO: Using Remoting version: 3107.v665000b_51092
Feb 24, 2025 8:13:38 AM hudson.remoting.Engine startEngine
WARNING: No Working Directory. Using the legacy JAR Cache location: /home/jenkins/.jenkins/cache/jars
Feb 24, 2025 8:13:38 AM hudson.remoting.jnlp.Main$CuiListener status
INFO: Locating server among [http://devops-jenkins.bx.crpharm.com/]
Feb 24, 2025 8:13:38 AM hudson.remoting.jnlp.Main$CuiListener error
SEVERE: Failed to connect to http://jenkins.dev.tanqidi.com/tcpSlaveAgentListener/: Connection refused
java.io.IOException: Failed to connect to http://jenkins.dev.tanqidi.com/tcpSlaveAgentListener/: Connection refused
at org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver.resolve(JnlpAgentEndpointResolver.java:216)
at hudson.remoting.Engine.innerRun(Engine.java:755)
at hudson.remoting.Engine.run(Engine.java:543)
Caused by: java.net.ConnectException: Connection refused
at java.base/sun.nio.ch.Net.pollConnect(Native Method)
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:682)
at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:549)
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:592)
at java.base/java.net.Socket.connect(Socket.java:751)
at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:531)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:636)
at java.base/sun.net.www.http.HttpClient.<init>(HttpClient.java:280)
at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:386)
at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:408)
at java.base/sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1304)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1237)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1123)
at java.base/sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:1052)
at org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver.resolve(JnlpAgentEndpointResolver.java:213)
... 2 more
方式二
如果还不行,可以直接用ip的方式连接 -url http://172.31.0.100:8080
docker run --name build-maven3.9.9-jdk21 -d \
--init \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /etc/sysconfig/docker:/etc/sysconfig/docker \
-v /usr/bin/docker:/usr/bin/docker \
-v /usr/bin/docker-current:/usr/bin/docker-current \
-v /data/maven/repository:/home/jenkins/.m2/repository \
tanqidi/jnlp-slave-maven3.9.9-jdk21:latest \
-url http://172.31.0.100:8080 \
你的秘钥 \
build-maven3.9.9-jdk21
[root@HYDOKVSRA15 appuser]# docker logs -f build-maven3.9.9-jdk21
Feb 24, 2025 9:07:53 AM hudson.remoting.jnlp.Main createEngine
INFO: Setting up agent: build-maven3.9.9-jdk21
Feb 24, 2025 9:07:53 AM hudson.remoting.Engine startEngine
INFO: Using Remoting version: 3107.v665000b_51092
Feb 24, 2025 9:07:53 AM hudson.remoting.Engine startEngine
WARNING: No Working Directory. Using the legacy JAR Cache location: /home/jenkins/.jenkins/cache/jars
Feb 24, 2025 9:07:54 AM hudson.remoting.jnlp.Main$CuiListener status
INFO: Locating server among [http://172.31.0.100:8080/jenkins/]
Feb 24, 2025 9:07:54 AM org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver resolve
INFO: Remoting server accepts the following protocols: [JNLP4-connect, Ping, JNLP3-connect]
Feb 24, 2025 9:07:54 AM hudson.remoting.jnlp.Main$CuiListener status
INFO: Agent discovery successful
Agent address: 10.153.114.200
Agent port: 50000
Identity: 80:64:8e:d0:92:fb:b6:53:3c:1c:81:63:a2:95:b4:cc
Feb 24, 2025 9:07:54 AM hudson.remoting.jnlp.Main$CuiListener status
INFO: Handshaking
Feb 24, 2025 9:07:54 AM hudson.remoting.jnlp.Main$CuiListener status
INFO: Connecting to 172.31.0.100:50000
Feb 24, 2025 9:07:54 AM hudson.remoting.jnlp.Main$CuiListener status
INFO: Trying protocol: JNLP4-connect
Feb 24, 2025 9:07:54 AM org.jenkinsci.remoting.protocol.impl.BIONetworkLayer$Reader run
INFO: Waiting for ProtocolStack to start.
Feb 24, 2025 9:07:54 AM hudson.remoting.jnlp.Main$CuiListener status
INFO: Remote identity confirmed: 80:64:8e:d0:92:fb:b6:53:3c:1c:81:63:a2:95:b4:cc
Feb 24, 2025 9:07:54 AM hudson.remoting.jnlp.Main$CuiListener status
INFO: Connected
4. 版本区别
jenkins/inbound-agent:3107.v665000b_51092-15
和 jenkins/inbound-agent:3107.v665000b_51092-15-jdk17
是 Jenkins 官方提供的两种不同的 Agent 镜像,它们的主要区别在于是否预装了 JDK(Java Development Kit)。以下是它们的详细区别:
4.1 jenkins/inbound-agent:3107.v665000b_51092-15
特点:
这是一个基础镜像,不包含任何 JDK。
只包含 Jenkins Agent 运行所需的最小化环境。
适用场景:
如果你需要完全自定义 JDK 版本(例如安装 JDK 21 或其他版本),可以使用这个镜像。
适合需要灵活控制 Java 环境的场景。
优点:
镜像体积较小,因为没有预装 JDK。
可以根据需要安装任意版本的 JDK。
缺点:
需要手动安装和配置 JDK。
4.2 jenkins/inbound-agent:3107.v665000b_51092-15-jdk17
特点:
这是一个预装 JDK 17 的镜像。
除了 Jenkins Agent 的基础环境外,还包含了 JDK 17 的安装。
适用场景:
如果你的项目需要 JDK 17,可以直接使用这个镜像,无需额外安装 JDK。
适合不需要频繁切换 JDK 版本的场景。
优点:
开箱即用,无需手动安装 JDK。
适合需要快速启动 Jenkins Agent 的场景。
缺点:
镜像体积较大,因为包含了 JDK 17。
如果需要其他版本的 JDK(如 JDK 21),需要手动安装并覆盖或替换 JDK 17。
4.3 主要区别总结
4.4. 如何选择?
如果你需要 JDK 21 或其他版本的 JDK:
使用
jenkins/inbound-agent:3107.v665000b_51092-15
,然后手动安装 JDK 21。这样可以完全控制 JDK 版本,避免与预装的 JDK 17 冲突。
如果你需要 JDK 17:
使用
jenkins/inbound-agent:3107.v665000b_51092-15-jdk17
。这样可以节省时间,无需手动安装 JDK。
如果你需要 同时支持多个 JDK 版本:
使用
jenkins/inbound-agent:3107.v665000b_51092-15
,然后手动安装多个 JDK 版本,并通过环境变量切换。
5. 镜像制品
欢迎直接使用我的构建结果
tanqidi/jnlp-slave-maven3.9.9-jdk21:latest
registry.cn-hangzhou.aliyuncs.com/tanqidi/jnlp-slave-maven3.9.9-jdk21:latest
6. 操作总结
有了前面篇章的jenkins-slave-nodejs的操作经验本篇章就简单很多了,构建镜像创建节点直接连接就好了,但如果依然操作失败欢迎给我留言。