Buenas prácticas JMeter
Apache JMeter es un software de código abierto diseñado y construido en Java que permite realizar baterías de pruebas sobre un sistema. Inicialmente nació para probar aplicaciones web, pero hoy está extendido para prácticamente cualquier tipo de aplicaciones.
Es un proyecto de la Apache Software Fundation y, como tal, recomendamos tener como referencia su página y documentación oficial.
JMeter en si mismo es una aplicación de escritorio multiplataforma con una interfaz gráfica que permite configurar suites de pruebas que se ejecutarán bajo demanda. Además, ofrece una herramienta CLI (Command Line Interface), la cual, como veremos, va a sernos muy útil en el ámbito de este artículo.
Este artículo explica una casuística de uso real de JMeter para probar una API Rest servida por artefactos de Springboot corriendo en Pods de Openshift Kubernetes.
No nos detendremos a explicar muy detalladamente las posibilidades que ofrece JMeter como tal, ya que son muy versátiles y extensas. Explicaremos someramente en qué consiste el caso de uso utilizado y cómo configurarlo. A continuación explicaremos una posible problemática que podría adulterar la precisión de los resultados de JMeter en entornos Cloud. Por último aportaremos una solución a dicha problemática.
Versiones utilizadas
- Infraestructura
- Apache JMeter 5.3
- Kubernetes 1.18.3
- Openshift 4.5
- Software (no son relevantes)
- Java 8
- Springboot 2.3.3
- Spring 5.2.9
Caso de uso
Esta entrada se basa en la experiencia de un Proyecto real llevado a cabo durante el último trimestre de 2020. Se trata de la refactorización de un sistema de medios de pago Java legacy (Java <= 7) monolítico a un sistema en Cloud con microservicios con Java (>=8) y Springboot.
El núcleo del requisito consistía en estudiar los tiempos de respuesta del aplicativo desplegado con la nueva arquitectura en condiciones similares a Producción. Para realizar dicha simulación y poder obtener los tiempos de respuesta, utilizamos Apache JMeter 5.3 configurando un módulo de peticiones HTTP dentro de un grupo de hilos que define la concurrencia deseada.
Configuración de las pruebas
Como se ha dicho anteriormente, no vamos extendernos en las muchas posibilidades que ofrece el cliente gráfico de JMeter como tal, aunque a continuación sí detallamos someramente cómo configuramos la test suite que necesitamos en nuestro caso de estudio.
Para iniciarlo, ejecutar el comando jmeter tras incluirlo en el PATH y sin ningún modificador.
Interfaz en árbol
JMeter ofrece una intuitiva interfaz. En la parte izquierda se añaden en forma de árbol los módulos, configuraciones y reportes que necesitamos para nuestras pruebas. Al seleccionar un nodo, en la parte derecha se configuran los elementos y parámetros relativos al nodo seleccionado.
Las pruebas se ejecutan leyendo los módulos de arriba a abajo, existiendo la posibilidad de crear bucles de ejecución concurrente, definición de contadores, variables y otros elementos de control, como veremos a continuación.
Nodo raíz – Test Plan
De este nodo cuelgan todas las configuraciones y módulos. Permite definir parametrización global y estrategias de ejecución de las pruebas. Obsérvese que los parámetros pueden llevar valores dinámicos aceptando código JavaScript (v. variable «DIA»).
Para hacer referencia a las variables definidas en este módulo (u otros módulos de parametrización), se emplea la nomenclatura de ${nombre_variable}.
Parámetros de usuario
Este módulo permite la configuración de diferentes usuarios y parámetros asociados a ellos.
Thread Group
Se configura un nodo base Thread Group por cada grupo de concurrencia que se vaya a lanzar. Los módulos que estén jerárquicamente bajo este grupo se ejecutarán concurrentemente y de arriba a abajo. En nuestra prueba, sólo hay un grupo de hilos. Para definir la concurrencia y el comportamiento del grupo de hilos, se deben definir los siguientes parámetros:
- Número de hilos: simula los usuarios concurrentes que estarán llevando a cabo las pruebas definidas en el grupo.
- Periodo de rampa de subida: define un número de segundos hasta alcanzar el rendimiento pleno de la prueba, generando un crecimiento aritmético desde 0 hasta N hilos en esos segundos.
Por ejemplo, si el número de hilos definido es 50 y se define una rampa de subida de 300 segundos (5 minutos), JMeter iniciará los hilos a razón de 10 nuevos hilos por minuto durante esos 5 primeros minutos. - Loop count: número de veces que se ejecutarán las pruebas definidas en el grupo por cada hilo configurado.
Counter
Permite generar un valor diferente para cada una de las pruebas que van a ejecutarse. Esto es muy útil para diferenciar cada test y permitir así una trazabilidad específica. Cuando un hilo termina una ejecución, y si no ha alcanzado el número de pruebas definido en Loop Count, volverá a empezar el bucle del Thread Group pasando por este contador para incrementarlo en uno, como se ve en la siguiente imagen. btc bookmakers
HTTP Request
Este módulo representa el módulo más importante de nuestras pruebas, ya que define la petición HTTP a nuestro sistema de microservicios de Springboot alojado en OpenShift.
Como se puede ver en la imagen, este módulo, junto a su hijo «Gestor de Cabecera HTTP» tiene todo lo necesario para construir una petición HTTP.
Delay
El último elemento incluido en el bucle de concurrencia es un retraso en milisegundos antes de pasar a la siguiente iteración.
Reports
JMeter tiene un amplio conjunto de módulos para generar informes. En nuestro proyecto no los hemos utilizado, ya que las mediciones las hemos hecho a través de la escritura de logs utilizando el stack EFG. Pero mostramos la imagen de lo que sería un módulo de salida de informe agregado.
Ejecución de la prueba
Desde la interfaz gráfica
Desde el propio cliente se muestran los botones de play, que tienen modificadores para pausarla, retomarla, reiniciar, etc. Es intuitivo y no merece una explicación detallada.
Es importante reseñar es que la propia herramienta advierte mediante un mensaje al inicio de que no está recomendado el uso de la versión gráfica para pruebas de carga, orientando su uso únicamente para creación de test y pruebas iniciales para comprobar la adecuación de los mismos.
Desde línea de comandos (CLI)
JMeter ofrece la posibilidad de trabajar a través de su herramienta CLI, lo cual es necesario cuando se trata de realizar pruebas de carga o rendimiento. Además, la versión de comandos nos da la posibilidad de acceder al shell de un contenedor en Cloud que tenga el la herramienta instalada y trabajar aprovechando el networking que la plataforma Cloud nos ofrece. De esta forma evitamos que posibles latencias de red adulteren los resultados que la propia herramienta mide y ofrece.
En el siguiente punto de este artículo describimos cómo configurarlo y ejecutar pruebas en Cloud utilizando OpenShift. Pero antes nos vamos a centrar en el propio comando y en los ficheros relacionados con JMeter.
Archivos relacionados:
- JMX: es el xml donde JMeter almacena la configuración de un test. Es decir, la herramienta gráfica, al configurar el test y guardarlo, lo graba en este formato. Lógicamente, permite también cargar un test en la herramienta utilizando este fichero.
- JTL: almacena mediante una estrategia CSV el resultado acumulado de las pruebas. El contenido de este fichero puede exportarse mediante JMeter a un formato legible en forma de Web estática.
- Report Folder: JMeter también permite generar directamente la carpeta con el contenido HTML estático (el mencionado en el punto anterior), sin tener que generarlo a través de un JTL previo.
Un ejemplo de comando sería:
jmeter -n -t test_suite.jmx -l resultado.jtl -o dashboard_directory
- -n: indica que ejecute la versión no gráfica.
- -t: indica el fuente JMX que va a ejecutarse.
- -l: indica el JTL de salida.
- -o: indica el nombre del directorio donde queremos que se almacene el HTML estático que compondrá la web con el resultado de las pruebas.
En este apartado de la documentación oficial de Apache JMeter se describen todos los modificadores del comando.
Configuración en Cloud
Una arquitectura Cloud nos da la oportunidad de ejecutar JMeter en el mismo entorno de red en el que están alojados los microservicios a los que se quiere atacar. En nuestro caso, en el que usamos Openshift, es muy conveniente utilizar los nombres de servicio a la hora de especificar el host de la API. Esto es posible mediante la configuración de un Pod con un contenedor que monte lo mínimo para poder ejecutar JMeter. Concretamente:
- Un sistema operativo. En nuestro caso la elección es alpine (latest version).
- Instalación mediante apk de openjdk8_jre.
- Descarga mediante curl y descompresión e instalación vía tar de JMeter 5.3.
Dockerfile
Este es el Dockerfile utilizado para la creación del contenedor:
FROM alpine:latest
ARG JMETER_VERSION="5.3"
ENV JMETER_HOME /opt/apache-jmeter-${JMETER_VERSION}
ENV JMETER_BIN ${JMETER_HOME}/bin
ENV JMETER_DOWNLOAD_URL https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-${JMETER_VERSION}.tgz
ARG TZ="Europe/Amsterdam"
RUN apk update \
&& apk upgrade \
&& apk add ca-certificates \
&& update-ca-certificates \
&& apk add --update openjdk8-jre tzdata curl unzip bash \
&& apk add --no-cache nss \
&& rm -rf /var/cache/apk/* \
&& mkdir -p /tmp/dependencies \
&& curl -L --silent ${JMETER_DOWNLOAD_URL} > /tmp/dependencies/apache-jmeter-${JMETER_VERSION}.tgz \
&& mkdir -p /opt \
&& tar -xzf /tmp/dependencies/apache-jmeter-${JMETER_VERSION}.tgz -C /opt \
&& rm -rf /tmp/dependencies
# Set global PATH such that "jmeter" command is found
ENV PATH $PATH:$JMETER_BIN
# Entrypoint has same signature as "jmeter" command
COPY entrypoint.sh /
WORKDIR ${JMETER_HOME}
ENTRYPOINT ["/entrypoint.sh"]
Configuración del Deployment
El yaml utilizado para configurar el Deployment en Kubernetes es el siguiente:
apiVersion: apps/v1
kind: Deployment
metadata:
name: jmeter
namespace: onpay-dev
labels:
app: jmeter
spec:
replicas: 1
selector:
matchLabels:
app: jmeter
template:
metadata:
creationTimestamp: null
labels:
app: jmeter
spec:
restartPolicy: Always
imagePullSecrets:
- name: solutionsregistry
schedulerName: default-
terminationGracePeriodSeconds: 30
securityContext: {}
containers:
- name: jmeter
image: 'solucionesregistry.azurecr.io/jmeter:dev'
command:
- /bin/sleep
resources:
limits:
cpu: '2'
memory: 1200Mi
requests:
cpu: 600m
memory: 1Gi
volumeMounts:
- name: jmeter-aux-files
mountPath: /opt/apache-jmeter-5.3/aux
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: Always
hostAliases:
- ip: 10.3.32.11
hostnames:
- >-
onpay-gateway-rest-preauthorization-onpay-dev.apps.soluciones-cluster-dev.b2bconnect.es
volumes:
- name: jmeter-aux-files
persistentVolumeClaim:
claimName: jmeter-pvc
dnsPolicy: ClusterFirst
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
Obsérvese que utilizamos una imagen en el Registry llamada jmeter:
image: 'solucionesregistry.azurecr.io/jmeter:dev'
Podemos observar también que hay que asociarlo a un PVC para que las escrituras en fichero persistan:
volumeMounts:
- name: jmeter-aux-files
mountPath: /opt/apache-jmeter-5.3/aux
Funcionamiento del contenedor
Como hemos dicho, se trata de un contenedor que no tiene un servicio activo, como pudiese ser un Springboot a la escucha de peticiones. Es un simple sistema operativo con JMeter instalado que, tras su arranque, sencillamente no necesitamos que haga nada. Sólo «existir» para poder conectarnos a él por línea de comandos.
Por este motivo, el «ENTRYPOINT» especificado en el Dockerfile es irrelevante, siendo el parámetro command del yaml el que cobra importancia:
command:
- /bin/sleep
Lo que le estamos especificando es que haga un sleep infinito para que, sencillamente, no haga nada. Pero, al mismo tiempo, la ejecución del contenedor queda viva y disponible para conectarnos al remote shell (rsh) vía el CLI de Openshift (OC).
Es decir, una vez el Pod de Kubernetes ha arrancado, ya podemos ejecutar lo siguiente para entrar en su shell:
oc rsh <nombre_pod>
Ejecución de una prueba
Como ya se explicó anteriormente, el comando a ejecutar es jmeter -n (no gui), pasándole -t test_suite.jmx y -l resultado.jtl
Dado que estamos en Cloud, no es útil añadirle el -o folder para generar el dashboard con los resultados, ya que el el mismo quedaría almacenado en el filesystem contenedor y su resultado no sería directamente accesible.
En cambio, el mecanismo utilizado para ejecutar en remoto y recoger los resultados en local es el siguiente:
- Copiar el jmx fuente: utilizar el comando oc cp (copy) para tenerlo disponible en el contenedor.
oc cp test_suite.jmx nombre_pod:/opt/apache-jmeter-5.3/test_suite.xml
- Ejecutar el comando jMeter y esperar al final de la ejecución:
jmeter -n -t test_suite.jmx -l resultado.jtl
- Copiar el jtl del resultado desde el contenedor a local:
oc nombre_pod:/opt/apache-jmeter-5.3/resultado.jtl resultado.jtl
- A partir de la copia local de resultado.jtl, mediante JMeter generar la carpeta de HTML estático con el Dashboard de los resultados:
jmeter -g resultado.jtl -o directorio_salida
En el directorio de salida se genera un conjunto de archivos y directorios de HTML estático con esta estructura:
El dashboard generado permite visualizar de forma gráfica medias, percentiles, transacciones por segundo, respuestas en error y otras estadísticas de mucha utilidad.
Conclusión
En este artículo hemos repasado la utilidad del Software Apache JMeter para configurar y ejecutar pruebas de rendimiento. Además, hemos explicado la estrategia utilizada para cargar el JMeter en un Pod de Openshift, ejecutar desde la nube y extraer el dashboard de resultados con el propio software de JMeter.