在容器中运行应用程序已成为企业部门公认的做法,因为带有Kubernetes (K8)的Docker现在提供了可扩展,可管理的应用程序平台。 基于容器的方法也适用于过去几年中获得了巨大发展的微服务架构 。
容器应用程序平台最重要的优势之一是能够动态启动具有资源限制的隔离容器。 让我们检查一下这如何改变我们运行连续集成/连续开发(CI / CD)任务的方式。
一旦我们可以访问容器平台(现场或在云中),就可以将资源密集型CI / CD任务执行转移到动态创建的容器中。 在这种情况下,可以为每个作业执行独立启动和配置构建环境。 构建期间的测试可以自由支配使用此隔离框中的可用资源,而我们也可以在仅针对此作业的生命周期存在的侧面容器中启动第三方应用程序。
听起来不错……让我们看看它在现实生活中是如何工作的。
注意:本文基于针对在Red Hat OpenShift v3.7集群上运行的项目的实际解决方案。 OpenShift是Kubernetes的企业级版本,因此这些做法也适用于K8s集群。 要尝试,下载红帽CDK和运行 jenkins-ephemeral
要么 jenkins-persistent
在OpenShift上创建预配置Jenkins母版的模板 。
在OpenShift上的容器中执行CI / CD任务(构建,测试等)的解决方案基于Jenkins分布式构建 ,这意味着:
从技术角度来看,运行作业的动态容器是Jenkins代理节点。 当构建启动时,首先启动一个新节点,并通过JNLP(端口5000)向Jenkins主服务器“报告职责”。 该构建将排队等待,直到代理节点出现并开始构建为止。 就像常规的Jenkins代理服务器一样,构建输出被发送回主服务器,但是一旦构建完成,代理容器就会关闭。
不同种类的构建(例如Java,NodeJS,Python等)需要不同的代理节点。 这并不是什么新鲜事物-以前可以使用标签来限制哪些代理节点应运行构建。 要为每个作业启动的这些Jenkins代理容器定义配置,我们将需要设置以下内容:
这里的核心组件是Jenkins Kubernetes插件 。 该插件与K8s群集进行交互(通过使用ServiceAccount)并启动/停止代理节点。 可以在插件的配置下将多种代理类型定义为Kubernetes Pod模板 (在项目中通过标签引用它们)。
这些代理程序映像是开箱即用的(也在CentOS7上 ):
注意:此解决方案与OpenShift的Source-to-Image(S2I)构建无关 ,该构建也可用于某些CI / CD任务。
有很多关于Jenkins构建在OpenShift上的好博客和文档。 最好从以下开始:
看看他们以了解整体解决方案。 在本文中,我们将研究应用这些实践时出现的不同问题。
对于我们的示例 ,我们假设一个Java项目具有以下构建步骤:
在CI / CD流程中,我们需要与Git和Nexus进行交互,因此Jenkins职位必须能够访问这些系统。 这需要可以在不同位置进行管理的配置和存储凭据:
您使用哪种方法是一个品味问题,您的最终解决方案可能是多种选择。 下面我们将看第二个选项,主要在OpenShift中管理配置。 通过设置环境变量和挂载文件,通过Kubernetes插件配置来自定义Maven代理容器。
注意:由于存在bug,通过UI添加环境变量不适用于Kubernetes插件v1.0。 更新插件或(作为解决方法) 直接 编辑 config.xml
并重新启动Jenkins。
拉公共Git是微不足道的。 对于私有Git存储库,需要身份验证,并且客户端还需要信任服务器以建立安全连接。 Git拉取通常可以通过两种协议完成:
git clone https://git.mycompany.com:443/myapplication.git
known_hosts
文件中找到其公钥的指纹时,该服务器便会受到信任。
git clone ssh://[email protected]:22/myapplication.git
手动完成后,可以通过带有用户名/密码的HTTP下载源; 对于自动构建,SSH更好。
对于SSH下载,我们需要确保代理容器和Git的SSH端口之间的SSH连接有效。 首先,我们需要一个公钥-私钥对。 要生成一个,运行:
ssh keygen -t rsa -b 2048 -f my-git-ssh -N ''
它在my-git-ssh
(空密码)中生成一个私钥,在my-git-ssh.pub
中my-git-ssh.pub
匹配的公钥。 将公用密钥添加到Git服务器上的用户(最好是ServiceAccount); Web UI通常支持上传。 要使SSH连接正常工作,我们需要在代理容器上有两个文件:
~/.ssh/id_rsa
~/.ssh/known_hosts
。 为此,请尝试ssh git.mycompany.com
并接受指纹。 这将在known_hosts
文件中创建新行。 用那个 在OpenShift机密(或配置映射)中,将私钥存储为id_rsa
,将服务器的公共密钥存储为known_hosts
。
apiVersion: v1
kind: Secret
metadata:
name: mygit-ssh
stringData:
id_rsa: |-
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
known_hosts: |-
git.mycompany.com ecdsa-sha2-nistp256 AAA...
然后在安装点/home/jenkins/.ssh/
的Kubernetes插件中为Maven pod配置一个卷。 机密中的每个项目都是与安装目录下的密钥名称匹配的文件。 我们可以使用UI( Manage Jenkins / Configure / Cloud / Kubernetes
),或编辑Jenkins config /var/lib/jenkins/config.xml
:
maven
...
/home/jenkins/.ssh
mygit-ssh
通过SSH提取Git源现在应该可以在此代理上运行的作业中工作。
注意:它也可以自定义的 SSH连接 ~/.ssh/config
, 例如,如果我们不想打扰known_hosts
或私有密钥安装到不同的位置:
Host git.mycompany.com
StrictHostKeyChecking no
IdentityFile /home/jenkins/.config/git-secret/ssh-privatekey
如果您希望通过HTTP下载,请将用户名/密码添加到以下位置的Git凭证存储文件中:
/home/jenkins/.config/git-secret/credentials
,每行一个站点:
/home/jenkins/.config/git/config
预期的git-config中启用它:
[credential]
helper = store --file=/home/jenkins/.config/git-secret/credentials
如果Git服务具有由自定义证书颁发机构(CA)签名的证书,则最快的破解方法是为代理设置GIT_SSL_NO_VERIFY=true
环境变量(EnvVar)。 正确的解决方案需要两件事:
/usr/ca/myTrustedCA.pem
),将自定义CA的公共证书添加到代理容器。 GIT_SSL_CAINFO=/usr/ca/myTrustedCA.pem
或上述git-config
文件中,告诉Git该证书的路径:
[http "https://git.mycompany.com"]
sslCAInfo = /usr/ca/myTrustedCA.pem
注意:在OpenShift v3.7(及更早版本)中,配置映射和秘密安装点不得重叠 ,因此我们无法映射 到 /home/jenkins
和 /home/jenkins/dir
在 同一时间。 这就是为什么我们没有使用上面众所周知的文件位置的原因。 OpenShift v3.9中应有修复程序。
要使Maven构建工作正常进行,通常需要做两件事:
如果构建在容器中运行,则具有内部Maven存储库实际上是必不可少的,因为它们以空的本地存储库(缓存)开头,因此Maven每次都会下载所有JAR。 从本地网络上的内部代理存储库下载显然比从Internet下载更快。
Maven Jenkins代理映像支持一个环境变量,可用于设置此代理的URL。 在Kubernetes插件容器模板中设置以下内容:
MAVEN_MIRROR_URL=https://nexus.mycompany.com/repository/maven-public
构建工件(JAR)也应存储在一个存储库中,该存储库可能与上述依赖关系的镜像相同或不同。 Maven deploy
需要在分发管理下的pom.xml
使用存储库URL(与代理映像无关):
mynexus
https://nexus.mycompany.com/repository/maven-snapshots/
mynexus
https://nexus.mycompany.com/repository/maven-releases/
上载工件可能需要身份验证。 在这种情况下,必须在settings.xml
与pom.xml
的服务器ID匹配的服务器ID下设置用户名/密码。 我们需要通过OpenShift秘密在Maven Jenkins代理容器上安装带有URL,用户名和密码的整个settings.xml
。 我们还可以使用以下环境变量:
MAVEN_SERVER_USERNAME=admin
MAVEN_SERVER_PASSWORD=admin123
settings.xml
到/home/jenkins/.m2/settings.xml
:
external:*
${env.MAVEN_MIRROR_URL}
mirror
mynexus
${env.MAVEN_SERVER_USERNAME}
${env.MAVEN_SERVER_PASSWORD}
通过对Maven命令使用-B
或向settings.xml
添加
来禁用交互模式(使用批处理模式)以跳过下载日志。
如果Maven仓库的HTTPS端点使用一个自定义的CA签名的证书,我们需要创建一个使用Java密钥存储密钥工具包含CA证书作为信任。 此KeyStore应该在OpenShift中作为配置映射上传。 使用oc
命令从文件创建配置映射:
oc create configmap maven-settings --from-file=settings.xml=settings.xml --from-
file=myTruststore.jks=myTruststore.jks
将配置映射挂载在Jenkins代理上的某个位置。 在此示例中,我们使用/home/jenkins/.m2
,但这仅是因为在同一配置映射中具有settings.xml
。 KeyStore可以位于任何路径下。
然后通过在容器的MAVEN_OPTS
环境变量中设置Java参数,使Maven Java进程将此文件用作信任存储:
MAVEN_OPTS=
-Djavax.net.ssl.trustStore=/home/jenkins/.m2/myTruststore.jks
-Djavax.net.ssl.trustStorePassword=changeit
这可能是最重要的部分-如果我们没有正确设置最大内存,在一切似乎正常之后,我们将遇到间歇性的构建失败。
如果我们未在Java命令行中设置堆,则在容器中运行Java会导致高内存使用错误。 JVM 将查看主机的总内存,而不是容器的内存限制,并相应地设置默认的最大堆 。 通常,这远远超过了容器的内存限制,并且当Java进程为堆分配更多的内存时,OpenShift只是杀死了该容器。
尽管基于jenkins
-slave-base
映像具有内置脚本来将最大堆设置为容器内存的一半(可以通过EnvVar CONTAINER_HEAP_PERCENT=0.50
进行修改),但它仅适用于Jenkins代理Java进程。 在Maven构建中,我们还有重要的其他Java进程正在运行:
mvn
命令本身是Java工具。 最终,我们将在容器中同时运行三个Java进程,估计它们的内存使用量以避免意外终止Pod很重要。 每个进程都有不同的方式来设置JVM选项:
JAVA_OPTS
适用于Jenkins代理。 mvn
工具。 设置MAVEN_OPTS
以自定义此Java进程。 surefire
插件单元测试可以通过定制argLine Maven的财产。 可以在pom.xml
, settings.xml
中的配置文件中进行settings.xml
也可以通过在MAVEN_OPTS
-DargLine=… to mvn
命令中添加-DargLine=… to mvn
进行MAVEN_OPTS
。 这是有关如何为Maven代理容器设置这些环境变量的示例:
JAVA_OPTS=-Xms64m -Xmx64m
MAVEN_OPTS=-Xms128m -Xmx128m -DargLine=${env.SUREFIRE_OPTS}
SUREFIRE_OPTS=-Xms256m -Xmx256m
这些数字在我们的测试中起作用,使用1024Mi代理容器内存限制构建和运行SpringBoot应用程序的单元测试。 这些数字相对较低,堆大小较大。 对于复杂的Maven项目和单元测试,可能需要更高的限制。
注意:Java8进程的实际内存使用情况 类似于 HeapSize + MetaSpace + OffHeapMemory
, 并且可能大大超过设置的最大堆大小。 通过上述设置,在我们的案例中,三个Java进程占用了900Mi以上的内存。 有关容器内的进程,请参见RSS内存: ps -e -o
pid
,user
,
rss
,comm
,args
Jenkins代理映像同时安装了64位和32位JDK。 对于mvn
和surefire
,默认情况下使用64位JVM。 为了降低内存使用量,只要-Xmx
小于1.5 GB,则强制使用32位JVM是有意义的:
JAVA_HOME=/usr/lib/jvm/Java-1.8.0-openjdk-1.8.0.161–0.b14.el7_4.i386
请注意,还可以在JAVA_TOOL_OPTIONS
EnvVar中设置Java参数,任何启动的JVM都会使用它。 在参数JAVA_OPTS
和MAVEN_OPTS
覆盖那些在JAVA_TOOL_OPTIONS
,这样我们就可以达到同样的堆配置我们的Java程序如上不使用argLine
:
JAVA_OPTS=-Xms64m -Xmx64m
MAVEN_OPTS=-Xms128m -Xmx128m
JAVA_TOOL_OPTIONS=-Xms256m -Xmx256m
仍然有些混乱,因为所有JVM日志都记录Picked up JAVA_TOOL_OPTIONS:
按照上述设置,我们应该为成功运行构建做好一切准备。 我们可以提取代码,下载依赖项,运行单元测试,并将工件上传到我们的存储库。 让我们创建一个执行此操作的Jenkins Pipeline项目:
pipeline {
/* Which container to bring up for the build. Pick one of the templates configured in Kubernetes plugin. */
agent {
label 'maven'
}
stages {
stage('Pull Source') {
steps {
git url: 'ssh://[email protected]:22/myapplication.git', branch: 'master'
}
}
stage('Unit Tests') {
steps {
sh 'mvn test'
}
}
stage('Deploy to Nexus') {
steps {
sh 'mvn deploy -DskipTests'
}
}
}
}
当然,对于一个真实的项目,CI / CD管道不仅可以完成Maven构建,还可以完成更多工作。 它可以部署到开发环境,运行集成测试,升级到更高的环境等。上面链接的学习文章显示了如何执行这些操作的示例。
一个pod可以运行多个容器,每个容器都有自己的资源限制。 它们共享相同的网络接口,因此我们可以在localhost
上访问已启动的服务,但是我们需要考虑端口冲突。 环境变量是单独设置的,但是在一个Kubernetes容器模板中配置的所有容器的安装卷是相同的。
当单元测试需要外部服务并且嵌入式解决方案不起作用时(例如,数据库,消息代理等),打开多个容器很有用。 在这种情况下,该第二个容器也将通过Jenkins代理启动和停止。
请参阅Jenkins config.xml
代码段,在该代码段中,我们在一侧为Maven构建启动了httpbin
服务:
maven
...
jnlp
registry.access.redhat.com/openshift3/jenkins-slave-maven-rhel7:v3.7
500m
1024Mi
...
...
httpbin
citizenstig/httpbin
256Mi
...
有关摘要,请参阅使用上述配置从Jenkins config.xml
创建的OpenShift资源和Kubernetes插件配置。
apiVersion: v1
kind: List
metadata: {}
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: git-config
data:
config: |
[credential]
helper = store --file=/home/jenkins/.config/git-secret/credentials
[http "http://git.mycompany.com"]
sslCAInfo = /home/jenkins/.config/git/myTrustedCA.pem
myTrustedCA.pem: |-
-----BEGIN CERTIFICATE-----
MIIDVzCCAj+gAwIBAgIJAN0sC...
-----END CERTIFICATE-----
- apiVersion: v1
kind: Secret
metadata:
name: git-secret
stringData:
ssh-privatekey: |-
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
credentials: |-
https://username:[email protected]
https://user:[email protected]
- apiVersion: v1
kind: ConfigMap
metadata:
name: git-ssh
data:
config: |-
Host git.mycompany.com
StrictHostKeyChecking yes
IdentityFile /home/jenkins/.config/git-secret/ssh-privatekey
known_hosts: '[git.mycompany.com]:22 ecdsa-sha2-nistp256 AAAdn7...'
- apiVersion: v1
kind: Secret
metadata:
name: maven-secret
stringData:
username: admin
password: admin123
从文件创建了另一个配置映射:
oc create configmap maven-settings --from-file=settings.xml=settings.xml
--from-file=myTruststore.jks=myTruststore.jks
Kubernetes插件配置:
...
openshift
maven
false
false
2147483647
100
0
jenkins37
NORMAL
false
false
/home/jenkins/.config/git-secret
git-secret
/home/jenkins/.ssh
git-ssh
/home/jenkins/.config/git
git-config
/home/jenkins/.m2
maven-settings
jnlp
registry.access.redhat.com/openshift3/jenkins-slave-maven-rhel7:v3.7
false
false
/tmp
${computer.jnlpmac} ${computer.name}
false
500m
1024Mi
500m
1024Mi
JAVA_HOME
/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.i386
JAVA_OPTS
-Xms64m -Xmx64m
MAVEN_OPTS
-Xms128m -Xmx128m -DargLine=${env.SUREFIRE_OPTS} -Djavax.net.ssl.trustStore=/home/jenkins/.m2/myTruststore.jks -Djavax.net.ssl.trustStorePassword=changeit
SUREFIRE_OPTS
-Xms256m -Xmx256m
MAVEN_MIRROR_URL
https://nexus.mycompany.com/repository/maven-public
MAVEN_SERVER_USERNAME
maven-secret
username
MAVEN_SERVER_PASSWORD
maven-secret
password
0
0
0
0
0
httpbin
citizenstig/httpbin
false
false
/run.sh
false
256Mi
256Mi
0
0
0
0
0
https://172.30.0.1:443
-----BEGIN CERTIFICATE-----
MIIC6jCC...
-----END CERTIFICATE-----
false
first
http://jenkins.cicd.svc:80
jenkins-jnlp.cicd.svc:50000
1a12dfa4-7fc5-47a7-aa17-cc56572a41c7
10
5
0
0
32
快乐的构建!
该文件最初在ITNext上发布,并经许可被转载。
翻译自: https://opensource.com/article/18/4/running-jenkins-builds-containers