Recently I re-read the Twelve-Factor App – which, if you haven’t already read it, is a must read for app developers. It’s not long, it’s available for free online, and it details 12 important practices involved in building a modern, reliable and performant DevOps-ready application. Give it a read here: https://12factor.net/.
What it got me thinking about was how each of the 12 pillars relate to app development for cloud environments, and how a lot of what it talks about has been made easier for us to do in this context. So I thought it might be useful to briefly summarise each of the 12 factors with particular reference to developing applications for Azure. This should hopefully serve as a short guide to good practice and a stepping stone to adopting DevOps-related practices in the cloud.
The Twelve-Factor Azure app
This pillar talks about the importance of source control and having one codebase per app, using technologies like Git or Mercurial. In a cloud context, this becomes increasingly important. Source control hosting providers like GitHub or BitBucket mean that the codebase of an app itself can be hosted in the cloud and enable developers to easily check out code and integrate their changes to a single place, and built-in interoperability between GitHub/BitBucket/other source control platforms and Azure make setting deploying that code seamless. For example, the source code for this blog is hosted on GitHub, and when I update the master branch, Azure will detect this and automatically deploy the code.
This section describes the need for packaging systems and dependency declaration manifests, to avoid relying on the existence of system-wide packages – essentially making any assumptions about the environment the app will be running in. Again, this is key to the portability of your app in cloud environments, as things like the Azure App Service or Azure Functions need to know all the dependencies for running your code so all the appropriate packages are installed in advance.
I can’t stress the importance of this one enough. To summarise, never hardcode any connection strings, API URLs, application keys etc. into your application’s code – anything that will change between deploys like staging or production. This has always been good practice in on-premises scenarios, but with use of cloud SQL databases, messaging queues, REST APIs and more becoming prevalent in modern apps, it’s become even more important. When you deploy code from a dev environment, to staging and to production, you want all of these configuration variables to be pre-defined for each of these environments, and abstracted away from the code.
This is also important from a security perspective, as anyone with access to the code would otherwise have access to your cloud services. The remedy here is to have a config file (like a web.config file for an ASP.NET Webapp), put in the essential components for the dev scenarios to run, and then if using App Service, make use of “slot settings” for your apps, where you can define the configuration values for a specific slot (like production), which will overwrite the .config file when deployed to that slot. If you’re using a different service, consider “Key Vault” as a safe space for these credentials, where you can lock them away and only provide your app permission to get them, instead of a human being.
4. Backing services
Another key one for us. This essentially explains that developers should treat networked services as attached resources, and that a deploy of the app should feature code that can handle swapping of services with others – for example the correct choice of swapping third-party services like Amazon’s RDS MySQL Database for Azure MySQL DB. This should just mean the changing of a connection string in the config. For apps that might need to be moved between different clouds, or one that’s being developed on-premises with a view of migrating down the line, this will make things a lot easier.
5. Build, release, run
This is at the heart of DevOps methodology, and means faster and more frequent code deploys to production. Cloud-ready tools like Jenkins, Octupus Deploy and Visual Studio Team Services make this extremely easy, and when a developer has kicked off a deploy, the build stage should allow them to tackle and isolate any errors that compiling has revealed and fix this more quickly, and should the app pass all of the required tests in the pipeline, this is when tools like VSTS allow continuous deployment into Azure for fast, iterative releases, instead of long traditional sprints.
12Factor.net describes that “Twelve-factor processes are stateless and share-nothing. Any data that needs to persist must be stored in a stateful backing service, typically a database.” This helps to make our apps more resilient against unexpected failure, and it’s crucial to think about stateless when considering the wider architecture off your app in a cloud environment. If you have a large codebase for a webapp running on App Service that does both the frontend and some complex backend processing based on user requests, where state is needed, if for some reason that user loses connection, the server becomes overloaded or unexpectedly crashes, it’s not going to be very resilient. This is where we start to think about micro-services style architectures where you break down each of your processes into separate smaller applications, that call each other and persist any state information in distributed cloud databases, so that should one element of this fail, the rest can pick up the slack, and each can scale independently of one another. Don’t worry, there’ll be some labs to come on this soon.
Not much more to add on this one so check it out on the 12factor.net website.
This one speaks to the importance of scaling out by process. This neatly follows on from what I talked about regarding micro-services architectures. When you’re designing your small interconnected services, think about whether it’s a process to serve the front-end to the user, or whether it’s a back-end long-running process. These will have very different compute requirements which is important when assessing where they should be hosted in Azure. A good example of this would be an e-commerce site running on App Service. I could integrate the shopping cart logic to record a customers order, post to a database, check stock, await a response and return this to the user to proceed to payment, all within the WebApp’s codebase, but this means I need some hefty compute power to do this for all my visitors, or I could separate out this process in a separate Logic App or Azure Function that goes off and executes this independently. Much more scalable, and cheaper too.
This is essentially a follow-on from the previous – when your processes are kept small and simple, they’re more efficient to start up and shut down, and more resilient to sudden failures, since another one can spin up to take its place.
10. Dev/prod parity
To summarise, keep the time between dev and prod as short as possible, and have the same people involved throughout the process. Going back to the DevOps buzzword, this means not taking weeks to go through code revisions and tests, then chucking it over the proverbial fence to a different team to figure out how to deploy it properly with different toolchains and skillsets, and deal with any errors that arise. Have both developers and ops involved throughout the process, commit regular changes, test and deploy. This means there’s no chance for massive differences between the dev and prod environments to creep in and complicate things.
So so crucial with apps deployed to the cloud. They may seem like they’re out of reach up there, and once they’re deployed, there’s no way to tell what’s going on. 12factor.net talks about treating logs as event streams, not dumps, which is spot on – in Azure, tools like App Insights, Live Debugging and Log Stream Analytics make viewing log data seamless, with the ability to set up alerts for key events as they happen, so you can respond to errors much more quickly.
12. Admin processes
This talks about running one-off admin processes using the same environment and codebase as the app itself, which again is something very important for cloud apps. Having this admin code as part of the source control and the build, test, deploy pipeline is key, to ensure there are no synchronisation issues. This also makes me think more widely about automation, where scripts etc. can be triggered to run on application code automatically through tools like Azure Automation, but that’s another article in itself.
So a brief summary there of what’s a very good read, thoroughly recommend it for any app developers who are currently or considering developing in a cloud environment like Azure. Go check it out: https://12factor.net/.
As always, let me know your thoughts below and hope you found it useful. Until next time!