A couple of years ago, I wrote an article about a similar topic, which explored how can we use Azure Devops pipelines to deploy solutions to any server which is not exposed to internet directly. That article was based on using classic release pipeline to configure the deployment steps.
With YAML based pipelines being more powerful and recommended now, when I got a similar requirement, I explored how can we achieve this using the same. There was surely some good learnings along the way.
Problem Statement
In this case, the requirement was a bit different than the similar implementation I had been involved with earlier. The customer wanted to host their internal web application in Azure App Service which should not be accessible from over the internet. They wanted this web application to be accessible only from within their corporate network.
To add to this, the development of the app was being done by some other vendor who would have the Integration environment hosted in a VM within their own corporate network.
So, the ask was that they have a central repository hosted in Azure Devops within the customer environment. The developers from vendor will connect with the same repo to do the development.
And use the build and release pipeline to deploy the output to their integration environment. Once the application is tested in integration environment, another pipeline deploys the same build output to Azure App Service in customer’s environment.
Azure App Service
First step was to find out the options available to close access of the website hosted within Azure App Service, as by default Azure App Service always gets a public IP. This MS article explains the networking options including Access Restrictions based on incoming IP addresses, Service Endpoints, Internal Load Balancer and Private Endpoint among others.
Since the customer didn’t want to have any public endpoint at all and the application was small enough, they decided to go for Private Endpoint. In this case, the App Service gets a Private IP from within the virtual network which restricts access to the app from resources within the virtual network and any other integrated networks (like VPN to corporate network).
Azure Devops Agent
During the discussions about App Service networking, this also became important to find out solution for how the Azure Devops will be able to reach the App Service.
Even with Access Restriction and Service Endpoints, it’s not easy to achieve if Microsoft hosted agents are used because Azure DevOps uses the Azure global network, IP ranges vary over time. MS publishes a JSON file weekly listing all IP addresses per geography. This article explains how to find and extract the list of IP addresses.
However, this essentially opens up the traffic from all the devops agents hosted in that region and not restricted the IP used by your pipeline agent. This was one of the considerations to choose Private Endpoint for the App Service.
This left us with 2 alternatives:
A simple comparison of the two highlights that “If you like Microsoft-hosted agents but are limited by what they offer, you should consider scale set agents“.
So, we decided to make use of Scale Set Agents. The same article explains step-by-step how to create it. We created the scale sets within the same Resource Group in which the App Service was created.
I created a scale set agent:
az group create \
--location westeurope \
--name RG-CUPID
az vmss create \
--name vmssagentspool \
--resource-group RG-CUPID \
--image UbuntuLTS \
--vm-sku Standard_D2_v3 \
--storage-sku StandardSSD_LRS \
--authentication-type SSH \
--generate-ssh-keys \
--instance-count 2 \
--disable-overprovision \
--upgrade-policy-mode manual \
--single-placement-group false \
--platform-fault-domain-count 1 \
--load-balancer ""
The script above created a resource group and a scale set inside it.
The same article shows how to create Azure Devops Scale Set Agent Pool based on the scale set agent.
Plus, it provides a cost advantage as well, since the VM instances can get scaled down to “0”, if there are no jobs pending to run.
Azure Devops Pipeline to Deploy to App Service
Now that we have our scale set agent running within the same VNET, it can reach the app service via the private IP. But how to configure the pipeline to use our scale set agent. It couldn’t have been any easier than this. All we have to do is to specify the name of the custom scale set agent pool under label “pool” in the YAML file.
trigger:
- main
pool: "CUPID Agent Pool"
resources:
- repo: self
clean: true
...<other deployment tasks>...
- task: AzureRmWebAppDeployment@4
inputs:
ConnectionType: 'AzureRM'
azureSubscription: 'XXXX'
appType: 'webApp'
WebAppName: 'Cupidweb'
packageForLinux: '$(System.ArtifactsDirectory)'
Now, when we trigger the pipeline, the app gets deployed in the App Service based on the deployment task over private endpoint.
So, this part works fine – essentially we have our devops agent running in VMs which are within the same VNET. But what about the development environment, which is completely outside this Azure environment and hosted within the vendor network?
Azure Devops Pipeline to Deploy to On-Premises
This scenario is practically same as explained in this article, however, we need a little different configuration for YAML based pipelines to achieve the same.
First step is to create an Environment with Virtual machine resource. We need to install devops agent on the VM for deployments. This MS article explains the steps, so I won’t repeat the same.
Copy the script and run it on the VM hosting integration environment. For this article, I just ran that on my windows laptop.
Do not use PowerShell ISE, it may hang showing “connecting to server…” and may not complete.
Once the script runs successfully, the resource gets added under the environment.
Now, all we have to do in the pipeline is to refer to that environment in the YAML file, which will ensure that the agent hosted in that corresponding VM resource will take care of it. For the sake of simplicity, I just used a File Copy task to copy the output files to the VM hosting Integration environment.
...<other deployment tasks>...
- stage: Deploy
dependsOn: Build
displayName: Deploy to SIT
jobs:
- deployment: VMDeploy
displayName: Deploy to VM
environment:
name: SIT-OnPrem
resourceType: VirtualMachine
strategy:
runOnce:
deploy:
steps:
- task: DownloadPipelineArtifact@2
displayName: "Download Build Artifact"
inputs:
artifact: Cupid-$(Build.BuildNumber)
targetPath: '$(System.ArtifactsDirectory)'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(System.ArtifactsDirectory)'
ArtifactName: 'drop'
publishLocation: 'FilePath'
TargetPath: '\\ANUPAM-PC\AzureDeploy'
FileCopyOptions: '/is /it'
When the pipeline runs, you can see the agent running on that Integration VM processes it.
And puts the text file (in this case) to the shared path specified. Of course, any other task can be used as per the requirement.
This article is to highlight this powerful capability of Azure Devops, where a single Azure devops instance can build and deploy any application across various closed networks.
Hope this helps.
Enjoy,
Anupam