An introduction to Cake.ServiceOrchestration

TL;DR; I recently published an open-source deployment orchestration framework for Cake on GitHub. You can get it now from NuGet.

A little background

This project started after one of my clients started cutting microservices out of a legacy application. I built them a huge Cake script to build and deploy services, but deploying to multiple instances was starting to become a game of copy-and-paste, which is always the ultimate red flag to me. So I built a messy set of helpers to deploy their service right from the Cake scripts. Following that, I gutted the real logic of the scripts and put together this library as a reference for how to orchestrate service and application deployments.

Aside: why deploy with your build tool?

If you've never used Cake scripts to deploy your applications, you're missing out! With the rich ecosystem of addins, Cake supports a huge range of existing packaging and deployment tools, such as MSBuild, Octopus, WiX, NSIS, ILRepack, Azure Storage, DocFX, HockeyApp, Kudu, SQL Server, Squirrel, TFX, Vagrant, and Wyam. This makes it really trivial to wire up your build, test, packaging, deployment and verification processes in one place, with one DSL and one simple entry point. There is something satisfying about running build.ps1 -Deploy and watching your application pipeline run from preparation to deployment with no intervention needed.

So what is it?

This project is a Cake addin library with a basic framework for defining, configuring and deploying services from a Cake script. It is designed to be flexible, easy-to-understand, and scalable to any application landscape.

The basic principle is to build a pipeline representing your service's deployment strategy, then execute the pipeline to deploy as many instances as required reliably and reproducibly.

Basically, this consists of:

  1. Define your service
  2. Register the actions to run during deployment
  3. Create instance of your service to be deployed
  4. Deploy your service to those instances

Enough talk, where's the code?

Alright, so that's enough background, what's involved in actually defining, registering and deploying a complex service? Something like the following:

//build.cake
Task("Deploy-Service")
.Does(() => {
	var service = DefineService("./src/Project.csproj", "MyService");

	//Register actions using the fluent syntax and extension methods
	service.RegisterSetupAction((ctx, i) => { ctx.CreateDirectory(i.RemotePath); })
    .RegisterDeployAction((ctx, i) => { ctx.Unzip("./artifacts/package.zip", i.RemotePath); })
    .RegisterConfigureAction((ctx, i) => {
        ctx.XmlTransform($"./transforms/{i.InstanceUri.Host}.xslt", $"{i.RemotePath}/template.config",
            $"{i.RemotePath}/app.config");
    });

	//Create our instance
	service.CreateInstanceFor("http://hostname:80/path", @"\\server\application", @"E:\Services\App");

	//Deploy!
	service.DeployService();
});

Done, that's it! You've just orchestrated your first deployment. When your script hits DeployService(), the library will create the remote directory, unzip your package there and run the XSLT transform on your instance. Want to deploy another copy of the service? Just add another line before DeployService():

service.CreateInstanceFor("http://backup:81/path", @"\\backupserver\application", @"D:\Services\App");

Now, when the deployment starts it will automatically deploy to both instances, exactly the same. You can keep chaining instances and the library will perform the same steps on every instance. Likewise, you can add a new action to the service, and it will automatically run on every instance.

You can also define your actions separately which lets you write your services more cleanly:

service.RegisterSetupAction<CreateWebsiteAction>()
	.RegisterSetupAction<StopWebsiteAction>()
	.RegisterDeployAction<PublishApi>()
	.RegisterDeployAction<CopyAppData>()
	.RegisterConfigureAction<CopyDataStoreLibs>()
	.RegisterConfigureAction<StartWebsiteAction>();

This also lets you separate your concerns a little more:

Task("Register-Actions")
.Does(() => {
    service.RegisterSetupAction<CreateWebsiteAction>()
	.RegisterDeployAction<PublishApi>();
});

Task("Deploy-Service")
.IsDependentOn("Register-Actions")
.Deploys(service);

Getting started

There's much more documentation and more samples in the documentation on GitHub, or you can install the package from NuGet and get started deploying.

How do I contribute?

I built this for the community of Cake users to build on, so I'd love to take advice, questions and contributions. Just raise an issue or a PR on the GitHub repo.

Stability note

Note that this library is still in the 0.x stages, which means there may be drastic, even breaking changes. Check the Release Notes in the package or on GitHub before upgrading.

This rapid upgrade cycle does mean I can deliver new functionality much faster like tags, filters, lambda support, and improved extension points.

Comments

comments powered by Disqus