Introduction
In this post will cover how we can incorporate Azure Load Testing into our Continuous Delivery/Continuous Integration Pipelines. At the end of this post there will be a complete YAML templated stage responsible for executing your Azure Load Tests, something that should be easily inserted into any existing CI/CD Pipeline.
If you’d like to jump to the code it is available on the GitHub repo TheYAMLPipelineOne. This process is referenced in my presentation to the West Michigan Azure User Group and available to watch here.
Background
Per Microsoft’s own documentation Azure Load Testing is described as:
generate high-scale load and run simulations with a fully managed load-testing service, built for Azure. Create tests quickly without knowledge of load-testing tools, or upload your existing Apache JMeter scripts. Gain actionable insights into performance, scalability, and capacity and support continuous improvement through automated continuous integration and continuous delivery (CI/CD) workflows.
https://azure.microsoft.com/en-us/products/load-testing/
This sounds awesome! Not only does this service help facilitate the DevOps need to build a better quality product faster, it already leverages the Azure native services!
For the YAML Pipeline post if you haven’t already I’d advise you to check out my posts on this topic to get a better understanding on how I approach creating YAML templates for CI/CD.
PreReqs
- ADO Service Connection will need Test Contributor Role
- Azure Load Testing Task is available on the agent, if it is a self hosted agent.
- Azure Load Testing on the Marketplace
Architecture
With this template I have decided to take the approach that Azure Load Testing will be it’s own stage in ADO. This stage will be tied to it’s own load testing environment for approvals. The rationale behind this is essentially creating an optional Load Testing stage that can be over written by a simple true/false statement. Additionally by doing this at the stage level one can easily insert this into any existing pipeline. Alternatively if we’d rather use the job and task both will be templated out to be piecemealed.
I’ve also included just some basic guidance on how to configure and access require for the ADO Service Account.
Task
Azure Load Testing has it’s own dedicated Azure DevOps Task. There is also a GitHub one; however, we will be focusing on the ADO version.
This task is pretty straightforward; however, to optimize for reuse it’s usually a good idea to provide parameters with defaulted values.
azure_load_test_task.yml
parameters:
- name: azureSubscriptionName
type: string
default: ''
- name: loadTestConfigFile
type: string
default: ''
- name: loadTestResourceGroupName
type: string
default: ''
- name: loadTestResourceName
type: string
default: ''
- name: secretsJSONObject
type: string
default: ''
- name: envJSONObject
type: string
default: ''
steps:
- task: AzureLoadTest@1
inputs:
azureSubscription: ${{ parameters.azureSubscriptionName }}
loadTestConfigFile: ${{ parameters.loadTestConfigFile }}
resourceGroup: ${{ parameters.loadTestResourceGroupName }}
loadTestResource: ${{ parameters.loadTestResourceName }}
secretsJSONObject: ${{ parameters.secretsJSONObject }}
secrets: ${{ parameters.secretsJSONObject }}
env: ${{ parameters.envJSONObject }}
Job
There can be additional tasks; however, for our purposes we will just be calling the above task. I am a proponent of even templating one task jobs as this will just optimize and provide for future flexibility. In this template we are leveraging a separate variable file to load the specifics azure environment information such as the Service Connection to use.
load_test_execute_job.yml
parameters:
- name: loadTestConfigFile
type: string
default: ''
- name: loadTestResourceName
type: string
default: ''
- name: loadTestResourceGroupName
type: string
default: ''
- name: secretsJSONObject
type: string
default: ''
- name: envJSONObject
type: string
default: ''
- name: regionAbrv
type: string
default: ''
- name: environmentName
type: string
default: ''
jobs:
- deployment: 'run_azure_load_test_${{ parameters.environmentName }}_${{ parameters.regionAbrv }}'
environment: ${{ parameters.environmentName }}_loadtest
variables:
- template: ../variables/azure_${{parameters.environmentName}}_variables.yml
strategy:
runOnce:
deploy:
steps:
- template: ../tasks/azure_load_test_task.yml
parameters:
azureSubscriptionName: ${{ variables.azureServiceConnectionName}}
loadTestConfigFile: ${{ parameters.loadTestConfigFile }}
loadTestResourceGroupName: ${{ parameters.loadTestResourceGroupName }}
loadTestResourceName: ${{ parameters.loadTestResourceName }}
secretsJSONObject: ${{ parameters.secretsJSONObject }}
envJSONObject: ${{ parameters.envJSONObject }}
Stage
Lastly we have the stage that will be called by the pipeline. This stage is just the next building block from the job above. There really isn’t anything out of the order in it.
load_test_stage.yml
parameters:
- name: environmentName
type: string
default: 'tst'
- name: regionAbrv
type: string
default: ''
- name: loadTestConfigFile
type: string
default: ''
- name: secretsJSONObject
type: string
default: ''
- name: envJSONObject
type: string
default: ''
- name: loadTestResourceName
type: string
default: ''
- name: loadTestResourceGroupName
type: string
default: ''
- name: serviceName
type: string
default: ''
stages:
- stage: '${{ parameters.serviceName }}_${{ parameters.environmentName}}_${{parameters.regionAbrv}}_load_test'
jobs:
- template: ../jobs/load_test_execute_job.yml
parameters:
environmentName: ${{ parameters.environmentName }}
loadTestConfigFile: ${{ parameters.loadTestConfigFile }}
secretsJSONObject: ${{ parameters.secretsJSONObject }}
envJSONObject: ${{ parameters.envJSONObject}}
regionAbrv: ${{ parameters.regionAbrv }}
loadTestResourceGroupName: ${{ parameters.loadTestResourceGroupName }}
loadTestResourceName: ${{ parameters.loadTestResourceName }}
Pipeline
For the calling Pipeline I have configured a repository, LoadTestingDemo, which will call this stage. Below is a snippet on how to do such an action. I do have an expression to detect a.) if load test is true and b.) add it after the tst stage.
loadtestingdemo_template.yml
- ${{ if eq(parameters.runAzureLoadTest, 'true') }} :
- ${{ each environmentObject in parameters.environmentObjects }} :
- ${{ if eq(environmentObject.environmentName, 'tst')}}:
- ${{ each regionAbrv in environmentObject.regionAbrvs }} :
- template: stages/load_test_stage.yml@templates
parameters:
environmentName: ${{ environmentObject.environmentName }}
regionAbrv: ${{ regionAbrv }}
loadTestConfigFile: 'tests/SampleApp.yaml'
serviceName: ${{ parameters.serviceName }}
envJSONObject: '
[
{
"name": "webapp",
"value": "$(webAppName).azurewebsites.net"
}
]'
loadTestResourceName: 'lt-test-dev-eus'
loadTestResourceGroupName: 'rg-test-dev-eus'
Conclusion
There it is! My intention is to package this in such a way that any organization can consume and leverage for their Azure Load Tests. We’ve just create not only a task template, but a job, and a stage template. All of which can be leverage in your organization. The complete YAML template as well as additionaly templates can be found on my GitHub Repo TheYAMLPipelineOne.