In the previous few chapters, we learned a lot about Crossplane, from its basics to many advanced patterns. Also, we introduced the idea of a unified approach to both application and infrastructure automation. This chapter will step back to analyze and approach configuration management for unified automation holistically. The chapter is heavily influenced by the white paper Declarative application management in Kubernetes (https://goo.gl/T66ZcD) by Brian Grant, a Kubernetes Steering Committee emeritus. The white paper mainly covers the Kubernetes Resource Model (KRM). We will cover the topics in this chapter from the KRM and Crossplane Resource Model (XRM) perspectives. The chapter will define the scope of the unified automation approach. It will continue by looking into many more concepts, such as the tools available, common pitfalls along the way, and the trade-off in using different patterns. In a way, it’s a revisit of the API boundaries discussion from Chapter 6, in more detail.
The following are the topics covered in the chapter:
Most of us will have different perceptions of what we mean by unified application and infrastructure automation. This section will help us understand the scope with a bit more clarity. Any application/product that we deploy is mostly a combination of custom-developed bespoke applications and common off-the-shelf (COTS) components. The term bespoke application means custom-written software for our requirements with a specific purpose. Bespoke applications are generally stateless, containerized workloads. From the perspective of Kubernetes, they are workloads that run on Pods, the basic unit of computing in Kubernetes. COTS components are generally stateful infrastructure components, such as databases, cache systems, storage systems, and messaging systems. In the cloud-native era, most COTS components are black-box, fully managed software as a service (SaaS) or platform as a service (PaaS). COTS vendors expose CRUD APIs to work with the resource with precise configuration knobs for security, scaling, monitoring, and updating concerns. These specific configuration knobs will support different use cases from consumers. When we talk about unified application and infrastructure automation, it’s an approach to use the same set of tools, processes, and patterns to configure both bespoke applications and COTS deployment. In other words, bespoke applications can follow KRM standards, and COTS dependencies can comply with XRM standards, an extension of KRM. There are many advantages to such unification:
The following section will cover a few requirements, patterns, and tools for approaching unified automation.
The configuration complexity clock is a concept that explains how configuration management can become complex over time. It explores different stages in the evolution of configuration management, its use cases, and its pitfalls. It was initially discussed from the perspective of application configuration in the blog post found here: http://mikehadlow.blogspot.com/2012/05/configuration-complexity-clock.html. We will look at the same concept from the Kubernetes configuration management perspective.
Let’s say we are deploying our first application workload into Kubernetes. To start with, every Kubernetes artifact, such as Deployment, Service, and Ingress, can be managed as individual configuration artifacts. It may not be an issue when we operate on a tiny scale. But soon we will realize that there is an issue with consistently performing releases and rollbacks as the application configuration is spread over multiple Kubernetes resources. We will start thinking about packaging and release management tools such as Helm. Helm is a template-based configuration management tool. We will parameterize values with variables to enable configuration abstraction and expose only limited attributes. A typical example is setting the replica count for every deployment target (production and staging). Soon, the organization will realize the power of Kubernetes and decide to deploy further similar workloads in the Kubernetes ecosystem. As the configuration data for the new application looks identical to the initial Helm template, we will choose to parametrize more variables to support multiple applications with the same Helm template. It will be an excellent way to push the reuse agenda and minimize the configuration to maintain. A typical example of such new parameters would be a namespace, labels, and an application name.
Again, the organization will realize more benefits from Kubernetes and decide to experiment with a few new workloads. These workloads may have similar configuration data to the initial Helm template with minor customization. We would decide to fork the main Helm template to do the required customization. Template-based abstractions are complex to reuse when customization is required. After a certain length of time of the configuration clock running, we will see too many forks that are difficult to keep in sync. On the other side, many individual templates would have introduced new parameters to support new local use cases. This is a leak in the abstraction we created with the Helm templates. We would have parameterized all values and completely eroded abstraction as the clock ticks further. Have a look at this WordPress chart as an example: https://github.com/helm/charts/tree/master/stable/wordpress. The number of parameters has increased slowly into multiple folds from the initial commit. Entirely eroded templates are complex to read, and end users will find it challenging to understand the usage of each parameter. Users will find it challenging to see parameters that are not their concern. For example, developers may not know how to define Ingress parameters in the preceding WordPress Helm chart.
As the configuration clock ticks further, we must harden the configurations for security. Infrastructure operators will want to own the configuration as the application owners do not know how to configure the security parameter. The Helm post rendering feature can rescue us from the situation and help inject the security configuration as late binding. But it will still be challenging for the infrastructure operators to manage the post-rendering step in the deployment pipeline because of too many customization forks and unexpected rendering outputs. Some developers may decide to use a DSL such as Terraform to manage configuration as it is simple to read. DSLs inherit the same parameterization problem in input variables, and the post-rendering customization step is also challenging with DSL. Additionally, DSLs have the issue of a complex learning curve for developers and limited tooling support for concerns such as testing. Vulnerability or compliance scanning is another area where we may face tooling issues. The Crossplane configuration can also face similar problems as we scale over time. The idea of discussing these limitations is not to discourage any specific tool usage. Each tool has its use cases and trade-offs. For example, Helm packing works well when operating on a small scale and a few other use cases, such as release management. In a way, we will be looking at how we can use a combination of tools and patterns to overcome this limitation.
To manage configurations without being trapped by the issues discussed regarding the complexity clock, we should keep a few guiding principles in mind. These guiding principles will be the technical requirements to perform trade-off analysis when selecting tools and patterns for configuration management. Baking these guiding principles into our configuration management recipes would allow us to evolve and scale with ease:
We will deep dive into all these guiding principles in the upcoming sections. The following section will examine different patterns available for configuration management and the trade-offs with each pattern.
Reusing the configuration as we scale is challenging. Most of the reuse patterns were discussed in the The configuration complexity clock section. The section will cover these patterns in more detail with their advantages and disadvantages. The following are the well-known approaches for configuration reuse:
Composition, patch/overlay, and template-based tools can complement each other and combining them can help you to build a robust deployment pipeline. We will look at a few such hands-on examples in the following two chapters. The next section of this chapter will look at the Open Application Model (OAM), an open source standard to define application and team-centric configuration management.
OAM is an open source standard developed by Microsoft and Alibaba to guide us in creating higher-level abstraction APIs for application deployment. In other words, it’s about creating a model for application configuration management using composition as a pattern. The standard focus on addressing the following three problems:
OAM proposes layered configuration management based on personas and composing abstractions accordingly to address the problems. OAM defines three personas to manage different application deployment concerns, as represented in the following figure:
The OAM community has developed KubeVela, a project that implements the specification. It is a CNCF sandbox project and a composing tool like Crossplane. KubeVela concentrates only on composing bespoke application configurations. But Crossplane composes both bespoke applications and COTS infrastructure/external dependencies. Both KubeVela and Crossplane can complement each other using the following two different patterns:
The following figure represents the KubeVela composes Crossplane pattern where both the Crossplane control plane and application workload cluster are the same:
The preceding figure represents a web service workload with an RDS database as a COTS dependency. The workload construct of KubeVela defines the application workload type. Traits define all workload characteristics, such as route and traffic. Components help define the COTS dependencies. First, we will compose RDS into an XR API using Crossplane. Later, we can compose the XR as a component inside KubeVela. The following figure represents the second pattern where Crossplane composes KubeVela:
The figure represents application management both in the same cluster and in a remote cluster. Additionally, the figure represents the usage of the Helm provider to set up KubeVela and other cluster dependencies. We will cover a hands-on example of KubeVela in Chapter 9.
Information
OAM and KubeVela are exciting topics to explore further. Visit https://oam.dev/ for more information.
The following section will cover two exciting patterns that can help platform teams to build extendable and reusable platform APIs.
As we scale the number of applications deployed in Kubernetes, there could be an exponential proliferation of configurations to manage. Managing a high volume of KRM/XRM configuration files is prone to human error, is challenging to keep in sync, and requires a highly skilled workforce. Reuse is the key to keeping the volume of configurations low. But customization requirements at the individual team level will not allow us to reuse unless we have an easy and quick way to do so. Also, agile and product engineering practices will add additional pressure from a minimal external dependency perspective. Specialized and extendable abstraction is vital to address these problems. Let’s start with specialized abstraction in the following section.
Specialized abstraction is a technique where we build a basic abstraction and reuse the base abstraction to make specialized abstractions that handle custom requirements. It is nothing but the inheritance pattern from object-oriented programming. Also, this is a well-known pattern in core Kubernetes. Think of the Pod resource type as an example. Deployment, DemonSet, and Job are specialized abstractions that use Pod as their base. Let’s look at some of the specialized abstractions we could build as a platform team:
These are just some general examples that may apply to your environment. Finding the correct abstractions that suit your environment will require a closer look at the organization structure, technical maturity, governance model, workloads, and so on. The base and specialized abstractions are typically developed by the platform team to be consumed by the application operator and developers. We may have a rare scenario where the application operator is building a specialized abstraction over the base developed by the platform team. We can look at extendable abstraction in the following section.
Extendable abstraction is a technique where we build a partial abstraction and allow other teams to implement it fully. It is nothing but the polymorphism pattern from object-oriented programming. Also, this is a well-known pattern in core Kubernetes. Ingress is an example from core Kubernetes. Each managed Kubernetes provider will come up with its implementation for Ingress. Extendable abstraction is meant for both platform teams and application operators. Let’s look at some examples of the pattern’s usage:
Again, these are indicative examples. This concludes the abstraction discussion. In the following section of the chapter, we will look at configuration change frequency in detail.
Looking at the configuration knobs from the perspective of frequency of change will help us categorize them between personas defined in the OAM. The change frequency detailing will bring in the discussion of ownership as these perspectives are linked. This section covers change frequency from the perspective of bespoke applications (KRM compliant) and COTS external dependencies (XRM compliant). We can classify configuration change frequency into the following three categories:
The XRM-compliant COTS external infrastructure dependencies mostly do not change frequently. For example, changes to the external infrastructure, such as region scaling, autoscaling setup, tuning security configuration, upgrade, and migration, will happen at a low phase after the initial setup. Building these infrastructure abstractions with XRs should be owned by platform teams. The application operator could do a few XR composing exercises to make a new, specialized recipe. In other words, they could create workload-specific abstraction using the nested XR pattern. As discussed earlier, XRM goes beyond infrastructure dependencies to manage the external application using providers such as Helm and SQL. These configurations could change frequently. For example, every application release could change the SQL schema of the database. Hence, the application operator persona can extend the existing recipes to meet the product team requirement.
This chapter discussed various aspects of unified configuration management of bespoke applications and COTS components at scale. We covered concepts such as different tools available for configuration management, common pitfalls along the way when we scale, and the trade-off in using different patterns. Also, we discussed how different combinations of tools and practices could complement each other under different circumstances.
The following two chapters will look at hands-on examples to try out a few recipes discussed in this chapter. The recipes will include KRM and XRM configuration management as we move toward end-to-end automation of the whole application and its COTS dependencies.