Skip to content

Instantly share code, notes, and snippets.

@kiok46
Last active January 23, 2020 14:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kiok46/e2d6050f656a16d76d5d50aef91bcb87 to your computer and use it in GitHub Desktop.
Save kiok46/e2d6050f656a16d76d5d50aef91bcb87 to your computer and use it in GitHub Desktop.
Designing Distributed Systems

The sidecar pattern

The sidecar pattern is a singlenode pattern made up of two containers. The first is the application container. It con‐ tains the core logic for the application. Without this container, the application would not exist. In addition to the application container, there is a sidecar container. The role of the sidecar is to augment and improve the application container, often without the application container’s knowledge.

Adding HTTPS to a Legacy Service

The application of the sidecar pattern to this situation is straightforward. The legacy web service is configured to serve exclusively on localhost (127.0.0.1), which means that only services that share the local network with the server will be able to access the service. Normally, this wouldn’t be a practical choice because it would mean that no one could access the web service. However, using the sidecar pattern, in addition to the legacy container, we will add an nginx sidecar container. This nginx container lives in the same network namespace as the legacy web application, so it can access the service that is running on localhost. At the same time, this nginx service can ter‐ minate HTTPS traffic on the external IP address of the pod and proxy that traffic to the legacy web application. Since this unencrypted traffic is only sent via the local loopback adapter inside the container group, the network security team is satisfied that the data is safe. Likewise, by using the sidecar pattern, the team has modernized a legacy application without having to figure out how to rebuild a new application to serve HTTPS.

Modular Application Containers

tops: To read processes of application container.

$ docker run -d <my-app-image>
<container-hash-value>

APP_ID is an environmental variable that has container has value.

$ docker run --pid=container:${APP_ID} \
 -p 8080:8080 \
 brendanburns/topz:db0fa58 \
 /server --addr=0.0.0.0:8080

http://localhost:8080/topz to get a complete readout of the processes that are running in the application container and their resource usage.

Building a Simple PaaS with Sidecars

The sidecar pattern can be used for more than adaptation and monitoring. It can also be used as a means to implement the complete logic for your application in a simpli‐ fied, modular manner. As an example, imagine building a simple platform as a service (PaaS) built around the git workflow. Once you deploy this PaaS, simply pushing new code up to a Git repository results in that code being deployed to the running servers. We’ll see how the sidecar pattern makes building this PaaS remarkably straightforward. As previously stated, in the sidecar pattern there are two containers: the main appli‐ cation container and the sidecar. In our simple PaaS application, the main container is a Node.js server that implements a web server. The Node.js server is instrumented so that it automatically reloads the server when new files are updated. This is accom‐ plished with the nodemon tool. The sidecar container shares a filesystem with the main application container and runs a simple loop that synchronizes the filesystem with an existing Git repository:

#!/bin/bash
while true; do
 git pull
 sleep 10
done

Parameterized Containers (Basically add environment variables)

docker run -e=PROXY_PORT=8080 -e=CERTIFICATE_PATH=/path/to/cert.crt ...

Define Each Container’s API

Minnimal breaking changes.

Documenting Your Containers

http://label-schema.org/rc1/

Ambassadors Pattern

An ambassador container brokers interactions between the application container and the rest of the world.

Using an Ambassador to Shard a Service

Sometimes the data that you want to store in a storage layer becomes too big for a single machine to handle. In such situations, you need to shard your storage layer. Sharding splits up the layer into multiple disjoint pieces, each hosted by a separate machine.

Q - how to integrate it with the frontend or middleware code that stores data?

A - Clearly there needs to be logic that routes a particular request to a particular shard, but often it is difficult to retrofit such a sharded client into existing source code that expects to connect to a single storage backend. Additionally, sharded services make it difficult to share configuration between development environments (where there is often only a single storage shard) and production environments (where there are often many storage shards).

Once approach is to build all of the sharding logic into the sharded service itself. In this approach, the sharded service also has a stateless load balancer that directs traffic to the appropriate shard. Effectively, this load balancer is a distributed ambassador as a service. This makes a client-side ambassador unnecessary at the expense of a more complicated deployment for the sharded service. The alternative is to integrate a single-node ambassador on the client side to route traffic to the appropriate shard. This makes deploying the client somewhat more complicated but simplifies the deployment of the sharded service.

Using an Ambassador to Do Experimentation or Request Splitting

Using an Ambassador for Service Brokering

Implementing a Sharded Redis

Adapters

Monitoring

Logging

Adding Rich Health Monitoring for MySQL

Replicated Load-Balanced Services

Stateless Services

Readiness Probes for Load Balancing

Session Tracked Services

Application-Layer Replicated Services

Expanding and deploying the Caching Layer

Rate Limiting and Denial-of-Service Defense

SSL Termination

Sharded Cache

Imagine, if you will, that you have a request-serving layer that can handle 1,000 RPS. After 1,000 RPS, the system starts to return HTTP 500 errors to users. If you place a cache with a 50% hit rate in front of this request-serving layer, adding this cache increases your maximum RPS from 1,000 RPS to 2,000 RPS. To understand why this is true, you can see that of the 2,000 inbound requests, 1,000 (50%) can be serviced by the cache, leaving 1,000 requests to be serviced by your serving layer.

scatter/gather pattern

Like replicated and sharded systems, the scatter/gather pattern is a tree pattern with a root that distributes requests and leaves that process those requests. However, in con‐ trast to replicated and sharded systems, with scatter/gather requests are simultane‐ ously farmed out to all of the replicas in the system. Each replica does a small amount of processing and then returns a fraction of the result to the root. The root server then combines the various partial results together to form a single complete response to the request and then sends this request back out to the client.

Scatter/gather is quite useful when you have a large amount of mostly independent processing that is needed to handle a particular request. Scatter/gather can be seen as sharding the computation necessary to service the request, rather than sharding the data (although data sharding may be part of it as well).

Links

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment