I recently had the pleasure of reading Google’s 2016 paper, Design Patterns for Container-Based Distributed Systems, which describes several ways to use containers in container choreography systems, How to combine containers of different services organically and make these patterns best practices to make distributed systems more reliable and maintainable. Although five years have passed, the ideas in this paper are still influencing the development of container technology and distributed systems.

If you’re interested, please join me on my wechat account: Packy’s Tech Grocery store

The article divides design patterns into three broad categories:

  • How should a single container for container management be implemented
  • How can multiple containers co-exist in a single node
  • Distributed algorithm in multi-node scenario

Single container management mode

In fact, this pattern simply illustrates how a single container should interact with upstream and downstream, and how boundaries should be defined. Containers are more than once compared to objects in object-oriented thinking. A container should have the same boundary as an object. Provide interfaces to the upper layer to expose application information, such as monitoring metrics (QPS, health information, etc.); A native lifecycle should be defined for the lower layers so that the management system can more easily control the container and develop related components.

The unified interface at the upper level is easier to understand, as the Readness probe and LiVENESS Probe monitoring services are already used in K8s. As for the lower-level lifecycle, the article gives an example: there are multiple tasks executing in a cluster, all of which have different priorities, some of which are higher and some of which are lower. When the cluster resource is tight, the system needs to execute the higher-priority tasks first, but the lower-priority tasks are already being executed. What should the system do? At this point, the lifecycle is required to dictate how the container is stopped. That is, a lifecycle is a contract between the developer, the management system, and the container that makes it easier for the orchestration system to manage the container error-free. In the above example, K8s closes and deletes low-priority tasks through graceful deletion. K8s sends a SIGTERM signal to the container first, indicating that you want to stop running immediately, save what should be saved, and close files that should be closed. A certain amount of time passes before a SIGKILL signal is sent to terminate the task.

Single-node, multi-container application pattern

Application for multiple containers, that is, multiple containers need to work together to complete the foreign service, therefore more containers is regarded as a whole operation on a node, through the Pod in K8s mechanism to deal with this scenario, which is more containers in the same Pod, there is a primary containers provide business services, to assist other container. In this scenario, three design patterns are summarized in the article: Sidecar, Ambassador and Adapter.

Sidecars mode

For those of you who are familiar with IStio, sidecar is not a sidecar in IStio. Sidecar here is justExtend and enhanceThe main container. Sidecar can share disk space with the main container, so it is a good choice to use log collection as a Sidecar container (such as Ari’s open source log-pilot).

Separating enhancements and main business functions through containers has several advantages:

  1. As a unit of resource allocation, containers can better estimate/divide the occupied resources,
  2. Different containers can be maintained independently by different teams with separate development responsibilities.
  3. Sidecar containers are easy to reuse
  4. The container provides a fault containment boundary so that even if the Sidecar container fails, the business services of the master container will not be affected
  5. Independent maintenance, including upgrade and rollback.

Ambassador mode

This pattern is actually the basis of ISTIO, hijacking the traffic outside the broker interacting with the master container. As shown in figure

The advantages of this are obvious. The master container does not need to pay attention to the complexity or simplicity of the external environment, but only needs to know the identification of the service provider and how to connect. Is the service provider a cluster or a single point; You don’t even care what protocol connection is used; it’s all done by the Ambassador container. When isTIO uses envoys to proxy traffic, more complex operations are provided to help ISTIO construct the entire control surface, including routing, fusing, service registration discovery, and so on.

Adapter pattern

The adapter pattern is the opposite of the Ambassador pattern, which simplifies the main container’s perception of the external environment, and the adapter pattern simplifies the presentation of the application. For example, service monitoring is now a mandatory option, but different service applications use a variety of monitoring schemes, from Prometheus to JMX. If we need to build a unified monitoring platform, metrics can be a headache with so many different technology stacks. In this case, a container is needed to translate the metrics of the master container according to a unified standard, and the monitoring platform only needs to connect to one standard.

Multi-node application mode

In addition to collaborating containers on a single machine, modular containers make it easier to build collaborative multi-node distributed applications. The following design patterns are also based on Pod concepts.

Leader election

This is a common problem in distributed systems, where the implementation of the Leader election is usually implemented by different programming languages. This paper presents a mode in which the master container in Pod runs the application service requiring leader election, and a separate container only executes the leader election algorithm (temporarily called leader election container), which exposes a general HTTP or other type of interface to the master container. The host container knows its current identity by calling the localhost interface. As shown in figure

This allows complex implementation parts to be placed in separate containers, making it easier to develop and maintain the main container.

The work queue

Tasks completed by work queues are similar to tasks scheduling systems (e.g. tasks in Python). Similar to the leader election pattern, the design pattern proposed in this paper is still to separate the common work queue logic in distributed system from the specific business logic, and make it platform-based and language independent. The container implements the Run () and mount() interfaces to build a generic work queue, meaning that developers only need to develop logic for a specific phase of the work queue, and the final actions such as code packaging can be handled by the framework.

The architecture of the recent CNCF incubator project ArgoProj/Argo-WorkFlows: Workflow Engine for Kubernetes (github.com) also draws on this.

Scatter/gather model

This mode can be popularly known as cloud-native MapReduce. A root server requests a large number of partitioned servers to process data in parallel, and each partitioned server returns a portion of the data, which is summarized by the root server. The process can be boiled down to one template: 1. 2. Collect response data; 3. Interact with the client. Therefore, only two containers are needed to support this pattern: one container for partitioning data processing; A container for data aggregation (as shown below).

conclusion

In summary, this article describes how containers should be implemented in distributed situations and how multiple container services for a single application can co-exist. Compared to SOA architecture systems, container-oriented and SOA both focus on the reuse of components through the interface of network communication, but the granularity of components split by SOA is larger and the coupling degree is more loose than the symbiotic container mentioned above. On the other hand, SOA is more business-focused, and the container-oriented thinking described in this article focuses more on building generic middleware, making it easier to build distributed systems.

In recent years, many of the landing schemes are actually developed from the design model in the article. For example, Istio’s Sidecar, like the Ambassador model here, manages traffic by hijacking it as an agent, separating the data side from the control side. Use of ali before open source tool for collecting container log log – pilot (AliyunContainerService/log – pilot: Collect logs for Docker containers (github.com)), is the sidecar mode used; And CRI CNI, a standard interface for defining container-specific lifecycles. There will certainly be more applications in the future that need to think about how distributed container services should be implemented, and this article will serve as a guide and inspiration. As summarized in the paper

We believe that the set of container patterns will only grow, and that in the coming years they will revolutionize distributed systems programming much as object-oriented programming did in earlier decades, in this case by enabling a standardization and regularization of distributed system development.

We believe that the set of container patterns will only grow, and that in the coming years they will revolutionize distributed systems programming much as object-oriented programming did in previous decades, standardizing and formalizing distributed system development.