kubernetes 测试

Linux容器已经改变了我们运行,构建和管理应用程序的方式。 随着越来越 多的平台成为云原生,容器在每个企业的基础架构中扮演着越来越重要的角色。 Kubernetes (K8s)是目前最著名的用于管理容器的解决方案,无论它们运行在私有,公共或混合云中

使用容器应用程序平台,我们可以动态创建一个完整的环境来运行任务,然后将其丢弃。 在较早的文章中,我们介绍了如何使用Jenkins 在容器中运行构建和单元测试 。 在进一步阅读之前,我建议您看一下该文章,以便您熟悉该解决方案的基本原理。

现在,让我们看一下如何通过启动多个容器以提供整个测试环境来运行集成测试。

假设我们有一个依赖于其他服务的后端应用程序,例如数据库,消息代理或Web服务。 在单元测试期间,我们尝试使用嵌入式解决方案或简单地模拟这些端点,以确保不需要网络连接。 这就要求我们更改测试范围的代码。

集成测试的目的是验证应用程序在解决方案堆栈的其他部分的行为。 提供服务不仅取决于我们的代码库。 总体解决方案是模块的混合(例如,具有存储过程的数据库,消息代理或具有服务器端脚本的分布式缓存),必须将这些模块正确地布线在一起以提供预期的功能。 这只能通过将所有这些部分彼此相邻运行而不在我们的应用程序中启用“测试模式”来进行测试。

在这种情况下,“单元测试”和“集成测试”是否正确是值得商is的。 为简单起见,我将在一个流程中运行且没有任何外部依赖项的测试称为“单元测试”,将在生产模式下运行应用程序的测试称为“集成测试”。

为此类测试维持静态环境可能会很麻烦,而且会浪费资源。 这是动态容器的短暂特性派上用场的地方。

kubernetes-integration-test GitHub存储库 。 它包含一个示例Red Hat Fuse 7应用程序( /app-users ),该/app-usersAMQ接收消息,从MariaDB查询数据,然后调用REST API。 该仓库还包含集成测试项目( /integration-test )和本文中介绍的不同的Jenkinsfiles。

以下是本教程中使用的软件版本:

  • 红帽容器开发套件(CDK)v3.4
  • OpenShift v3.9
  • Kubernetes v1.9
  • 詹金斯图像v3.9
  • Jenkins kubernetes插件v1.7

每次都有新的开始

我们希望通过集成测试实现以下目标:

  • 开始测试我们的应用程序的生产就绪软件包,
  • 启动所需的所有依赖系统的实例,
  • 运行仅通过公共服务端点与应用进行交互的测试,
  • 确保执行之间没有任何持久性,因此我们不必担心恢复初始状态,并且
  • 仅在测试执行期间分配资源。

该解决方案基于Jenkinsjenkins-kubernetes-plugin 。 Jenkins可以在不同的代理节点上运行任务,而该插件可以在Kubernetes上动态创建这些节点。 仅为任务执行创建代理节点,之后将其删除。

我们需要首先定义代理节点容器模板。 OpenShift的jenkins-master映像随附了用于Maven和NodeJS构建的预定义 pod模板,管理员可以将此类“静态” pod模板添加到插件配置中。

幸运的是,如果使用Jenkins管道,则可以在项目中直接为我们的代理节点定义pod模板。 这显然是一种更灵活的方法,因为开发团队可以用代码维护整个执行环境。 让我们来看一个例子:


   
   
podTemplate (
  label : 'app-users-it' ,
  cloud : 'openshift' , //This needs to match the cloud name in jenkins-kubernetes-plugin config
  containers : [
    //Jenkins agent. Also executes the integration test. Having a 'jnlp' container is mandatory.
    containerTemplate ( name : 'jnlp' ,
                      image : 'registry.access.redhat.com/openshift3/jenkins-slave-maven-rhel7:v3.9' ,
                      resourceLimitMemory : '512Mi' ,
                      args : '${computer.jnlpmac} ${computer.name}' ,
                      envVars : [
                        //Heap for mvn and surefire process is 1/4 of resourceLimitMemory by default
                        envVar ( key : 'JNLP_MAX_HEAP_UPPER_BOUND_MB' , value : '64' )
                      ] ) ,
    //App under test
    containerTemplate ( name : 'app-users' ,
                      image : '172.30.1.1:5000/myproject/app-users:latest' ,
                      resourceLimitMemory : '512Mi' ,
                      envVars : [
                        envVar ( key : 'SPRING_PROFILES_ACTIVE' , value : 'k8sit' ) ,
                        envVar ( key : 'SPRING_CLOUD_KUBERNETES_ENABLED' , value : 'false' )
                      ] ) ,
    //DB
    containerTemplate ( name : 'mariadb' ,
                      image : 'registry.access.redhat.com/rhscl/mariadb-102-rhel7:1' ,
                      resourceLimitMemory : '256Mi' ,
                      envVars : [
                        envVar ( key : 'MYSQL_USER' , value : 'myuser' ) ,
                        envVar ( key : 'MYSQL_PASSWORD' , value : 'mypassword' ) ,
                        envVar ( key : 'MYSQL_DATABASE' , value : 'testdb' ) ,
                        envVar ( key : 'MYSQL_ROOT_PASSWORD' , value : 'secret' )
                      ] ) ,
    //AMQ
    containerTemplate ( name : 'amq' ,
                      image : 'registry.access.redhat.com/jboss-amq-6/amq63-openshift:1.3' ,
                      resourceLimitMemory : '256Mi' ,
                      envVars : [
                        envVar ( key : 'AMQ_USER' , value : 'test' ) ,
                        envVar ( key : 'AMQ_PASSWORD' , value : 'secret' )
                      ] ) ,
    //External Rest API (provided by mockserver)
    containerTemplate ( name : 'mockserver' ,
                      image : 'jamesdbloom/mockserver:mockserver-5.3.0' ,
                      resourceLimitMemory : '256Mi' ,
                      envVars : [
                        envVar ( key : 'LOG_LEVEL' , value : 'INFO' ) ,
                        envVar ( key : 'JVM_OPTIONS' , value : '-Xmx128m' ) ,
                      ] )
    ]
    )
{
    node ( 'app-users-it' ) {
        /* Run the steps:
         * - pull source
         * - prepare dependency systems, run sqls
         * - run integration test
         * ...
         */

    }
}

该管道将​​创建所有容器,拉出在同一容器中运行它们的给定Docker映像。 这意味着容器将共享localhost接口,因此服务可以访问彼此的端口(但我们必须考虑端口绑定冲突)。 这是运行中的Pod在OpenShift Web控制台中的外观:

The running pod in OpenShift web console

这些映像是通过其Docker URL设置的(此处不支持OpenShift映像流),因此群集必须访问这些注册表。 在上面的示例中,我们先前在同一Kubernetes集群中构建了应用程序的映像,现在将其从内部注册表172.30.1.1 (docker-registry.default.svc) 。 该映像是我们的发行包,可以将其部署到开发,测试或生产环境中。 它从k8sit应用程序属性配置文件开始,其中连接URL指向127.0.0.1

考虑运行Java进程的容器的内存使用情况很重要。 当前版本的Java(v1.8,v1.9)默认情况下会忽略容器内存限制,并设置更大的堆大小。 3.9版jenkins-slave映像通过环境变量支持内存限制,这比早期版本要好得多。 设置JNLP_MAX_HEAP_UPPER_BOUND_MB=64足以让我们运行512MiB限制的Maven任务。

容器内的所有容器在/home/jenkins上安装了一个共享的empty dir卷(默认为workingDir )。 Jenkins代理使用它在容器内运行管道步骤脚本,这是我们检查集成测试库的地方。 这也是执行步骤的当前目录,除非它们在dir('relative_dir')块中。 以下是上述示例的管道步骤:


   
   
podTemplate ( ... )
{
    node ( 'app-users-it' ) { //must match the label in the podTemplate
        stage ( 'Pull source' ) {
          checkout scm // pull the git repo of the Jenkinsfile
                        //or: git url: 'https://github.com/bszeti/kubernetes-integration-test.git'
        }
        dir ( "integration-test" ) { //In this example the integration test project is in a sub directory
            stage ( 'Prepare test' ) {
                container ( 'mariadb' ) {
                    //requires mysql tool
                    sh 'sql/setup.sh'
                }
                //requires curl and python
                sh 'mockserver/setup.sh'
               
            }

            //These env vars are used by the tests to send message to users.in queue
            withEnv ( [ 'AMQ_USER=test' ,
                      'AMQ_PASSWORD=secret' ] ) {
                stage ( 'Build and run test' ) {
                    try {
                        //Execute the integration test
                        sh 'mvn -s ../configuration/settings.xml -B clean test'
                    } finally {
                        //Save test results in Jenkins
                        junit 'target/surefire-reports/*.xml'
                    }
                }
            }
        }
    }
}

管道步骤在jnlp容器上运行,除非它们在container('container_name')块内:

  • 首先,我们检查集成项目的来源。 在这种情况下,它位于存储库内的integration-test子目录中。
  • sql/setup.sh脚本创建表并将测试数据加载到数据库中。 它需要mysql工具,因此必须在mariadb容器中运行。
  • 我们的应用程序( app-users )调用Rest API。 我们没有启动该服务的映像,因此我们使用MockServer来启动HTTP端点。 它由mockserver/setup.sh配置。
  • 集成测试是用Java与JUnit编写的,并由Maven执行。 可能还有其他任何事情-这只是我们熟悉的堆栈。

遵循Kubernetes资源 API的podTemplatecontainerTemplate有很多配置参数, 有一些区别。 例如,可以在容器级别以及容器级别定义环境变量。 可以将卷添加到Pod,但是它们被安装在每个容器的同一mountPath


   
   
podTemplate ( ...
  containers : [ ... ] ,
  volumes : [
      configMapVolume ( mountPath : '/etc/myconfig' ,
        configMapName : 'my-settings' ) ,
      persistentVolumeClaim ( mountPath : '/home/jenkins/myvolume' ,
        claimName : 'myclaim' )
      ] ,
  envVars : [
     envVar ( key : 'ENV_NAME' , value : 'my-k8sit' )
    ]
)

听起来很简单,但是…

在同一个容器中运行多个容器是连接它们的一种好方法,但是如果容器的入口点具有不同的用户ID,则可能会遇到一个问题。 Docker镜像曾经以root身份运行进程,但出于安全考虑 ,不建议在生产环境中使用Docker镜像,因此许多镜像会切换到非root用户。 不幸的是,不同的映像可能使用不同的uid (Dockerfile中的USER ),如果它们使用相同的卷,则可能导致文件权限问题。

在这种情况下,冲突的根源是workingDir卷( /home/jenkins/workspace/ )上的Jenkins工作/home/jenkins/workspace/ 。 这用于管道执行并在每个容器中保存步骤输出。 如果我们在container(…)块中有步骤,并且此映像中的uidjnlp容器中的uid不同(非root用户),则会得到以下错误:

 touch : cannot touch '/home/jenkins/workspace/k8sit-basic/integration-test@tmp/durable-aa8f5204/jenkins-log.txt' : Permission denied 

让我们在示例中查看图像中的USER

jnlp容器中的默认umask0022 ,因此具有uid 185uid 27容器中的步骤将遇到权限问题。 解决方法是更改jnlp容器中的默认umask ,以便任何uid均可访问该workspace


   
   
containerTemplate ( name : 'jnlp' ,
  image : 'registry.access.redhat.com/openshift3/jenkins-slave-maven-rhel7:v3.9' ,
  resourceLimitMemory : '512Mi' ,
  command : '/bin/sh -c' ,
  //change umask so any uid has permission to the jenkins workspace
  args : '"umask 0000; /usr/local/bin/run-jnlp-client ${computer.jnlpmac} ${computer.name}"' ,
  envVars : [
    envVar ( key : 'JNLP_MAX_HEAP_UPPER_BOUND_MB' , value : '64' )
  ] )

要查看在运行集成测试之前首先构建应用程序和Docker映像的整个Jenkinsfile,请转到kubernetes-integration-test / Jenkinsfile

在这些示例中,集成测试在jnlp容器上运行,因为我们为测试项目选择了Java和Maven,而jenkins-slave-maven映像可以执行该测试。 当然,这不是强制性的; 我们可以将基于jenkins-slave的映像用作jnlp并使用一个单独的容器来执行测试。 请参阅kubernetes-integration-test / Jenkinsfile-jnlp-base示例,在此示例中,我们有意分离jnlp并将另一个容器用于Maven

YAML模板

podTemplate和containerTemplate定义支持许多配置,但缺少一些参数。 例如:

  • 他们不能从ConfigMap分配环境变量,只能从Secret分配环境变量。
  • 他们无法为容器设置就绪探测器。 如果没有它们,Kubernetes报告说,启动容器后,吊舱正在运行。 Jenkins将在流程准备好接受请求之前开始执行这些步骤。 这可能由于赛车条件而导致故障。 这些示例管道通常可以正常工作,因为checkout scm为容器启动提供了足够的时间。 当然, 睡眠会有所帮助,但是定义就绪探针是正确的方法。

为了解决该问题,在kubernetes-plugin(v1.5 +)中的podTemplate()中添加了一个YAML参数。 它支持完整的Kubernetes Pod 资源定义 ,因此我们可以为Pod定义任何配置:


   
   
podTemplate (
  label : 'app-users-it' ,
  cloud : 'openshift' ,
  //yaml configuration inline. It's a yaml so indentation is important.
  yaml : '' '
apiVersion: v1
kind: Pod
metadata:
  labels:
    test: app-users
spec:
  containers:
  #Java agent, test executor
  - name: jnlp
    image: registry.access.redhat.com/openshift3/jenkins-slave-maven-rhel7:v3.9
    command:
    - /bin/sh
    args:
    - -c
      #Note the args and syntax for run-jnlp-client
    - umask 0000; /usr/local/bin/run-jnlp-client $(JENKINS_SECRET) $(JENKINS_NAME)
    resources:
      limits:
        memory: 512Mi
  #App under test
  - name: app-users
    image: 172.30.1.1:5000/myproject/app-users:latest        
  ...
'
'' ,
  //volumes for example can be defined in the yaml our as parameter
  volumes : [ ... ]
) { ... }

确保将Jenkins中的Kubernetes插件更新到v1.5 +,否则YAML参数将被忽略。

YAML定义和其他podTemplate参数应该以某种方式合并,但是仅使用一个或另一个就不太容易出错。 如果很难在管道中定义YAML内联,请参阅kubernetes-integration-test / Jenkinsfile-yaml ,这是从file加载它的示例。

声明式管道语法

上面所有示例管道均使用脚本管道语法,该语法实际上是带有管道步骤的Groovy脚本。 声明性管道语法是一种新方法,它通过提供较少的灵活性并且不允许出现“ Groovy hacks”来在脚本上强制使用更多结构。 这样可以使代码更简洁,但是在复杂的情况下,您可能必须切换回脚本化语法。

在声明性管道中,kubernetes-plugin(v1.7 +) 仅支持YAML定义来定义Pod:


   
   
pipeline {
  agent {
    kubernetes {
      label 'app-users-it'
      cloud 'openshift'
      defaultContainer 'jnlp'
      yaml '' '
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: app-users
spec:
  containers:
  #Java agent, test executor
  - name: jnlp
    image: registry.access.redhat.com/openshift3/jenkins-slave-maven-rhel7:v3.9
    command:
    - /bin/sh
    args:
    - -c
    - umask 0000; /usr/local/bin/run-jnlp-client $(JENKINS_SECRET) $(JENKINS_NAME)
    ...
'
''
      }
    }
    stages {
        stage ( 'Run integration test' ) {
            environment {
                AMQ_USER = 'test'
                AMQ_PASSWORD = 'secret'
            }
            steps {
                dir ( "integration-test" ) {
                    container ( 'mariadb' ) {
                        sh 'sql/setup.sh'
                    }
                    sh 'mockserver/setup.sh'

                    //Run the tests.
                    //Somehow simply "mvn ..." doesn't work here
                    sh '/bin/bash -c "mvn -s ../configuration/settings.xml -B clean test"'
                }
            }
            post {
                always {
                    junit testResults : 'integration-test/target/surefire-reports/*.xml' , allowEmptyResults : true
                }
            }
        }
    }
}

也可以在每个阶段设置不同的代理,如kubernetes-integration-test / Jenkinsfile-declarative所示

在Minishift上尝试

如果您想尝试上述解决方案,则需要访问Kubernetes集群。 在Red Hat,我们使用OpenShift ,它是Kubernetes的企业级版本。 有几种访问完整集群的方法:

也可以在本地计算机上运行一个小的单节点群集,这可能是最简单的尝试方法。 让我们看看如何设置Red Hat CDK (或Minikube )来运行我们的测试。

下载Red Hat CDK之后, 准备 Minishift环境:

  • 运行安装程序: minishift setup-cdk
  • 将内部Docker注册表设置为不安全:
    minishift config set insecure-registry 172.30.0.0/16这是必需的,因为kubernetes-plugin直接从内部注册表(不是HTTPS)中拉取映像。
  • 启动Minishift虚拟机(使用您的免费Red Hat帐户 ): minishift --username me@mymail.com --password ... --memory 4GB start
  • 注意控制台URL(或者您可以通过输入: minishift console --url来获取)
  • oc工具添加到路径: eval $(minishift oc-env)
  • 登录到OpenShift API(admin / admin):
    oc login https://192.168.42.84:8443

使用可用的模板在集群中启动Jenkins主服务器:
oc new-app --template=jenkins-persistent -p MEMORY_LIMIT=1024Mi

Jenkins启动后,应该可以通过模板创建的路径使用它(例如https://jenkins-myproject.192.168.42.84.nip.io )。 登录与OpenShift(admin / admin)集成在一起。

创建一个新的Pipeline项目,该项目采用Pipeline script from SCMPipeline script from SCM指向具有Jenkins文件要执行的Git存储库(例如kubernetes-integration-test.git )。 然后只需Build Now

由于从Docker注册表中下载了映像,因此第一次运行需要更长的时间。 如果一切顺利,我们可以在Jenkins构建的Console Output上看到测试执行。 动态创建的窗格可以在“我的项目/窗格”下的OpenShift控制台上看到。

如果出现问题,请尝试通过查看以下内容进行调查:

  • 詹金斯建立输出
  • Jenkins主播日志
  • Jenkins kubernetes插件配置
  • 创建的pod的事件(Maven或集成测试)
  • 创建的吊舱日志

如果您想更快地执行其他操作,则可以将卷用作本地Maven存储库,这样Maven不必每次都下载依赖项。 创建一个PersistentVolumeClaim


   
   
# oc create - f - << EOF
kind : PersistentVolumeClaim
apiVersion : v1
metadata :
  name : mavenlocalrepo
spec :
  accessModes :
    - ReadWriteOnce
  resources :
    requests :
      storage : 10Gi
EOF

将卷添加到podTemplate(以及kubernetes-plugin中的Maven模板)。 参见kubernetes-integration-test / Jenkinsfile-mavenlocalrepo


   
   
volumes : [
  persistentVolumeClaim ( mountPath : '/home/jenkins/.m2/repository' ,
    claimName : 'mavenlocalrepo' )
]

请注意,Maven本地存储库声称是“非线程安全的”,不应同时被多个版本使用。 我们在这里使用ReadWriteOnce声明,该声明一次只能安装到一个吊舱。

jenkins-2-rhel7:v3.9映像已安装kubernetes-plugin v1.2。 要运行Jenkinsfile-declarativeJenkinsfile-yaml示例,您需要将Jenkins中的插件更新为v1.7 +。

要在停止Minishift后完全清理,请删除~/.minishift目录。

局限性

每个项目都是不同的,因此了解以下限制和因素对您的案例的影响很重要:

  • 使用jenkins-kubernetes-plugin创建测试环境独立于集成测试本身。 可以使用任何语言编写测试,并可以使用任何测试框架执行测试-这是一种强大的功能,但同时也承担着巨大的责任。
  • 整个测试容器在测试执行之前创建,然后关闭。 此处没有提供用于在测试执行期间管理容器的解决方案。 可以使用不同的Pod模板将测试分为不同的阶段,但这会增加很多复杂性。
  • 容器在执行第一个流水线步骤之前启动。 此时无法访问集成测试项目中的文件,因此我们无法运行准备脚本或为这些过程提供配置文件。
  • 所有容器都属于同一容器,因此它们必须在同一节点上运行。 如果我们需要许多容器,并且容器需要太多资源,则可能没有节点可用于运行容器。
  • 集成测试环境的大小和规模应保持较小。 尽管可以在一个容器中启动多个微服务并运行端到端测试,但是所需容器的数量会Swift增加。 该环境也不是测试高可用性和可伸缩性要求的理想选择。
  • 每次执行都会重新创建测试容器,但是容器的状态在其运行期间仍保持不变。 这意味着各个测试用例不是彼此独立的。 如果需要,测试项目有责任在它们之间进行一些清理。

摘要

使用Jenkins管道和kubernetes-plugin,在由代码动态创建的环境中运行集成测试相对容易。 我们只需要一个Kubernetes集群和一些容器方面的经验。 幸运的是,越来越多的平台在一个公共注册表上提供了官方的Docker镜像。 在最坏的情况下,我们必须自己构建一些。 准备管道和集成测试的工作使您很快得到了回报,特别是如果您想在应用程序的生命周期中尝试不同的配置或依赖版本升级时。


该手册最初发布在Medium上 ,经许可转载。

翻译自: https://opensource.com/article/18/6/running-integration-tests-kubernetes

kubernetes 测试

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐