Cloud Native and 12-Factor
Moving an app to cloud-native is a journey, but the bottom line is that the patterns and primitives at the core of cloud-native architectures are among the biggest enablers of Modern On-prem applications. Beyond that, the principles behind 12-factor apps are valuable when shipping a cloud-native application.
Although some 12-factor ideas like “config should always be in environment variables” have fallen out of favor due to security concerns, many are still quite valuable. Among some of the most valuable principles for delivering reliable, self-healing applications are disposability and concurrency. Embracing disposability in particular has a number of consequences that are quite valuable to vendors delivering Modern On-prem applications.
Disposability, at its core, is the discipline of ensuring that your application can tolerate the loss of processes without interruption. This goes a little deeper than the core concepts around High Availability, they can also improve the operability of your application. Disposability is not just that you can lose services and keep serving traffic, it implies that your application is coordination-free. To be truly tolerant of failures, you need to a handful of things including being agnostic of startup order and performing idempotent migrations. When done right, Disposability means end users don’t have to understand the internal interactions of services, and can deploy everything with one command.
Agnostic of startup order
One of the biggest implications of embracing disposability is that your application becomes agnostic to startup order of its components. If components can lose other parts of the stack gracefully, and then return to a healthy state when those dependencies return, then the application becomes much more robust.
Being agnostic of startup order is one of the biggest principles that is not directly describe by 12-factor, but is essentially implied by core principles like Disposability.
There are a number of tools that can help to do this, many of which are optimized for container-based workloads.
These can be used as part of core containers, or used in an init container.
Similarly, its important that any one-time changes that need to be made to an application’s state be done in an idempotent way. This includes but is not limited to:
- Migrating Database Schemas
- Bootstrapping initial application users or teams
- Creating and managing Kubernetes state like secrets and namespaces
For all of these concerns, its important to use tools that will check what work has already been done, and can progressively converge toward the desired state expressed by a given application version’s migration. Many database migration tools do this automatically by storing executed migrations in a metadata table. More sophisticated tools like schemahero and some ORMs can even inspect the existing table structure and decide what schema mutations are required to reach the desired state. For Kubernetes-native custom convergence, tools like kubebuilder and the Operator Framework can handle a lot of the boilerplate.