A Template Stack is an infrastructure stack source code project that is designed to be used to create multiple instances of the same thing. This is in contrast to singleton stacks, where a separate copy of the source code is maintained for each stack instance, and the many headed stack, where multiple environments are all included in a single stack.
When to use it
One of the reasons for defining infrastructure as code is to make it easy to replicate the infrastructure. This has a variety of uses, including:
- Consistency - use the same stack source code to create each environment used for testing new releases of software, as well as the production environment, to ensure accurate testing,
- Testability - when making a change to infrastructure code, provision and test an instance of it before applying the change to production environments,
- Availability - when a system fails, provision a new instance on demand to replace it,
- Scalability - provision additional instances of infrastructure in different locations,
- Multiple customers - provision an additional instance of an essentially identical service for each customer,
In all of these cases, consistency is a key goal. We want to be confident that we can make a change to the source code for the stack, test the change in one instance, and then easily apply new version of the code to all of the other instances of the stack.
How to implement it
A defining characteristic of the template stack pattern consistency between instances of the stack. Some differences will be needed, but these should be kept to a minimum, and clearly defined.
The way in which a stack can be varied between instances is managed by exposing parameters, or variables, which can be set to specific values when applying the stack code to a particular instance. Different techniques for providing parameter values to stack instances are described in the stack configuration patterns section of this pattern catalogue.
Typically, parameters are used to define names and IDs to distinguish the elements of each instance from one another. This is particularly useful to avoid clashes between instances of infrastructure. For example, it may not be possible to create more than one subnet called
myAppSubnet, so it would be useful to have a parameter named
INSTANCE_NAME, and use this to name the subnet
Parameters may also be used to vary sizing, for example creating different minimum and maximum cluster sizes, or different size servers.
In cases where there is greater variation between instances of a stack, either the template stack may not be the appropriate pattern, or else more thought may be needed to keep a clean architecture. As a rule, the parameters used to define differences between stack instances should be very simple - strings, numbers, or in some cases lists. Additionally, parameters should not cause significant differences in which code is applied.
It is a red flag when a parameter is used as a conditional that decides whether to create large chunks of infrastructure. An example would be a parameter that indicates whether or not to provision a database cluster. If some instances require a database, and some do not, it may be preferable to split the database cluster into its own stack. The decision is then taken at a higher level of which stacks to provision. This keeps each stack simple, and easier to test.
Some teams use the singleton stack anti-pattern to manage multiple instances of a stack. This involves creating a new copy of the stack code for each new environment or other instance. While this is a straightforward approach to implement, it makes it difficult to keep each instance consistent.
Stack code modules allow code to be defined once, and then shared across multiple stacks. But unlike a template stack project, a stack module is not used directly to create infrastructure. Instead, it is imported into a stack project, which is then used to provision infrastructure.
In other words, a stack module’s code is shared by stack projects; a stack template is a single, complete stack project that is used to create multiple instances.
Like a template stack, wrapper stacks are used to create multiple stack instances from a single codebase. However, the infrastructure code for a wrapper stack is defined in a module, and a separate stack project is created for each instance, to define the instance-specific parameters.
Variations between instances created from a single stack source code project create friction for rolling out changes. Each time a change is made, testing needs to be carried out to ensure the code works correctly in each case.
For example, imagine a stack source code project that uses a parameter named ‘cluster’. If set to true, then an autoscaling cluster and load balancer are created. If set to false, then a single server instance is created. This needs two different sets of tests, one for each of these configurations.
This type of variation adds to the work needed to design, test, and debug changes to the code. This slows the pace of delivering changes and fixes to the infrastructure, and increases the risk for each change.
When different instances of a template stack are becoming customized more than simple parameters can support, this is a design smell. Often, a template stack is not the appropriate pattern for the situation if significant customization is needed. It may be better to break the template down to the true common core, and then implement new template stacks for each variation. Changes to each of the new template stacks can then be tested before being applied to production instances, creating more confidence in the change process.
In other cases, a stack is highly customized because there are different subsets of elements which are needed in different situations. For example, maybe a database is deployed in some scenarios, but not in others. In these cases, it’s probably a good idea to split the stack into multiple stacks. This way, each stack template represents a clear set of infrastructure which doesn’t tend to vary, and each stack can be provisioned only in those situations where it is required, rather than adding complexity to a single stack.