运行Jenkins构建在容器中

在容器中运行应用程序已成为企业部门公认的做法,因为带有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功能/插件照常提供,因此可以使用现有项目
  • Jenkins GUI可用于配置,运行和浏览作业输出
  • 如果您喜欢代码,也可以使用Jenkins Pipeline

从技术角度来看,运行作业的动态容器是Jenkins代理节点。 当构建启动时,首先启动一个新节点,并通过JNLP(端口5000)向Jenkins主服务器“报告职责”。 该构建将排队等待,直到代理节点出现并开始构建为止。 就像常规的Jenkins代理服务器一样,构建输出被发送回主服务器,但是一旦构建完成,代理容器就会关闭。

运行Jenkins构建在容器中_第1张图片

不同种类的构建(例如Java,NodeJS,Python等)需要不同的代理节点。 这并不是什么新鲜事物-以前可以使用标签来限制哪些代理节点应运行构建。 要为每个作业启动的这些Jenkins代理容器定义配置,我们将需要设置以下内容:

  • 要启动的Docker映像
  • 资源限制
  • 环境变量
  • 卷已安装

这里的核心组件是Jenkins Kubernetes插件 。 该插件与K8s群集进行交互(通过使用ServiceAccount)并启动/停止代理节点。 可以在插件的配置下将多种代理类型定义为Kubernetes Pod模板 (在项目中通过标签引用它们)。

这些代理程序映像是开箱即用的(也在CentOS7上 ):

  • jenkins-slave-base-rhel7 :启动连接到Jenkins master的代理的基础映像; Java堆是根据容器内存设置的
  • jenkins-slave-maven-rhel7 :Maven和Gradle构建的图像(扩展基础)
  • jenkins-slave-nodejs-rhel7 :使用NodeJS4工具的图像(扩展了基础)

注意:此解决方案与OpenShift的Source-to-Image(S2I)构建无关 ,该构建也可用于某些CI / CD任务。

背景学习资料

有很多关于Jenkins构建在OpenShift上的好博客和文档。 最好从以下开始:

  • OpenShift Jenkins图像文档和源
  • CI / CD与OpenShift网络广播
  • 外部Jenkins集成手册

看看他们以了解整体解决方案。 在本文中,我们将研究应用这些实践时出现的不同问题。

建立我的应用程式

对于我们的示例 ,我们假设一个Java项目具有以下构建步骤:

  • 来源:从Git存储库中提取项目源
  • 使用Maven进行构建:依赖关系来自内部存储库(让我们使用Apache Nexus),可镜像外部Maven存储库
  • 部署工件:构建的JAR被上传到存储库

在CI / CD流程中,我们需要与Git和Nexus进行交互,因此Jenkins职位必须能够访问这些系统。 这需要可以在不同位置进行管理的配置和存储凭据:

  • 在Jenkins中:我们可以向Jenkins添加Git插件可以使用的凭据,并将文件添加到项目中(使用容器不会改变任何内容)。
  • 在OpenShift中:使用ConfigMap和作为文件或环境变量添加到Jenkins代理容器的秘密对象。
  • 在完全自定义的Docker映像中:这些已预先配置,可以运行某种工作; 只需扩展其中一个代理映像即可。

您使用哪种方法是一个品味问题,您的最终解决方案可能是多种选择。 下面我们将看第二个选项,主要在OpenShift中管理配置。 通过设置环境变量和挂载文件,通过Kubernetes插件配置来自定义Maven代理容器。

注意:由于存在bug,通过UI添加环境变量不适用于Kubernetes插件v1.0。 更新插件或(作为解决方法) 直接 编辑 config.xml 并重新启动Jenkins。

从Git提取源

拉公共Git是微不足道的。 对于私有Git存储库,需要身份验证,并且客户端还需要信任服务器以建立安全连接。 Git拉取通常可以通过两种协议完成:

  • HTTPS:身份验证使用用户名/密码。 作业必须信任服务器的SSL证书,这只有在由自定义CA签名的情况下才是棘手的。

git clone https://git.mycompany.com:443/myapplication.git 
  • SSH:身份验证使用私钥。 当在known_hosts文件中找到其公钥的指纹时,该服务器便会受到信任。

git clone ssh://[email protected]:22/myapplication.git 

手动完成后,可以通过带有用户名/密码的HTTP下载源; 对于自动构建,SSH更好。

使用SSH进行Git

对于SSH下载,我们需要确保代理容器和Git的SSH端口之间的SSH连接有效。 首先,我们需要一个公钥-私钥对。 要生成一个,运行:


ssh keygen -t rsa -b 2048 -f my-git-ssh -N '' 

它在my-git-ssh (空密码)中生成一个私钥,在my-git-ssh.pubmy-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

如果您希望通过HTTP下载,请将用户名/密码添加到以下位置的Git凭证存储文件中:

  • 例如,来自OpenShift机密的/home/jenkins/.config/git-secret/credentials ,每行一个站点:


     
     
     
     
https://username:[email protected]
https://user:[email protected]
  • /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的公共证书添加到代理容器。
  • 在EnvVar 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存储库(例如Apache Nexus)以充当外部存储库的代理。 将此用作镜子。
  • 此内部存储库可能具有HTTPS终结点,该终结点带有由自定义CA签名的证书。

如果构建在容器中运行,则具有内部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.xmlpom.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添加false来禁用交互模式(使用批处理模式)以跳过下载日志。

如果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工具。
  • 默认情况下, Maven Surefire插件在派生的JVM中执行单元测试。

最终,我们将在容器中同时运行三个Java进程,估计它们的内存使用量以避免意外终止Pod很重要。 每个进程都有不同的方式来设置JVM选项:

  • Jenkins代理堆的计算如上所述,但是我们绝对不应该让代理拥有如此大的堆。 其他两个JVM需要内存。 设置JAVA_OPTS适用于Jenkins代理。
  • Jenkins工作调用了mvn工具。 设置MAVEN_OPTS以自定义此Java进程。
  • 在JVM催生由Maven的surefire插件单元测试可以通过定制argLine Maven的财产。 可以在pom.xmlsettings.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。 对于mvnsurefire ,默认情况下使用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_OPTSMAVEN_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

你可能感兴趣的:(运行Jenkins构建在容器中)