Azure’s Platform-as-a-Service offering provides the promise of deploying applications, databases, file shares, buses, caches, and other infrastructure without ever needing to spin up a server. That means no operating system to manage, no IP addresses, no need to configure IIS or SQL Server or any of those other platforms. This hopefully lets us spend less time yak-shaving; but most of us are used to deploying things to servers, and so we’ll need to integrate this new serverless mindset with our existing deployment tool chain.
I dove into this proof-of-concept expecting to write piles of PowerShell, but it turns out the teams at Microsoft and Octopus Deploy have already done most of the heavy lifting – as you’ll see.
My goal is to be able to build entirely new test environments from scratch in as few steps as possible. It turns out, with the right planning, fully-automated deployments of both application and infrastructure are possible.
The Azure Story
First of all, you’ll want a fully-updated Visual Studio installation with the Azure SDK enabled. We like to keep everything necessary to run an application or service together in one repository: code, schema, and now – infrastructure! The Azure Resource Manager (ARM) lets us define infrastructure using JSON files called templates, and Octopus lets us deploy them just as easily as we deploy applications.
I’m going to show a fairly limited example here – just a Nancy service and a database – but ARM templates are very powerful: you can build just about anything Azure provides with them, and Visual Studio has a number of templates to get you started. To start out, create a new Azure Resource Group project alongside your existing application projects.
You’ll have the opportunity straight away to select a template: all I need is a SQL database and a web application, so I’m choosing the “Web app + SQL” template. You’ll be able to add more resources later, so just pick whichever template gives you the best start towards what you need.
The first thing you’ll notice is that you have a .json file, a .parameters.json file, and a deployment script. We’re going to use Octopus to handle the deployment and variable replacement, so we’re mainly interested in the .json file.
Open up the JSON Outline window in Visual Studio. It will give you a great overview of the template you’re working on:
This template is ready to deploy to Azure now, but it needs a few changes to work nicely with Octopus. Octopus ties in really nicely with the parameters in the ARM template, but it doesn’t work so well with the variables you can see in the JSON outline – they tie back to the .template.json file, and we don’t want that.
It’s quite straight-forward to change those variables into parameters, and then you have something ready to start putting into Octopus.
There’s a lot more to getting your ARM template just right, and I highly recommend you spend some time taking a deep-dive into ARM and all the features it gives you. Get something running, and start trying things.
The Octopus Story
Start with a Project
If you’ve never used Octopus before, there’s a lot to learn, but there’s one easy place to start: create a project. Projects are things you want to deploy: they can be small services, or they can be entire application environments with many steps. It turns out they don’t just deploy software; they also deploy infrastructure.
Azure templates are all about resource groups. A resource group is exactly what the name says: a grouping of resources which you can treat as a single unit. Unfortunately, Octopus doesn’t create our resource group for us. Fortunately, it’s very easy to create one using PowerShell. This is easier than it sounds: in your new project, click the “Add step” button, and select “Run an Azure PowerShell Script”.
I called my step “Create Resource Group”. This ends up being a single line:
New-AzureRmResourceGroup -Name Application-#{Octopus.Environment.Name} -Location australiaeast -Force
Notice that I’m using {Octopus.Environment.Name} here: you’re going to see that a lot. I don’t want to waste time setting up variables for things like database connection strings for each environment, so I’m going to use the environment name as much as possible.
The next step you need to create will deploy your ARM template: again, Octopus is ready with a pre-made step to do exactly that.
I named this step “Provision Environment” – it’s going to pass your ARM template to Azure, and ask it to create all the infrastructure you need to deploy your environment.
It might look like you need to select a fixed resource group for this step, but if you choose the “Use a custom expression” option from the drop-down to the right of the Resource Group box, you can write an expression.
Make it match the resource group we created in the previous step:
Application-#{Octopus.Environment.Name}
You’ll need to understand the difference between Complete and Incremental modes: Complete essentially means that any deployment to the template will delete any resources which aren’t in the deployment. Incremental means it will only update existing resources and create new ones. There are arguments both ways, and I won’t go into that in this post.
The really important thing is the Template. For now, it’s easiest to paste your template from Visual Studio straight into Octopus:
Eventually, you’re going to want your build environment to publish your template project as a package, so Octopus will stay up-to-date with any template changes automatically.
Octopus auto-magically exposes all the parameters from your ARM template, and you can use all the usual Octopus variables to complete these. Once again, I highly recommend driving everything off the environment name: you don’t want lots of variables to configure every time you create a new environment.
Remember, the goal we’re working towards is being able to create a fresh environment and deploy straight to it with no additional steps.
Now that your infrastructure step is complete, you need to deploy your actual application. I’m going to skip all the detail of publishing nuget packages to the Octopus feed: if you don’t have an existing CI/CD pipeline, you can upload nuget packages containing your application straight into the Octopus UI.
Once Octopus knows about your nuget package, you can create a “Deploy an Azure Web App” step to publish your application to the endpoint you created in step two.
You’ll need to build the web app name using the same expression you used to create it in step two:
Our project is a NancyFx project rather than Mvc or WebApi, but it all just worked. Our database schema is deployed using a Deploy.ps1 script (use a schema management system like NSchemer or DbUp to deploy and update your database schema), and that just works too.
You’ll need to setup up any connection strings and other environmental variables your application needs: again, focus on building these using #{Octopus.Environment.Name} so there’s no need to set up per-environment values.
Create an environment, hit deploy, and you should find your application is up and running – and any changes you make in your project, to either environment, schema, or application, get deployed to your new environment.
If you want to stop paying for all this infrastructure, just sign in to the Azure portal and delete the entire matching resource group. Boom! Everything is gone. (Don’t do this to production!)
Where Next?
This is really just a proof of concept. There’s no reason this couldn’t be extended to include VMs running services, if you really need that. You can add other resources to the base template you added.
We have a number of applications with a microservices backend. I want to be able to deploy feature branches across services: an environment containing all of the feature branches for a particular ticket or story, along with the master branch for any other dependencies. This feature-branch-environment will become a target for automated integration tests, as well as end-user feedback.
I haven’t planned out the whole system yet, but the integration between Octopus and Azure has been so seamless that I expect to be able to build exactly the CI/CD pipeline I want.