scothis / website

Repository hosting the website files (guides, etc)

Home Page:https://servicebinding.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

title permalink
Service Binding for Kubernetes
/

Today in Kubernetes, the exposure of secrets for connecting application workloads to external services such as REST APIs, databases, event buses, and many more is manual and bespoke. Each service provider suggests a different way to access their secrets, and each application developer consumes those secrets in a custom way to their workloads. While there is a good deal of value to this flexibility level, large development teams lose overall velocity dealing with each unique solution. To combat this, we already see teams adopting internal patterns for how to achieve this workload-to-service linkage.

This project specifies a Kubernetes-wide specification for communicating service secrets to workloads in an automated way. It aims to create a widely applicable mechanism but without excluding other strategies for systems that it does not fit easily. The benefit of Kubernetes-wide specification is that all of the actors in an ecosystem can work towards a clearly defined abstraction at the edge of their expertise and depend on other parties to complete the chain.

  • Application Developers expect their secrets to be exposed consistently and predictably.
  • Service Providers expect their secrets to be collected and exposed to users consistently and predictably.
  • Platforms expect to retrieve secrets from Service Providers and expose them to Application Developers consistently and predictably.

Consuming the Bindings from Workloads

The Workload Projection section of the specification describes how bindings are projected into the workload. The primary mechanism of projection is through files mounted at a specific directory. The bindings directory path is discovered through the mandatory $SERVICE_BINDING_ROOT environment variable set on all containers where bindings are created.

Within this service binding root directory, multiple Service Bindings may be projected. For example, a workload that requires both a database and event stream will declare one ServiceBinding for the database, a second ServiceBinding for the event stream, and both bindings will be projected as subdirectories of the root.

Let's take a look at the example given in the spec:

$SERVICE_BINDING_ROOT
├── account-database
│   ├── type
│   ├── provider
│   ├── uri
│   ├── username
│   └── password
└── transaction-event-stream
    ├── type
    ├── connection-count
    ├── uri
    ├── certificates
    └── private-key

In the above example, there are two bindings under the $SERVICE_BINDING_ROOT directory with the names account-database and transaction-event-stream. In order for a workload to configure itself, it must select the proper binding for each client type. Each binding directory has a special file named type and you can use the content of that file to identify the type of the binding projected into that directory (e.g. mysql, kafka). Some bindings optionally also contain another special file named provider which is an additional identifier used to further narrow down ambiguous types. Choosing a binding "by-name" is not considered good practice as it makes your workload less portable (although it may be unavoidable). Wherever possible use the type field and, if necessary, provider to select a binding.

Usually, operators use ServiceBinding resource name (.metadata.name) as the bindings directory name, but the spec also provides a way to override that name through the .spec.name field. So, there is a chance for bindings name collision. However, due to the nature of the volume mount in Kubernetes, the bindings directory will contain values from only one of the Secret resources. This is a caveat of using the bindings directory name to look up the bindings.

Purpose of the type and the provider fields in the workload projection

The specification mandates the type field and recommends provider field in the projected binding. In many cases the type field should be good enough to select the appropriate binding. In cases where it is not (e.g. when there are different providers for the same Provisioned Service), the provider field may be used. For example, when the type is mysql, the provider value might be mariadb, oracle, bitnami, aws-rds, etc. When the workload is selecting a binding, if necessary, it could consider type and provider as a composite key to avoid ambiguity. This could be helpful if a workload needs to choose a particular provider based on the deployment environment. In the deployment environment (stage, prod, etc.), at any given time, you need to ensure only one binding projection exists for a given type or type and provider -- unless your workload needs to connect to all the services.

Environment Variables

The specification also has support for projecting binding values as environment variables. You can use the built-in language feature of your programming language of choice to read environment variables. The container must restart to read updated environment variable values.

Programmatic Language Bindings

While the projection of a binding into a Pod can be consumed directly with features typically found in any programming language, it is often preferable to use a language binding that adds semantic meaning to the interaction. For example:

Java

(Example taken from https://github.com/nebhale/client-jvm)

import com.nebhale.bindings.Binding;
import com.nebhale.bindings.Bindings;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class Application {

    public static void main(String[] args) {
        Binding[] bindings = Bindings.fromServiceBindingRoot();
        bindings = Bindings.filter(bindings, "postgresql");
        if (bindings.length != 1) {
            System.err.printf("Incorrect number of PostgreSQL drivers: %d\n", bindings.length);
            System.exit(1);
        }

        String url = bindings[0].get("url");
        if (url == null) {
            System.err.println("No URL in binding");
            System.exit(1);
        }

        Connection conn;
        try {
            conn = DriverManager.getConnection(url);
        } catch (SQLException e) {
            System.err.printf("Unable to connect to database: %s", e);
            System.exit(1);
        }

        // ...
    }

}

Go

(Example taken from https://github.com/baijum/servicebinding)

import (
	"context"
	"fmt"
	"github.com/jackc/pgx/v4"
	"github.com/baijum/servicebinding/binding"
	"os"
)

func main() {
	sb, err := bindings.FromServiceBindingRoot()
	if err != nil {
		_, _ = fmt.Fprintln(os.Stderr, "Could not read service bindings")
		os.Exit(1)
	}

	b, err := sb.Bindings("postgresql")
	if err != nil {
		_, _ = fmt.Fprintln(os.Stderr, "Unable to find postgresql binding")
		os.Exit(1)
	}
	if len(b) != 1 {
		_, _ = fmt.Fprintf(os.Stderr, "Incorrect number of PostgreSQL bindings: %d\n", len(b))
		os.Exit(1)
	}

	u, ok := b[0]["url"]
	if !ok {
		_, _ = fmt.Fprintln(os.Stderr, "No URL in binding")
		os.Exit(1)
	}

	conn, err := pgx.Connect(context.Background(), u)
	if err != nil {
		_, _ = fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
		os.Exit(1)
	}
	defer conn.Close(context.Background())

	// ...
}

Known Language Bindings

(If you've created an implementation, please submit a PR for inclusion)

Specification

About

Repository hosting the website files (guides, etc)

https://servicebinding.io

License:Apache License 2.0


Languages

Language:Ruby 100.0%