# Continuous Deployment with Kubernetes and Load Testing with JMeter

# Challenge: Part 2 Continuous Deployment and Load Testing

Deploy the provided application to your chosen cloud provider using any service that runs the application as a Docker container. Then, create a test plan for the application's home page and generate a testing report using JMeter.

The application needs to:

Available for anyone with Internet access only during the challenge recording

## Prerequisites

* Azure Devops Account
    
* Cloud Azure Account
    
* Completed Challenge 1. This is a continuation of Series 1. Challenge 1 link: [https://hashnode.com/edit/cm795upao000609js5scu0j9u](https://hashnode.com/edit/cm795upao000609js5scu0j9u)
    

### **Step 1: Create Azure Container Registry and Azure Devops Connections**

**Create Azure Registry:**

1. Go to the Azure portal.
    
2. Create a new resource group.
    
3. Click on "Container registries".
    
4. Click on "Create container registry".
    
5. On the "Create container registry" page, enter the following information:
    

* Registry name - Enter **mdcregistryID**. # The name needs to be unique change the last two letters by the initials of your name
    
* Region - Select the region where you want to create your registry.
    
* Pricing tier - Select the pricing tier that you want to use for your registry.
    
* Security - Select the security options that you want to use for your registry.
    
* Choose the Resource Group you created earlier
    

**Azure Devops Connections:**

1. Navigate to Azure DevOps Service Connections
    
2. Sign in to Azure DevOps.
    
3. Select your organization and project.
    
4. In the left sidebar, go to Project Settings.
    
5. Under Pipelines, click Service Connections.
    
6. Click New Service Connection.
    
7. Choose Docker Registry
    
8. Authentication Type Select Service Principal
    
9. Enter your settings and click create
    

### **Step 2: Create Kubernetes**

1. Log in to Azure
    
2. Log in to your **Azure account** via CLI: `az login`
    
3. Create the **Kubernetes cluster**: `az aks create --resource-group projeto-flask --name k8s-cluster --node-count 2 --enable-addons monitoring --generate-ssh-keys`
    
4. **Step 3: Get ACR credentials and create a secret on Kubernetes**
    
5. Get ACR name: `az acr list --query "[].{Name:name, ResourceGroup:resourceGroup}" --output table`
    
6. Get Docker Server Credential: `az acr show --name Your_ACR_Name --query loginServer --output tsv`
    
7. Get Docker Username: `az acr credential show --name Your_ACR_Name --query username --output tsv`
    
8. Get Docker Password: `az acr credential show --name Your_ACR_Name --query passwords[0].value --output tsv`
    
9. Access your Kubernetes: `az aks get-credentials --resource-group Your_Resource_Group_Name --name mdc-aks`
    
10. Create Secret:
    
    ```plaintext
      kubectl create secret docker-registry acr-secret \
              --docker-server=Your_Docker_Server \ 
              --docker-username=Your_Docker_Username \ 
              --docker-password=Your_Docker_Password \
    ```
    
11. Configure Assign AcrPull Role:
    
    ```plaintext
    ACR_ID=$(az acr show --name mdcrepositorychiroli --query "id" -o tsv)
    
    AKS_ID=$(az aks show --resource-group projeto-flask --name k8scluster --query "identityProfile.kubeletidentity.objectId" -o tsv)
    
    az role assignment create --assignee $AKS_ID --role AcrPull --scope $ACR_ID
    ```
    

### **Step 4: Create Azure Resource Manager on Connections**

1. Navigate to Azure DevOps Service Connections
    
2. Sign in to Azure DevOps.
    
3. Select your organization and project.
    
4. In the left sidebar, go to Project Settings.
    
5. Under Pipelines, click Service Connections.
    
6. Click New Service Connection.
    
7. Choose Azure Resource Manager
    
8. Enter your settings, you can use the default setting and click create.
    

### **Step 5: Create Dockerfile**

1. Access Azure Repo
    
2. Go to: `assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0`
    
3. Edit Dockerfile
    

```dockerfile
# Etapa de build
FROM node:15 AS builder

# Define o diretório de trabalho
WORKDIR /app

# Copia os arquivos necessários
COPY package.json package-lock.json ./

# Instala dependências
RUN npm install

# Copia o restante dos arquivos da aplicação
COPY . .

# Faz o build da aplicação (se necessário)
RUN npm run build

# Etapa de produção
FROM node:15

# Define o diretório de trabalho
WORKDIR /app

# Copia arquivos do estágio anterior
COPY --from=builder /app /app

# Instala as dependências de produção
RUN npm install --only=production

# Define o comando para iniciar a aplicação
CMD ["npm", "start"]

# Exposição de porta (opcional)
EXPOSE 3000
```

### **Step 6: Create Deployment and Load Balancer**

1. Access Azure Repo
    
2. Access the repository on the same place where azure-pipelines.yml and create the file `deployment_service.yml`
    
    ```yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nodejs-deployment
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nodejs
      template: 
        metadata:
          name: nodejs
          labels:
            app: nodejs  
        spec:
          containers:
            - name: nodejsapp
              image: mdcrepositorychiroli.azurecr.io/node.js:latest
              ports:
                - containerPort: 3000
          imagePullSecrets:
            - name: acr-secret    
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: nodejs-service
      labels: 
        app: nodejs
    spec:
      type: LoadBalancer 
      selector:
        app: nodejs
      ports: 
        - port: 3000
          targetPort: 3000
    ```
    

### **Step 7: Create a file for the Jmeter test**

1. Access Azure Repo
    
2. Access the repository on the same place where azure-pipelines.yml and create the file `test.jmx`
    
    ```plaintext
    <?xml version="1.0" encoding="UTF-8"?>
    <jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
      <hashTree>
        <!-- Test Plan -->
        <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Basic Test Plan" enabled="true">
          <stringProp name="TestPlan.comments"></stringProp>
        </TestPlan>
        <hashTree>
          <!-- Thread Group -->
          <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Load Test Group" enabled="true">
            <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
            
            <!-- Adicionando o Loop Controller -->
            <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControllerGui" testclass="LoopController" testname="Loop Controller" enabled="true">
              <boolProp name="LoopController.continue_forever">false</boolProp>
              <stringProp name="LoopController.loops">1</stringProp>
            </elementProp>
            
            <stringProp name="ThreadGroup.num_threads">10</stringProp>
            <stringProp name="ThreadGroup.ramp_time">5</stringProp>
            <boolProp name="ThreadGroup.scheduler">false</boolProp>
          </ThreadGroup>
          <hashTree>
            <!-- HTTP Sampler -->
            <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true">
              <stringProp name="HTTPSampler.domain">${__P(host, localhost)}</stringProp>
              <stringProp name="HTTPSampler.port">3000</stringProp>
              <stringProp name="HTTPSampler.protocol">http</stringProp>
              <stringProp name="HTTPSampler.path">/</stringProp>
              <stringProp name="HTTPSampler.method">GET</stringProp>
            </HTTPSamplerProxy>
            <hashTree/>
          </hashTree>
        </hashTree>
      </hashTree>
    </jmeterTestPlan>
    ```
    

### **Step 7: Configure the Pipeline**

1. Access Azure Repo
    
2. Edit file `azure-pipelines.yml`
    
3. With the new configuration and the configuration of the last challenge the pipeline looks like this:
    

```yaml
trigger:
- main

pr:
  branches:
    include:
    -  main

stages:
  - stage: BuildAndTest
    displayName: Build and Test Job
    pool:
      vmImage: 'ubuntu-latest'
    jobs:
    - job: Build
      displayName: 'Build and Test Job'
      steps:
      - checkout: self
        path: 'src'
        displayName: 'Checkout code'

      - task: UseNode@1
        inputs:
          version: '15.x'
        displayName: 'Install Node.js'

      - script: npm install
        workingDirectory: $(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0
        displayName: 'Install dependencies'

      - script: npm run lint
        workingDirectory: $(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0
        displayName: 'Linter (ESLint)'

      - script: npm install --save-dev prettier
        workingDirectory: $(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0
        displayName: 'Install Formatter (Prettier)'

      - script: npm run prettier -- --write
        workingDirectory: $(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0
        displayName: 'Format Code (Prettier)'

      - script: CI=true npm run test
        workingDirectory: $(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0
        displayName: 'Test (Jest)'

      - script: npm run build
        workingDirectory: $(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0
        displayName: 'Run Build'

      - task: Docker@2
        inputs:
          containerRegistry: 'MdcRegistry'
          repository: 'Node.js'
          command: 'buildAndPush'
          Dockerfile: '$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0/Dockerfile'
          buildContext: '$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'
          tags: 'latest'
        displayName: 'Build and Push Docker Image'
        continueOnError: true  

      - task: AzureCLI@2
        inputs:
          azureSubscription: 'MdcAzureRm'
          scriptType: 'bash'
          scriptLocation: 'inlineScript'
          inlineScript: |
            az aks get-credentials --resource-group projeto-flask --name k8scluster --overwrite-existing
            kubectl apply -f $(Build.SourcesDirectory)/deployment_service.yml
            kubectl get services
        displayName: 'Execute Kubernetes'
        continueOnError: true


      - script: |
          echo "Installing JMeter..."
          sudo apt-get update -y
          sudo apt-get install -y wget openjdk-11-jre  # Install Java and wget
    
          wget https://downloads.apache.org//jmeter/binaries/apache-jmeter-5.6.3.tgz
          tar -xvzf apache-jmeter-5.6.3.tgz
          sudo mv apache-jmeter-5.6.3 /opt/jmeter
          
          # Adiciona JMeter ao PATH corretamente
          echo "export PATH=/opt/jmeter/bin:\$PATH" | sudo tee -a /etc/profile
          export PATH=/opt/jmeter/bin:$PATH  # Atualiza PATH na sessão atual

          echo "JMeter installed successfully."
        displayName: 'Install JMeter'
      
      - script: |
          echo "Creating JMeter test file..."
          cp $(Build.SourcesDirectory)/test.jmx /opt/jmeter/test.jmx
        displayName: 'Create JMeter Test File'

      - task: AzureCLI@2
        inputs:
          azureSubscription: 'MdcAzureRm'
          scriptType: 'bash'
          scriptLocation: 'inlineScript'
          inlineScript: |
            NODEJS_IP=$(kubectl get svc nodejs-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
            echo "##vso[task.setvariable variable=NODEJS_IP]$NODEJS_IP"
            echo "Fetched IP: $NODEJS_IP"
        displayName: 'Retrieve NodeJS Service External IP'
        continueOnError: true

      - script: |
          export PATH=/opt/jmeter/bin:$PATH
          jmeter -v
          jmeter -n -t /opt/jmeter/test.jmx -l /opt/jmeter/results.jtl -j jmeter.log -Jhost=$NODEJS_IP
          cat /opt/jmeter/results.jtl
          cat jmeter.log
        displayName: 'Run JMeter'
```

### **Step 8: Access the application**

1. Log in to Azure
    
2. Log in to your **Azure account** via CLI: `az login`
    
3. Access your Kubernetes: `az aks get-credentials --resource-group Your_Resource_Group_Name --name mdc-aks`
    
4. Execute the command `az get services` and access the page with the external IP
    
5. Access [`http://Your_external_ip:3000/`](http://4.246.235.225:3000/)
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1739821392267/89cdac73-cffe-43b4-a1f5-abb7357c4614.png align="center")

### **Step 8: See the Jmeter test results**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1739828359065/9a9c69f9-6aa5-40ae-ba0e-d7a8fe7a02ed.png align="center")
