What is Backman again?

I mentioned Backman already once in a previous post, but in short it’s an application for managing database backups. Backman can be deployed on Cloud Foundry or Kubernetes and automatically detects and configures your databases through bindings, creates backups and stores these on an S3-compatible object storage. Backups can later be restored on-demand or downloaded.

Run Backman on Kubernetes

Deploying and running Backman on Kubernetes has gotten fairly straightforward these days. I wrote new documentation about it, added multiple example deployment manifests, and there’s also a bit of ytt templates and kapp magic included if you want to dig deeper into it.

The main issue I had to overcome was that since Backman was originally written specifically to run on Cloud Foundry, it had a few hardcoded assumptions and idiosyncrasies about its runtime environment, and those didn’t translate all too well over to Kubernetes.

In Cloud Foundry Backman is configured mainly through a single large environment variable, $BACKMAN_CONFIG, which contains an entire JSON configuration file. While this of course works as well on Kubernetes it is rather unwieldy and not very K8s-idiomatic. Also in order to auto-discover databases and their bindings in Cloud Foundry there is the special environment variable $VCAP_SERVICES that gets automatically injected into the running container by the platform. Since there’s no such thing on Kubernetes I had to recreate that entire variable and its contents and add it to the deployment. The whole thing was very cumbersome.

So I did a big code refactor in Backman and also added the following new features:

  • Can read a configuration file from the local filesystem via -config <file>
  • Can have service bindings fully expressed within the configuration file
  • Supports the servicebinding.io specification

Configuration file

Armed with those new abilities the configuration of Backman on Kubernetes now looks as simple as this:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backman
spec:
  selector:
    matchLabels:
      app: backman
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: backman
    spec:
      containers:
      - name: backman
        image: jamesclonk/backman:latest
        ports:
        - containerPort: 8080
        command:
        - backman
        args:
        - -config
        - /backman/config.json
        volumeMounts:
        - mountPath: /backman/config.json
          name: configuration
          subPath: config.json
      volumes:
      - name: configuration
        secret:
          secretName: configuration

---
apiVersion: v1
kind: Secret
metadata:
  name: configuration
type: Opaque
stringData:
  config.json: |
    {
      "log_level": "info",
      "logging_timestamp": true,
      "disable_metrics_logging": true,
      "disable_health_logging": true,
      "unprotected_metrics": true,
      "unprotected_health": true,
      "username": "john.doe",
      "password": "foobar",
      "s3": {
        "service_label": "s3",
        "bucket_name": "backman-storage",
        "host": "s3.amazonaws.com",
        "access_key": "BKIKJAA5BMMU2RHO6IBB",
        "secret_key": "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12"
      }
    }    

All we needed to do was define a config.json key in a K8s Secret, and then mount it as a file into the Backman container.

Pretty neat huh? This is much more K8s-idiomatic than before.

Service Bindings

“Now what about those service bindings you keep talking about, how do I get Backman to know about my databases?” 🤔

While it would be possible to configure service bindings or credentials also directly in the config.json, one other major feature that I added to Backman was support for the servicebinding.io specification.

Backman on Kubernetes can then use this specification to automatically detect your databases. All you need to do is create and mount Secret’s whose contents conform to the spec. I’ve explained this in detail in the service bindings section in the new Backman documentation.

But to give you a short overview what this means: Basically you’ll need to either create a new Secret containing all necessary binding data and credentials of your database instance, or if you are using an Operator that already supports the spec it should create such a secret for you automatically.

---
apiVersion: v1
kind: Secret
metadata:
  name: my-production-db
type: Opaque
stringData:
  type: mysql
  provider: bitnami
  uri: mysql://root:root-pw@productdb.mysql.svc.cluster.local:3306/productdb
  username: root
  password: root-pw
  database: productdb

And then all you have to do is mount the contents of that secret under /bindings/* inside the Backman container, thus allowing Backman to automatically detect the service bindings.

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backman
spec:
  template:
    spec:
      containers:
      - name: backman
        volumeMounts:
        - mountPath: /bindings/my-production-db
          name: my-production-db
        - mountPath: /bindings/session-cache
          name: session-cache
      volumes:
      - name: my-production-db
        secret:
          secretName: my-production-db
      - name: session-cache
        secret:
          secretName: session-cache

Mounting these secrets under /bindings/* will result in the following directory and file structure being present within the Backman container:

/bindings/
├── my-production-db/
│   ├── type
│   ├── provider
│   ├── uri
│   ├── username
│   ├── password
│   └── database
└── session-cache/
    ├── type
    ├── provider
    ├── host
    ├── port
    └── password

Backman then can read and parse all of these files and their contents and use it for its service instance configuration.

This makes service bindings an elegant and simple way to provide service configuration and credentials to your workload.

servicebinding.io

Check out the whole Service Binding for Kubernetes specification for more details on how to benefit from it as an app developer and how exactly this workload projection should work.