Kubernetes Patterns: The Ambassador Pattern

By Mohamed Ahmed
October 31, 2019

Want to learn about various Kubernetes patterns? Check out our blog post regarding the ambassador pattern.

Related posts

Cloud-Native Logging And Monitoring Pattern

Environment Variables Configuration Pattern

Kubernetes Patterns: The Adapter Pattern

This is the third article in the Sidecar Pattern variation articles. As we previously discussed, a sidecar container is just another container that lives with the application container on the same Kubernetes Pod. Sharing the same Pod entails sharing the same volume and network resources. Both containers are deployed as one unit. Hence, we can use the sidecar container for modifying how the application container works without having to make any changes to the code. In the Sidecar Pattern, we used the sidecar container as a log shipper. In the Adapter Pattern, we used the sidecar container to expose the main application’s health metrics in a unified way for other services to consume (for example, Prometheus).

In this article, we will be discussing the Ambassador Pattern.

What is an Ambassador Container?

An Ambassador container is a sidecar container that is in charge of proxying connections from the application container to other services. However, while the Adapter container acts as a reverse proxy, the Ambassador container acts as a client proxy. You might be wondering, why do we need to proxy the application connection requests? Because we need to follow the separation of concerns principle. Each container should do its task and do it well. If there are other tasks that require the application’s function in order to work correctly, we may hand those tasks to the sidecar container.

For example, almost all applications need a database connection at some phase. In a multi-environment place, there would be a test database, a staging database, and a production database. When writing the Pod definition for their application’s container, developers must pay attention to which database they’ll be connecting to. A database connection string can be easily changed through an environment variable or a configMap. We could also use a sidecar pattern that proxies DB connections to the appropriate server depending on where it runs. Developers needn’t change the connection string, they could leave the DB server at localhost as usual. When deployed to a different environment, the Ambassador container detects which environment it is running on (possibly through the Reflection pattern), and connects to the correct server.

Another well-known use case for the Ambassador container is when your application needs to connect to a caching server like Memcached or Redis. Let’s have a Redis example scenario to demonstrate this pattern.

Scenario: Connecting to Multiple Redis Servers

Let’s assume that your application needs to connect to a caching server like Redis. In the development environment, Redis is installed on your laptop and listening on the localhost. In other environments, you have more than one Redis instance for high availability. You need to configure your application and select a healthy node then connect to it. One possible solution to this case is to use an Ambassador container that proxies connections to the backend Redis servers. The Ambassador container listens on the localhost as it is sharing the same Pod as the application container. In our example, we use Twemproxy to handle connecting to the Redis instances. The scenario can be depicted in the following illustration:

Kubernetes_Patterns_-_The_Ambassador_Pattern_.png


Our definition file may look as follows:

apiVersion: v1
kind: Pod
metadata:
  name: ambassador-example
spec:
  containers:
  - name: redis-client
    image: redis
  - name: ambassador
    image: malexer/twemproxy
    env:
    - name: REDIS_SERVERS
      value: redis-st-0.redis-svc.default.svc.cluster.local:6379:1 
redis-st-1.redis-svc.default.svc.cluster.local:6379:1
    ports:
    - containerPort: 6380

The definition defines two Pods:

  • The application Pod: It is the first pod in the list that runs Redis image, but we are using it as a client to other Redis servers.
  • The ambassador Pod: It is the second container that runs the Twemproxy as we discussed. We used the malexer/twemproxy image, which needs the Redis instances passed as environment variables (lines 11-13) in this form address:port:weight.
  • The container by default listens on port 6380 (for advanced configurations, please visit the image’s project page at malexer/twemproxy).

Therefore, the workflow can be summarized as follows:

  1. The application (simulated by the Redis client) initiates a request to localhost:6380. As far as the application is concerned, this is the Redis server.

  2. The Ambassador container picks up the request and relays it to the Redis servers defined in its configuration.

The only missing thing here is the two Redis instances that the Ambassador container is configured to communicate with. The following StatefulSet definition creates them:

apiVersion: v1
kind: Service
metadata:
  name: redis-svc
  labels:
    app: redis
spec:
  ports:
  - port: 6379
    name: redis-port
  clusterIP: None
  selector:
    app: redis
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-st
spec:
  serviceName: "redis-svc"
  replicas: 2
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: redis-data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: redis-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

For an in-depth discussion of how StatefulSets work, you can refer to our StatefulSet 101 article.

TL;DR

  • Among the best practices of designing, containerized applications are segregated responsibilities among containers. Ideally, each container should be tasked with the one job that it does best.
  • If there are any additional tasks, they should be handed to other containers. If the task is closely related to the first container, a sidecar container can be used. A sidecar container lives with the application container on the same Pod, and thus, shares the same resources with the application container.
  • The Ambassador container is also a form of sidecar container. It is used as a proxy between the application container and one or more outside services. The Ambassador container forms an abstraction layer so that the application developer can focus on the application itself without worrying about the infrastructure connectivity details.

*The outline of this article outline is inspired by the book of Roland Huss and Bilgin Ibryam : Kubernetes Patterns.


Related posts

Cloud-Native Logging And Monitoring Pattern

Environment Variables Configuration Pattern

Kubernetes Patterns: The Adapter Pattern

Whitepaper: Production Ready Checklists for Kubernetes

Download these comprehensive checklists to help determine your internal readiness and gain an understanding of the areas you should update

Download your Copy