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: bash $ 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: bash $ 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: bash 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: bash $ 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:
Last updated: September 20, 2023