I have been using Azure Devops for a while. Like most of the cloud products out there this is one which gets constant refresh. My plan is to document the steps for building, testing and deploying an app to Azure Kubernetes Service using Azure Devops. So let's start.
Prerequisties
1) My code & Dockerfile
What I got is a simple .NET web api project. And below is my Dockerfile.
2) My Github link
3) Azure Container Registry
This is where we push our Docker image to. Below is a screenshot of Azure Container Registry from Azure Portal.
Azure Kubernetes Service.
This is the Azure managed Kubernetes Service. This is where we will deploy the containers into.
Azure Devops
Now that we have talked about the prerequisite required we will get ritht to it. We will walk through how we configure Azure Devops to build and push Docker image to registry and then deploying that image to AKS and running integration tests against it using SoapUI Pro
So we will create 2 pipelines in Azure Devops. (Details below).
At the end of build pipeline the expected output is to have a new Docker Image in Azure Contaoner registry. This will be the artifact we will deploying to AKS as part of release pipeline.
Below is my yaml file.
# Dotnet
# Unit the dotnet project. xUnit and NSubtitute
# Docker
# Build and push an image to Azure Container Registry
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker
# Publsih
# Now we get the tag of the published id and update the k8 Deployment yaml image
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker
trigger:
- master
resources:
- repo: self
variables:
# Container registry service connection established during pipeline creation
dockerRegistryServiceConnection: '7c46ccde-aa97-4bd0-be94-abcd31bbe20b'
containerRegistry: 'dockerstore'
imageRepository: 'hospital'
dockerfilePath: '**/Dockerfile'
tag: '$(Build.BuildId)-$(Build.SourceVersion)'
# Agent VM image name
vmImageName: 'ubuntu-latest'
stages:
- stage: UnitTestBuildAndPublish
displayName: Unit Test then Build and Push Docket to Register then Publish of release pipeline
jobs:
- job: UnitTest
displayName: Running Unit tests for the Hospital Microservice
pool:
vmImage: $(vmImageName)
steps:
- task: DotNetCoreCLI@2
inputs:
command: 'test'
- job: Build
dependsOn: UnitTest
displayName: Build and push to container registry
pool:
vmImage: $(vmImageName)
steps:
- task: Docker@2
displayName: Build and push an image to container registry
inputs:
command: buildAndPush
repository: 'hospital'
dockerfile: $(dockerfilePath)
containerRegistry: $(containerRegistry)
tags: |
$(tag)
- job: PreReleasePrepForhospitalMicroservice
dependsOn: Build
displayName: Pre Release Preparation (Bash build id and Publish for Release pipeline)
pool:
vmImage: $(vmImageName)
steps:
- task: Bash@3
inputs:
targetType: 'inline'
script: |
# Write your commands here
cat '$(Build.SourcesDirectory)/hospital.yaml'
value=`cat '$(Build.SourcesDirectory)/hospital.yaml'`
value=${value//##BUILD_ID##/$(tag)}
echo "$value" > '$(Build.SourcesDirectory)/hospital_build.yaml'
value1=`cat '$(Build.SourcesDirectory)/hospital_build.yaml'`
echo "$value1"
mkdir '$(Pipeline.Workspace)/hospital'
echo 'after creation of hospital'
- task: PublishPipelineArtifact@1
inputs:
targetPath: '$(Pipeline.Workspace)'
artifact: 'hospital'
publishLocation: 'pipeline'
Before talking about what is happening in above pipeliene; it maybe better to look at Azure Pipeline hierachy first.
stages:
- stage: A
jobs:
- job: A1
timeoutInMinutes: 10
pool:
vmImage: 'ubuntu-16.04'
steps:
- bash: echo "Hello world"
- job: A2
steps:
- bash: echo "A"
- stage: B
jobs:
- job: B1
steps:
- bash: echo "B"
- job: B2
steps:
- bash: echo "A"
So the hierarchy is like above. You can have a list of stages which is the top level. Underneath it you can have a list of jobs which can further be broken down into steps and then steps into tasks. Also you can assign the kind of Build agent you want at job level. So you can have one job using Windows 10 agent another using Ubuntu.
OK now that the pipeline hierarchy is clear; let's go back to the original build pipeline I have above.
In that we have only 1 Stage called UnitTestBuildAndPublish.
There are 3 jobs within it.
Release Pipeline
Below is the screenshot of my release pipeline tasks page. As you can see I am using two Agents.
Download Pipeline Artifact (running on Ubuntu agent)
So first step is to Download the Pipeline Artifact. We need this for K8 yaml file. In the Build pipleine we have updated the ##BuildID## placeholder with the real tag of the image that was pushed to ACR.
Kubectl Apply (running on Ubuntu agent)
We will use this task to apply the above yaml file against the "NM" namespace of AKS. The namespace is a way to logically have multpile environments in Kubernetes. The above yaml shows that the pods and services will be deployed to "NM" namespace.
The above command confirms that the services and pods are deployed in that namespace.
Bash Script Task (running on Ubuntu agent)
This is more of a hack I put in there. This task just sleeps and holds the pipeline for few seconds. I wanted to ensure that the Pods are given enough time to be up and running. This is required because if the Pods are not Up SoapUI Pro will fail all the tests. Which is not the right thing.
Azure SQL Dacpac Task (running on Ubuntu agent)
This is for database deployment. I am not doing anything currently for this project. That is why it is in disabled state. But this is the task you would use to run the DDL and DML statements against your Azure SQL.
API Management - Create/Update API (running on Windows 10 agent)
This is task, I got from the market lace which helps me deploying API definitions to Azure API Management. It accepts OAS 3.0 defintion that I build using Swagger editor. Below is my API definition.
openapi: 3.0.0
# Added by API Auto Mocking Plugin
servers:
- description: SwaggerHub API Auto Mocking
url: https://virtserver.swaggerhub.com/BRB/Hospital/1.0.0
info:
description: Demo API on Hospital.
version: "1.0.0"
title: Hospital
contact:
email: [email protected]
license:
name: Apache 2.0
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
tags:
- name: Hospital
description: Hospital related matters
paths:
/hospital/{hospitalId}:
get:
tags:
- Hospital
summary: Finds hospital by id
operationId: GetHospitalById
description: |
By passing in the valid id, you can search for
the hospial details in the database
parameters:
- in: path
name: hospitalId
description: pass the hospitalId for looking up the database
required: true
schema:
type: integer
example: 415
responses:
'200':
description: search result matching criteria
content:
application/json:
schema:
$ref: '#/components/schemas/Hospital'
'400':
description: bad input parameter
delete:
tags:
- Hospital
summary: deletes a hospital from list
operationId: DeleteHospial
description: Deletes a hospital from list
parameters:
- name: hospitalId
in: path
description: Hospital id to delete
required: true
schema:
type: string
responses:
'400':
description: "Invalid ID supplied"
'404':
description: "Hospital not found"
/hospital:
post:
tags:
- Hospital
summary: adds a hospital to the list
operationId: AddHospital
description: Adds a hospital to the list
parameters:
- name: Authorization
in: header
required: true
schema:
type: string
responses:
'201':
description: hospital added
'400':
description: invalid input, object invalid
'409':
description: hospital already exists
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Hospital'
description: Add Hospital to list
patch:
tags:
- Hospital
summary: Updates a hospital in the list
operationId: UpdateHospital
description: Updates a hospital to the list
responses:
'200':
description: hospital updated
'400':
description: invalid input, object invalid
'404':
description: hospital does not exists
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Hospital'
description: Updates Hospital in the list
components:
schemas:
Hospital:
type: object
required:
- Id
- Name
- Address
- City
- Pincode
properties:
id:
type: integer
example: 56
name:
type: string
example: Epidemic Diseases Hospial
Address:
type: string
example: MAJESTIC
City:
type: string
example: Bangalore
Pincode:
type: integer
example: 562110
SoapUI Pro for Azure DevOps (running on Win10 agent)
After all of the above steps are completed; it is time to do the integration tests. And make sure everything is complying. SLA will be met. We didn't break anything.
Once the tests are completed SoapUI exports some of the reports to Azure Devops. Below is one such report.
Once all the test passes; we can promote the deployments to higher environments. This too can be automated nicely in Azure Devops. Each environment is called stages and we can add Manual Approver too as part of environment promotion. Below is a screenshot of the stages graphic that Azure Devops provide.
So that's it. This is my github page: https://github.com/mohammednaseem/aksistio-hospital
if you run into trouble with Azure Devops let me know.