
7 principles for designing container-based applications
- Transfer
At the end of last year, Red Hat published a report outlining the principles that containerized applications must follow in order to become an integral part of the cloud world: “Following these principles will make applications ready for automation on cloud-based platforms like Kubernetes ", They say at Red Hat. And we, having studied this document, agree with their conclusions, and therefore decided to share them with the Russian-speaking IT community.

Please note that this article is not a literal translation of the original document ( PDF ) prepared by Bilgin Ibryam- an architect from Red Hat, an active participant in several Apache projects and the author of the books "Camel Design Patterns" and "Kubernetes Patterns", - and presents his main theses in a fairly free presentation.
Typically, with cloud-based (cloud native) applications, you can anticipate failures, and their functioning and scaling is possible even when the underlying infrastructure is experiencing problems. To make this possible, platforms designed to run such applications impose certain obligations and restrictions on the applications launched in them. In short, an application is not enough to just put into a container and run - the ability to efficiently orchestrate it on platforms like Kubernetes requires additional effort. What are they like?
The ideas suggested here are inspired by various other works (for example,
The Twelve-Factor App ), covering many areas: from source control to application scalability models. However, the scope of the principles discussed here is limited to the design of containerized microservice-based applications for cloud native platforms like Kubernetes.
In the description of all the principles, the container image is used as the main primitive, and the container orchestration platform is used as the target environment for its launch. Following these principles is designed to ensure that (prepared in accordance with them) containers receive full support in most orchestration engines, i.e. will be served by the scheduler, scaled and monitored automatically. The principles are listed in random (rather than priority) order.
In many ways, SCP is similar to the Single Responsibility Principle (SRP) in SOLID , which states that each class must have one responsibility . The motivation behind SRP - there should be only one reason that can lead to a change in class.
The word “concern” (translated as “concern”, “concern”, “interest”, “task”) emphasizes that concern is a higher level of abstraction than responsibility, which better describes the range of tasks of the container (compared to the class). If the main motivation for SRP is the only reason for the change, then for SCP it is possible to reuse the container image and its replaceability. If you create a container that is responsible for one task and it completely solves it, the likelihood of reusing this image in other circumstances increases.
In general, the principle of SCP states that each container should solve a single problem and do it well (the classic from the UNIX philosophy - DOTADIW , “Do one thing and do it well” immediately comes to mind . ). If the microservice needs to be responsible for many problems, you can use patterns such as sidecar and init containers to combine many containers into a single deployable platform (under), where each container will continue to be engaged in a single task.

Containers are a unified way to package and run applications, turning them into a black box. However, any container must provide application programming interfaces (APIs) for the environment in which it runs, making it possible to monitor the state and behavior of the container. This is a prerequisite for the ability to automate container updates and maintain its life cycle.
From a practical point of view, a containerized application should provide at least (at least!) An API for various checks of its state: liveness ( readiness ) and readiness(readiness to service requests). Even better, if other ways to monitor the status of the application are offered, in particular, logging important events in STDERR and STDOUT for their subsequent aggregation with utilities like Fluentd and Logstash, integration with tools for building metrics: OpenTracing, Prometheus, etc.

The conclusion is: contact your application as a black box, but implement all the necessary APIs that help the platform monitor the application and manage it as well as possible.
If the HOP talks about providing APIs from which the platform can “read”, then LCP is the flip side: your application should be able to learn about events from the platform. And even more than that: not only to learn about them, but also to respond to them - this is where the name of this principle comes from (“conformance” translates as “conform”, “agree”, “obey the rules”) .
The management platform can have many events that will help in managing the container's life cycle, but some of them are more important than others. For example, to correctly complete the process, the application needs to receive a message with the appropriate signal (SIGTERM) in order to avoid an urgent termination of work through SIGKILL. There are other significant events - for example, PostStart and PreStop, which are necessary to “warm up” the application at the beginning of its operation or, conversely, release resources at the end.
The containerized applications laid immutability (immutability) : they are collected once, after which they run without modification in different environments. This implies the use of external tools for storing the data used in their work, as well as the creation / application of various configurations for different environments. Any change in a containerized application should lead to the assembly of a new container image, which will be used in all environments. The same principle, known as immutable infrastructure, is used to manage the server infrastructure.

One of the main reasons for switching to containerized applications is that containers should be as short-lived as possible, and ready to be replaced with another container at any time. There can be many reasons to replace the container: checking the state, scale down the application, migrating to another host, lack of resources ...
Therefore, containerized applications need to keep their state distributed and redundant. In addition, the application must start and stop quickly and even be prepared for a sudden (and complete) hardware failure. Another good practice in implementing this principle is to create small containers, as containers are automatically launched on different hosts, and their smaller size will speed up the launch time (since they must be physically copied to the host system first).
The container should contain everything you need at the time of assembly, relying only on the presence of the Linux kernel (all additional libraries “appear” at the time of assembly). In addition to libraries, this also means the need to contain executable programming language environments, an application platform (if used), and any other dependencies for running a containerized application. The only exception to this is configurations that will be different in different environments and should be provided at startup (for example,

Some applications are made up of many containerized components. For example, a containerized web application may require a container with a database. This principle does not suggest combining containers: just a container with a database should have everything necessary for its operation, and a container with a web application should have everything for the web application to work (web server, etc.).
The S-CP principle considers containers from the perspective of assembly time and the resulting binary with its contents, but the container is not a one-dimensional black box lying on the disk. Other "measurements" of the container appear when it is launched - these are "measurements" of the consumption of memory, processor and other resources.

Any container must declare its resource requirements and transmit this information to the platform, because its requests for CPU, memory, network, disk affect the way the platform performs planning, auto-scaling, resource management, provides a general SLA level for the container. In addition, it is important that the application fits in the resources allocated to it. In the event of a lack of resources, the platform is less likely to stop or migrate such containers.
In addition to these principles, less fundamental, but still often useful, practices related to containers are suggested:
Links to additional resources about patterns and best practices on the topic:
Some of these principles — in particular, the Image Immutability Principle (IIP), which we called “One image to rule them all,” and the Self-Containment Principle (S-CP) - were covered in our report , CI / Best Practices CD with Kubernetes and GitLab " (link - text squeeze and full video) .
Read also in our blog:

Please note that this article is not a literal translation of the original document ( PDF ) prepared by Bilgin Ibryam- an architect from Red Hat, an active participant in several Apache projects and the author of the books "Camel Design Patterns" and "Kubernetes Patterns", - and presents his main theses in a fairly free presentation.
Typically, with cloud-based (cloud native) applications, you can anticipate failures, and their functioning and scaling is possible even when the underlying infrastructure is experiencing problems. To make this possible, platforms designed to run such applications impose certain obligations and restrictions on the applications launched in them. In short, an application is not enough to just put into a container and run - the ability to efficiently orchestrate it on platforms like Kubernetes requires additional effort. What are they like?
Red Hat Approach to Cloud Native Applications
The ideas suggested here are inspired by various other works (for example,
The Twelve-Factor App ), covering many areas: from source control to application scalability models. However, the scope of the principles discussed here is limited to the design of containerized microservice-based applications for cloud native platforms like Kubernetes.
In the description of all the principles, the container image is used as the main primitive, and the container orchestration platform is used as the target environment for its launch. Following these principles is designed to ensure that (prepared in accordance with them) containers receive full support in most orchestration engines, i.e. will be served by the scheduler, scaled and monitored automatically. The principles are listed in random (rather than priority) order.
1. Single Concern Principle (SCP)
In many ways, SCP is similar to the Single Responsibility Principle (SRP) in SOLID , which states that each class must have one responsibility . The motivation behind SRP - there should be only one reason that can lead to a change in class.
The word “concern” (translated as “concern”, “concern”, “interest”, “task”) emphasizes that concern is a higher level of abstraction than responsibility, which better describes the range of tasks of the container (compared to the class). If the main motivation for SRP is the only reason for the change, then for SCP it is possible to reuse the container image and its replaceability. If you create a container that is responsible for one task and it completely solves it, the likelihood of reusing this image in other circumstances increases.
In general, the principle of SCP states that each container should solve a single problem and do it well (the classic from the UNIX philosophy - DOTADIW , “Do one thing and do it well” immediately comes to mind . ). If the microservice needs to be responsible for many problems, you can use patterns such as sidecar and init containers to combine many containers into a single deployable platform (under), where each container will continue to be engaged in a single task.

2. High Observability Principle (HOP)
Containers are a unified way to package and run applications, turning them into a black box. However, any container must provide application programming interfaces (APIs) for the environment in which it runs, making it possible to monitor the state and behavior of the container. This is a prerequisite for the ability to automate container updates and maintain its life cycle.
From a practical point of view, a containerized application should provide at least (at least!) An API for various checks of its state: liveness ( readiness ) and readiness(readiness to service requests). Even better, if other ways to monitor the status of the application are offered, in particular, logging important events in STDERR and STDOUT for their subsequent aggregation with utilities like Fluentd and Logstash, integration with tools for building metrics: OpenTracing, Prometheus, etc.

The conclusion is: contact your application as a black box, but implement all the necessary APIs that help the platform monitor the application and manage it as well as possible.
3. Life-cycle Conformance Principle (LCP)
If the HOP talks about providing APIs from which the platform can “read”, then LCP is the flip side: your application should be able to learn about events from the platform. And even more than that: not only to learn about them, but also to respond to them - this is where the name of this principle comes from (“conformance” translates as “conform”, “agree”, “obey the rules”) .
The management platform can have many events that will help in managing the container's life cycle, but some of them are more important than others. For example, to correctly complete the process, the application needs to receive a message with the appropriate signal (SIGTERM) in order to avoid an urgent termination of work through SIGKILL. There are other significant events - for example, PostStart and PreStop, which are necessary to “warm up” the application at the beginning of its operation or, conversely, release resources at the end.
4. Image Immutability Principle (IIP)
The containerized applications laid immutability (immutability) : they are collected once, after which they run without modification in different environments. This implies the use of external tools for storing the data used in their work, as well as the creation / application of various configurations for different environments. Any change in a containerized application should lead to the assembly of a new container image, which will be used in all environments. The same principle, known as immutable infrastructure, is used to manage the server infrastructure.

5. Process Disposability Principle (PDP)
One of the main reasons for switching to containerized applications is that containers should be as short-lived as possible, and ready to be replaced with another container at any time. There can be many reasons to replace the container: checking the state, scale down the application, migrating to another host, lack of resources ...
Therefore, containerized applications need to keep their state distributed and redundant. In addition, the application must start and stop quickly and even be prepared for a sudden (and complete) hardware failure. Another good practice in implementing this principle is to create small containers, as containers are automatically launched on different hosts, and their smaller size will speed up the launch time (since they must be physically copied to the host system first).
6. Self-Containment Principle (S-CP)
The container should contain everything you need at the time of assembly, relying only on the presence of the Linux kernel (all additional libraries “appear” at the time of assembly). In addition to libraries, this also means the need to contain executable programming language environments, an application platform (if used), and any other dependencies for running a containerized application. The only exception to this is configurations that will be different in different environments and should be provided at startup (for example,
ConfigMap
Kubernetes).
Some applications are made up of many containerized components. For example, a containerized web application may require a container with a database. This principle does not suggest combining containers: just a container with a database should have everything necessary for its operation, and a container with a web application should have everything for the web application to work (web server, etc.).
7. Runtime Confinement Principle (RCP)
The S-CP principle considers containers from the perspective of assembly time and the resulting binary with its contents, but the container is not a one-dimensional black box lying on the disk. Other "measurements" of the container appear when it is launched - these are "measurements" of the consumption of memory, processor and other resources.

Any container must declare its resource requirements and transmit this information to the platform, because its requests for CPU, memory, network, disk affect the way the platform performs planning, auto-scaling, resource management, provides a general SLA level for the container. In addition, it is important that the application fits in the resources allocated to it. In the event of a lack of resources, the platform is less likely to stop or migrate such containers.
Other recommendations
In addition to these principles, less fundamental, but still often useful, practices related to containers are suggested:
- Strive for small images. Delete temporary files and avoid installing unnecessary packages. This reduces not only the size of the container, but also the build time, as well as the time it takes to transfer data over the network when copying images.
- Support any UID. Avoid using the sudo command or requiring a specific user / UID to start the container.
- Mark important ports. Labeling them with a command
EXPOSE
makes it easy to use images for both people and software. - Use volumes for persistent data (such that should be retained after container destruction).
- Define metadata in images - using tags, labels, annotations. This simplifies their further use by developers.
- Sync the host and image. Some containerized applications may need to synchronize with the host for certain attributes (for example, time and machine ID).
Links to additional resources about patterns and best practices on the topic:
- Container Patterns (Matthias Luebken) ;
- Best practices for writing Dockerfiles (Docker) ;
- Container Best Practices (Project Atomic) ;
- OpenShift Enterprise 3.0 Creating Images Guidelines (Red Hat) ;
- Design patterns for container-based distributed systems (Brendan Burns, David Oppenheimer) ;
- Kubernetes Patterns (Bilgin Ibryam, Roland Huß) ;
- The Twelve-Factor App (Adam Wiggins) .
PS from the translator
Some of these principles — in particular, the Image Immutability Principle (IIP), which we called “One image to rule them all,” and the Self-Containment Principle (S-CP) - were covered in our report , CI / Best Practices CD with Kubernetes and GitLab " (link - text squeeze and full video) .
Read also in our blog:
- “The Death of Microservice Madness in 2018 ”;
- " CNCF Guide to Open Source Solutions (and more) for cloud native ";
- “ How many developers think that Continuous Integration is not needed? ";
- " Our experience with Kubernetes in small projects " (video report, which includes an introduction to the Kubernetes technical device).