Skip to content

Instantly share code, notes, and snippets.

@baijum
Forked from trisberg/four-bindings.adoc
Created October 24, 2020 13:57
Show Gist options
  • Save baijum/17d526c54eeb1171aeff9f8e38bdb5a3 to your computer and use it in GitHub Desktop.
Save baijum/17d526c54eeb1171aeff9f8e38bdb5a3 to your computer and use it in GitHub Desktop.
A tale of four backing service bindings

A tale of four backing service bindings

Introduction

Cody is writing an app and a function that need a backing service like a MySQL database.

A backing service is any service the app consumes over the network as part of its normal operation. Examples include datastores (such as MySQL or CouchDB), messaging/queueing systems (such as RabbitMQ or Beanstalkd), SMTP services for outbound email (such as Postfix), and caching systems (such as Memcached).
https://12factor.net/backing-services

The question "How does Cody connect his function or app to a backing service?" quickly comes up. So far, we don’t have a very good answer when deploying the apps and functions to Kubernetes. This document discusses a Proof-of-concept implementation to solve this problem for code deployed using riff or PFS.

Cody can provision a backing service in a variety of ways and we might have to adjust how we bind to them based on the way that they are provisioned.

The four ways of provisioning that we cover in this document are:

To allow for several ways of provisioning the service we can introduce a Binding object to hold the information that is needed for a succesfull binding and we can populate this object in different ways depending on the way the service was provisioned.

The Binding object will be used to bridge the various ways of provisioning the service with the needs of the app for properties needed to connect to the service at runtime. It will either refer to keys in a secret or provide the actual value based on user input. It might contain fields like:

- name
- secretRef
- uri
- uriKey
- jdbcUrl
- jdbcUrlKey
- database
- databaseKey
- username
- usernameKey
- passwordKey

The Binding resource could look something like this:

apiVersion: service.projectriff.io/v1alpha1
kind: Binding
metadata:
  name: mypets
  namespace: demo
spec:
  secretRef: mypets
  uriKey: uri
  usernameKey: username
  passwordKey: password

Most of the work is going to be in the area of mapping and providing the connection properties needed by the app based on data provided by the service bindings and secrets and any additional info provided by the user.

Service Provisioning and Binding

Helm

Cody can create a new database using the following Helm command:

helm install --name mypets --namespace demo \
  --set mysqlUser=riff --set mysqlDatabase=mypets stable/mysql

We can see the following in the NOTES section when the chart is executed :

MySQL can be accessed via port 3306 on the following DNS name from within your cluster:
mypets-mysql.demo.svc.cluster.local

This gives us most of the info we need to bind to the new database. The uri can be constructed by using mysql://<service-host>:<service-port>/<database>

The only missing piece is the generated password. When the chart is executed it also creates a secret named mypets-mysql with mysql-password and mysql-root-password keys, so this should give us all that we need.

Now Cody can create the service binding:

riff binding create mypets --namespace demo --secret-ref mypets-mysql \
  --uri mysql://mypets-mysql.demo.svc.cluster.local:3306/mypets \
  --username riff \
  --password-key mysql-password

Service Catalog with minibroker

Note

We are using the minibroker implementation provided by kubernetes-sigs/minibroker.

Cody can provision the database using the svcat CLI:

svcat provision mypets --namespace demo \
  --class mysql --plan 5-7-14 -p mysqlDatabase=mypets

He can then check on the status:

$ svcat get instances --namespace demo
   NAME    NAMESPACE   CLASS    PLAN    STATUS
+--------+-----------+-------+--------+--------+
  mypets   default     mysql   5-7-14   Ready

Next Cody creates the Service Catalog binding:

svcat bind mypets --name mypets --namespace demo

This command creates a servicebinding.servicecatalog.k8s.io object as well as a Secret named mypets in the demo namespace.

We can use the Secret and create a binding.service.projectriff.io object that will provide all the information needed to create a connection.

riff binding create mypets --namespace demo --secret-ref mypets \
  --uri-key uri \
  --username-key username \
  --password-key password
Note

Depending on the broker that was used to provision the service there might not be enough information available in the Secret and we might have to supplement additional connection parameters like we had to above when using the Helm chart.

ISM with ksm-broker

ISM is still a work-in-progress and this section is based on the its-always-sunny branch and installed as outlined in the End-to-End PKS ISV Service document.

Note

We are using a slightly modified MySQL chart for this example. Starting with the kibosh-sample mysql chart we are then adding database name and username to the binding/secret that is generated.

First we’ll see what services are available for provisioning:

$ ism service list
SERVICE   PLANS           BROKER       DESCRIPTION
mysql     medium, small   ksm-broker   Fast, reliable, scalable, and easy to use open-source relational database system.

Cody can now provision the database using the ism CLI:

ism instance create --name=mypets \
  --service=mysql --plan=small --broker=ksm-broker \
  --parameters='{"mysqlDatabase":"mypets"}'

He can then check on the status:

$ ism instance list
NAME     SERVICE   PLAN    BROKER       STATUS    CREATED AT
mypets   mysql     small   ksm-broker   created   2019-07-08 11:30:22 -0400 EDT

Cody can now create the ISM service binding:

ism binding create --name=mypets --instance=mypets --runtime=pfs-demo/demo/mypets

This command creates a servicebinding.osbapi.ism.io resource as well as a Secret named mypets in the demo namespace.

We can use this Secret to create a binding.service.projectriff.io resource that extracts the information needed to connect to the service.

riff binding create mypets --namespace demo --secret-ref mypets

Crossplane

Crossplane provides a way to provision a database in addition to all other features that it provides. We’ll provision a MySQL database using GCP Cloud SQL in this example.

Alana has already configured Crossplane so Cody can now simply provision a database:

Note
We could use the experimental "xpl" CLI generated by Mark Fisher, but decided to show the YAML file here for completenes.
apiVersion: storage.crossplane.io/v1alpha1
kind: MySQLInstance
metadata:
  name: mypets-cloud
  namespace: demo
spec:
  classReference:
    name: standard-mysql
    namespace: crossplane-system
  engineVersion: "5.7"

Applying this MySQLInstance resource creates a Secret in the demo namespace with the following content:

apiVersion: v1
kind: Secret
type: Opaque
metadata:
  name: mypets-cloud
  namespace: demo
data:
  endpoint: MTAuMTEuMTIuMTM=
  password: ZYbnFQVnZ180UnlSQXI4MlBuTk44OWlmQWdB
  username: cm9vdA==

Using the information in the secret Cody can create the following binding:

riff binding create mypets --namespace demo --secret-ref mypets-cloud \
  --host-key endpoint \
  --uri 'mysql://${MYPETS_HOST}/mypets' \
  --username-key username \
  --password-key password

Notes

It seems like we are creating two binding resources sometimes?

Yes, that is something we’ll address going forward. In some instances we don’t need to create the projectriff binding and could use the servicebinding.osbapi.ism.io or servicebinding.servicecatalog.k8s.io resources directly.

What about other types of services?

We’ll need to add some coverage of services like MongoDB, Redis, Rabbit MQ etc. Stay tuned …​

Deploying Functions and Apps

The sample app

We have a sample app mypets-app that is a Spring Boot app that uses Spring Data Rest to build a web app that provides a REST style API for persisted data. The entity model is very simple consisting of a Pet entity that has a name field and a PetType reference that defines what type of pet it is.

@Entity
@Table(name = "pets")
public class Pet {

	@Id @GeneratedValue
	Long id;

	String name;

	@ManyToOne(cascade = CascadeType.ALL)
	@JoinColumn(name = "type_id")
	private PetType type;
...
@Entity
@Table(name = "types")
public class PetType {

	@Id @GeneratedValue
	Long id;

	String name;
...

We can create an application.build.projectriff.io resource to build the app.

riff application create mypets --namespace demo \
  --git-repo https://github.com/trisberg/mypets-app.git
Note

If the database service was provisioned by Crossplane then we need to add a JDBC Socket Factory dependency to allow the app to connect to the Cloud SQL instance. The branch cloud-sql already includes this dependency so we need to add --git-revision cloud-sql to the application create command above.

Creating the sample app Handler using the Binding

When Cody deploys a function or an app he can simply reference the binding that he created earlier. Let’s see what Binding resources we have available:

$ riff binding list --namespace demo
NAME     REF                   AGE
mypets   secret:mypets-mysql   17m
Note

The binding-type defines how the connection properties are bound. Selecting vcap would create a VCAP_SERVICES environment variable similar to what is currently provided in Cloud Foundry Application Runtime. Selecting spring-boot will create Spring Boot style environment variables like SPRING_DATASOURCE_USERNAME, SPRING_DATASOURCE_PASSWORD etc. for the connection properties.

Since Cody is deploying a Spring Boot based application he selects spring-boot as the binding type.

riff handler create mypets --namespace demo \
  --application-ref mypets \
  --binding-ref mypets \
  --binding-type spring-boot

Invoking the sample app

We can use the following command to invoke the app:

riff handler invoke mypets /pets --namespace demo -- -w '\n'

This should show an empty list of Pets. To add a Pet we can use this command:

riff handler invoke mypets /pets --json --namespace demo -- \
  -d '{"name": "Millie", "type": {"name": "dog"}}' -w '\n'

This should produce the following output:

{
  "name" : "Millie",
  "type" : {
    "name" : "dog"
  },
  "_links" : {
    "self" : {
      "href" : "http://mypets.demo.example.com/pets/1"
    },
    "pet" : {
      "href" : "http://mypets.demo.example.com/pets/1"
    }
  }
}

Now we should see the new Pet in the list of pets when invoking the /pets endpoint:

$ riff handler invoke mypets /pets --namespace demo -- -w '\n'
{
  "_embedded" : {
    "pets" : [ {
      "name" : "Millie",
      "type" : {
        "name" : "dog"
      },
      "_links" : {
        "self" : {
          "href" : "http://mypets.demo.example.com/pets/1"
        },
        "pet" : {
          "href" : "http://mypets.demo.example.com/pets/1"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://mypets.demo.example.com/pets{?page,size,sort}",
      "templated" : true
    },
    "profile" : {
      "href" : "http://mypets.demo.example.com/profile/pets"
    },
    "search" : {
      "href" : "http://mypets.demo.example.com/pets/search"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}

The sample function

We have a sample function pets that is a Spring Boot based function that queries the mypets database based on a pet type argument. It will return the name of all pets that match the provided pet type.

@Bean
public Function<String, String> pets() {
	return s -> {
		List<String> pets = jdbcTemplate.queryForList(
			"select p.name from pets p" +
			" where p.type_id in (select t.id from types t where t.name = ?)",
			String.class, s);
		return pets.toString();
	};
}

We can create an function.build.projectriff.io resource to build the function.

riff function create pets --namespace demo \
  --git-repo https://github.com/trisberg/pets.git
Note

If the database service was provisioned by Crossplane then we need to add a JDBC Socket Factory dependency to allow the app to connect to the Cloud SQL instance. The branch cloud-sql already includes this dependency so we need to add --git-revision cloud-sql to the function create command above.

Creating the sample function Handler using the Binding

Since Cody is deploying a Spring Boot based function he selects spring-boot as the binding type.

riff handler create pets --namespace demo \
  --function-ref pets \
  --binding-ref mypets \
  --binding-type spring-boot

Cody should now have the pets function deployed and connected to the provisioned database.

Invoking the sample function

We can use the following command to invoke the function:

riff handler invoke pets --namespace demo --text -- -w '\n' -d 'dog'

This should show the name of our Pet like:

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