The Azure App Service is a great service that makes hosting web-facing applications extremely easy, with support for many value adds out of the box e.g. scale out, A/B testing and authentication are all included. I’ve recently been looking at how you can use this service within the context of some F# frameworks and libraries e.g. Suave. I’ll blog about the Suave side of things in another post – there’s a lot to it – but one of the other parts I wanted to mention was that FAKE now has support for Kudu, the Azure App Service SCM deployment engine.
What is Kudu?
One of the features that App Service offers is a multitude of deployment options, including FTP, HTTP and also source control web hooks. The latter supports a number of providers, including GitHub, BitBucket, VSTS and even a locally hosted git repository. The App Service listens to push events on a specific branch, downloads the source code onto the web server (into a sandboxed location) and then copies it into the website proper. The latter stage – the copy – is the one of most interest. Essentially, the app service runs a batch file which can do whatever is needed to do a build and deploy. For a .NET application, this typically includes: –
- Perform an MSBuild of the application.
- Copy the outputs to the “staging” directory on the web site.
- Run KuduSync to deploy to the actually web application folder.
KuduSync itself essentially does a few things: –
- Does a diff of the current files to deploy from the previous deployment.
- Removes any obsolete files from the app.
- Copies over any new / updated files from the staging directory to the app.
- Makes a list of the deployed files for comparison the next time it runs.
The Azure CLI extensions come with some commands to “pre-generate” a batch file for specific, common use cases e.g. ASP .NET application etc.., but you’ll often need to do something more than just that – and this means getting your hands dirty with a kudu script.
A Sample Kudu script
So here’s a standard Kudu build script (which I’ve actually minimised as much as possible) which deploys some raw web assets (HTML, JS etc.), builds a .NET application and deploys a web job : –
:: Restore NuGet packages .paket\paket.bootstrapper.exe .paket\paket.exe restore :: Copy static site content over - note the "excludes.txt" which contains file types to ignore.... xcopy src\webhost "%DEPLOYMENT_TEMP%\" /Y /E /Q /EXCLUDE:excludes.txt IF !ERRORLEVEL! NEQ 0 goto error :: Deploy an F# script as a continuously running Web Job xcopy src\Sample.fsx "%DEPLOYMENT_TEMP%\app_data\jobs\continuous\Sample\" /Y IF !ERRORLEVEL! NEQ 0 goto error :: Build to the temporary path cd "%DEPLOYMENT_SOURCE%" call :ExecuteCmd "%MSBUILD_PATH%" /m /t:Build /p:Configuration=Release;OutputPath="%DEPLOYMENT_TEMP%";UseSharedCompilation=false %SCM_BUILD_ARGS% /v:m IF !ERRORLEVEL! NEQ 0 goto error cd .. :: KuduSync call :ExecuteCmd "%KUDU_SYNC_CMD%" -v 50 -f "%DEPLOYMENT_TEMP%" -t "%DEPLOYMENT_TARGET%" -n "%NEXT_MANIFEST_PATH%" -p "%PREVIOUS_MANIFEST_PATH%" -i ".git;.hg;.deployment;deploy.cmd" IF !ERRORLEVEL! NEQ 0 goto error
There’s actually a whole host of things here: –
- First I’m pulling down my Nuget dependencies with Paket (this could just as easily be Nuget.exe) before moving onto our main build.
- xcopy across the website assets. There are some files I don’t want, so I also have to add an “excludes.txt” file which contains file types I don’t want as an argument to xcopy. This was a real pain to figure out and to use the correct arguments to xcopy.
- Copy across an .fsx file as a web job. I needed to figure out how web jobs are stored on the app service in order to know the path to build up, of course.
- Do some jumping around of folders before doing an MSBuild of my application.
- Call Kudu Sync to do the final deploy, passing in the set of folder locations needed for the tool.
Using batch files for a built pipeline probably isn’t the best way to go. The problem with this is that managing a set of build sets quickly becomes a pain with a batch file – you have GOTOs everywhere, labels etc., you can’t do complex control flow etc. etc. Imagine you wanted to now run unit tests, and if that failed, do some set of tasks, but if it passed, do something else etc. etc. – it quickly becomes a nightmare.
On the other hand, FAKE is an excellent library and DSL designed to act manage a build pipeline. Not only does it have loads of helpers for e.g. file system access, config file rewriting, environment variables and MSBuild etc. but it allows us to define build pipelines with dependencies – even conditional stages. Finally, because FAKE is just F# and runs on the full .NET framework, you can always break out and just run any .NET code you want directly from within a FAKE script. With FAKE, you can have a single build script for e.g. local builds, CI builds and also now supports Kudu deployment builds through the newly-added Kudu module in FAKE. Let’s see what the above build script looks like in FAKE: –
You can see here that there are several distinct build steps, which are composed together as dependencies on one another at the very end using the ==> operator. Note that the code above is actually just F# although we’re using a specific DSL with custom operators to set up a “build chain”. So if any of the stages fail, the whole build will fail and we’ll be presented with a summary log (which we can see directly in the Azure portal) of the results of the build. Notice also the lack of environment variables etc. – the Kudu helper module takes care of all of that for us – whilst we don’t need gotos anymore because FAKE handles the build pipeline.
Now our Kudu script is much simpler, because we’re delegating control of the main build orchestration to a language better able to reason about and define program flow: –
:: Restore NuGet packages .paket\paket.bootstrapper.exe .paket\paket.exe restore :: Start main build script packages\FAKE\tools\FAKE.exe build.fsx
Kudu and Azure App Service are great tools. By plugging FAKE into the mix, we get both a succinct and easy to use scripting experience with the power of the .NET framework and a fantastic language like F# as well.