Kubernetes + OpenShift featured image

The Kubernetes ecosystem has inconsistent ways to expose Secrets to applications in order to allow them to connect to services. Many service providers have their own bespoke methods of binding an application to their services, which can slow down development teams considerably.

The Service Binding Operator remedies this by managing the binding process. This article walks through a simple example of service binding in action using the open source RabbitMQ message broker.

How Service Binding Operator manages the binding process

When you request a binding, the Service Binding Operator looks at information stored within the custom resource and its corresponding custom resource definition. This information tells the Service Binding Operator the proper method for binding the application. The Service Binding Operator then projects the binding method into the application's container through environment variables or files mounted within the container.

To learn more about other features of the Service Binding Operator and its integration with other products, read our release announcement Announcing Service Binding Operator 1.0 GA.

About the example

Let's say you have two Kubernetes services, producer and consumer, that talk to a RabbitMQ instance using the Advanced Message Queuing Protocol (AMQP). The producer periodically produces data that the consumer reads and acts on. For the sake of this demonstration, the consumer's action is simply to print whatever it receives to the standard output (stdout).

Prerequisites

First, install the RabbitMQ Operator:

$ kubectl apply -f https://github.com/rabbitmq/cluster-operator/releases/latest/download/cluster-operator.yml

Next, install the Operator Lifecycle Manager (OLM), a prerequisite for the Service Binding Operator:

$ curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.19.1/install.sh | bash -s v0.19.1

Note: Yes, running curl ... | bash isn't the best security. If this is a concern for you, save the installation script to a location in your filesystem and execute the script there after inspecting its contents.


Finally, you'll also need to install the Service Binding Operator itself:

$ kubectl apply -f https://operatorhub.io/install/service-binding-operator.yaml

Deploy the application on Kubernetes

Next, you'll want to have the producer and consumer running on the Kubernetes cluster. For convenience, I've authored two containers that provide this functionality; their sources can be found in my GitHub repository.

The Service Binding Operator can operate against deployments, and deployments make the most sense for running the applications in this example. You can deploy them with the following commands:

$ kubectl create deployment producer --image=quay.io/ansadler/rabbitmq-producer:1.0
$ kubectl create deployment consumer --image=quay.io/ansadler/rabbitmq-consumer:1.0

You'll also need a RabbitMQ cluster to run them against:

apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
  name: rabbitmq
spec:
  service:
    type: ClusterIP

Now, if you inspect the container logs for the consumer (which monitors the consumer process's stdout), you'll see something similar to this:

$ kubectl logs consumer-deployment-f877cffb6-p9sks
Error:
   0: $RABBITMQCLUSTER_HOST not defined

Location:
   src/consumer.rs:16

Backtrace omitted.
Run with RUST_BACKTRACE=1 environment variable to display it.
Run with RUST_BACKTRACE=full to include source snippets.

Note: The pod whose logs you will be inspecting will not be the same as this example, since the name of the pod that runs our applications will be different every time the deployment is changed. You can retrieve the name of the pod using the following command:

$ kubectl get pods --selector=app=consumer

If you inspect the logs for the producer as well, you'll see that it throws a similar error. This happens because you haven't bound your RabbitMQ cluster to the producer and consumer, so neither of them knows where to find it. Let's fix that.

Bind the services together with ServiceBindings

If you were not using the Service Binding Operator, you would need to tell both the producer and the consumer how to connect to the RabbitMQ instance. This would require distributing at least the following information to these applications:

  • The hostname of the RabbitMQ instance.
  • The port that the RabbitMQ instance is listening on.
  • Authentication credentials (such as username and password).

This in turn would require you to expose your secrets to your applications, either by having the applications directly fetch that information from Kubernetes's API or by projecting that information into your applications yourself. Both of these methods are rather intrusive toward the applications, and it stands to reason that the process could be automated. And that's where the Service Binding Operator comes in.

To bind your applications and services together, the Service Binding Operator introduces a new custom resource called ServiceBinding, which represents the binding between an application and a service. In this particular example, the bindings for our producer and consumer applications look like this:

---
apiVersion: binding.operators.coreos.com/v1alpha1
kind: ServiceBinding
metadata:
  name: servicebinding-consumer
spec:
  bindAsFiles: false
  services:
  - group: rabbitmq.com
    version: v1beta1
    kind: RabbitmqCluster
    name: rabbitmq
  application:
    name: consumer-deployment
    version: v1
    group: apps
    resource: deployments
---
apiVersion: binding.operators.coreos.com/v1alpha1
kind: ServiceBinding
metadata:
  name: servicebinding-producer
spec:
  bindAsFiles: false
  services:
  - group: rabbitmq.com
    version: v1beta1
    kind: RabbitmqCluster
    name: rabbitmq
  application:
    name: producer-deployment
    version: v1
    group: apps
    resource: deployments
---

Note: If you are running this against an Operator not already supported by the Service Binding Operator (see our README for a list of supported Operators), you will to give Service Binding Operator permission to read from this service according to the rules of role-based access control (RBAC). You can read more about how to grant this permission that in our documentation.


Now, if you inspect the logs of your consumer deployment, you'll see that producer has been sending messages to it. You should see something similar to the following:

$ kubectl logs consumer-deployment-6f48dbfb7d-5dsgd
connecting to: amqp://default_user_7Jba_ZP7NKD-UjJK8AQ:HIhVZ4a_6Xm60Z7bmbEDADDpwr2e_tch@rabbitmq.default.svc:5672
Waiting for messages, press Ctrl-C to exit.
(  0) Received [hello, world!]
(  1) Received [hello, world!]
(  2) Received [hello, world!]
(  3) Received [hello, world!]
(  4) Received [hello, world!]
(  5) Received [hello, world!]
(  6) Received [hello, world!]
(  7) Received [hello, world!]
(  8) Received [hello, world!]
(  9) Received [hello, world!]
( 10) Received [hello, world!]
( 11) Received [hello, world!]
( 12) Received [hello, world!]

producer says something similar:

$ kubectl logs producer-deployment-6d8d55949d-8qd9c
connecting to: amqp://default_user_7Jba_ZP7NKD-UjJK8AQ:HIhVZ4a_6Xm60Z7bmbEDADDpwr2e_tch@rabbitmq.default.svc:5672
sending [hello, world!] to queue hello
sending [hello, world!] to queue hello
sending [hello, world!] to queue hello
sending [hello, world!] to queue hello
sending [hello, world!] to queue hello
sending [hello, world!] to queue hello
sending [hello, world!] to queue hello
sending [hello, world!] to queue hello
sending [hello, world!] to queue hello
sending [hello, world!] to queue hello
sending [hello, world!] to queue hello
sending [hello, world!] to queue hello

Resources

To learn more about the Service Binding Operator, check out the following resources:

Comments