diff --git a/.github/workflows/main_app-7tmd2uumpfvoi.yml b/.github/workflows/main_app-7tmd2uumpfvoi.yml new file mode 100644 index 00000000..7052f677 --- /dev/null +++ b/.github/workflows/main_app-7tmd2uumpfvoi.yml @@ -0,0 +1,65 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Node.js app to Azure Web App - app-7tmd2uumpfvoi + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Node.js version + uses: actions/setup-node@v1 + with: + node-version: '18.x' + + - name: npm install, build, and test + run: | + npm install + npm run build --if-present + npm run test --if-present + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v2 + with: + name: node-app + path: . + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v2 + with: + name: node-app + + - name: Login to Azure + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID92B4 }} + tenant-id: ${{ secrets.AZURE_TENANT_ID92B4 }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID92B4 }} + + - name: 'Deploy to Azure Web App' + id: deploy-to-webapp + uses: azure/webapps-deploy@v2 + with: + app-name: 'app-7tmd2uumpfvoi' + slot-name: 'production' + package: . + \ No newline at end of file diff --git a/.github/workflows/main_jjnodeapp.yml b/.github/workflows/main_jjnodeapp.yml new file mode 100644 index 00000000..43524ad7 --- /dev/null +++ b/.github/workflows/main_jjnodeapp.yml @@ -0,0 +1,56 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Node.js app to Azure Web App - jjnodeapp + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js version + uses: actions/setup-node@v3 + with: + node-version: '14.16.0' + + - name: npm install, build, and test + run: | + npm install + npm run build --if-present + npm run test --if-present + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v3 + with: + name: node-app + path: . + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v3 + with: + name: node-app + + - name: 'Deploy to Azure Web App' + uses: azure/webapps-deploy@v2 + id: deploy-to-webapp + with: + app-name: 'jjnodeapp' + slot-name: 'Production' + package: . + publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_B92DE938C7D24A50B04CF415EAD14B00 }} \ No newline at end of file diff --git a/.github/workflows/main_jjtestnodebp.yml b/.github/workflows/main_jjtestnodebp.yml new file mode 100644 index 00000000..75e4d03a --- /dev/null +++ b/.github/workflows/main_jjtestnodebp.yml @@ -0,0 +1,56 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Node.js app to Azure Web App - jjtestnodebp + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js version + uses: actions/setup-node@v3 + with: + node-version: '14.16.0' + + - name: npm install, build, and test + run: | + npm install + npm run build --if-present + npm run test --if-present + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v3 + with: + name: node-app + path: . + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v3 + with: + name: node-app + + - name: 'Deploy to Azure Web App' + uses: azure/webapps-deploy@v2 + id: deploy-to-webapp + with: + app-name: 'jjtestnodebp' + slot-name: 'Production' + package: . + publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_AE1ACAC9766D4799A10ABB678419525B }} \ No newline at end of file diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index e5175a0a..6bd61e36 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -1,6 +1,6 @@ # This is a basic workflow to help you get started with Actions -name: Sample App deploy +name: Sample App load test # Controls when the workflow will run on: @@ -9,58 +9,15 @@ on: - main env: - AZURE_WEBAPP_NAME: "" # set this to your application's name - LOAD_TEST_RESOURCE: "" - LOAD_TEST_RESOURCE_GROUP: "" - AZURE_WEBAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root - NODE_VERSION: '14.15.1' # set this to the node version to use - LOCATION: "West US" - APPINSIGHTLOCATION: "West US" - DATABASEACCOUNTLOCATION: "westus" + LOAD_TEST_RESOURCE: "eastloadtest" + LOAD_TEST_RESOURCE_GROUP: "loadtest-rg" + ENDPOINT_URL: "jjttarg.azurewebsites.net" # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - build-and-deploy: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - name: Checkout GitHub Actions - uses: actions/checkout@v2 - - - name: Login to Azure - uses: azure/login@v1 - continue-on-error: false - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - # Deploy Arm template - - name: Deploy ARM Template - uses: azure/powershell@v1 - continue-on-error: false - with: - inlineScript: | - az group create --name "${{ env.AZURE_WEBAPP_NAME }}-rg" --location "${{ env.LOCATION }}" - $deploymentOutputs = az deployment group create --resource-group "${{ env.AZURE_WEBAPP_NAME }}-rg" --mode Incremental --template-file ./windows-webapp-template.json --parameters webAppName="${{ env.AZURE_WEBAPP_NAME }}" --parameters hostingPlanName="${{ env.AZURE_WEBAPP_NAME }}-host" --parameters appInsightsLocation="${{ env.APPINSIGHTLOCATION }}" --parameters databaseAccountId="${{ env.AZURE_WEBAPP_NAME }}db" --parameters databaseAccountLocation="${{ env.DATABASEACCOUNTLOCATION }}" -o json - $deploymentOutputs = $deploymentOutputs | ConvertFrom-Json - $connectionString = [String]$deploymentOutputs.properties.outputs.azureCosmosDBAccountKeys.value - $setConnectionString = az webapp config appsettings set --name "${{ env.AZURE_WEBAPP_NAME }}" --resource-group "${{ env.AZURE_WEBAPP_NAME }}-rg" --settings CONNECTION_STRING="$connectionString" - $setAppSettings = az webapp config appsettings set --name "${{ env.AZURE_WEBAPP_NAME }}" --resource-group "${{ env.AZURE_WEBAPP_NAME }}-rg" --settings MSDEPLOY_RENAME_LOCKED_FILES=1 - $setAppSettings = az webapp config appsettings set --name "${{ env.AZURE_WEBAPP_NAME }}" --resource-group "${{ env.AZURE_WEBAPP_NAME }}-rg" --settings SCM_DO_BUILD_DURING_DEPLOYMENT=true - $setAppSettings = az webapp config appsettings set --name "${{ env.AZURE_WEBAPP_NAME }}" --resource-group "${{ env.AZURE_WEBAPP_NAME }}-rg" --settings HEADER_VALUE="${{ secrets.MY_SECRET }}" - azPSVersion: "latest" - - - name: 'Deploy to Azure WebApp' - uses: azure/webapps-deploy@v2 - with: - app-name: ${{ env.AZURE_WEBAPP_NAME }} - package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} loadTest: name: Load Test - needs: build-and-deploy runs-on: ubuntu-latest steps: - name: Checkout GitHub Actions @@ -78,7 +35,14 @@ jobs: loadTestConfigFile: 'SampleApp.yaml' loadTestResource: ${{ env.LOAD_TEST_RESOURCE }} resourceGroup: ${{ env.LOAD_TEST_RESOURCE_GROUP }} - + env: | + [ + { + "name": "webapp", + "value": "${{env.ENDPOINT_URL}}" + } + ] + - uses: actions/upload-artifact@v2 with: name: loadTestResults diff --git a/Container-readme.md b/Container-readme.md new file mode 100644 index 00000000..b58447e4 --- /dev/null +++ b/Container-readme.md @@ -0,0 +1,292 @@ +# Load Test Challenge Workshop (pre-built container) + +This repository has been forked from the [Azure load test demo](https://github.com/Azure-Samples/nodejs-appsvc-cosmosdb-bottleneck) repository and amended into a challenge-based set of exercises for an interactive workshop to get hands on experience in using Azure load testing on an application. + +It uses the sample application and sample load test script in this repository as a starting point. + +The demonstration application is an web app hosted in Azure App Services with a Cosmos Database backend. + +The mission is to see if you can get **500 requests per second** from this application without spending more money than is necessary on the Azure resources. + +The final challenge is about adapting this to one of your own application or service. + +**In this version we will deploy the application manually to avoid issues with naming conventions and policies that bar basic authentication. These instructions used a containerised version of the application that has been placed on the public Docker Hub to make it straightword to deploy** + +# Challenge One - Create Load Test Resource + +This may be done in the Azure portal or using automation. You could also try the [Quickstart](https://docs.microsoft.com/en-us/azure/load-testing/quickstart-create-and-run-load-test) + +![alt text](https://docs.microsoft.com/en-us/azure/load-testing/media/quickstart-create-and-run-load-test/quick-test-resource-overview.png "Quick start page") + +You need to consider the location of the load testing service with respect to the target system's location. Discuss why this may be important. There may be limitations on the location of the database, so it is suggested that a resource group is created and all resources are put in that region. As safe bet is **North Europe**. + +So, now we have a load testing service and we have tested it out against a URL, now let's deploy an application in the next challenge. + + +# Challenge Two - Create a Demo System Under Test + +This challenge is about having our own application to test that we can later change to meet our performance requirements. + +All of the steps can be done in the Azure portal. There is no need for a command prompt or development environment. You can therefore choose your own naming convention for the web application and Cosmos database, but there are limits on which regions Azure CosmosDB for MongoDB can be deployed. North Europe is a good option. It also makes sense to deploy the app into the same region. + +## Detailed steps + +### Azure CosmosDB for MongoDB +In the resource group use the **Create** button to bring up the Marketplace. + +In the search type **Cosmos** and you should see in the list "Azure CosmosDB for MongoDB", when the tile for this appears, hit "Create" + +![alt-text](./img/select-cosmos.png "Select Azure CosmosDB for MongoDB") + +This will bring up a wizard. +1. in the next step pick **Request unit (RU) database account** +2. Enter an account name that meets your naming convention and is unique +3. Choose the region **North Europe** +4. Leave the other settings +5. Hit **Review and Create** +6. If the validation succeeds, hit **Create** +7. This should take a few minutes to provision + +### Web app and its service plan + +The web application needs to be created, code deployed for the application and then some configuration so that the code can access the database. + +It is suggested the web app is deployed in the same region as the database. **North Europe** is suggested. + +1. In the resource group use the **Create** button to bring up the Marketplace. +2. In the search type **web app** and you should see in the list "Azure CosmosDB for MongoDB", when the tile for this appears, hit "Create" + +![alt-text](./img/select-web-app.png "Select Azure Web App") + +3. There is a multi-page wizard and the main sections are the basics and container sections. + +![alt-text](./img/create-web-app-basic.png "Create Web App - Basics") + +6. In the above you need to make sure that you choose a unique name that matches your naming convention +7. Choose the Container option +8. Linux +9. The region - suggested **North Europe** +10. Create new Linux plan with a name of your choosing and Basic B1 tier. +11. Move to the container tab + +![alt-text](./img/create-web-app-container.png "Create Web App - Container") + +This is where you choose the container settings. They need to be: + +12. Image source needs to be Docker Hub or other registries +13. Single Container +14. Leave registry server URL as-is +15. Image and tag needs to be **johnm60/node-app-cosmos:latest** +16. Press **Review and Create** +17. If validation succeeds, the **Create** +18. The application will take a few minutes to provision. + +### Configuring the web app +After deployment, the application should exist and it should pull a container image of the code to allow the app to run inside the web application. The application creates items in the Cosmos database and so needs to have a setting that points to the connection string of the Cosmos database. + +1. Open up the web app settings +2. Select **Environment variables** +3. In this section, press **Add+** and this should allow you to enter a name and value +4. The name is **CONNECTION_STRING** +5. The value can now be got from the Cosmos database **Connection Strings** and then **Primary Connection String** - it is suggested that it is easiest to grab this value in another browser tab. +6. The changes need to be accepted and now the application (after a couple of minutes) should be working. +7. Test the application by going to its URL on its main summary. It should look like below: + +![alt-text](./img/test-web-app.png "Test Web App") + +## Discussion +Once deployed, discuss: +1. the application location +2. the application moving parts and how these may impact the performance of the application. + + +# Challenge Three - Run a load test against the application + +In this challenge, we are going to use the JMeter JMX file from the cloned GitHub repo. This makes a series of requests against the application. So you load test will need to use this script for this challenge. + +Follow the [steps](https://docs.microsoft.com/en-gb/azure/load-testing/how-to-create-and-run-load-test-with-jmeter-script#create-a-load-test) to configure a load test in the load test resource you created in challenge one. + +You will need to upload the "SampleAppParam.JMX" file from the cloned repository + +![alt-text](https://docs.microsoft.com/en-gb/azure/load-testing/media/how-to-create-and-run-load-test-with-jmeter-script/create-new-test-test-plan.png "upload test plan") + +The SampleApp.JMX file is parameterised, so that the URL of the target application is not defined in the JMX file, but can be injected by JMeter at runtime. The parameter is called "webapp". It will need to be entered in the page shown below: + +![alt-text](img/azure-load-test-parameters.png "set a parameter for the load test") + +Make sure to also set the monitoring to point to your applications resources. This will allow Azure load testing to capture metrics from this application. + +![alt-text](img/azure-load-test-monitoring.png "Set monitoring for the load test") + +Once the configuration is complete. If the load test has not started, press "Run" and wait. + +![alt-text](img/azure-load-test-run.png "load test run") + +Look closely at the results from the run. Some discussion points: +1. What is the overall request rate +2. Is this what is needed +3. Are there any errors? If, so any ideas why? +4. Look at the Cosmos database metrics + +The next step will be to tune the application and re-test to see if we can meet the original target request rate. + + +# Challenge Four - Tune the application and re-check + +For this challenge, we need to be able to hit a higher request rate. Some things to think about: + +1. How can we drive more requests into the application +2. What can we alter on the application to improve its throughput? +3. Remember, the application should not be over provisioned as that is a waste of money. + +What needed to change to achieve the desired request rate? + +When you do multiple test runs, these will all be listed under the same test. You can use the description field to annotate the tests to summarise the changes. + +![alt-text](img/azure-load-test-multiple-runs.png "load test multiple runs") + +You can also compare different runs to see their differences. This is really useful to see how things have changed between runs. + +![alt-text](img/azure-load-test-compare-multiple-runs.png "load test compare multiple runs") + +## Discuss +1. Did you hit the target requests per second? +2. What needed to change to do this? + + +# Challenge Five - Automate load testing in a GitHub Action + +You will need to think about: +1. In which GitHub repository to run the action +2. when the action will run +3. How the action step is authenticated +4. How to drive parameters into the test +5. How to set success criteria + +The overall approach for building a load testing action is documented [here](https://docs.microsoft.com/en-us/azure/load-testing/tutorial-cicd-github-actions) + +This repository has an action built for this, so in your cloned repository look for .github/workflows/worflow.yaml + +``` +# This is a basic workflow to help you get started with Actions + +name: Sample App deploy + +# Controls when the workflow will run +on: + push: + branches: + - main + +env: + LOAD_TEST_RESOURCE: "your-loadtest-resource-name" + LOAD_TEST_RESOURCE_GROUP: "your-loadtest-rg" + ENDPOINT_URL: "yourwebsite.azurewebsites.net" + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + + loadTest: + name: Load Test + runs-on: ubuntu-latest + steps: + - name: Checkout GitHub Actions + uses: actions/checkout@v2 + + - name: Login to Azure + uses: azure/login@v1 + continue-on-error: false + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: 'Azure Load Testing' + uses: azure/load-testing@v1 + with: + loadTestConfigFile: 'SampleApp.yaml' + loadTestResource: ${{ env.LOAD_TEST_RESOURCE }} + resourceGroup: ${{ env.LOAD_TEST_RESOURCE_GROUP }} + env: | + [ + { + "name": "webapp", + "value": "${{env.ENDPOINT_URL}}" + } + ] + + - uses: actions/upload-artifact@v2 + with: + name: loadTestResults + path: ${{ github.workspace }}/loadTest +``` + +The workflow has three steps: +1. Authenticate using an Azure AD service principal. This is the "AZURE_CREDENTIALS" GitHub secret. This service principal creation is outside of this pipelien and is documented [here](https://docs.microsoft.com/en-us/azure/load-testing/tutorial-cicd-github-actions#set-up-github-access-permissions-for-azure) +2. Run the load test using the "SampleApp.yaml" - which references the JMeter test file "SampleAppParam.xml" +3. Upload the results + +The heart of the load test configuration is the YAML file. It is this that defines the load test to be run + +``` +version: v0.1 +testName: SampleAppTestParam +testPlan: SampleAppParam.jmx +description: 'SampleApp Test Run' +engineInstances: 1 +failureCriteria: +- avg(response_time_ms) > 5000 +- percentage(error) > 20 +``` + +If all is correctly configured, any change to the repository will cause the GitHub action to run. + +![alt-text](img/azure-load-test-github-action.png "load test GitHub Action") + +So, now we have a working test that can be run interactively or as part of CI/CD pipeline. + +If you have managed to this successfully, all that is left is to apply this to your own application. + + + +# Challenge Six - Generate a JMeter Dashboard of the results + +This is an optional challenge. + +The Azure Load Test service is currently in preview. The feature to generate JMeter dashboards has been disabled. In the meantime, if the JMeter HTML dashboard is needed, then it needs to be done directly in JMeter. + +How you may generate the JMeter dashboard yourself. + +1. You need a copy of JMeter on a PC. The download is https://jmeter.apache.org/download_jmeter.cgi +2. There is a menu to generate an HTML report and command-line options +3. Both of these require the output csv from a test run + +Firstly, you need to download the load test results file from the Azure portal. This will be used later whether in the JMeter application or on the command-line. The download is zipped, so will need to be unzipped before it is used. + +![alt-text](img/azure-load-test-download-results.png "Download load test results") + +If you want to generate the HTML report interactively, then start JMeter and choose Generate HTML Report. This requires you to add the test results CSV and a default properties file https://github.com/apache/jmeter/blob/master/bin/user.properties + +![alt-text](img/azure-load-test-generate-html-report1.png "JMeter generate HTML Report") + +![alt-text](img/azure-load-test-generate-html-report2.png "JMeter generate HTML Report") + +This will then generate a folder holding the HTML to the standard JMeter report. + +![alt-text](img/azure-load-test-html-report.png "JMeter HTML Report") + +The command line approach is: + +``` +./jmeter.sh -g testreport.csv -o out +``` + +![alt-text](img/azure-load-test-command-line-html-report.png "JMeter HTML Report") + + + +# Challenge Seven - Load test your own application's endpoint + +This is where things get more interesting - to apply all of the above to an application of your own. What you will need to do is: +1. Create/amend a JMX file. You can either use the JMeter user interface or use an interactive JMX editor http://jmeter-plugins.org/editor/ +2. Run load test interactively +3. Discuss what changes may be needed to the application or test to get better results +4. Automate in GitHub action - setting success criteria diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..4a565f2f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# Use the official Node.js 14 image. +# https://hub.docker.com/_/node +FROM node:14.16 + +# Create and change to the app directory. +WORKDIR /usr/src/app + +# Copy application dependency manifests to the container image. +# A wildcard is used to ensure both package.json AND package-lock.json are copied. +# Copying this separately prevents re-running npm install on every code change. +COPY package*.json ./ + +# Install production dependencies. +RUN npm install --only=production + +# Copy local code to the container image. +COPY . . + +# Set the environment variable to make the app run on port 8092. +ENV PORT 8092 + +# Connection string to Cosmos +ENV CONNECTION_STRING="AccountEndpoint=https://.documents.azure.com:443/;AccountKey" + +# Expose port 8092 to the Docker host, so we can access it +# from the outside. +EXPOSE 8092 + +# Run the web service on container startup. +CMD [ "node", "server.js" ] diff --git a/README-OLD.md b/README-OLD.md new file mode 100644 index 00000000..2a752613 --- /dev/null +++ b/README-OLD.md @@ -0,0 +1,98 @@ +# WebApp with Cosmos DB + + A sample webapp deployed on app service with cosmos db as database. It counts the number of visitors visiting the page and inserts the same into a sample collection in Cosmos DB. + +### Installation + +1. In your terminal window, log into Azure and set a subscription(subscription which would contain the webapp) : + + az login + az account set -s mySubscriptionName + +2. Clone the sample application's source repository. The sample application is a Node.js app consisting of an Azure App Service web component and a Cosmos DB database. The repo also contains a PowerShell script that deploys the sample app to your Azure subscription. + + git clone https://github.com/Azure-Samples/nodejs-appsvc-cosmosdb-bottleneck.git + +3. Deploy the sample app using the PowerShell script. (Tip: macOS users can install PowerShell [here](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-macos?view=powershell-7.1)) + + cd SampleApp + .\deploymentscript.ps1 + +4. You will be prompted to supply a unique application name and a location (default is `eastus`). A resource group for the resources would be created with the same name. +5. Once deployment is complete, browse to the running sample application with your browser. + + https://.azurewebsites.net +## **Clean up resources** + +You may want to delete the resources to avoid to continue incurring charges. Use the `az group delete` command to remove the resource group and all related resources. + + az group delete --name myResourceGroup + +Similarly, you can utilize the **Delete resource group** toolbar button on the sample application's resource group to remove all the resources. + +## Load Test automation with GitHub Actions + +It is often useful to be able to do some form of load test of a new application deployment. Azure load test achieves this with a [GitHub Action](https://docs.microsoft.com/en-us/azure/load-testing/tutorial-cicd-github-actions). This may be used to drive a load test, but to also set some pass/fail criteria for the load test - allowing the action to report success or failure depending on whether the load test criteria are met or not. + +Besides the JMX file which defines the actual load test, there are 2 other files involved: +1. The load test YAML +2. The GitHib action which references the load test YAML definition. + +This repository has examples of all these configuration files. This includes a sample GitHub action. + +### Load test YAML + +The YAML file defines how to run the load test. A sample YAML is shown below: + +``` +version: v0.1 +testName: SampleAppTestParam +testPlan: SampleAppParam.jmx +description: 'SampleApp Test Run' +engineInstances: 1 +failureCriteria: +- avg(response_time_ms) > 5000 +- percentage(error) > 20 +``` + +Looking at the above YAML file: +1. This names and describes the load test +2. Which JMeter JMX file this will use +3. How many engine instances to run the load test +4. Some failure criteria. In this case, if the average response time is greater than 5 seconds or that the error is greater than 20%, the test fails. + + +### The GitHub Action + +Below is the GitHib Action step that does the load testing: + +``` +- name: 'Azure Load Testing' + uses: azure/load-testing@v1 + with: + loadTestConfigFile: 'SampleApp.yaml' + loadTestResource: ${{ env.LOAD_TEST_RESOURCE }} + resourceGroup: ${{ env.LOAD_TEST_RESOURCE_GROUP }} + env: | + [ + { + "name": "webapp", + "value": "${{env.ENDPOINT_URL}}" + } + ] +``` + +Looking at the action above: +1. it points to the YAML file which describes the test +2. It points to the name of the Azure load test service - which must have been previously provisioned +3. Sets an environment variable "webapp" into the load test with the endpoint of the system under test. +4. It should be noted that there is no mechanism at this time to define where to capture server-side metrics. + +Following this, there also needs to be an action to gather up the results of the load test: + +``` + - uses: actions/upload-artifact@v2 + with: + name: loadTestResults + path: ${{ github.workspace }}/loadTest +``` diff --git a/README.md b/README.md index 1528bdb7..0a404157 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,259 @@ -# WebApp with Cosmos DB - - A sample webapp deployed on app service with cosmos db as database. It counts the number of visitors visiting the page and inserts the same into a sample collection in Cosmos DB. +# Load Test Challenge Workshop -### Installation +This repository has been forked from the [Azure load test demo](https://github.com/Azure-Samples/nodejs-appsvc-cosmosdb-bottleneck) repository and amended into a challenge-based set of exercises for an interactive workshop to get hands on experience in using Azure load testing on an application. -1. In your terminal window, log into Azure and set a subscription(subscription which would contain the webapp) : +It uses the sample application and sample load test script in this repository as a starting point. + +The demonstration application is an web app hosted in Azure App Services with a Cosmos Database backend. + +The mission is to see if you can get **500 requests per second** from this application without spending more money than is necessary on the Azure resources. + +The final challenge is about adapting this to one of your own application or service. + +**There is an alternative deployment mechanism, that uses a pre-published container that can be used. Look [here](./Container-readme.md)** + +# Challenge One - Create Load Test Resource + +This may be done in the Azure portal or using automation. You could also try the [Quickstart](https://docs.microsoft.com/en-us/azure/load-testing/quickstart-create-and-run-load-test) + +![alt text](https://docs.microsoft.com/en-us/azure/load-testing/media/quickstart-create-and-run-load-test/quick-test-resource-overview.png "Quick start page") + +You need to consider the location of the load testing service with respect to the target system's location. Discuss why this may be important. + +So, now we have a load testing service and we have tested it out against a URL, now let's deploy an application in the next challenge. + + +# Challenge Two - Create a Demo System Under Test + +This challenge is about having our own application to test that we can later change to meet our performance requirements. + +On a Windows PC, there are three possible destinations for the code: +1. Directly on the Windows PC file system +2. In a Windows Subsystem for Linux (WSL) session +3. Azure Cloud Shell + +You need to decide which destination suits you best. Azure Cloud Shell should have all of the tooling installed already. + +## Installation + +1. Clone this GitHub repository to your PC + +``` +git clone https://github.com/jometzg/nodejs-appsvc-cosmosdb-bottleneck.git +``` + +2. In your terminal window, log into Azure and set a subscription(subscription which would contain the webapp) : az login az account set -s mySubscriptionName -2. Clone the sample application's source repository. The sample application is a Node.js app consisting of an Azure App Service web component and a Cosmos DB database. The repo also contains a PowerShell script that deploys the sample app to your Azure subscription. +3. Clone the sample application's source repository. The sample application is a Node.js app consisting of an Azure App Service web component and a Cosmos DB database. The repo also contains a PowerShell script that deploys the sample app to your Azure subscription. - git clone https://github.com/Azure-Samples/nodejs-appsvc-cosmosdb-bottleneck.git + git clone https://github.com/jometzg/nodejs-appsvc-cosmosdb-bottleneck.git -3. Deploy the sample app using the PowerShell script. (Tip: macOS users can install PowerShell [here](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-macos?view=powershell-7.1)) +4. Deploy the sample app using the PowerShell script. (Tip: macOS users can install PowerShell [here](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-macos?view=powershell-7.1)) cd SampleApp .\deploymentscript.ps1 -4. You will be prompted to supply a unique application name and a location (default is `eastus`). A resource group for the resources would be created with the same name. -5. Once deployment is complete, browse to the running sample application with your browser. +5. You will be prompted to supply a unique application name and a location (default is `eastus`). **It is best to leave this at 'eastus'**. A resource group for the resources would be created with the same name. +6. Once deployment is complete, browse to the running sample application with your browser. https://.azurewebsites.net -## **Clean up resources** -You may want to delete the resources to avoid to continue incurring charges. Use the `az group delete` command to remove the resource group and all related resources. +## Discussion +Once deployed, discuss: +1. the application location +2. the application moving parts and how these may impact the performance of the application. + + +# Challenge Three - Run a load test against the application + +In this challenge, we are going to use the JMeter JMX file from the cloned GitHub repo. This makes a series of requests against the application. So you load test will need to use this script for this challenge. + +Follow the [steps](https://docs.microsoft.com/en-gb/azure/load-testing/how-to-create-and-run-load-test-with-jmeter-script#create-a-load-test) to configure a load test in the load test resource you created in challenge one. + +You will need to upload the "SampleAppParam.JMX" file from the cloned repository + +![alt-text](https://docs.microsoft.com/en-gb/azure/load-testing/media/how-to-create-and-run-load-test-with-jmeter-script/create-new-test-test-plan.png "upload test plan") + +The SampleApp.JMX file is parameterised, so that the URL of the target application is not defined in the JMX file, but can be injected by JMeter at runtime. The parameter is called "webapp". It will need to be entered in the page shown below: + +![alt-text](img/azure-load-test-parameters.png "set a parameter for the load test") + +Make sure to also set the monitoring to point to your applications resources. This will allow Azure load testing to capture metrics from this application. + +![alt-text](img/azure-load-test-monitoring.png "Set monitoring for the load test") + +Once the configuration is complete. If the load test has not started, press "Run" and wait. + +![alt-text](img/azure-load-test-run.png "load test run") + +Look closely at the results from the run. Some discussion points: +1. What is the overall request rate +2. Is this what is needed +3. Are there any errors? If, so any ideas why? +4. Look at the Cosmos database metrics + +The next step will be to tune the application and re-test to see if we can meet the original target request rate. + + +# Challenge Four - Tune the application and re-check + +For this challenge, we need to be able to hit a higher request rate. Some things to think about: + +1. How can we drive more requests into the application +2. What can we alter on the application to improve its throughput? +3. Remember, the application should not be over provisioned as that is a waste of money. + +What needed to change to achieve the desired request rate? + +When you do multiple test runs, these will all be listed under the same test. You can use the description field to annotate the tests to summarise the changes. + +![alt-text](img/azure-load-test-multiple-runs.png "load test multiple runs") + +You can also compare different runs to see their differences. This is really useful to see how things have changed between runs. + +![alt-text](img/azure-load-test-compare-multiple-runs.png "load test compare multiple runs") + +## Discuss +1. Did you hit the target requests per second? +2. What needed to change to do this? + + +# Challenge Five - Automate load testing in a GitHub Action + +You will need to think about: +1. In which GitHub repository to run the action +2. when the action will run +3. How the action step is authenticated +4. How to drive parameters into the test +5. How to set success criteria + +The overall approach for building a load testing action is documented [here](https://docs.microsoft.com/en-us/azure/load-testing/tutorial-cicd-github-actions) + +This repository has an action built for this, so in your cloned repository look for .github/workflows/worflow.yaml + +``` +# This is a basic workflow to help you get started with Actions + +name: Sample App deploy + +# Controls when the workflow will run +on: + push: + branches: + - main + +env: + LOAD_TEST_RESOURCE: "your-loadtest-resource-name" + LOAD_TEST_RESOURCE_GROUP: "your-loadtest-rg" + ENDPOINT_URL: "yourwebsite.azurewebsites.net" + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + + loadTest: + name: Load Test + runs-on: ubuntu-latest + steps: + - name: Checkout GitHub Actions + uses: actions/checkout@v2 + + - name: Login to Azure + uses: azure/login@v1 + continue-on-error: false + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: 'Azure Load Testing' + uses: azure/load-testing@v1 + with: + loadTestConfigFile: 'SampleApp.yaml' + loadTestResource: ${{ env.LOAD_TEST_RESOURCE }} + resourceGroup: ${{ env.LOAD_TEST_RESOURCE_GROUP }} + env: | + [ + { + "name": "webapp", + "value": "${{env.ENDPOINT_URL}}" + } + ] + + - uses: actions/upload-artifact@v2 + with: + name: loadTestResults + path: ${{ github.workspace }}/loadTest +``` + +The workflow has three steps: +1. Authenticate using an Azure AD service principal. This is the "AZURE_CREDENTIALS" GitHub secret. This service principal creation is outside of this pipelien and is documented [here](https://docs.microsoft.com/en-us/azure/load-testing/tutorial-cicd-github-actions#set-up-github-access-permissions-for-azure) +2. Run the load test using the "SampleApp.yaml" - which references the JMeter test file "SampleAppParam.xml" +3. Upload the results + +The heart of the load test configuration is the YAML file. It is this that defines the load test to be run + +``` +version: v0.1 +testName: SampleAppTestParam +testPlan: SampleAppParam.jmx +description: 'SampleApp Test Run' +engineInstances: 1 +failureCriteria: +- avg(response_time_ms) > 5000 +- percentage(error) > 20 +``` + +If all is correctly configured, any change to the repository will cause the GitHub action to run. + +![alt-text](img/azure-load-test-github-action.png "load test GitHub Action") + +So, now we have a working test that can be run interactively or as part of CI/CD pipeline. + +If you have managed to this successfully, all that is left is to apply this to your own application. + + + +# Challenge Six - Generate a JMeter Dashboard of the results + +This is an optional challenge. + +The Azure Load Test service is currently in preview. The feature to generate JMeter dashboards has been disabled. In the meantime, if the JMeter HTML dashboard is needed, then it needs to be done directly in JMeter. + +How you may generate the JMeter dashboard yourself. + +1. You need a copy of JMeter on a PC. The download is https://jmeter.apache.org/download_jmeter.cgi +2. There is a menu to generate an HTML report and command-line options +3. Both of these require the output csv from a test run + +Firstly, you need to download the load test results file from the Azure portal. This will be used later whether in the JMeter application or on the command-line. The download is zipped, so will need to be unzipped before it is used. + +![alt-text](img/azure-load-test-download-results.png "Download load test results") + +If you want to generate the HTML report interactively, then start JMeter and choose Generate HTML Report. This requires you to add the test results CSV and a default properties file https://github.com/apache/jmeter/blob/master/bin/user.properties + +![alt-text](img/azure-load-test-generate-html-report1.png "JMeter generate HTML Report") + +![alt-text](img/azure-load-test-generate-html-report2.png "JMeter generate HTML Report") + +This will then generate a folder holding the HTML to the standard JMeter report. + +![alt-text](img/azure-load-test-html-report.png "JMeter HTML Report") + +The command line approach is: + +``` +./jmeter.sh -g testreport.csv -o out +``` + +![alt-text](img/azure-load-test-command-line-html-report.png "JMeter HTML Report") + + - az group delete --name myResourceGroup +# Challenge Seven - Load test your own application's endpoint -Similarly, you can utilize the **Delete resource group** toolbar button on the sample application's resource group to remove all the resources. +This is where things get more interesting - to apply all of the above to an application of your own. What you will need to do is: +1. Create/amend a JMX file. You can either use the JMeter user interface or use an interactive JMX editor http://jmeter-plugins.org/editor/ +2. Run load test interactively +3. Discuss what changes may be needed to the application or test to get better results +4. Automate in GitHub action - setting success criteria diff --git a/SampleApp.jmx b/SampleApp.jmx index eaabd5b4..2ad0e14f 100644 --- a/SampleApp.jmx +++ b/SampleApp.jmx @@ -33,7 +33,7 @@ - issacnitineastustest76eastus.azurewebsites.net + jjnewsuttotest.azurewebsites.net https @@ -93,7 +93,7 @@ - issacnitineastustest76eastus.azurewebsites.net + jjnewsuttotest.azurewebsites.net https @@ -146,7 +146,7 @@ - issacnitineastustest76eastus.azurewebsites.net + jjnewsuttotest.azurewebsites.net https diff --git a/SampleApp.yaml b/SampleApp.yaml index 90701e72..d222d444 100644 --- a/SampleApp.yaml +++ b/SampleApp.yaml @@ -1,5 +1,8 @@ version: v0.1 -testName: SampleAppTest -testPlan: SampleApp.jmx +testName: SampleAppTestParam +testPlan: SampleAppParam.jmx description: 'SampleApp Test Run' -engineInstances: 1 \ No newline at end of file +engineInstances: 1 +failureCriteria: +- avg(response_time_ms) > 5000 +- percentage(error) > 20 diff --git a/SampleAppParam.jmx b/SampleAppParam.jmx new file mode 100644 index 00000000..976c5cbf --- /dev/null +++ b/SampleAppParam.jmx @@ -0,0 +1,194 @@ + + + + + + false + true + false + + + + + + + + + + 10 + 0 + 180 + 240 + 5 + + + + false + -1 + + continue + + + + + + + ${udv_webapp} + + https + + lasttimestamp + GET + true + false + true + false + + HttpClient4 + 60000 + 60000 + + + + + + 10 + 50 + 180 + + + 50 + 50 + 240 + + + + + + + + + 10 + 0 + 180 + 240 + 5 + + + + false + -1 + + continue + + + + true + + + + false + 1 + = + + + + ${udv_webapp} + + https + + add + POST + true + false + true + false + + HttpClient4 + 60000 + 60000 + + + + + + 10 + 50 + 180 + + + 50 + 50 + 240 + + + + + + + + + 10 + 0 + 180 + 240 + 5 + + + + false + -1 + + continue + + + + + + + ${udv_webapp} + + https + + get + GET + true + false + true + false + + HttpClient4 + 60000 + 60000 + + + + + + 10 + 50 + 180 + + + 50 + 50 + 240 + + + + + + + + + udv_webapp + ${__BeanShell( System.getenv("webapp") )} + Web App URL + = + + + + + + + diff --git a/aks-manifest.yml b/aks-manifest.yml new file mode 100644 index 00000000..472fbfc3 --- /dev/null +++ b/aks-manifest.yml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: Service +metadata: + name: my-app-service +spec: + type: LoadBalancer + ports: + - port: 8092 + targetPort: 8092 + selector: + app: my-app + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-app-deployment +spec: + replicas: 3 + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-app + image: "johnm60/node-app-cosmos:latest" + ports: + - containerPort: 8092 + env: + - name: PORT + value: "8092" + - name: CONNECTION_STRING + value: "" diff --git a/deploymentscript.ps1 b/deploymentscript.ps1 index ff09d36f..b3358dc0 100644 --- a/deploymentscript.ps1 +++ b/deploymentscript.ps1 @@ -18,7 +18,7 @@ $subscriptionList = az account list -o json | ConvertFrom-Json $subscriptionList | Format-Table name, id, tenantId -AutoSize $selectedSubscription = $output.name Write-Host "Currently logged in to subscription """$output.name.Trim()""" in tenant " $output.tenantId -$selectedSubscription = Read-Host "Enter subscription Id ("$output.id")" +$selectedSubscription = Read-Host "Enter subscription Id ("$output.id") " $selectedSubscription = $selectedSubscription.Trim() if([string]::IsNullOrWhiteSpace($selectedSubscription)) { $selectedSubscription = $output.id @@ -98,4 +98,4 @@ while($true) { Write-Host "To delete the app, run command 'az group delete --name $resourceGroup'" exit } -} \ No newline at end of file +} diff --git a/img/azure-load-test-command-line-html-report.png b/img/azure-load-test-command-line-html-report.png new file mode 100644 index 00000000..d1b9fa01 Binary files /dev/null and b/img/azure-load-test-command-line-html-report.png differ diff --git a/img/azure-load-test-compare-multiple-runs.png b/img/azure-load-test-compare-multiple-runs.png new file mode 100644 index 00000000..eb0b632f Binary files /dev/null and b/img/azure-load-test-compare-multiple-runs.png differ diff --git a/img/azure-load-test-download-results.png b/img/azure-load-test-download-results.png new file mode 100644 index 00000000..fa5b951e Binary files /dev/null and b/img/azure-load-test-download-results.png differ diff --git a/img/azure-load-test-generate-html-report1.png b/img/azure-load-test-generate-html-report1.png new file mode 100644 index 00000000..6e2505c3 Binary files /dev/null and b/img/azure-load-test-generate-html-report1.png differ diff --git a/img/azure-load-test-generate-html-report2.png b/img/azure-load-test-generate-html-report2.png new file mode 100644 index 00000000..301c9ab6 Binary files /dev/null and b/img/azure-load-test-generate-html-report2.png differ diff --git a/img/azure-load-test-github-action.png b/img/azure-load-test-github-action.png new file mode 100644 index 00000000..0c8036fe Binary files /dev/null and b/img/azure-load-test-github-action.png differ diff --git a/img/azure-load-test-html-report.png b/img/azure-load-test-html-report.png new file mode 100644 index 00000000..b54e0cba Binary files /dev/null and b/img/azure-load-test-html-report.png differ diff --git a/img/azure-load-test-monitoring.png b/img/azure-load-test-monitoring.png new file mode 100644 index 00000000..ae1be5ec Binary files /dev/null and b/img/azure-load-test-monitoring.png differ diff --git a/img/azure-load-test-multiple-runs.png b/img/azure-load-test-multiple-runs.png new file mode 100644 index 00000000..2e4104b4 Binary files /dev/null and b/img/azure-load-test-multiple-runs.png differ diff --git a/img/azure-load-test-parameters.png b/img/azure-load-test-parameters.png new file mode 100644 index 00000000..6947ca2a Binary files /dev/null and b/img/azure-load-test-parameters.png differ diff --git a/img/azure-load-test-run.png b/img/azure-load-test-run.png new file mode 100644 index 00000000..b6db21ca Binary files /dev/null and b/img/azure-load-test-run.png differ diff --git a/img/create-web-app-basic.png b/img/create-web-app-basic.png new file mode 100644 index 00000000..ea2b4775 Binary files /dev/null and b/img/create-web-app-basic.png differ diff --git a/img/create-web-app-container.png b/img/create-web-app-container.png new file mode 100644 index 00000000..a380eb87 Binary files /dev/null and b/img/create-web-app-container.png differ diff --git a/img/select-cosmos.png b/img/select-cosmos.png new file mode 100644 index 00000000..78cd201e Binary files /dev/null and b/img/select-cosmos.png differ diff --git a/img/select-web-app.png b/img/select-web-app.png new file mode 100644 index 00000000..749a92d7 Binary files /dev/null and b/img/select-web-app.png differ diff --git a/img/test-web-app.png b/img/test-web-app.png new file mode 100644 index 00000000..b83c97c3 Binary files /dev/null and b/img/test-web-app.png differ diff --git a/load-alone-pipelines.yml b/load-alone-pipelines.yml new file mode 100644 index 00000000..3f0c4b63 --- /dev/null +++ b/load-alone-pipelines.yml @@ -0,0 +1,34 @@ + +trigger: +- main + +pool: + vmImage: ubuntu-latest + +variables: + webAppName: 'jjloadtestdemo' + serviceConnection: 'johns' + azureSubscriptionId: '2e500704-1ab3-49c6-bbbb-8b2bec98ffdc' + loadTestResource: 'loadtestingpreview' + loadTestResourceGroup: 'loadtest-rg' + location: 'North Europe' + + +stages: +- stage: LoadTest + displayName: Load Test + jobs: + - job: LoadTest + displayName: Load Test + pool: + vmImage: ubuntu-latest + steps: + - task: AzureLoadTest@1 + inputs: + azureSubscription: $(serviceConnection) + loadTestConfigFile: 'SampleApp.yaml' + resourceGroup: $(loadTestResourceGroup) + loadTestResource: $(loadTestResource) + + - publish: $(System.DefaultWorkingDirectory)/loadTest + artifact: results diff --git a/load-test-pipeline.yml b/load-test-pipeline.yml new file mode 100644 index 00000000..29ca89b9 --- /dev/null +++ b/load-test-pipeline.yml @@ -0,0 +1,138 @@ +# Node.js +# Build a general Node.js project with npm. +# Add steps that analyze code, save build artifacts, deploy, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript + +trigger: +- main + +pool: + vmImage: ubuntu-latest + +variables: + webAppName: 'jjloadtestdemo' + serviceConnection: 'johns' + azureSubscriptionId: '2e500704-1ab3-49c6-bbbb-8b2bec98ffdc' + loadTestResource: 'loadtestingpreview' + loadTestResourceGroup: 'loadtest-rg' + location: 'North Europe' + + +stages: +- stage: Build + displayName: Build + jobs: + - job: Build + displayName: Build + pool: + vmImage: windows-latest + + steps: + + - task: ArchiveFiles@2 + displayName: 'Archive files' + inputs: + rootFolderOrFile: '$(System.DefaultWorkingDirectory)' + includeRootFolder: false + archiveType: zip + archiveFile: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip + replaceExistingArchive: true + + - publish: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip + artifact: drop + +- stage: Deploy + displayName: Deploy + dependsOn: Build + condition: succeeded() + jobs: + - job: Deploy + displayName: Deploy + pool: + vmImage: windows-latest + + steps: + - task: AzureResourceManagerTemplateDeployment@3 + inputs: + deploymentScope: 'Resource Group' + azureResourceManagerConnection: $(serviceConnection) + subscriptionId: $(azureSubscriptionId) + action: 'Create Or Update Resource Group' + resourceGroupName: '$(webAppName)-rg' + location: '$(location)' + templateLocation: 'Linked artifact' + csmFile: '$(System.DefaultWorkingDirectory)/windows-webapp-template.json' + overrideParameters: '-webAppName $(webAppName) -hostingPlanName $(webAppName)-host -appInsightsLocation "East US" -databaseAccountId $(webAppName)db -databaseAccountLocation "East US"' + deploymentMode: 'Incremental' + deploymentOutputs: 'output' + + - task: PowerShell@2 + inputs: + targetType: 'inline' + script: | + $deploymentOutput= ConvertFrom-Json '$(output)' + $connectionStringValue= $deploymentOutput.azureCosmosDBAccountKeys.value + Write-Host "##vso[task.setvariable variable=connectionString;issecret=true;]$connectionStringValue" + + - task: AzureAppServiceSettings@1 + inputs: + azureSubscription: $(serviceConnection) + appName: '$(webAppName)' + resourceGroupName: '$(webAppName)-rg' + appSettings: | + [ + { + "name": "CONNECTION_STRING", + "value": "$(connectionString)", + "slotSetting": false + }, + { + "name": "MSDEPLOY_RENAME_LOCKED_FILES", + "value": 1, + "slotSetting": false + }, + { + "name": "SCM_DO_BUILD_DURING_DEPLOYMENT", + "value": true, + "slotSetting": false + }, + { + "name": "HEADER_VALUE", + "value": "$(mySecret)", + "slotSetting": false + } + ] + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: drop + + - task: AzureRmWebAppDeployment@4 + inputs: + ConnectionType: 'AzureRM' + azureSubscription: $(serviceConnection) + appType: 'webApp' + WebAppName: $(webAppName) + packageForLinux: '$(Pipeline.Workspace)/$(Build.BuildId).zip' + ScriptType: 'Inline Script' + InlineScript: 'npm install' + +- stage: LoadTest + displayName: Load Test + dependsOn: Deploy + condition: succeeded() + jobs: + - job: LoadTest + displayName: Load Test + pool: + vmImage: ubuntu-latest + steps: + - task: AzureLoadTest@1 + inputs: + azureSubscription: $(serviceConnection) + loadTestConfigFile: 'SampleApp.yaml' + resourceGroup: $(loadTestResourceGroup) + loadTestResource: $(loadTestResource) + + - publish: $(System.DefaultWorkingDirectory)/loadTest + artifact: results diff --git a/windows-webapp-template.json b/windows-webapp-template.json index 0633f246..f3896321 100644 --- a/windows-webapp-template.json +++ b/windows-webapp-template.json @@ -65,16 +65,40 @@ "[resourceId('microsoft.insights/components/', parameters('webAppName'))]" ] }, + { + "type": "Microsoft.Web/sites/basicPublishingCredentialsPolicies", + "apiVersion": "2022-03-01", + "name": "[concat(parameters('webAppName'), '/scm')]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('webAppName'))]" + ], + "properties": { + "allow": "true" + } + }, + { + "type": "Microsoft.Web/sites/basicPublishingCredentialsPolicies", + "apiVersion": "2022-03-01", + "name": "[concat(parameters('webAppName'), '/ftp')]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('webAppName'))]" + ], + "properties": { + "allow": "true" + } + }, { "type": "Microsoft.Web/serverfarms", "apiVersion": "2018-02-01", "name": "[parameters('hostingPlanName')]", "location": "[resourceGroup().location]", "sku": { - "name": "P2v3", - "tier": "PremiumV3", - "size": "P2v3", - "family": "Pv3", + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", "capacity": 1 }, "kind": "app", diff --git a/workshop.md b/workshop.md new file mode 100644 index 00000000..009dff02 --- /dev/null +++ b/workshop.md @@ -0,0 +1,250 @@ +# Load Test Workshop Challenges + +This is a short series of challanges that can be used in an interactive workshop to get hands on experience with Azure Load Testing. + +It uses the sample application and sample load test script in this repository as a starting point. + +The demo application is an web app hosted in Azure App Services with a Cosmos Database backend. The mission is to see if you can get 500 requests per second from this application without spending more money than is necessary on the Azure resources. + +Later challenges are about adapting this to one of your own application or service. + + +# Challenge One - Create Load Test Resource + +This may be done in the Azure portal or using automation. You could also try the [Quickstart](https://docs.microsoft.com/en-us/azure/load-testing/quickstart-create-and-run-load-test) + +![alt text](https://docs.microsoft.com/en-us/azure/load-testing/media/quickstart-create-and-run-load-test/quick-test-resource-overview.png "Quick start page") + +You need to consider the location of the load testing service with respect to the target system's location. Discuss why this may be important. + +So, now we have a load testing service and we have tested it out against a URL, now let's deploy an application in the next challenge. + + +# Challenge Two - Create a Demo System Under Test + +This challenge is about having our own application to test that we can later change to meet our performance requirements. + +## Installation + +1. Clone this GitHub repository to your PC + +``` +git clone https://github.com/jometzg/nodejs-appsvc-cosmosdb-bottleneck.git +``` + +2. In your terminal window, log into Azure and set a subscription(subscription which would contain the webapp) : + + az login + az account set -s mySubscriptionName + +3. Clone the sample application's source repository. The sample application is a Node.js app consisting of an Azure App Service web component and a Cosmos DB database. The repo also contains a PowerShell script that deploys the sample app to your Azure subscription. + + git clone https://github.com/Azure-Samples/nodejs-appsvc-cosmosdb-bottleneck.git + +4. Deploy the sample app using the PowerShell script. (Tip: macOS users can install PowerShell [here](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-macos?view=powershell-7.1)) + + cd SampleApp + .\deploymentscript.ps1 + +5. You will be prompted to supply a unique application name and a location (default is `eastus`). A resource group for the resources would be created with the same name. +6. Once deployment is complete, browse to the running sample application with your browser. + + https://.azurewebsites.net + +## Discussion +Once deployed, discuss: +1. the application location +2. the application moving parts and how these may impact the performance of the application. + + +# Challenge Three - Run a load test against the application + +In this challenge, we are going to use the JMeter JMX file from the cloned GitHub repo. This makes a series of requests against the application. So you load test will need to use this script for this challenge. + +Follow the [steps](https://docs.microsoft.com/en-gb/azure/load-testing/how-to-create-and-run-load-test-with-jmeter-script#create-a-load-test) to configure a load test in the load test resource you created in challenge one. + +You will need to upload the "SampleApp.JMX" file from the cloned repository + +![alt-text](https://docs.microsoft.com/en-gb/azure/load-testing/media/how-to-create-and-run-load-test-with-jmeter-script/create-new-test-test-plan.png "upload test plan") + +The SampleApp.JMX file is parameterised, so that the URL of the target application is not defined in the JMX file, but can be injected by JMeter at runtime. The parameter is called "webapp". It will need to be entered in the page shown below: + +![alt-text](img/azure-load-test-parameters.png "set a parameter for the load test") + +Make sure to also set the monitoring to point to your applications resources. This will allow Azure load testing to capture metrics from this application. + +![alt-text](img/azure-load-test-monitoring.png "Set monitoring for the load test") + +Once the configuration is complete. If the load test has not started, press "Run" and wait. + +![alt-text](img/azure-load-test-run.png "load test run") + +Look closely at the results from the run. Some discussion points: +1. What is the overall request rate +2. Is this what is needed +3. Are there any errors? If, so any ideas why? +4. Look at the Cosmos database metrics + +The next step will be to tune the application and re-test to see if we can meet the original target request rate. + + +# Challenge Four - Tune the application and re-check + +For this challenge, we need to be able to hit a higher request rate. Some things to think about: + +1. How can we drive more requests into the application +2. What can we alter on the application to improve its throughput? +3. Remember, the application should not be over provisioned as that is a waste of money. + +What needed to change to achieve the desired request rate? + +When you do multiple test runs, these will all be listed under the same test. You can use the description field to annotate the tests to summarise the changes. + +![alt-text](img/azure-load-test-multiple-runs.png "load test multiple runs") + +You can also compare different runs to see their differences. This is really useful to see how things have changed between runs. + +![alt-text](img/azure-load-test-compare-multiple-runs.png "load test compare multiple runs") + +## Discuss +1. Did you hit the target requests per second? +2. What needed to change to do this? + + +# Challenge Five - Automate load testing in a GitHub Action + +You will need to think about: +1. In which GitHub repository to run the action +2. when the action will run +3. How the action step is authenticated +4. How to drive parameters into the test +5. How to set success criteria + +The overall approach for building a load testing action is documented [here](https://docs.microsoft.com/en-us/azure/load-testing/tutorial-cicd-github-actions) + +This repository has an action built for this, so in your cloned repository look for .github/workflows/worflow.yaml + +``` +# This is a basic workflow to help you get started with Actions + +name: Sample App deploy + +# Controls when the workflow will run +on: + push: + branches: + - main + +env: + LOAD_TEST_RESOURCE: "eastloadtest" + LOAD_TEST_RESOURCE_GROUP: "loadtest-rg" + ENDPOINT_URL: "jjnewsuttotest.azurewebsites.net" + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + + loadTest: + name: Load Test + runs-on: ubuntu-latest + steps: + - name: Checkout GitHub Actions + uses: actions/checkout@v2 + + - name: Login to Azure + uses: azure/login@v1 + continue-on-error: false + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: 'Azure Load Testing' + uses: azure/load-testing@v1 + with: + loadTestConfigFile: 'SampleApp.yaml' + loadTestResource: ${{ env.LOAD_TEST_RESOURCE }} + resourceGroup: ${{ env.LOAD_TEST_RESOURCE_GROUP }} + env: | + [ + { + "name": "webapp", + "value": "${{env.ENDPOINT_URL}}" + } + ] + + - uses: actions/upload-artifact@v2 + with: + name: loadTestResults + path: ${{ github.workspace }}/loadTest +``` + +The workflow has three steps: +1. Authenticate using an Azure AD service principal. This is the "AZURE_CREDENTIALS" GitHub secret. This service principal creation is outside of this pipelien and is documented [here](https://docs.microsoft.com/en-us/azure/load-testing/tutorial-cicd-github-actions#set-up-github-access-permissions-for-azure) +2. Run the load test using the "SampleApp.yaml" - which references the JMeter test file "SampleApp.xml" +3. Upload the results + +The heart of the load test configuration is the YAML file. It is this that defines the load test to be run + +``` +version: v0.1 +testName: SampleAppTestParam +testPlan: SampleAppParam.jmx +description: 'SampleApp Test Run' +engineInstances: 1 +failureCriteria: +- avg(response_time_ms) > 5000 +- percentage(error) > 20 +``` + +If all is correctly configured, any change to the repository will cause the GitHub action to run. + +![alt-text](img/azure-load-test-github-action.png "load test GitHub Action") + +So, now we have a working test that can be run interactively or as part of CI/CD pipeline. + +If you have managed to this successfully, all that is left is to apply this to your own application. + + + +# Challenge Six - Generate a JMeter Dashboard of the results + +This is an optional challenge. + +The Azure Load Test service is currently in preview. The feature to generate JMeter dashboards has been disabled. In the meantime, if the JMeter HTML dashboard is needed, then it needs to be done directly in JMeter. + +How you may generate the JMeter dashboard yourself. + +1. You need a copy of JMeter on a PC. The download is https://jmeter.apache.org/download_jmeter.cgi +2. There is a menu to generate an HTML report and command-line options +3. Both of these require the output csv from a test run + +Firstly, you need to download the load test results file from the Azure portal. This will be used later whether in the JMeter application or on the command-line. The download is zipped, so will need to be unzipped before it is used. + +![alt-text](img/azure-load-test-download-results.png "Download load test results") + +If you want to generate the HTML report interactively, then start JMeter and choose Generate HTML Report. This requires you to add the test results CSV and a default properties file https://github.com/apache/jmeter/blob/master/bin/user.properties + +![alt-text](img/azure-load-test-generate-html-report1.png "JMeter generate HTML Report") + +![alt-text](img/azure-load-test-generate-html-report2.png "JMeter generate HTML Report") + +This will then generate a folder holding the HTML to the standard JMeter report. + +![alt-text](img/azure-load-test-html-report.png "JMeter HTML Report") + +The command line approach is: + +``` +./jmeter.sh -g testreport.csv -o out +``` + +![alt-text](img/azure-load-test-command-line-html-report.png "JMeter HTML Report") + + + +# Challenge Seven - Load test your own application's endpoint + +This is where things get more interesting - to apply all of the above to an application of your own. What you will need to do is: +1. Create/amend a JMX file. You can either use the JMeter user interface or use an interactive JMX editor http://jmeter-plugins.org/editor/ +2. Run load test interactively +3. Discuss what changes may be needed to the application or test to get better results +4. Automate in GitHub action - setting success criteria +